src/app/deploy/sections/automatic/automatic.component.ts
| encapsulation | ViewEncapsulation.None |
| selector | app-automatic-deploy |
| styleUrls | ./automatic.component.scss |
| templateUrl | ./automatic.component.html |
Properties |
|
Methods |
|
Inputs |
Outputs |
constructor(eventService: EventService, deployService: DeployService, sasService: SasService, sasViyaService: SasViyaService, loggerService: LoggerService, helperService: HelperService)
|
|||||||||||||||||||||
|
Parameters :
|
| 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 : ''
|
|
| onNavigateToHome | |
Type : EventEmitter<any>
|
|
| 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 :
Returns :
void
|
| Public createDatabase |
createDatabase()
|
|
Runs the
Returns :
void
|
| Public downloadFile | ||||||||||||||||
downloadFile(content: any, filename: string, extension: string)
|
||||||||||||||||
|
Parameters :
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
Returns :
any
|
| Public extractRunAs | ||||||
extractRunAs(details: ComputeContextDetails)
|
||||||
|
Pulls the runAs (batch) identity out of a compute context. The batch user
is exposed as
Parameters :
Returns :
string | null
|
| Public Async getAdminGroups |
getAdminGroups()
|
|
Returns :
unknown
|
| Public Async getComputeContextDetails |
getComputeContextDetails()
|
|
The
Returns :
any
|
| Public getComputeContextName | ||||||
getComputeContextName(id: string)
|
||||||
|
Parameters :
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
(
Returns :
ComputeContextInfo[]
|
| 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 :
Returns :
any
|
| Public Async onJsonFileChange | ||||||
onJsonFileChange(event: any)
|
||||||
|
Parameters :
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 :
Returns :
void
|
| Public Async runAutoDeploy | ||||||||
runAutoDeploy(executeJson: boolean)
|
||||||||
|
Parameters :
Returns :
any
|
| Public runMakedataInNewWindow | ||||||
runMakedataInNewWindow(params: { contextName: string; admin: string; dcPath: string })
|
||||||
|
Parameters :
Returns :
void
|
| Public Async updateIndexHtmlComputeContext |
updateIndexHtmlComputeContext()
|
|
Only when on Viya streamed app, this method will update the
Returns :
any
|
| Private updateRunningAsUser |
updateRunningAsUser()
|
|
Returns :
void
|
| 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.
|
| 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 |
| 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 |
| 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 — 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 —
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