fix: viya deploy page improved flow
All checks were successful
Build / Build-and-ng-test (pull_request) Successful in 4m10s
Build / Build-and-test-development (pull_request) Successful in 8m43s

This commit is contained in:
Mihajlo Medjedovic
2025-05-21 14:13:03 +02:00
parent 6a7dd451b5
commit 4bd215491f
11 changed files with 246 additions and 61 deletions

View File

@ -21,7 +21,7 @@
"@clr/icons": "^13.0.2", "@clr/icons": "^13.0.2",
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz", "@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
"@handsontable/angular": "^15.3.0", "@handsontable/angular": "^15.3.0",
"@sasjs/adapter": "file:../../../sasjs/adapter/build/sasjs-adapter-5.0.0.tgz", "@sasjs/adapter": "^4.11.0",
"@sasjs/utils": "^3.4.0", "@sasjs/utils": "^3.4.0",
"@sheet/crypto": "file:libraries/sheet-crypto.tgz", "@sheet/crypto": "file:libraries/sheet-crypto.tgz",
"@types/d3-graphviz": "^2.6.7", "@types/d3-graphviz": "^2.6.7",
@ -5912,9 +5912,9 @@
] ]
}, },
"node_modules/@sasjs/adapter": { "node_modules/@sasjs/adapter": {
"version": "5.0.0", "version": "4.11.3",
"resolved": "file:../../../sasjs/adapter/build/sasjs-adapter-5.0.0.tgz", "resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-4.11.3.tgz",
"integrity": "sha512-pAjHuL3UZw8LB+Ac2XlY2Td1EF0/mlbJRjZaF/V+2IV5V0ioBQTB5B2fMbtm+W2UJCPphajrXQTn+NEoCQ5Syw==", "integrity": "sha512-KF6G4vzs4l4efjpCD02og3kB44uFfJ1u2UWu749VdHtLKNN9l+PO26/moR+YAmRmmz2I9sC3X09fZE1nlN6zgw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {

View File

@ -57,25 +57,6 @@ export class DeployComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
if (this.sasJsConfig.serverType === ServerType.SasViya) {
fetch('sasbuild/viya.json')
.then((res) => res.text())
.then((res) => {
let initJsonFile: any = null
try {
initJsonFile = JSON.parse(res)
} catch (err) {
console.error(err)
}
if (initJsonFile) {
this.jsonFile = initJsonFile
this.loggerService.log(this.jsonFile)
}
})
}
this.setDeployDefaults() this.setDeployDefaults()
} }

View File

@ -9,14 +9,17 @@
<p class="m-0 align-self-start">Done</p> <p class="m-0 align-self-start">Done</p>
<hr class="w-100" /> <hr class="w-100" />
<div class="deploy-status-row"> <div
*ngIf="autoDeployStatus.deployServicePack !== null"
class="deploy-status-row"
>
<clr-icon <clr-icon
*ngIf="autoDeployStatus.deployServicePack" *ngIf="autoDeployStatus.deployServicePack === true"
class="deploy-success" class="deploy-success"
shape="success-standard" shape="success-standard"
></clr-icon> ></clr-icon>
<clr-icon <clr-icon
*ngIf="!autoDeployStatus.deployServicePack" *ngIf="!autoDeployStatus.deployServicePack === false"
class="deploy-error" class="deploy-error"
shape="times-circle" shape="times-circle"
></clr-icon> ></clr-icon>
@ -94,9 +97,9 @@
</div> </div>
<label for="dcloc" class="mt-20 clr-control-label">DC Loc</label> <label for="dcloc" class="mt-20 clr-control-label">DC Loc</label>
<div class="mb-10 clr-control-container"> <div class="mb-10 clr-control-container dc-loc-input-wrapper">
<div class="clr-input-wrapper"> <div class="clr-input-wrapper">
<p class="mt-0">{{ dcPath }}</p> <input clrInput name="dcloc" [(ngModel)]="dcPath" />
</div> </div>
</div> </div>
@ -107,7 +110,9 @@
</div> </div>
</div> </div>
<clr-checkbox-wrapper> <!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
<!-- <clr-checkbox-wrapper>
<input <input
clrCheckbox clrCheckbox
[(ngModel)]="recreateDatabase" [(ngModel)]="recreateDatabase"
@ -116,19 +121,28 @@
checked checked
/> />
<label>Recreate database</label> <label>Recreate database</label>
</clr-checkbox-wrapper> </clr-checkbox-wrapper> -->
<hr /> <hr />
<button <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()" (click)="executeJson()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10" class="btn-autodeploy btn btn-primary d-inline-block mr-10"
[disabled]="!jsonFile" [disabled]="!jsonFile"
> >
Deploy {{ !jsonFile ? '(json file is not available)' : '' }} Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
</button> </button> -->
<button <!-- <button
(click)="uploadJsonAuto.click()" (click)="uploadJsonAuto.click()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10" class="btn-autodeploy btn btn-primary d-inline-block mr-10"
> >
@ -140,7 +154,7 @@
hidden hidden
(click)="clearUploadInput($event)" (click)="clearUploadInput($event)"
(change)="onJsonFileChange($event)" (change)="onJsonFileChange($event)"
/> /> -->
<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false"> <clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
<h3 class="modal-title">Warning</h3> <h3 class="modal-title">Warning</h3>

