src/app/services/sas.service.ts
| providedIn | root |
Properties |
|
Methods |
|
constructor(appStoreService: AppStoreService, userService: UserService, eventService: EventService, sasjsService: SasjsService, sasViyaService: SasViyaService, loggerService: LoggerService, startupCheckService: StartupCheckService, router: Router)
|
|||||||||||||||||||||||||||
|
Defined in src/app/services/sas.service.ts:66
|
|||||||||||||||||||||||||||
|
Parameters :
|
| Public appLocCheck | ||||||
appLocCheck(path: string)
|
||||||
|
Defined in src/app/services/sas.service.ts:710
|
||||||
|
Parameters :
Returns :
Promise<{ found: boolean; errorMessage?: string }>
|
| Public appLocCheckPreAxiosdAdapter | ||||||||
appLocCheckPreAxiosdAdapter(path: string)
|
||||||||
|
Defined in src/app/services/sas.service.ts:749
|
||||||||
|
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.
Parameters :
Returns :
Promise<boolean>
|
| Public Async checkSasjsDeploy |
checkSasjsDeploy()
|
|
Defined in src/app/services/sas.service.ts:466
|
|
Returns :
any
|
| Public Async checkViyaDeploy | ||||||
checkViyaDeploy(path: string)
|
||||||
|
Defined in src/app/services/sas.service.ts:559
|
||||||
|
Parameters :
Returns :
any
|
| Private collectBrowserUrlVars |
collectBrowserUrlVars()
|
|
Defined in src/app/services/sas.service.ts:357
|
|
Collects the page URL parameters as name/value pairs for the
Returns :
Array<{ name: string; value: string }>
|
| Public getDebugUrlParam |
getDebugUrlParam()
|
|
Defined in src/app/services/sas.service.ts:834
|
|
Returns the
Returns :
string
|
| Public getExecutionPath |
getExecutionPath()
|
|
Defined in src/app/services/sas.service.ts:890
|
|
Returns :
any
|
| Public getFileContent |
getFileContent(folderPath: string, fileName: string)
|
|
Defined in src/app/services/sas.service.ts:907
|
|
Returns :
any
|
| Public getLicenseSiteId |
getLicenseSiteId()
|
|
Defined in src/app/services/sas.service.ts:454
|
|
Returns :
string[]
|
| Public getSasjsConfig |
getSasjsConfig()
|
|
Defined in src/app/services/sas.service.ts:816
|
|
Returns :
any
|
| Public getSasjsInstance |
getSasjsInstance()
|
|
Defined in src/app/services/sas.service.ts:846
|
|
Returns :
SASjs
|
| Public getSasRequests |
getSasRequests()
|
|
Defined in src/app/services/sas.service.ts:820
|
|
Returns :
any
|
| Public getServerType |
getServerType()
|
|
Defined in src/app/services/sas.service.ts:880
|
|
Returns :
string
|
| Private isLoginRequired | ||||||
isLoginRequired(response: string)
|
||||||
|
Defined in src/app/services/sas.service.ts:804
|
||||||
|
Parameters :
Returns :
any
|
| Public Async login |
login(username: string, password: string)
|
|
Defined in src/app/services/sas.service.ts:416
|
|
Returns :
unknown
|
| Public logout |
logout()
|
|
Defined in src/app/services/sas.service.ts:810
|
|
Returns :
void
|
| Public openServiceInNewWindow | ||||||||||||
openServiceInNewWindow(service: string, params: { })
|
||||||||||||
|
Defined in src/app/services/sas.service.ts:860
|
||||||||||||
|
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
Parameters :
Returns :
void
|
| Public reloadStartupData |
reloadStartupData()
|
|
Defined in src/app/services/sas.service.ts:450
|
|
Returns :
void
|
| Public request | ||||||||||||||||||||
request<responseType>(url: string, data: any, config?: any, wrapperOptions?: RequestWrapperOptions)
|
||||||||||||||||||||
|
Defined in src/app/services/sas.service.ts:146
|
||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||
|
Runing a backend request against a service. Function also handles the displaying of success or error modals. for example to suppress error or success abort modals after request is finished 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.
Parameters :
adapter response or an error. It will return the |
| Public Async sasjsMakedataChecking |
sasjsMakedataChecking()
|
|
Defined in src/app/services/sas.service.ts:510
|
|
Returns :
Promise<boolean>
|
| Private Async sasjsMakedataSuccessfull |
sasjsMakedataSuccessfull()
|
|
Defined in src/app/services/sas.service.ts:528
|
|
Returns :
Promise<boolean>
|
| Public sasServiceInit |
sasServiceInit()
|
|
Defined in src/app/services/sas.service.ts:84
|
|
Same as
Returns :
void
|
| Public setDebugState | ||||||
setDebugState(state: boolean)
|
||||||
|
Defined in src/app/services/sas.service.ts:824
|
||||||
|
Parameters :
Returns :
void
|
| Public setLicenseSiteId | ||||||
setLicenseSiteId(value: string | string[])
|
||||||
|
Defined in src/app/services/sas.service.ts:458
|
||||||
|
Parameters :
Returns :
void
|
| Public updateFileContent |
updateFileContent(folderPath: string, fileName: string, content: string)
|
|
Defined in src/app/services/sas.service.ts:911
|
|
Returns :
any
|
| Public uploadFile | ||||||||||||||||
uploadFile(sasService: string, files: UploadFile[], params: any)
|
||||||||||||||||
|
Defined in src/app/services/sas.service.ts:385
|
||||||||||||||||
|
Uploads a file to the backend, using the adapter upload function.
Parameters :
Returns :
Promise<UploadFileResponse>
HTTP Response |
| Public Async viyaMakedataSuccessfull |
viyaMakedataSuccessfull()
|
|
Defined in src/app/services/sas.service.ts:639
|
|
Returns :
Promise<boolean>
|
| Private appLocCheckPending |
Type : boolean
|
Default value : false
|
|
Defined in src/app/services/sas.service.ts:63
|
| checkingInterval |
Type : any
|
|
Defined in src/app/services/sas.service.ts:509
|
| Private dcAdapterSettings |
Type : DcAdapterSettings | undefined
|
|
Defined in src/app/services/sas.service.ts:61
|
| Public incorrectSiteIdEmitter |
Type : EventEmitter<string>
|
Default value : new EventEmitter()
|
|
Defined in src/app/services/sas.service.ts:57
|
| Private license_site_id |
Type : unknown
|
Default value : new BehaviorSubject<string[] | null>(null)
|
|
Defined in src/app/services/sas.service.ts:66
|
| Public loadStartupServiceEmitter |
Type : EventEmitter<any>
|
Default value : new EventEmitter()
|
|
Defined in src/app/services/sas.service.ts:56
|
| Public requestSiteIdEmitter |
Type : EventEmitter<string>
|
Default value : new EventEmitter()
|
|
Defined in src/app/services/sas.service.ts:58
|
| Private sasjsAdapter |
Type : SASjs
|
Default value : new SASjs()
|
|
Defined in src/app/services/sas.service.ts:60
|
| Public serverType |
Type : any
|
|
Defined in src/app/services/sas.service.ts:62
|
| Public shouldLogin |
Type : unknown
|
Default value : new BehaviorSubject(false)
|
|
Defined in src/app/services/sas.service.ts:65
|
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)
}
}