src/app/deploy/sections/automatic/automatic.component.ts

Implements

OnInit

Metadata

Relationships

Used by

Depends on

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(eventService: EventService, deployService: DeployService, sasService: SasService, sasViyaService: SasViyaService, loggerService: LoggerService, helperService: HelperService)
Parameters :
Name Type Optional
eventService EventService No
deployService DeployService No
sasService SasService No
sasViyaService SasViyaService No
loggerService LoggerService No
helperService HelperService No

Inputs

appLoc
Type : string
Default value : ''
dcAdapterSettings
Type : DcAdapterSettings | undefined
dcPath
Type : string
Default value : ''
sasJs
Type : SASjs
sasJsConfig
Type : SASjsConfig
Default value : new SASjsConfig()
selectedAdminGroup
Type : string
Default value : ''

Outputs

onNavigateToHome
Type : EventEmitter<any>

Methods

Private applyAdminGroupFilter
applyAdminGroupFilter()

Applies the member/all groups filter to the dropdown and makes sure the currently selected group exists in the displayed list.

Returns : void
Public clearUploadInput
clearUploadInput(event: Event)
Parameters :
Name Type Optional
event Event No
Returns : void
Public createDatabase
createDatabase()

Runs the makedata request sending the ADMIN and DCPATH values

Returns : void
Public downloadFile
downloadFile(content: any, filename: string, extension: string)
Parameters :
Name Type Optional Default value
content any No
filename string No
extension string No 'txt'
Returns : void
Private ensureSelectedComputeContext
ensureSelectedComputeContext()
Returns : void
Public Async executeJson
executeJson()

Executes sas.json file to deploy the backend Method will first try to run the auto deploy If that fails the rest of the code is ignored. If request is successfull, method will continue to try to create database if checkbox is toggled on

Returns : any
Public extractRunAs
extractRunAs(details: ComputeContextDetails)

Pulls the runAs (batch) identity out of a compute context. The batch user is exposed as attributes.runServerAs in the Viya compute context API response. Not all contexts expose one.

Parameters :
Name Type Optional
details ComputeContextDetails No
Returns : string | null
Public Async getAdminGroups
getAdminGroups()
Returns : unknown
Public Async getComputeContextDetails
getComputeContextDetails()

The /compute/contexts list view is minimal, so each context is fetched in full (in parallel) to pick up its batch identity (runServerAs) and whether it reuses server processes. Only reusable contexts with a batch identity feed the batch id dropdown.

Returns : any
Public getComputeContextName
getComputeContextName(id: string)
Parameters :
Name Type Optional
id string No
Returns : string | undefined
Public Async getComputeContexts
getComputeContexts()
Returns : unknown
Public Async getCurrentUser
getCurrentUser()
Returns : unknown
Public getFilteredComputeContexts
getFilteredComputeContexts()

The contexts offered in the dropdown. By default only reusable contexts (reuseServerProcesses) matching the selected batch id are shown. When the showAllContexts toggle is on, all contexts are shown.

Public Async getMemberAdminGroups
getMemberAdminGroups()

Fetches the groups the current user is a member of, from the Viya identities API. If the memberships cannot be fetched, the member list falls back to all groups.

Returns : unknown
Public Async loadData
loadData()
Returns : any
Public navigateToHome
navigateToHome()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Public onBatchIdChange
onBatchIdChange()

Changing the batch id re-filters the compute context dropdown and makes sure the currently selected context exists in the displayed list.

Returns : void
Public Async onComputeContextChange
onComputeContextChange(computeContextId: string)
Parameters :
Name Type Optional
computeContextId string No
Returns : any
Public Async onJsonFileChange
onJsonFileChange(event: any)
Parameters :
Name Type Optional
event any No
Returns : any
Public onShowAllContextsChange
onShowAllContextsChange()

Toggles the compute context dropdown between contexts matching the selected batch id and all contexts on the Viya server.

Returns : void
Public onShowAllGroupsChange
onShowAllGroupsChange()

Toggles the SAS Admin group dropdown between the groups the current user is a member of and all groups on the Viya server.

Returns : void
Public openSasRequestsModal
openSasRequestsModal()
Returns : void
Private pollMakedataCompletion
pollMakedataCompletion()
Returns : void
Public recreateDatabaseClicked
recreateDatabaseClicked(event: Event)
Parameters :
Name Type Optional
event Event No
Returns : void
Public Async runAutoDeploy
runAutoDeploy(executeJson: boolean)
Parameters :
Name Type Optional Default value
executeJson boolean No false
Returns : any
Public runMakedataInNewWindow
runMakedataInNewWindow(params: { contextName: string; admin: string; dcPath: string })
Parameters :
Name Type Optional
params { contextName: string; admin: string; dcPath: string } No
Returns : void
Public Async updateIndexHtmlComputeContext
updateIndexHtmlComputeContext()