View File

@ -36,7 +36,11 @@ export class AutomaticComponent implements OnInit {
public recreateDatabaseModal: boolean = false public recreateDatabaseModal: boolean = false
public isSubmittingJson: boolean = false public isSubmittingJson: boolean = false
public isJsonSubmitted: boolean = false public isJsonSubmitted: boolean = false
public recreateDatabase: 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 createDatabaseLoading: boolean = false
/** autoDeployStatus /** autoDeployStatus
@ -71,7 +75,6 @@ export class AutomaticComponent implements OnInit {
* to create database if checkbox is toggled on * to create database if checkbox is toggled on
*/ */
public async executeJson() { public async executeJson() {
this.autodeploying = true
this.isSubmittingJson = true this.isSubmittingJson = true
try { try {
@ -106,6 +109,14 @@ export class AutomaticComponent implements OnInit {
} }
this.isSubmittingJson = false this.isSubmittingJson = false
}
public async runAutoDeploy(executeJson: boolean = false) {
this.autodeploying = true
if (executeJson) {
this.executeJson()
}
if (this.recreateDatabase) { if (this.recreateDatabase) {
this.createDatabase() this.createDatabase()
@ -133,7 +144,7 @@ export class AutomaticComponent implements OnInit {
* contextName: null is the MUST field for it. * contextName: null is the MUST field for it.
*/ */
let overrideConfig = { let overrideConfig = {
useComputeApi: false, useComputeApi: null,
contextName: this.sasJsConfig.contextName, contextName: this.sasJsConfig.contextName,
debug: true debug: true
} }

View File

@ -274,7 +274,7 @@ export class ManualComponent implements OnInit {
* contextName: null is the MUST field for it. * contextName: null is the MUST field for it.
*/ */
let overrideConfig = { let overrideConfig = {
useComputeApi: false, useComputeApi: null,
contextName: this.sasJsConfig.contextName, contextName: this.sasJsConfig.contextName,
debug: true debug: true
} }

View File

@ -4,6 +4,8 @@ import { Observable } from 'rxjs'
import { Collection } from '../viya-api-explorer/models/collection.model' import { Collection } from '../viya-api-explorer/models/collection.model'
import { AppStoreService } from './app-store.service' import { AppStoreService } from './app-store.service'
import { ViyaApis } from '../viya-api-explorer/models/viya-apis.models' import { ViyaApis } from '../viya-api-explorer/models/viya-apis.models'
import { ViyaApiFolderMembers } from '../viya-api-explorer/models/viya-api-folder-content.model'
import { ViyaApiFolder } from '../viya-api-explorer/models/viya-api-folder.model'
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -22,7 +24,8 @@ export class SasViyaService {
}, },
Compute: { Compute: {
jobs: '/jobDefinitions', jobs: '/jobDefinitions',
jobExecution: '/jobExecution' jobExecution: '/jobExecution',
contexts: '/compute/contexts'
}, },
Decision_Management: { Decision_Management: {
modelManagement: '/modelManagement', modelManagement: '/modelManagement',
@ -97,4 +100,32 @@ export class SasViyaService {
withCredentials: true withCredentials: true
}) })
} }
getComputeContexts(): Observable<any> {
return this.http.get<any>(`${this.serverUrl}/compute/contexts`, {
withCredentials: true
})
}
/**
* @param path Path to the folder
* @returns The folder info object
*/
getFolderByPath(path: string): Observable<ViyaApiFolder> {
return this.http.get<any>(
`${this.serverUrl}/folders/folders/@item?path=${path}`,
{
withCredentials: true
}
)
}
getFolderMembers(folderId: string): Observable<ViyaApiFolderMembers> {
return this.http.get<any>(
`${this.serverUrl}/folders/folders/${folderId}/members`,
{
withCredentials: true
}
)
}
} }

View File

@ -16,6 +16,9 @@ import { RequestWrapperOptions } from '../models/request-wrapper/RequestWrapperO
import { ErrorBody } from '../models/ErrorBody' import { ErrorBody } from '../models/ErrorBody'
import { UploadFileResponse } from '../models/UploadFile' import { UploadFileResponse } from '../models/UploadFile'
import { RequestWrapperResponse } from '../models/request-wrapper/RequestWrapperResponse' 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'
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -38,6 +41,7 @@ export class SasService {
private userService: UserService, private userService: UserService,
private eventService: EventService, private eventService: EventService,
private sasjsService: SasjsService, private sasjsService: SasjsService,
private sasViyaService: SasViyaService,
private loggerService: LoggerService, private loggerService: LoggerService,
private router: Router private router: Router
) {} ) {}
@ -423,21 +427,93 @@ export class SasService {
typeof this.sasjsAdapter.getFolder !== 'undefined' typeof this.sasjsAdapter.getFolder !== 'undefined'
let appLocExists: boolean = false let appLocExists: boolean = false
let errorMessage: string | undefined = undefined
if (getFolderExistsInAdapter) { if (getFolderExistsInAdapter) {
appLocExists = await this.appLocCheck(path) const results = await this.appLocCheck(path)
appLocExists = results.found
errorMessage = results.errorMessage
} else { } else {
appLocExists = await this.appLocCheckPreAxiosdAdapter(path) appLocExists = await this.appLocCheckPreAxiosdAdapter(path)
} }
if (appLocExists) { if (appLocExists) {
this.loadStartupServiceEmitter.emit() // 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.viyaMakedataSuccessfull().then(
(success: boolean) => {
if (success) {
this.loadStartupServiceEmitter.emit()
} else {
this.eventService.startupDataLoaded()
this.router.navigateByUrl('/deploy')
}
},
(error: any) => {
console.error('Error while looking for the file: makedata.sas', error)
}
)
} else {
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 appLocCheck(path: string): Promise<boolean> { private async viyaMakedataSuccessfull(): Promise<boolean> {
return new Promise((resolve, reject) => {
const sasjsConfig = this.getSasjsConfig()
const configuratorFolder = `${sasjsConfig.appLoc}/services/admin`
this.sasViyaService.getFolderByPath(configuratorFolder).subscribe(
(folderInfo: ViyaApiFolder) => {
const folderId = folderInfo.id
if (!folderId) {
console.error(
`Folder ID is not present. ${configuratorFolder}`,
sasjsConfig
)
resolve(false)
}
this.sasViyaService.getFolderMembers(folderId).subscribe(
(members: ViyaApiFolderMembers) => {
if (
!members.items.some((item: any) => item.name === 'makedata')
) {
// Makedata.sas is not present, which means it was run
resolve(true)
} else {
// Makedata.sas is present, which means it was not run
resolve(false)
}
},
(err: any) => {
console.error('Error getting folder contents', err)
reject()
}
)
},
(err: any) => {
console.warn('Error getting folder info', err)
reject(err)
}
)
})
}
public appLocCheck(
path: string
): Promise<{ found: boolean; errorMessage?: string }> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
let statusNotFound: boolean = false let fetchError: string = ''
let res: any let res: any
@ -448,20 +524,21 @@ export class SasService {
this.appLocCheckPending = true this.appLocCheckPending = true
this.shouldLogin.next(true) this.shouldLogin.next(true)
resolve(false) resolve({ found: false })
} else if (err.name === 'NotFoundeError') {
fetchError = err.message
} else { } else {
statusNotFound = true fetchError =
'Viya services are not present on the current appLoc, or API not reachable. Check the ADAPTER configuration.'
} }
} }
if (statusNotFound) { if (fetchError.length) {
console.warn('Viya services are not present on the current appLoc.') console.warn(fetchError)
this.eventService.startupDataLoaded() return resolve({ found: false, errorMessage: fetchError })
this.router.navigateByUrl('/deploy')
return resolve(false)
} }
resolve(true) resolve({ found: true })
}) })
} }

