474 lines
13 KiB
TypeScript
474 lines
13 KiB
TypeScript
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'
|
|
|
|
@Component({
|
|
selector: 'app-automatic-deploy',
|
|
templateUrl: './automatic.component.html',
|
|
styleUrls: ['./automatic.component.scss'],
|
|
encapsulation: ViewEncapsulation.None
|
|
})
|
|
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 }[] = []
|
|
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.getAdminGroups()
|
|
await this.getComputeContexts()
|
|
await this.getCurrentUser()
|
|
|
|
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(
|
|
(res: ViyaComputeContexts) => {
|
|
this.computeContextsLoading = false
|
|
|
|
const defaultContext = res.items.find(
|
|
(item: ComputeContextItem) =>
|
|
item.name === 'SAS Job Execution compute context'
|
|
)
|
|
|
|
if (defaultContext) {
|
|
this.selectedComputeContext = defaultContext.id
|
|
}
|
|
|
|
this.computeContexts = res.items
|
|
|
|
resolve()
|
|
},
|
|
(err) => {
|
|
reject(err)
|
|
}
|
|
)
|
|
})
|
|
}
|
|
|
|
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((res: ViyaApiIdentities) => {
|
|
this.adminGroupsLoading = false
|
|
// Map admin groups with only needed fields
|
|
this.adminGroups = res.items.map((item: Item) => {
|
|
return {
|
|
id: item.id,
|
|
name: item.name
|
|
}
|
|
})
|
|
|
|
resolve()
|
|
}),
|
|
(err: any) => {
|
|
this.adminGroupsLoading = false
|
|
this.loggerService.error('Error while getting admin groups', err)
|
|
this.eventService.showAbortModal('admin groups', err)
|
|
|
|
reject(err)
|
|
})
|
|
})
|
|
}
|
|
|
|
public async onComputeContextChange(computeContextId: string) {
|
|
this.sasViyaService
|
|
.getComputeContextById(computeContextId)
|
|
.subscribe((res: ComputeContextDetails) => {
|
|
if (res.attributes && res.attributes.runServerAs) {
|
|
this.runningAsUser = res.attributes.runServerAs
|
|
} else {
|
|
this.runningAsUser = this.currentUserInfo?.id || 'unknown'
|
|
}
|
|
})
|
|
}
|
|
|
|
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
|
|
})
|
|
}
|
|
|
|
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}`
|
|
let debug = `&_debug=131`
|
|
|
|
let programUrl =
|
|
serverUrl +
|
|
execPath +
|
|
'/?_program=' +
|
|
appLoc +
|
|
'/services/admin/makedata' +
|
|
contextname +
|
|
admin +
|
|
dcPath +
|
|
debug
|
|
|
|
window.open(programUrl)
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|