Submitting a rejected MPE_DERIVED row showed the SAS abort message, and then a
second modal on top of it reading
Submit error
SAS Service: editors/stagedata
Submit request failed
undefined
Two separate faults. sasService.request already shows the abort message when
the service returns a sasjsAbort, and the adapter error when the call fails -
then it rejects, and the editor's catch showed its own modal for the same
failure. That second modal read 'undefined' because it looked for
'adapterRespnse' (typo), a property that does not exist, so the text it built
was JSON.stringify(undefined).
- sas.service.ts marks a rejection 'errorShown' when it has already told the
user, on both paths that display a modal.
- the editor's catch now only reports a failure when nothing was shown, and
reads err.adapterResponse.error when it does.
- app.component.ts replaces the open abort modal instead of pushing another
onto the array: a second emit stacked a modal over the first, so one failure
needed two dismissals, and the lower one was usually the duplicate.
919 lines
29 KiB
TypeScript
919 lines
29 KiB
TypeScript
import { Injectable, EventEmitter } from '@angular/core'
|
|
|
|
import SASjs, { UploadFile } from '@sasjs/adapter'
|
|
import { BehaviorSubject } from 'rxjs'
|
|
import { UserService } from '../shared/user.service'
|
|
import { StartupCheckService } from './startup-check.service'
|
|
|
|
import { Router } from '@angular/router'
|
|
import { EventService } from './event.service'
|
|
import { SasjsService } from './sasjs.service'
|
|
import { SASjsApiDriveFolderContents } from '../models/sasjs-api/SASjsApiDriveFolderContents.model'
|
|
import { ServerType } from '@sasjs/utils/types/serverType'
|
|
import { DcAdapterSettings } from '../models/DcAdapterSettings'
|
|
import { AppStoreService } from './app-store.service'
|
|
import { LoggerService } from './logger.service'
|
|
import { RequestWrapperOptions } from '../models/request-wrapper/RequestWrapperOptions'
|
|
import { ErrorBody } from '../models/ErrorBody'
|
|
import { UploadFileResponse } from '../models/UploadFile'
|
|
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse'
|
|
import { SasViyaService } from './sas-viya.service'
|
|
import { ViyaApiFolder } from '../viya-api-explorer/models/viya-api-folder.model'
|
|
import { ViyaApiFolderMembers } from '../viya-api-explorer/models/viya-api-folder-content.model'
|
|
import { parseUserAgent } from '../shared/utils/parse-user-agent'
|
|
import { buildServiceUrl } from '../shared/utils/service-url'
|
|
import { VERSION } from '../../environments/version'
|
|
|
|
/**
|
|
* Services that receive the `browser_info` / `browser_url_vars` diagnostics
|
|
* tables: the startup service (so every session logs its context once) and
|
|
* every service that executes customer-provided code - hook scripts via
|
|
* %mpe_runhook (getdata, stagedata, loadfile, restore, postdata) and the
|
|
* dynamic cell dropdown programs (getdynamiccolvals). Other services never
|
|
* see the tables, so the payload stays off the high-frequency calls.
|
|
*/
|
|
const DIAGNOSTICS_SERVICES = [
|
|
'services/public/startupservice',
|
|
'services/editors/getdata',
|
|
'services/editors/getdynamiccolvals',
|
|
'services/editors/stagedata',
|
|
'services/editors/loadfile',
|
|
'services/editors/restore',
|
|
'services/auditors/postdata'
|
|
]
|
|
|
|
/**
|
|
* Where the debug toggle state is kept. The `<sasjs>` tag in index.html is
|
|
* re-read on every load, so without this a refresh would drop the state the user
|
|
* set from the UI.
|
|
*/
|
|
export const DEBUG_STATE_KEY = 'dc-debug-state'
|
|
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class SasService {
|
|
public loadStartupServiceEmitter: EventEmitter<any> = new EventEmitter()
|
|
public incorrectSiteIdEmitter: EventEmitter<string> = new EventEmitter()
|
|
public requestSiteIdEmitter: EventEmitter<string> = new EventEmitter()
|
|
|
|
private sasjsAdapter: SASjs = new SASjs()
|
|
private dcAdapterSettings: DcAdapterSettings | undefined
|
|
public serverType: any
|
|
private appLocCheckPending: boolean = false
|
|
|
|
public shouldLogin = new BehaviorSubject(false)
|
|
private license_site_id = new BehaviorSubject<string[] | null>(null)
|
|
|
|
constructor(
|
|
private appStoreService: AppStoreService,
|
|
private userService: UserService,
|
|
private eventService: EventService,
|
|
private sasjsService: SasjsService,
|
|
private sasViyaService: SasViyaService,
|
|
private loggerService: LoggerService,
|
|
private startupCheckService: StartupCheckService,
|
|
private router: Router
|
|
) {}
|
|
|
|
/**
|
|
* Same as `setup` function in the sasjs.service, this is the constructor replacement.
|
|
* This function is being called by `app.service`.
|
|
* Because of timing and dependency issues
|
|
*/
|
|
public sasServiceInit() {
|
|
this.dcAdapterSettings = this.appStoreService.getDcAdapterSettings()
|
|
|
|
this.sasjsService.setup()
|
|
this.sasViyaService.setup()
|
|
|
|
if (!this.dcAdapterSettings) {
|
|
this.eventService.showInfoModal(
|
|
'Error',
|
|
'Adapter settings (index.html) are not present.'
|
|
)
|
|
return
|
|
}
|
|
|
|
this.sasjsAdapter = new SASjs(this.dcAdapterSettings)
|
|
|
|
/* The tag resets debug on every load, so reinstate the state the user last
|
|
chose from the UI. */
|
|
const storedDebugState = localStorage.getItem(DEBUG_STATE_KEY)
|
|
if (storedDebugState !== null) {
|
|
this.sasjsAdapter.setDebugState(storedDebugState === 'true')
|
|
}
|
|
|
|
switch (this.dcAdapterSettings.serverType) {
|
|
case ServerType.SasViya: {
|
|
this.checkViyaDeploy(this.dcAdapterSettings.appLoc || '')
|
|
break
|
|
}
|
|
case ServerType.Sas9: {
|
|
this.loadStartupServiceEmitter.emit()
|
|
break
|
|
}
|
|
case ServerType.Sasjs: {
|
|
this.checkSasjsDeploy()
|
|
break
|
|
}
|
|
}
|
|
|
|
if (this.getSasjsConfig().loginMechanism === 'Redirected') {
|
|
this.shouldLogin.subscribe((shouldLogin) => {
|
|
if (shouldLogin) {
|
|
this.sasjsAdapter.logIn().then((res) => {
|
|
console.log('res', res)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runing a backend request against a service.
|
|
* Function also handles the displaying of success or error modals.
|
|
*
|
|
* @param url service to run reuqest against
|
|
* @param data to be sent to backend service
|
|
* @param config additional parameters to force eg. { debug: false }
|
|
* @param wrapperOptions used to provide options to the request wrapper function
|
|
* for example to suppress error or success abort modals after request is finished
|
|
* @returns adapter response or an error. It will return the `log` as well.
|
|
* The log could be potentially be wrong if multiple requests happen because the log this
|
|
* function return is the last request in the Adapter Array for the given URL.
|
|
*/
|
|
public request<responseType = any>(
|
|
url: string,
|
|
data: any,
|
|
config?: any,
|
|
wrapperOptions?: RequestWrapperOptions
|
|
): Promise<RequestWrapperResponse<responseType>> {
|
|
url = 'services/' + url
|
|
|
|
if (!wrapperOptions) wrapperOptions = {}
|
|
|
|
// Support diagnostics: tell the backend where the request came from.
|
|
// Sent only to the services that can act on it - the startup service
|
|
// (so every session logs its context once) and the services that
|
|
// %include customer-provided code (hook scripts, dynamic cell dropdown
|
|
// programs). This is the URL of this page - the Data Controller iframe -
|
|
// not the document that embeds it, so an embedded report can be
|
|
// distinguished. The browser_url_vars table carries the URL parameters
|
|
// as name/value pairs, which is easier for a SAS developer to read than
|
|
// parsing the url string.
|
|
// The startup service is called with a null payload - there is nothing to
|
|
// send - so give it one to carry the diagnostics. It is the service whose
|
|
// log a support ticket is read from, and without this it never receives
|
|
// them.
|
|
if (
|
|
DIAGNOSTICS_SERVICES.includes(url) &&
|
|
(data === null || data === undefined)
|
|
) {
|
|
data = {}
|
|
}
|
|
|
|
if (data && typeof data === 'object' && !data.browser_info) {
|
|
if (DIAGNOSTICS_SERVICES.includes(url)) {
|
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
const ua = parseUserAgent(navigator.userAgent)
|
|
data.browser_info = [
|
|
{
|
|
url: window.location.href,
|
|
referrer: document.referrer,
|
|
timezone: tz || '',
|
|
tz_offset: new Date().getTimezoneOffset(),
|
|
locale: navigator.language || '',
|
|
dc_version: VERSION.semverString,
|
|
adapter_version: VERSION.adapterVersion,
|
|
browser: ua.browser,
|
|
browser_version: ua.browserVersion,
|
|
platform: ua.platform,
|
|
user_agent: navigator.userAgent || ''
|
|
}
|
|
]
|
|
const urlVars = this.collectBrowserUrlVars()
|
|
if (urlVars.length) data.browser_url_vars = urlVars
|
|
}
|
|
}
|
|
|
|
// If debug is on it will print what is going inside the adapter
|
|
this.loggerService.logRequestData(url, data)
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.sasjsAdapter
|
|
.request(url, data, config, () => {
|
|
this.shouldLogin.next(true)
|
|
})
|
|
.then(
|
|
(res: any) => {
|
|
const sasRequest = this.sasjsAdapter
|
|
.getSasRequests()
|
|
.find((rq) => rq.serviceLink === url)
|
|
|
|
if (res.login === false) {
|
|
this.shouldLogin.next(true)
|
|
reject({
|
|
adapterResponse: false,
|
|
log: sasRequest?.logFile
|
|
})
|
|
}
|
|
|
|
if (!this.userService.user && res.MF_GETUSER) {
|
|
this.userService.user = {
|
|
username: res.MF_GETUSER
|
|
}
|
|
}
|
|
|
|
if (res.SYSSITE) {
|
|
this.requestSiteIdEmitter.emit(res.SYSSITE)
|
|
|
|
const licenseSiteId = this.getLicenseSiteId()
|
|
|
|
if (licenseSiteId.length > 0) {
|
|
if (!this.getLicenseSiteId().includes(res.SYSSITE)) {
|
|
this.incorrectSiteIdEmitter.emit(res.SYSSITE)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (res.status === 404) {
|
|
reject({
|
|
adapterResponse: {
|
|
MESSAGE: res.body || 'SAS Responded with error'
|
|
},
|
|
log: sasRequest?.logFile
|
|
})
|
|
}
|
|
|
|
if (typeof res.sasjsAbort !== 'undefined') {
|
|
const abortRes = res
|
|
const abortMsg = abortRes.sasjsAbort[0].MSG
|
|
const macMsg = abortRes.sasjsAbort[0].MAC
|
|
|
|
if (
|
|
abortMsg.includes(
|
|
'Data_Controller_Settings(StoredProcess) not found'
|
|
)
|
|
) {
|
|
this.eventService.startupDataLoaded()
|
|
this.router.navigateByUrl('/deploy')
|
|
|
|
reject({
|
|
adapterResponse: {
|
|
error: abortMsg
|
|
},
|
|
log: sasRequest?.logFile
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
if (!wrapperOptions?.suppressSuccessAbortModal) {
|
|
this.eventService.showAbortModal(
|
|
url.replace('services/', ''),
|
|
abortMsg,
|
|
{
|
|
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
|
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
|
MAC: macMsg,
|
|
_PROGRAM: abortRes._PROGRAM
|
|
}
|
|
)
|
|
}
|
|
|
|
reject({
|
|
adapterResponse: {
|
|
error: abortMsg
|
|
},
|
|
log: sasRequest?.logFile,
|
|
/* the modal above already told the user: a caller that also
|
|
reports the failure would stack a second one on top */
|
|
errorShown: !wrapperOptions?.suppressSuccessAbortModal
|
|
})
|
|
}
|
|
|
|
resolve({
|
|
adapterResponse: res,
|
|
log: sasRequest?.logFile
|
|
})
|
|
},
|
|
(err: { error: ErrorBody | undefined }) => {
|
|
console.error(err)
|
|
|
|
const sasRequest = this.sasjsAdapter
|
|
.getSasRequests()
|
|
.find((rq) => rq.serviceLink === url)
|
|
|
|
if (err.error) {
|
|
let errorMessage: string | undefined = err.error.message
|
|
let log: string | undefined
|
|
|
|
if (err.error.details && err.error.details.log) {
|
|
log = err.error.details.log
|
|
}
|
|
|
|
// If not a single useful info is returned from adapter
|
|
// We display that it's `unknown` SAS service error
|
|
if (!errorMessage || errorMessage.trim().length < 1) {
|
|
errorMessage = 'SAS Service error ocurred'
|
|
}
|
|
|
|
// Otherwise we display error message from adapter
|
|
if (!wrapperOptions?.suppressErrorAbortModal) {
|
|
this.eventService.showAbortModal(
|
|
url,
|
|
errorMessage,
|
|
{ LOG: log },
|
|
'Request error'
|
|
)
|
|
}
|
|
reject({
|
|
adapterResponse: {
|
|
error: errorMessage
|
|
},
|
|
log: sasRequest?.logFile,
|
|
errorShown: !wrapperOptions?.suppressErrorAbortModal
|
|
})
|
|
}
|
|
|
|
reject({
|
|
adapterResponse: err,
|
|
log: sasRequest?.logFile
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Collects the page URL parameters as name/value pairs for the
|
|
* `browser_url_vars` table. Parameters appear both in the search string
|
|
* (before the hash) and in the hash query string (Angular routes carry
|
|
* them after the `#`), so both are read - search first, and the first
|
|
* occurrence of a name wins, so the search string value beats the hash
|
|
* value on a collision.
|
|
*/
|
|
private collectBrowserUrlVars(): Array<{ name: string; value: string }> {
|
|
const vars: { name: string; value: string }[] = []
|
|
const seen = new Set<string>()
|
|
|
|
const collect = (search: string) => {
|
|
new URLSearchParams(search).forEach((value, name) => {
|
|
if (!seen.has(name)) {
|
|
seen.add(name)
|
|
vars.push({ name, value })
|
|
}
|
|
})
|
|
}
|
|
|
|
if (window.location.search) collect(window.location.search.slice(1))
|
|
const hashQuery = window.location.hash.split('?')[1]
|
|
if (hashQuery) collect(hashQuery)
|
|
|
|
return vars
|
|
}
|
|
|
|
/**
|
|
* Uploads a file to the backend, using the adapter upload function.
|
|
*
|
|
* @param sasService Service to which the file will be sent
|
|
* @param files Files to be sent
|
|
* @param params Aditional parameters eg. { debug: false }
|
|
* @returns HTTP Response
|
|
*/
|
|
public uploadFile(
|
|
sasService: string,
|
|
files: UploadFile[],
|
|
params: any
|
|
): Promise<UploadFileResponse> {
|
|
return new Promise((resolve, reject) => {
|
|
this.sasjsAdapter.uploadFile(sasService, files, params).then(
|
|
(res) => {
|
|
const sasRequest = this.sasjsAdapter
|
|
.getSasRequests()
|
|
.find((rq) => rq.serviceLink === 'services/editors/loadfile')
|
|
|
|
resolve({
|
|
adapterResponse: res,
|
|
log: sasRequest?.logFile
|
|
})
|
|
},
|
|
(err) => {
|
|
const sasRequest = this.sasjsAdapter
|
|
.getSasRequests()
|
|
.find((rq) => rq.serviceLink === 'services/editors/loadfile')
|
|
|
|
reject({
|
|
response: err,
|
|
log: sasRequest?.logFile
|
|
})
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
public async login(username: string, password: string) {
|
|
const clientId =
|
|
this.getServerType() === ServerType.Sasjs ? 'clientID1' : undefined
|
|
|
|
return this.sasjsAdapter
|
|
.logIn(username, password, clientId)
|
|
.then(
|
|
(res: { isLoggedIn: boolean; userName: string }) => {
|
|
if (res.isLoggedIn) {
|
|
this.userService.user = { username: res.userName }
|
|
|
|
if (this.appLocCheckPending) {
|
|
this.checkViyaDeploy(this.dcAdapterSettings?.appLoc || '')
|
|
this.appLocCheckPending = false
|
|
}
|
|
}
|
|
|
|
this.shouldLogin.next(!res.isLoggedIn)
|
|
return res.isLoggedIn
|
|
},
|
|
(err: any) => {
|
|
console.error(err)
|
|
this.shouldLogin.next(true)
|
|
return false
|
|
}
|
|
)
|
|
.catch((e: any) => {
|
|
if (e === 403) {
|
|
console.error('Invalid host')
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
public reloadStartupData() {
|
|
this.loadStartupServiceEmitter.emit()
|
|
}
|
|
|
|
public getLicenseSiteId(): string[] {
|
|
return this.license_site_id.value || []
|
|
}
|
|
|
|
public setLicenseSiteId(value: string | string[]) {
|
|
if (typeof value === 'object') {
|
|
this.license_site_id.next(value)
|
|
} else {
|
|
this.license_site_id.next([value])
|
|
}
|
|
}
|
|
|
|
public async checkSasjsDeploy() {
|
|
const sasjsConfig = this.getSasjsConfig()
|
|
const configuratorFolder = `${sasjsConfig.appLoc}/services/admin`
|
|
|
|
this.sasjsService.getFolderContentsFromDrive(configuratorFolder).subscribe(
|
|
(contents: SASjsApiDriveFolderContents) => {
|
|
if (
|
|
contents.files.includes('makedata.sas') ||
|
|
contents.files.includes('makedata.js')
|
|
) {
|
|
this.eventService.startupDataLoaded()
|
|
this.router.navigateByUrl('/deploy')
|
|
} else {
|
|
this.loadStartupServiceEmitter.emit()
|
|
|
|
if (this.router.url.includes('deploy')) this.router.navigateByUrl('/')
|
|
}
|
|
},
|
|
(err: any) => {
|
|
const errorMessage =
|
|
typeof err.error === 'string'
|
|
? err.error
|
|
: JSON.stringify(err.error || err)
|
|
|
|
if (errorMessage.includes('Unauthorized')) {
|
|
this.shouldLogin.next(true)
|
|
|
|
this.shouldLogin.subscribe((res: boolean) => {
|
|
if (res === false) location.reload()
|
|
})
|
|
} else if (errorMessage.includes(`Folder doesn't exist.`)) {
|
|
console.warn(
|
|
'SASjs SAS services are not present on the current appLoc.'
|
|
)
|
|
this.eventService.startupDataLoaded()
|
|
this.router.navigateByUrl('/deploy')
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
// Required type is NodeJS.Timeout
|
|
// But NodeJS is not available in browser so we have to go with any
|
|
checkingInterval: any
|
|
public async sasjsMakedataChecking(): Promise<boolean> {
|
|
return new Promise(async (resolve, reject) => {
|
|
this.checkingInterval = setInterval(async () => {
|
|
this.sasjsMakedataSuccessfull()
|
|
.then((success: boolean) => {
|
|
if (!!success) {
|
|
clearInterval(this.checkingInterval)
|
|
resolve(success)
|
|
}
|
|
})
|
|
.catch((err: any) => {
|
|
clearInterval(this.checkingInterval)
|
|
reject(err)
|
|
})
|
|
}, 1000)
|
|
})
|
|
}
|
|
|
|
private async sasjsMakedataSuccessfull(): Promise<boolean> {
|
|
return new Promise((resolve, reject) => {
|
|
const sasjsConfig = this.getSasjsConfig()
|
|
const configuratorFolder = `${sasjsConfig.appLoc}/services/admin`
|
|
|
|
this.sasjsService
|
|
.getFolderContentsFromDrive(configuratorFolder)
|
|
.subscribe(
|
|
(contents: SASjsApiDriveFolderContents) => {
|
|
if (
|
|
!contents.files.includes('makedata.sas') &&
|
|
!contents.files.includes('makedata.js')
|
|
) {
|
|
resolve(true)
|
|
} else {
|
|
resolve(false)
|
|
}
|
|
},
|
|
(err: any) => {
|
|
const errorMessage =
|
|
typeof err.error === 'string'
|
|
? err.error
|
|
: JSON.stringify(err.error || err)
|
|
if (errorMessage.includes(`Folder doesn't exist.`)) {
|
|
reject()
|
|
}
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
public async checkViyaDeploy(path: string) {
|
|
console.log('checkViyaDeploy: checking appLoc', path)
|
|
this.startupCheckService.updateStep(0, 'in_progress')
|
|
|
|
const getFolderExistsInAdapter =
|
|
typeof this.sasjsAdapter.getFolder !== 'undefined'
|
|
|
|
let appLocExists: boolean = false
|
|
let errorMessage: string | undefined = undefined
|
|
|
|
if (getFolderExistsInAdapter) {
|
|
const results = await this.appLocCheck(path)
|
|
|
|
appLocExists = results.found
|
|
errorMessage = results.errorMessage
|
|
} else {
|
|
appLocExists = await this.appLocCheckPreAxiosdAdapter(path)
|
|
}
|
|
|
|
console.log(
|
|
'checkViyaDeploy: appLoc exists?',
|
|
appLocExists,
|
|
'error?',
|
|
errorMessage
|
|
)
|
|
|
|
if (appLocExists) {
|
|
this.startupCheckService.updateStep(0, 'done', path)
|
|
|
|
// Check if there is appLoc/services/admin/makedata.sas present
|
|
// if yes, it needs to be run, so we redirect to /deploy
|
|
// if not, we load the startup service
|
|
|
|
this.startupCheckService.updateStep(1, 'in_progress')
|
|
this.viyaMakedataSuccessfull().then(
|
|
(success: boolean) => {
|
|
console.log(
|
|
'checkViyaDeploy: makedata job already run?',
|
|
success,
|
|
success
|
|
? '-> loading startupservice'
|
|
: '-> redirecting to /deploy (setup screen)'
|
|
)
|
|
if (success) {
|
|
this.startupCheckService.updateStep(1, 'done', 'makedata complete')
|
|
this.startupCheckService.updateStep(2, 'in_progress')
|
|
this.loadStartupServiceEmitter.emit()
|
|
} else {
|
|
this.startupCheckService.updateStep(
|
|
1,
|
|
'done',
|
|
'makedata job found - setup needed'
|
|
)
|
|
this.eventService.startupDataLoaded()
|
|
this.router.navigateByUrl('/deploy')
|
|
}
|
|
},
|
|
(error: any) => {
|
|
console.error(
|
|
'checkViyaDeploy: error while looking for makedata job',
|
|
error
|
|
)
|
|
this.startupCheckService.updateStep(1, 'error', String(error))
|
|
}
|
|
)
|
|
} else {
|
|
this.startupCheckService.updateStep(
|
|
0,
|
|
'error',
|
|
errorMessage || 'not found'
|
|
)
|
|
const errorMessageToShow =
|
|
(errorMessage ||
|
|
'Viya services are not present on the current appLoc, or API not reachable. Check the ADAPTER configuration.') +
|
|
`\nAppLoc: ${path}`
|
|
|
|
this.eventService.showInfoModal('Error', errorMessageToShow)
|
|
}
|
|
}
|
|
|
|
public async viyaMakedataSuccessfull(): Promise<boolean> {
|
|
return new Promise((resolve, reject) => {
|
|
const sasjsConfig = this.getSasjsConfig()
|
|
const configuratorFolder = `${sasjsConfig.appLoc}/services/admin`
|
|
|
|
console.log(
|
|
'viyaMakedataSuccessfull: checking folder',
|
|
configuratorFolder
|
|
)
|
|
|
|
this.sasViyaService.getFolderByPath(configuratorFolder).subscribe(
|
|
(folderInfo: ViyaApiFolder) => {
|
|
const folderId = folderInfo.id
|
|
|
|
if (!folderId) {
|
|
console.error(
|
|
`viyaMakedataSuccessfull: folder ID not present for ${configuratorFolder}`,
|
|
sasjsConfig
|
|
)
|
|
resolve(false)
|
|
return
|
|
}
|
|
|
|
this.sasViyaService.getFolderMembers(folderId).subscribe(
|
|
(members: ViyaApiFolderMembers) => {
|
|
const memberNames = members.items.map((item: any) => item.name)
|
|
const hasMakedata = members.items.some(
|
|
(item: any) => item.name === 'makedata'
|
|
)
|
|
|
|
console.log(
|
|
'viyaMakedataSuccessfull: folder members:',
|
|
memberNames.join(', ') || '(none)'
|
|
)
|
|
console.log(
|
|
'viyaMakedataSuccessfull: makedata job present?',
|
|
hasMakedata,
|
|
hasMakedata
|
|
? '-> setup needed (redirect to /deploy)'
|
|
: '-> setup already done (load startupservice)'
|
|
)
|
|
|
|
if (!hasMakedata) {
|
|
resolve(true)
|
|
} else {
|
|
resolve(false)
|
|
}
|
|
},
|
|
(err: any) => {
|
|
console.error(
|
|
'viyaMakedataSuccessfull: error getting folder members for',
|
|
configuratorFolder,
|
|
err
|
|
)
|
|
// On error, assume setup is needed rather than skipping it
|
|
resolve(false)
|
|
}
|
|
)
|
|
},
|
|
(err: any) => {
|
|
console.warn(
|
|
'viyaMakedataSuccessfull: error getting folder info for',
|
|
configuratorFolder,
|
|
err
|
|
)
|
|
reject(err)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
public appLocCheck(
|
|
path: string
|
|
): Promise<{ found: boolean; errorMessage?: string }> {
|
|
return new Promise(async (resolve, reject) => {
|
|
let fetchError: string = ''
|
|
|
|
let res: any
|
|
|
|
try {
|
|
res = await this.sasjsAdapter.getFolder(path)
|
|
} catch (err: any) {
|
|
if (err.name === 'LoginRequiredError') {
|
|
this.appLocCheckPending = true
|
|
this.shouldLogin.next(true)
|
|
|
|
resolve({ found: false })
|
|
} else if (err.name === 'NotFoundeError') {
|
|
fetchError = err.message
|
|
} else {
|
|
fetchError =
|
|
'Viya services are not present on the current appLoc, or API not reachable. Check the ADAPTER configuration.'
|
|
}
|
|
}
|
|
|
|
if (fetchError.length) {
|
|
console.warn(fetchError)
|
|
return resolve({ found: false, errorMessage: fetchError })
|
|
}
|
|
|
|
resolve({ found: true })
|
|
})
|
|
}
|
|
|
|
/**
|
|
* This is a function written before axios adapter where we
|
|
* are getting the folder directly from DC. axios adapter
|
|
* has getFolder() function that provides the folder.
|
|
* @param path The path of the folder of which details we are getting
|
|
*/
|
|
public appLocCheckPreAxiosdAdapter(path: string): Promise<boolean> {
|
|
return new Promise((resolve, reject) => {
|
|
let url = `/folders/folders/@item?path=${path}`
|
|
let statusNotFound: boolean = false
|
|
|
|
return fetch(url)
|
|
.then((res) => {
|
|
if (res.status === 404) {
|
|
statusNotFound = true
|
|
}
|
|
|
|
return res.text()
|
|
})
|
|
.then((res) => {
|
|
if (this.isLoginRequired(res)) {
|
|
this.appLocCheckPending = true
|
|
this.shouldLogin.next(true)
|
|
} else {
|
|
if (statusNotFound) {
|
|
console.warn(
|
|
'Viya services are not present on the current appLoc.'
|
|
)
|
|
this.eventService.startupDataLoaded()
|
|
this.router.navigateByUrl('/deploy')
|
|
return resolve(false)
|
|
}
|
|
|
|
let jsonResponse: any = null
|
|
|
|
try {
|
|
jsonResponse = JSON.parse(res)
|
|
} catch (ex) {}
|
|
|
|
if (jsonResponse) {
|
|
if (jsonResponse.httpStatusCode) {
|
|
if (jsonResponse.httpStatusCode === 404) {
|
|
console.warn(
|
|
'Viya services are not present on the current appLoc.'
|
|
)
|
|
this.eventService.startupDataLoaded()
|
|
this.router.navigateByUrl('/deploy')
|
|
return resolve(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve(true)
|
|
}
|
|
})
|
|
.catch((error: any) => {
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
|
|
private isLoginRequired(response: string) {
|
|
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm
|
|
const matches = pattern.test(response)
|
|
return matches
|
|
}
|
|
|
|
public logout() {
|
|
this.sasjsAdapter.logOut().then(() => {
|
|
location.reload()
|
|
})
|
|
}
|
|
|
|
public getSasjsConfig() {
|
|
return this.sasjsAdapter.getSasjsConfig()
|
|
}
|
|
|
|
public getSasRequests() {
|
|
return this.sasjsAdapter.getSasRequests()
|
|
}
|
|
|
|
public setDebugState(state: boolean) {
|
|
localStorage.setItem(DEBUG_STATE_KEY, String(state))
|
|
this.sasjsAdapter.setDebugState(state)
|
|
}
|
|
|
|
/**
|
|
* Returns the `&_debug=...` URL segment honoring the live adapter
|
|
* config. Empty string when debug is off. `128` on the Viya WEB JES path
|
|
* with `runAsTask` enabled, `131` otherwise.
|
|
*/
|
|
public getDebugUrlParam(): string {
|
|
const config = this.sasjsAdapter.getSasjsConfig()
|
|
if (!config.debug) return ''
|
|
const value =
|
|
config.serverType === ServerType.SasViya &&
|
|
config.useComputeApi === null &&
|
|
config.runAsTask === true
|
|
? 128
|
|
: 131
|
|
return `&_debug=${value}`
|
|
}
|
|
|
|
public getSasjsInstance() {
|
|
return this.sasjsAdapter
|
|
}
|
|
|
|
/**
|
|
* Runs a SAS service in a new browser window, bypassing the adapter.
|
|
*
|
|
* The adapter's request path can hide the backend log when a service fails:
|
|
* on Viya a long startup service may be submitted as an execution task and
|
|
* time out before it finishes, returning neither webout nor log. A browser
|
|
* request to the JES / STP web app runs the same `_program` with no task and
|
|
* returns the SAS log (`_debug=131`), so this is the diagnostic path offered
|
|
* when a failed request leaves nothing usable to display.
|
|
*/
|
|
public openServiceInNewWindow(
|
|
service: string,
|
|
params: { [name: string]: string | number | null | undefined } = {}
|
|
) {
|
|
const config = this.getSasjsConfig()
|
|
|
|
const url = buildServiceUrl({
|
|
serverUrl: config.serverUrl || '',
|
|
appLoc: config.appLoc || '',
|
|
executionPath: this.getExecutionPath() || '',
|
|
service,
|
|
params
|
|
})
|
|
|
|
// noopener: the SAS log page is on a different origin (Viya's serverUrl is
|
|
// not the app's), and without it the opened window keeps a handle on
|
|
// window.opener and can navigate this tab.
|
|
window.open(url, '_blank', 'noopener')
|
|
}
|
|
|
|
public getServerType(): string {
|
|
const sasjsConfig = this.sasjsAdapter.getSasjsConfig()
|
|
|
|
if (sasjsConfig.serverType) {
|
|
return sasjsConfig.serverType
|
|
}
|
|
|
|
return 'SASVIYA'
|
|
}
|
|
|
|
public getExecutionPath() {
|
|
const sasjsConfig = this.sasjsAdapter.getSasjsConfig()
|
|
|
|
switch (sasjsConfig.serverType) {
|
|
case ServerType.SasViya: {
|
|
return sasjsConfig.pathSASViya
|
|
}
|
|
case ServerType.Sas9: {
|
|
return sasjsConfig.pathSAS9
|
|
}
|
|
case ServerType.Sasjs: {
|
|
return sasjsConfig.pathSASJS
|
|
}
|
|
}
|
|
}
|
|
|
|
// Viya specific functions
|
|
public getFileContent(folderPath: string, fileName: string) {
|
|
return this.sasjsAdapter.getFileContent(folderPath, fileName)
|
|
}
|
|
|
|
public updateFileContent(
|
|
folderPath: string,
|
|
fileName: string,
|
|
content: string
|
|
) {
|
|
return this.sasjsAdapter.updateFileContent(folderPath, fileName, content)
|
|
}
|
|
}
|