src/app/services/sas.service.ts

Metadata

Index

Properties
Methods

Constructor

constructor(appStoreService: AppStoreService, userService: UserService, eventService: EventService, sasjsService: SasjsService, sasViyaService: SasViyaService, loggerService: LoggerService, startupCheckService: StartupCheckService, router: Router)
Parameters :
Name Type Optional
appStoreService AppStoreService No
userService UserService No
eventService EventService No
sasjsService SasjsService No
sasViyaService SasViyaService No
loggerService LoggerService No
startupCheckService StartupCheckService No
router Router No

Methods

Public appLocCheck
appLocCheck(path: string)
Parameters :
Name Type Optional
path string No
Returns : Promise<{ found: boolean; errorMessage?: string }>
Public appLocCheckPreAxiosdAdapter
appLocCheckPreAxiosdAdapter(path: string)

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 :
Name Type Optional Description
path string No

The path of the folder of which details we are getting

Returns : Promise<boolean>
Public Async checkSasjsDeploy
checkSasjsDeploy()
Returns : any
Public Async checkViyaDeploy
checkViyaDeploy(path: string)
Parameters :
Name Type Optional
path string No
Returns : any
Private collectBrowserUrlVars
collectBrowserUrlVars()

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.

Returns : Array<{ name: string; value: string }>
Public getDebugUrlParam
getDebugUrlParam()

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.

Returns : string
Public getExecutionPath
getExecutionPath()
Returns : any
Public getFileContent
getFileContent(folderPath: string, fileName: string)
Parameters :
Name Type Optional
folderPath string No
fileName string No
Returns : any
Public getLicenseSiteId
getLicenseSiteId()
Returns : string[]
Public getSasjsConfig
getSasjsConfig()
Returns : any
Public getSasjsInstance
getSasjsInstance()
Returns : SASjs
Public getSasRequests
getSasRequests()
Returns : any
Public getServerType
getServerType()
Returns : string
Private isLoginRequired
isLoginRequired(response: string)
Parameters :
Name Type Optional
response string No
Returns : any
Public Async login
login(username: string, password: string)
Parameters :
Name Type Optional
username string No
password string No
Returns : unknown
Public logout
logout()
Returns : void
Public openServiceInNewWindow
openServiceInNewWindow(service: string, params: { })

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.

Parameters :
Name Type Optional Default value
service string No
params { } No {}
Returns : void
Public reloadStartupData
reloadStartupData()
Returns : void
Public request
request<responseType>(url: string, data: any, config?: any, wrapperOptions?: RequestWrapperOptions)
Type parameters :
  • responseType

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 :
Name Type Optional Description
url string No

service to run reuqest against

data any No

to be sent to backend service

config any Yes

additional parameters to force eg. { debug: false }

wrapperOptions RequestWrapperOptions Yes

used to provide options to the request wrapper function for example to suppress error or success abort modals after request is finished

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 Async sasjsMakedataChecking
sasjsMakedataChecking()
Returns : Promise<boolean>
Private Async sasjsMakedataSuccessfull
sasjsMakedataSuccessfull()
Returns : Promise<boolean>
Public sasServiceInit
sasServiceInit()

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

Returns : void
Public setDebugState
setDebugState(state: boolean)
Parameters :
Name Type Optional
state boolean No
Returns : void
Public setLicenseSiteId
setLicenseSiteId(value: string | string[])
Parameters :
Name Type Optional
value string | string[] No
Returns : void
Public updateFileContent
updateFileContent(folderPath: string, fileName: string, content: string)
Parameters :
Name Type Optional
folderPath string No
fileName string No
content string No
Returns : any
Public uploadFile
uploadFile(sasService: string, files: UploadFile[], params: any)

Uploads a file to the backend, using the adapter upload function.

Parameters :
Name Type Optional Description
sasService string No

Service to which the file will be sent

files UploadFile[] No

Files to be sent

params any No

Aditional parameters eg. { debug: false }

HTTP Response

Public Async viyaMakedataSuccessfull
viyaMakedataSuccessfull()
Returns : Promise<boolean>

Properties

Private appLocCheckPending
Type : boolean
Default value : false
checkingInterval
Type : any
Private dcAdapterSettings
Type : DcAdapterSettings | undefined
Public incorrectSiteIdEmitter
Type : EventEmitter<string>
Default value : new EventEmitter()
Private license_site_id
Type : unknown
Default value : new BehaviorSubject<string[] | null>(null)
Public loadStartupServiceEmitter
Type : EventEmitter<any>
Default value : new EventEmitter()
Public requestSiteIdEmitter
Type : EventEmitter<string>
Default value : new EventEmitter()
Private sasjsAdapter
Type : SASjs
Default value : new SASjs()
Public serverType
Type : any
Public shouldLogin
Type : unknown
Default value : new BehaviorSubject(false)
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)
  }
}

results matching ""

    No results matching ""