Only when on Viya streamed app, this method will update the contextname in the index.html on the SAS drive This is needed to ensure that the DC will use the same compute context makedata service used to run against.

Returns : any
Private updateRunningAsUser
updateRunningAsUser()
Returns : void

Properties

Public adminGroups
Type : { id: string; name: string }[]
Default value : []
Public adminGroupsLoading
Type : boolean
Default value : false
Public allAdminGroups
Type : { id: string; name: string }[]
Default value : []

All groups on the Viya server

Public autodeployDone
Type : boolean
Default value : false
Public autodeploying
Type : boolean
Default value : false
Public autoDeployStatus
Type : { deployServicePack: any; runMakeData: any }
Default value : { deployServicePack: null, runMakeData: null }

autoDeployStatus This object presents the status for two steps that we have for deploy. deployServicePack - Creating services based on viya.json runMakeData - Running makedata service If any of them is null or false that means step failed and will be shown to user on deploy done modal.

Public batchIds
Type : string[]
Default value : []

Distinct batch identities (runServerAs) across reusable contexts

Public computeContextInfos
Type : ComputeContextInfo[]
Default value : []

Compute contexts enriched with their batch identity and reusability

Public computeContexts
Type : ComputeContextItem[]
Default value : []
Public computeContextsLoading
Type : boolean
Default value : false
Public createDatabaseLoading
Type : boolean
Default value : false
Public currentUserInfo
Type : ViyaApiCurrentUser | null
Default value : null
Public currentUserInfoLoading
Type : boolean
Default value : false
Public deployInNewWindow
Type : boolean
Default value : true

makedata service will be run in a new window This is needed to ensure that the user can see the logs and the progress of the service execution. If this is set to false, the service will be run in the same window using the adapter request method.

Public isJsonSubmitted
Type : boolean
Default value : false
Public isSubmittingJson
Type : boolean
Default value : false
Public jsonFile
Type : any
Default value : null
Private makedataPollInterval
Type : any
Default value : null
Public makeDataResponse
Type : string
Default value : ''
Public memberAdminGroups
Type : { id: string; name: string }[]
Default value : []

Groups the current user is a member of

Public recreateDatabase
Type : boolean
Default value : true

Default was false when deploy was done with frontend and backend separately. Now we are using only streaming app, so we always want to recreate database (makedata)

Public recreateDatabaseModal
Type : boolean
Default value : false
Public runningAsUser
Type : string | undefined
Public sasjsConfig
Type : unknown
Default value : this.sasService.getSasjsConfig()
Public selectedBatchId
Type : string
Default value : ''
Public selectedComputeContext
Type : string
Default value : ''
Public showAllContexts
Type : boolean
Default value : false

When enabled, any compute context can be chosen (the batch id dropdown is hidden and no reusability / batch id filtering is applied)

Public showAllGroups
Type : boolean
Default value : false
import {
  Component,
  EventEmitter,
  Input,
  OnInit,
  Output,
  ViewEncapsulation
} from '@angular/core'
import SASjs, { SASjsConfig } from '@sasjs/adapter'
import { DcAdapterSettings } from 'src/app/models/DcAdapterSettings'
import { HelperService } from 'src/app/services'
import { DeployService } from 'src/app/services/deploy.service'
import { EventService } from 'src/app/services/event.service'
import { LoggerService } from 'src/app/services/logger.service'
import { SasViyaService } from 'src/app/services/sas-viya.service'
import { SasService } from 'src/app/services/sas.service'
import { ViyaApiCurrentUser } from 'src/app/viya-api-explorer/models/viya-api-current-user.model'
import {
  Item,
  ViyaApiIdentities
} from 'src/app/viya-api-explorer/models/viya-api-identities.model'
import { ComputeContextDetails } from 'src/app/viya-api-explorer/models/viya-compute-context-details.model'
import {
  ViyaComputeContexts,
  Item as ComputeContextItem
} from 'src/app/viya-api-explorer/models/viya-compute-contexts.model'

export interface ComputeContextInfo {
  id: string
  name: string
  runAs: string | null
  reusable: boolean
}