View File

@ -0,0 +1,45 @@
export interface ViyaApiFolderMembers {
version: number
accept: string
count: number
start: number
limit: number
name: string
items: Item[]
links: Link2[]
}
export interface Link2 {
method: string
rel: string
href: string
uri: string
type: string
itemType?: string
responseType?: string
}
export interface Item {
creationTimeStamp: string
createdBy: string
modifiedTimeStamp: string
modifiedBy: string
version: number
id: string
name: string
added: string
parentFolderUri: string
uri: string
type: string
contentType: string
links: Link[]
}
export interface Link {
method: string
rel: string
href: string
uri: string
type?: string
responseType?: string
}

View File

@ -0,0 +1,26 @@
/**
* Viya API Folder info object
*/
export interface ViyaApiFolder {
creationTimeStamp: string
createdBy: string
modifiedTimeStamp: string
modifiedBy: string
version: number
id: string
name: string
parentFolderUri: string
type: string
memberCount: number
links: Link[]
}
export interface Link {
method: string
rel: string
href: string
uri: string
type?: string
responseType?: string
itemType?: string
}

18
sas/package-lock.json generated
View File

@ -6,8 +6,8 @@
"": { "": {
"name": "dc-sas", "name": "dc-sas",
"dependencies": { "dependencies": {
"@sasjs/cli": "^4.12.2", "@sasjs/cli": "^4.12.5",
"@sasjs/core": "^4.56.1" "@sasjs/core": "^4.57.0"
} }
}, },
"node_modules/@coolaj86/urequest": { "node_modules/@coolaj86/urequest": {
@ -45,14 +45,14 @@
} }
}, },
"node_modules/@sasjs/cli": { "node_modules/@sasjs/cli": {
"version": "4.12.2", "version": "4.12.5",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.12.2.tgz", "resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-4.12.5.tgz",
"integrity": "sha512-/U0V11WI4QJezCpYgbqil/InN46Hges4sknh/Jyqo10VemcezEAGjTdfVIS1P09RUQ0Atdx0OSCh6TpMMXNWwQ==", "integrity": "sha512-y6JFATKlTyTl0gRPpDBPL1rwZsyeuyp5uEz7HMA7raSzQuNa6QZ1oO1Er91I7+cLUg0Ndh5aSNGKYOdBRStQ2g==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@sasjs/adapter": "4.11.3", "@sasjs/adapter": "4.11.3",
"@sasjs/core": "4.56.1", "@sasjs/core": "4.57.0",
"@sasjs/lint": "2.4.3", "@sasjs/lint": "2.4.3",
"@sasjs/utils": "3.5.2", "@sasjs/utils": "3.5.2",
"adm-zip": "0.5.10", "adm-zip": "0.5.10",
@ -77,9 +77,9 @@
} }
}, },
"node_modules/@sasjs/core": { "node_modules/@sasjs/core": {
"version": "4.56.1", "version": "4.57.0",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.56.1.tgz", "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.57.0.tgz",
"integrity": "sha512-RI/DrQ+aluFsX2i+K6M66N+sUNnMLHpFKF5IDDYkgA8vLaiEXfJaqrNeNL2FhTRFRrdvv0bCspmeZEK8fYelhQ==", "integrity": "sha512-iJiLnW4oY15InGerXXWtrjc1YpJ9UDz72+r7Odfr/yYR7RxIhtXGjYQIqyQu6US+cS/0b2pi12LZB6VnfMS/pA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sasjs/lint": { "node_modules/@sasjs/lint": {

View File

@ -28,7 +28,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/cli": "^4.12.2", "@sasjs/cli": "^4.12.5",
"@sasjs/core": "^4.56.1" "@sasjs/core": "^4.57.0"
} }
} }