@Component({
  selector: 'app-automatic-deploy',
  templateUrl: './automatic.component.html',
  styleUrls: ['./automatic.component.scss'],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class AutomaticComponent implements OnInit {
  @Input() sasJs!: SASjs
  @Input() sasJsConfig: SASjsConfig = new SASjsConfig()
  @Input() dcAdapterSettings: DcAdapterSettings | undefined
  @Input() appLoc: string = ''
  @Input() dcPath: string = ''
  @Input() selectedAdminGroup: string = ''

  @Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>()

  public selectedComputeContext: string = ''
  public makeDataResponse: string = ''
  public jsonFile: any = null
  public autodeploying: boolean = false
  public autodeployDone: boolean = false
  public recreateDatabaseModal: boolean = false
  public isSubmittingJson: boolean = false
  public isJsonSubmitted: boolean = false
  /**
   * Default was `false` when deploy was done with frontend and backend separately.
   * Now we are using only streaming app, so we always want to recreate database (makedata)
   */
  public recreateDatabase: boolean = true
  public createDatabaseLoading: boolean = false
  public adminGroupsLoading: boolean = false
  public currentUserInfoLoading: boolean = false
  public computeContextsLoading: boolean = false
  public adminGroups: { id: string; name: string }[] = []
  /** Compute contexts enriched with their batch identity and reusability */
  public computeContextInfos: ComputeContextInfo[] = []
  /** Distinct batch identities (runServerAs) across reusable contexts */
  public batchIds: string[] = []
  public selectedBatchId: string = ''
  /**
   * When enabled, any compute context can be chosen (the batch id dropdown
   * is hidden and no reusability / batch id filtering is applied)
   */
  public showAllContexts: boolean = false
  /** All groups on the Viya server */
  public allAdminGroups: { id: string; name: string }[] = []
  /** Groups the current user is a member of */
  public memberAdminGroups: { id: string; name: string }[] = []
  public showAllGroups: boolean = false
  public runningAsUser: string | undefined
  public currentUserInfo: ViyaApiCurrentUser | null = null
  public computeContexts: ComputeContextItem[] = []

  /** autoDeployStatus
   * This object presents the status for two steps that we have for deploy.
   * `deployServicePack` - Creating services based on `viya.json`
   * `runMakeData` - Running `makedata` service
   * If any of them is `null` or `false` that means step failed
   * and will be shown to user on deploy done modal.
   */
  public autoDeployStatus: {
    deployServicePack: any
    runMakeData: any
  } = {
    deployServicePack: null,
    runMakeData: null
  }

  public sasjsConfig = this.sasService.getSasjsConfig()

  /**
   * makedata service will be run in a new window
   * This is needed to ensure that the user can see the logs
   * and the progress of the service execution.
   * If this is set to `false`, the service will be run in the same window
   * using the adapter request method.
   */
  public deployInNewWindow: boolean = true

  constructor(
    private eventService: EventService,
    private deployService: DeployService,
    private sasService: SasService,
    private sasViyaService: SasViyaService,
    private loggerService: LoggerService,
    private helperService: HelperService
  ) {}

  ngOnInit(): void {
    this.loadData()
  }

  public async loadData() {
    await this.getCurrentUser()
    await this.getAdminGroups()
    await this.getComputeContexts()

    setTimeout(() => {
      if (this.selectedComputeContext) {
        this.onComputeContextChange(this.selectedComputeContext)
      }
    }, 500)
  }

  public async getComputeContexts() {
    return new Promise<void>((resolve, reject) => {
      this.computeContextsLoading = true

      this.sasViyaService.getComputeContexts().subscribe(
        async (res: ViyaComputeContexts) => {
          this.computeContexts = res.items

          await this.getComputeContextDetails()

          this.computeContextsLoading = false

          resolve()
        },
        (err) => {
          reject(err)
        }
      )
    })
  }

  /**
   * The `/compute/contexts` list view is minimal, so each context is fetched
   * in full (in parallel) to pick up its batch identity (runServerAs) and
   * whether it reuses server processes.  Only reusable contexts with a batch
   * identity feed the batch id dropdown.
   */
  public async getComputeContextDetails() {
    const infos = await Promise.all(
      this.computeContexts.map(
        (context: ComputeContextItem) =>
          new Promise<ComputeContextInfo>((resolveInfo) => {
            this.sasViyaService.getComputeContextById(context.id).subscribe(
              (details: ComputeContextDetails) => {
                resolveInfo({
                  id: context.id,
                  name: context.name,
                  runAs: this.extractRunAs(details),
                  reusable: details.attributes?.reuseServerProcesses === 'true'
                })
              },
              (err: any) => {
                this.loggerService.error(
                  `Error while getting compute context ${context.name}`,
                  err
                )

                resolveInfo({
                  id: context.id,
                  name: context.name,
                  runAs: null,
                  reusable: false
                })
              }
            )
          })
      )
    )

    this.computeContextInfos = infos

    this.batchIds = [
      ...new Set(
        infos
          .filter((info) => info.reusable && info.runAs)
          .map((info) => info.runAs as string)
      )
    ]

    if (this.batchIds.length) {
      this.selectedBatchId = this.batchIds[0]
    }

    const filteredContexts = this.getFilteredComputeContexts()

    const defaultContext =
      filteredContexts.find(
        (info: ComputeContextInfo) =>
          info.name === 'SAS Job Execution compute context'
      ) || filteredContexts[0]

    if (defaultContext) {
      this.selectedComputeContext = defaultContext.id
    }
  }

  /**
   * Pulls the runAs (batch) identity out of a compute context. The batch user
   * is exposed as `attributes.runServerAs` in the Viya compute context API
   * response. Not all contexts expose one.
   */
  public extractRunAs(details: ComputeContextDetails): string | null {
    return details.attributes?.runServerAs || null
  }

  /**
   * The contexts offered in the dropdown.  By default only reusable contexts
   * (`reuseServerProcesses`) matching the selected batch id are shown.  When
   * the `showAllContexts` toggle is on, all contexts are shown.
   */
  public getFilteredComputeContexts(): ComputeContextInfo[] {
    if (this.showAllContexts) {
      return this.computeContextInfos
    }

    return this.computeContextInfos.filter(
      (info: ComputeContextInfo) =>
        info.reusable && info.runAs === this.selectedBatchId
    )
  }

  /**
   * Changing the batch id re-filters the compute context dropdown and makes
   * sure the currently selected context exists in the displayed list.
   */
  public onBatchIdChange() {
    this.ensureSelectedComputeContext()
  }

  /**
   * Toggles the compute context dropdown between contexts matching the
   * selected batch id and all contexts on the Viya server.
   */
  public onShowAllContextsChange() {
    this.ensureSelectedComputeContext()
  }

  private ensureSelectedComputeContext() {
    const filteredContexts = this.getFilteredComputeContexts()

    const selectedContextExists = filteredContexts.some(
      (info: ComputeContextInfo) => info.id === this.selectedComputeContext
    )

    if (!selectedContextExists) {
      this.selectedComputeContext = filteredContexts.length
        ? filteredContexts[0].id
        : ''
    }

    this.updateRunningAsUser()
  }

  private updateRunningAsUser() {
    const selectedContext = this.computeContextInfos.find(
      (info: ComputeContextInfo) => info.id === this.selectedComputeContext
    )

    this.runningAsUser =
      selectedContext?.runAs || this.currentUserInfo?.id || 'unknown'
  }

  public async getCurrentUser() {
    return new Promise<void>((resolve, reject) => {
      this.currentUserInfoLoading = true

      this.sasViyaService.getCurrentUser().subscribe(
        (res: ViyaApiCurrentUser) => {
          this.currentUserInfoLoading = false

          this.currentUserInfo = res

          this.dcPath = `/export/viya/homes/${res.id}`

          resolve()
        },
        (err) => {
          console.error('Error while getting current user', err)
          reject(err)
        }
      )
    })
  }

  public async getAdminGroups() {
    return new Promise<void>((resolve, reject) => {
      this.adminGroupsLoading = true
      ;(this.sasViyaService
        .getAdminGroups()
        .subscribe(async (res: ViyaApiIdentities) => {
          // Map admin groups with only needed fields
          this.allAdminGroups = res.items.map((item: Item) => {
            return {
              id: item.id,
              name: item.name
            }
          })

          await this.getMemberAdminGroups()

          this.applyAdminGroupFilter()

          this.adminGroupsLoading = false

          resolve()
        }),
        (err: any) => {
          this.adminGroupsLoading = false
          this.loggerService.error('Error while getting admin groups', err)
          this.eventService.showAbortModal('admin groups', err)

          reject(err)
        })
    })
  }

  /**
   * Fetches the groups the current user is a member of, from the Viya
   * identities API.  If the memberships cannot be fetched, the member list
   * falls back to all groups.
   */
  public async getMemberAdminGroups() {
    return new Promise<void>((resolve) => {
      this.sasViyaService.getCurrentUserGroupMemberships().subscribe(
        (res: ViyaApiIdentities) => {
          const memberIds = res.items.map((item: Item) => item.id)

          this.memberAdminGroups = this.allAdminGroups.filter(
            (group: { id: string; name: string }) =>
              memberIds.includes(group.id)
          )

          resolve()
        },
        (err: any) => {
          this.loggerService.error(
            'Error while getting user group memberships',
            err
          )
          this.memberAdminGroups = this.allAdminGroups

          resolve()
        }
      )
    })
  }

  /**
   * Toggles the SAS Admin group dropdown between the groups the current user
   * is a member of and all groups on the Viya server.
   */
  public onShowAllGroupsChange() {
    this.applyAdminGroupFilter()
  }

  /**
   * Applies the member/all groups filter to the dropdown and makes sure the
   * currently selected group exists in the displayed list.
   */
  private applyAdminGroupFilter() {
    this.adminGroups =
      this.showAllGroups || !this.memberAdminGroups.length
        ? this.allAdminGroups
        : this.memberAdminGroups

    const selectedGroupExists = this.adminGroups.some(
      (group: { id: string; name: string }) =>
        group.id === this.selectedAdminGroup
    )

    if (!selectedGroupExists && this.adminGroups.length) {
      this.selectedAdminGroup = this.adminGroups[0].id
    }
  }

  public async onComputeContextChange(computeContextId: string) {
    // The explicit (ngModelChange) handler runs before the [(ngModel)]
    // assignment updates the field, so synchronise the selected context id
    // here before deriving the running user. Otherwise the displayed user
    // lags behind the dropdown by one change.
    this.selectedComputeContext = computeContextId
    this.updateRunningAsUser()
  }

  public getComputeContextName(id: string): string | undefined {
    return (
      this.computeContexts.find(
        (context: ComputeContextItem) => context.id === id
      )?.name || undefined
    )
  }

  /**
   * Executes sas.json file to deploy the backend
   * Method will first try to run the `auto deploy`
   * If that fails the rest of the code is ignored.
   * If request is successfull, method will continue to try
   * to create database if checkbox is toggled on
   */
  public async executeJson() {
    this.isSubmittingJson = true

    try {
      let uploadJsonFile = await this.sasJs.deployServicePack(
        this.jsonFile,
        this.dcAdapterSettings?.appLoc,
        undefined,
        undefined,
        true
      )

      this.autoDeployStatus.deployServicePack = true
      this.isJsonSubmitted = true
    } catch (ex: any) {
      let textEx = ''

      if (typeof ex.message !== 'string') {
        textEx = JSON.stringify(ex).replace(/\\/gm, '')
      } else {
        textEx = ex.message
      }

      this.autoDeployStatus.deployServicePack = false
      this.eventService.showInfoModal(
        'Deploy error',
        `Exception: \n ${textEx !== '' ? textEx : ex}`
      )
      this.autodeploying = false
      this.autodeployDone = false

      return
    }

    this.isSubmittingJson = false
  }

  public async runAutoDeploy(executeJson: boolean = false) {
    if (!this.deployInNewWindow) this.autodeploying = true

    if (executeJson) {
      this.executeJson()
    }

    if (this.recreateDatabase) {
      this.createDatabase()
    } else {
      if (!this.deployInNewWindow) this.autodeployDone = true
    }
  }

  /**
   * Runs the `makedata` request sending the ADMIN and DCPATH values
   */
  public createDatabase() {
    let data = {
      fromjs: [
        {
          ADMIN: this.selectedAdminGroup,
          DCPATH: this.dcPath
        }
      ]
    }

    // Get and run service using the selected context name
    let selectedComputeContextName = this.sasJsConfig.contextName

    if (this.selectedComputeContext.length && this.computeContexts.length) {
      const computeContextName = this.getComputeContextName(
        this.selectedComputeContext
      )

      if (computeContextName) {
        selectedComputeContextName = computeContextName
      }
    }

    /**
     * We are overriding default `sasjsConfig` object fields with this object fields.
     * Here we want to run this request using original WEB method.
     * contextName: null is the MUST field for it.
     */
    let overrideConfig = {
      useComputeApi: null,
      contextName: selectedComputeContextName,
      debug: true
    }

    if (this.deployInNewWindow) {
      this.runMakedataInNewWindow({
        contextName: selectedComputeContextName,
        admin: this.selectedAdminGroup,
        dcPath: this.dcPath
      })
    } else {
      this.sasJs
        .request(`services/admin/makedata`, data, overrideConfig, () => {
          this.sasService.shouldLogin.next(true)
        })
        .then((res: any) => {
          this.autodeployDone = true

          try {
            this.makeDataResponse = JSON.stringify(res)
          } catch {
            this.makeDataResponse = res
          }

          if (res.result && res.result.length > 0) {
            this.autoDeployStatus.runMakeData = true
          } else {
            this.autoDeployStatus.runMakeData = false
          }

          if (typeof res.sasjsAbort !== 'undefined') {
            const abortRes = res
            const abortMsg = abortRes.sasjsAbort[0].MSG
            const macMsg = abortRes.sasjsAbort[0].MAC

            this.eventService.showAbortModal('makedata', abortMsg, {
              SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
              SYSERRORTEXT: abortRes.SYSERRORTEXT,
              MAC: macMsg,
              _PROGRAM: abortRes._PROGRAM
            })
          }

          if (this.helperService.isStreamingViya())
            this.updateIndexHtmlComputeContext()
        })
        .catch((err: any) => {
          this.eventService.showAbortModal('makedata', JSON.stringify(err))
          this.autoDeployStatus.runMakeData = false
          this.autodeployDone = true

          try {
            this.makeDataResponse = JSON.stringify(err)
          } catch {
            this.makeDataResponse = err
          }
        })
    }
  }

  public runMakedataInNewWindow(params: {
    contextName: string
    admin: string
    dcPath: string
  }) {
    let serverUrl = this.sasjsConfig.serverUrl
    let appLoc = this.sasjsConfig.appLoc
    const execPath = this.sasService.getExecutionPath()
    let contextname = `&_contextname=${params.contextName}`
    let admin = `&admin=${params.admin}`
    let dcPath = `&dcpath=${params.dcPath}`
    // Debug is ALWAYS enabled for makedata — it runs only once during
    // deployment and the full log is needed to diagnose any issues.
    let debug = '&_debug=131'

    let programUrl =
      serverUrl +
      execPath +
      '/?_program=' +
      appLoc +
      '/services/admin/makedata' +
      contextname +
      admin +
      dcPath +
      debug

    window.open(programUrl)

    // Poll for makedata completion. The makedata service self-deletes
    // when it finishes successfully, so we check if it's still present
    // in the admin folder. When it's gone, reload the app fresh.
    this.pollMakedataCompletion()
  }

  private makedataPollInterval: any = null

  private pollMakedataCompletion() {
    if (this.makedataPollInterval) {
      clearInterval(this.makedataPollInterval)
    }

    let attempts = 0
    const maxAttempts = 120 // 10 minutes at 5s intervals

    this.makedataPollInterval = setInterval(async () => {
      attempts++
      console.log(
        `pollMakedataCompletion: checking if makedata is gone (attempt ${attempts}/${maxAttempts})`
      )

      try {
        const makedataGone = await this.sasService.viyaMakedataSuccessfull()
        if (makedataGone) {
          console.log(
            'pollMakedataCompletion: makedata job is gone, reloading app'
          )
          clearInterval(this.makedataPollInterval)
          this.makedataPollInterval = null

          // Reload the app fresh from the SASJobExecution URL
          window.location.href = window.location.href.split('#')[0]
        }
      } catch (err) {
        console.error('pollMakedataCompletion: error checking', err)
      }

      if (attempts >= maxAttempts) {
        console.warn(
          'pollMakedataCompletion: timed out after 10 minutes, stopping poll'
        )
        clearInterval(this.makedataPollInterval)
        this.makedataPollInterval = null
      }
    }, 5000)
  }

  /**
   * Only when on Viya streamed app, this method will update the `contextname` in the `index.html` on the SAS drive
   * This is needed to ensure that the DC will use the same compute context `makedata` service used to run against.
   */
  public async updateIndexHtmlComputeContext() {
    const filenamePath = location.search.split('/').pop()
    const filename = filenamePath?.includes('.') ? filenamePath : undefined

    if (!filename) {
      this.eventService.showAbortModal(
        null,
        'We could not figure out the file name of `index.html` based on the url.'
      )
      return
    }

    const indexHtmlContent = await this.sasService.getFileContent(
      `${this.appLoc}/services`,
      filename
    )

    if (!indexHtmlContent) {
      this.loggerService.error(
        `Failed to get ${filename} at ${this.appLoc}/services`
      )
      return
    }

    const computeContextName = this.getComputeContextName(
      this.selectedComputeContext
    )

    if (!computeContextName) {
      this.loggerService.error(
        `Compute context name not found for ID: ${this.selectedComputeContext} | List: ${JSON.stringify(this.computeContexts)}`
      )
      return
    }

    /* Use a function replacement so $ in computeContextName is treated as a
      literal character. In a string replacement, $ has special meaning
      ($&, $1, $` etc.); a function return value is used verbatim. */
    const updatedContent = indexHtmlContent.replace(
      /contextname="[^"]*"/g,
      () => `contextname="${computeContextName}"`
    )

    await this.sasService
      .updateFileContent(`${this.appLoc}/services`, filename, updatedContent)
      .catch((err: any) => {
        this.loggerService.error(`Failed to update DataController.html: ${err}`)
      })
  }

  public downloadFile(
    content: any,
    filename: string,
    extension: string = 'txt'
  ) {
    this.deployService.downloadFile(content, filename, extension)
  }

  public async onJsonFileChange(event: any) {
    let file = event.target.files[0]

    this.jsonFile = await this.deployService.readFile(file)
  }

  public recreateDatabaseClicked(event: Event) {
    ;(<HTMLInputElement>event.target).checked === true
      ? (this.recreateDatabaseModal = true)
      : ''
  }

  public clearUploadInput(event: Event) {
    this.deployService.clearUploadInput(event)
  }

  public openSasRequestsModal() {
    this.eventService.openRequestsModal()
  }

  public navigateToHome() {
    this.onNavigateToHome.emit()
  }
}
@if (autodeploying) {
  <div class="auto-deploy">
    <div class="spinner-box">
      @if (!autodeployDone) {
        <span class="spinner spinner-md"> Loading... </span>
        <p>Deploying...</p>
      }
      @if (autodeployDone) {
        <p class="m-0 align-self-start">Done</p>
        <hr class="w-100" />
        @if (autoDeployStatus.deployServicePack !== null) {
          <div class="deploy-status-row">
            @if (autoDeployStatus.deployServicePack === true) {
              <clr-icon
                class="deploy-success"
                shape="success-standard"
              ></clr-icon>
            }
            @if (!autoDeployStatus.deployServicePack === false) {
              <clr-icon class="deploy-error" shape="times-circle"></clr-icon>
            }
            <p>Deploy SAS Jobs</p>
          </div>
        }
        @if (autoDeployStatus.runMakeData !== null) {
          <div class="deploy-status-row">
            @if (autoDeployStatus.runMakeData) {
              <clr-icon
                class="deploy-success"
                shape="success-standard"
              ></clr-icon>
            }
            @if (autoDeployStatus.runMakeData === false) {
              <clr-icon class="deploy-error" shape="times-circle"></clr-icon>
            }
            <p>Create database</p>
          </div>
        }
        <hr class="w-100" />
        <div class="buttons">
          <button (click)="navigateToHome()" class="btn btn-primary mt-15 mr-0">
            @if (
              autoDeployStatus.deployServicePack === false ||
              autoDeployStatus.runMakeData === false
            ) {
              <clr-icon class="deploy-error" shape="times-circle"></clr-icon>
            }
            LAUNCH
          </button>
          <button
            (click)="
              downloadFile(makeDataResponse, 'create-database-log', 'txt')
            "
            class="btn btn-primary-outline mt-15 mr-0"
          >
            Download log
          </button>
        </div>
        <hr class="w-100" />
        <div class="buttons">
          <button
            (click)="autodeploying = false; autodeployDone = false"
            class="btn btn-primary-outline mt-15 mr-0 align-self-end"
          >
            Close
          </button>
          <button
            (click)="openSasRequestsModal()"
            class="btn btn-primary-outline mt-15 mr-0 align-self-end"
          >
            SAS Requests
          </button>
        </div>
      }
    </div>
  </div>
}

<h4 class="text-center my-15">Viya Deploy</h4>
<p class="deploy-intro">
  Configure and deploy Data Controller to your Viya environment. Deployment
  creates the backend SAS services and the control library used by the
  application.
</p>
<hr />

<div class="deploy-field">
  <label class="clr-control-label">App Loc</label>
  <p class="deploy-field-description">
    The SAS drive folder in which the Data Controller frontend and services are
    deployed. Derived from your deployment configuration &mdash; it cannot be
    changed here.
  </p>
  <div class="clr-control-container">
    <div class="clr-input-wrapper">
      <p class="m-0">{{ appLoc }}</p>
    </div>
  </div>
</div>

<div class="deploy-field">
  <label for="dcloc" class="clr-control-label">DC Loc</label>
  <p class="deploy-field-description">
    A physical directory on the compute server to which the deployment user can
    write. Data Controller stores its control tables, staging area and audit
    history here.
  </p>
  <div class="clr-control-container dc-loc-input-wrapper">
    <div class="clr-input-wrapper small-mt">
      <input clrInput name="dcloc" [(ngModel)]="dcPath" />
    </div>
  </div>
</div>

<div class="deploy-field">
  <label for="adminGroup" class="clr-control-label">SAS Admin group</label>
  <p class="deploy-field-description">
    The group you select becomes the Data Controller Admin Group &mdash;
    everyone in it will have unrestricted access to Data Controller. By default
    the list is filtered to the groups your identity
    @if (currentUserInfo?.id) {
      (<strong>{{ currentUserInfo?.id }}</strong
      >)
    }
    is a member of. Toggle below to list every group on the Viya server.
  </p>
  <clr-toggle-container class="mt-0">
    <clr-toggle-wrapper>
      <input
        id="show-all-groups-toggle"
        type="checkbox"
        [(ngModel)]="showAllGroups"
        (ngModelChange)="onShowAllGroupsChange()"
        clrToggle
      />
      <label for="show-all-groups-toggle">Show all groups</label>
    </clr-toggle-wrapper>
  </clr-toggle-container>
  <div class="clr-control-container">
    <div class="clr-input-wrapper small-mt">
      @if (!adminGroupsLoading) {
        <select clrSelect name="adminGroup" [(ngModel)]="selectedAdminGroup">
          @for (adminGroup of adminGroups; track adminGroup) {
            <option [value]="adminGroup.id">
              {{ adminGroup.name }}
            </option>
          }
        </select>
      }
      @if (adminGroupsLoading) {
        <clr-spinner clrInline class="spinner-sm"></clr-spinner>
      }
    </div>
  </div>
</div>

@if (!showAllContexts) {
  <div class="deploy-field">
    <label for="batchId" class="clr-control-label">Batch ID</label>
    <p class="deploy-field-description">
      The batch identity (<code>runServerAs</code>) under which Data Controller
      jobs will execute. It is used to filter the Compute Context dropdown below
      to only the contexts running as this identity.
    </p>
    <div class="clr-control-container">
      <div class="clr-input-wrapper small-mt">
        @if (!computeContextsLoading) {
          <select
            clrSelect
            name="batchId"
            [(ngModel)]="selectedBatchId"
            (ngModelChange)="onBatchIdChange()"
          >
            @for (batchId of batchIds; track batchId) {
              <option [value]="batchId">
                {{ batchId }}
              </option>
            }
          </select>
        }
        @if (computeContextsLoading) {
          <clr-spinner clrInline class="spinner-sm"></clr-spinner>
        }
      </div>
    </div>
  </div>
}

<div class="deploy-field">
  <label for="computeContext" class="clr-control-label">Compute Context</label>
  <p class="deploy-field-description">
    The compute context used to run the Data Controller services (including the
    <code>makedata</code> deployment job). Only reusable contexts matching the
    selected Batch ID are shown, unless the toggle below is enabled.
  </p>
  <clr-toggle-container class="mt-0">
    <clr-toggle-wrapper>
      <input
        id="show-all-contexts-toggle"
        type="checkbox"
        [(ngModel)]="showAllContexts"
        (ngModelChange)="onShowAllContextsChange()"
        clrToggle
      />
      <label for="show-all-contexts-toggle">Show all compute contexts</label>
    </clr-toggle-wrapper>
  </clr-toggle-container>
  <div class="clr-control-container">
    <div class="clr-input-wrapper small-mt">
      @if (!computeContextsLoading) {
        <select
          clrSelect
          name="computeContext"
          (ngModelChange)="onComputeContextChange($event)"
          [(ngModel)]="selectedComputeContext"
        >
          @for (
            computeContext of getFilteredComputeContexts();
            track computeContext
          ) {
            <option [value]="computeContext.id">
              {{ computeContext.name }}
            </option>
          }
        </select>
      }
      @if (computeContextsLoading) {
        <clr-spinner clrInline class="spinner-sm"></clr-spinner>
      }
    </div>
  </div>
</div>

@if (runningAsUser) {
  <div class="deploy-field">
    <label class="clr-control-label">Running as user</label>
    <p class="deploy-field-description">
      The identity under which Data Controller jobs will run with the selected
      compute context. This user must be able to write to the DC Loc above.
    </p>
    <div class="clr-control-container">
      <div class="clr-input-wrapper">
        <p class="m-0">
          <strong>{{ runningAsUser }}</strong>
        </p>
      </div>
    </div>
  </div>
}

<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->

<!-- <clr-checkbox-wrapper>
  <input
    clrCheckbox
    [(ngModel)]="recreateDatabase"
    (click)="recreateDatabaseClicked($event)"
    type="checkbox"
    checked
    />
  <label>Recreate database</label>
</clr-checkbox-wrapper> -->

<hr />

<button
  (click)="runAutoDeploy()"
  class="btn-autodeploy btn btn-primary d-inline-block mr-10"
>
  Deploy
</button>

<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->

<!-- <button
(click)="executeJson()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
[disabled]="!jsonFile"
>
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
</button> -->

<!-- <button
(click)="uploadJsonAuto.click()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
>
Upload different file to deploy
</button>
<input
  #uploadJsonAuto
  type="file"
  hidden
  (click)="clearUploadInput($event)"
  (change)="onJsonFileChange($event)"
  /> -->

<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
  <h3 class="modal-title">Warning</h3>
  <div class="modal-body">
    This action will recreate the database (if it exists). For an initial
    deployment, this is expected. If this is a re-deployment, you will lose any
    existing tables in <strong>{{ dcPath }}</strong
    >.
  </div>
  <div class="modal-footer">
    <button
      type="button"
      class="btn btn-sm btn-primary"
      (click)="recreateDatabaseModal = false; recreateDatabase = false"
    >
      Cancel
    </button>
    <button
      type="button"
      class="btn btn-sm btn-primary"
      (click)="recreateDatabaseModal = false; recreateDatabase = true"
    >
      Ok
    </button>
  </div>
</clr-modal>

./automatic.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""