feat: viya deploy context
Some checks failed
Build / Build-and-ng-test (pull_request) Failing after 47s
Build / Build-and-test-development (pull_request) Successful in 8m35s

This commit is contained in:
Mihajlo Medjedovic 2025-06-03 20:22:39 +02:00
parent 997f09adde
commit 6c96ef7fb0
6 changed files with 242 additions and 36 deletions

View File

@ -98,14 +98,14 @@
<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 dc-loc-input-wrapper"> <div class="mb-10 clr-control-container dc-loc-input-wrapper">
<div class="clr-input-wrapper"> <div class="clr-input-wrapper small-mt">
<input clrInput name="dcloc" [(ngModel)]="dcPath" /> <input clrInput name="dcloc" [(ngModel)]="dcPath" />
</div> </div>
</div> </div>
<label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label> <label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label>
<div class="mb-10 clr-control-container"> <div class="mb-10 clr-control-container">
<div class="clr-input-wrapper"> <div class="clr-input-wrapper small-mt">
<select <select
*ngIf="!adminGroupsLoading" *ngIf="!adminGroupsLoading"
clrSelect clrSelect
@ -124,6 +124,37 @@
</div> </div>
</div> </div>
<label for="computeContext" class="mt-20 clr-control-label">Compte Context</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper small-mt">
<select
*ngIf="!computeContextsLoading"
clrSelect
name="options"
(ngModelChange)="onComputeContextChange($event)"
[(ngModel)]="selectedComputeContext"
>
<option *ngFor="let computeContext of computeContexts" [value]="computeContext.id">
{{ computeContext.name }}
</option>
</select>
<clr-spinner
clrInline
class="spinner-sm"
*ngIf="computeContextsLoading"
></clr-spinner>
</div>
</div>
<ng-container *ngIf="runningAsUser">
<label for="dcloc" class="mt-20 clr-control-label">Running as user:</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<p class="mt-0">{{ runningAsUser }}</p>
</div>
</div>
</ng-container>
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) --> <!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
<!-- <clr-checkbox-wrapper> <!-- <clr-checkbox-wrapper>

View File

@ -18,6 +18,8 @@ import {
Item, Item,
ViyaApiIdentities ViyaApiIdentities
} from 'src/app/viya-api-explorer/models/viya-api-identities.model' } 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({ @Component({
selector: 'app-automatic-deploy', selector: 'app-automatic-deploy',
@ -35,6 +37,7 @@ export class AutomaticComponent implements OnInit {
@Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>() @Output() onNavigateToHome: EventEmitter<any> = new EventEmitter<any>()
public selectedComputeContext: string = ''
public makeDataResponse: string = '' public makeDataResponse: string = ''
public jsonFile: any = null public jsonFile: any = null
public autodeploying: boolean = false public autodeploying: boolean = false
@ -50,8 +53,11 @@ export class AutomaticComponent implements OnInit {
public createDatabaseLoading: boolean = false public createDatabaseLoading: boolean = false
public adminGroupsLoading: boolean = false public adminGroupsLoading: boolean = false
public currentUserInfoLoading: boolean = false public currentUserInfoLoading: boolean = false
public computeContextsLoading: boolean = false
public adminGroups: { id: string; name: string }[] = [] public adminGroups: { id: string; name: string }[] = []
public runningAsUser: string | undefined
public currentUserInfo: ViyaApiCurrentUser | null = null public currentUserInfo: ViyaApiCurrentUser | null = null
public computeContexts: ComputeContextItem[] = []
/** autoDeployStatus /** autoDeployStatus
* This object presents the status for two steps that we have for deploy. * This object presents the status for two steps that we have for deploy.
@ -77,42 +83,97 @@ export class AutomaticComponent implements OnInit {
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.getAdminGroups() const promiseGetAadminGroups = this.getAdminGroups()
this.getCurrentUser() const getCurrentUser = this.getCurrentUser()
const getComputeContexts = this.getComputeContexts()
Promise.all([promiseGetAadminGroups, getCurrentUser, getComputeContexts])
.then(() => {
if (this.selectedComputeContext) {
this.onComputeContextChange(this.selectedComputeContext)
}
})
}
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 Studio compute context'
)
if (defaultContext) {
this.selectedComputeContext = defaultContext.id
}
this.computeContexts = res.items
resolve()
}, (err) => {
reject(err)
})
})
} }
public async getCurrentUser() { public async getCurrentUser() {
this.currentUserInfoLoading = true return new Promise<void>((resolve, reject) => {
this.currentUserInfoLoading = true
this.sasViyaService this.sasViyaService
.getCurrentUser() .getCurrentUser()
.subscribe((res: ViyaApiCurrentUser) => { .subscribe((res: ViyaApiCurrentUser) => {
this.currentUserInfoLoading = false this.currentUserInfoLoading = false
this.currentUserInfo = res this.currentUserInfo = res
this.dcPath = `/export/viya/homes/${res.id}` this.dcPath = `/export/viya/homes/${res.id}`
resolve()
}, (err) => {
reject(err)
}) })
})
} }
public async getAdminGroups() { public async getAdminGroups() {
this.adminGroupsLoading = true return new Promise<void>((resolve, reject) => {
this.adminGroupsLoading = true
this.sasViyaService.getAdminGroups().subscribe((res: ViyaApiIdentities) => { 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
}
})
}),
(err: any) => {
this.adminGroupsLoading = false this.adminGroupsLoading = false
this.loggerService.error('Error while getting admin groups', err) // Map admin groups with only needed fields
this.eventService.showAbortModal('admin groups', err) 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'
} }
})
} }
/** /**

View File

@ -1,6 +1,6 @@
import { HttpClient } from '@angular/common/http' import { HttpClient, HttpContext, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Observable } from 'rxjs' import { catchError, Observable, throwError } 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'
@ -8,6 +8,8 @@ import { ViyaApiFolderMembers } from '../viya-api-explorer/models/viya-api-folde
import { ViyaApiFolder } from '../viya-api-explorer/models/viya-api-folder.model' import { ViyaApiFolder } from '../viya-api-explorer/models/viya-api-folder.model'
import { ViyaApiIdentities } from '../viya-api-explorer/models/viya-api-identities.model' import { ViyaApiIdentities } from '../viya-api-explorer/models/viya-api-identities.model'
import { ViyaApiCurrentUser } from '../viya-api-explorer/models/viya-api-current-user.model' import { ViyaApiCurrentUser } from '../viya-api-explorer/models/viya-api-current-user.model'
import { ViyaComputeContexts } from '../viya-api-explorer/models/viya-compute-contexts.model'
import { ComputeContextDetails } from '../viya-api-explorer/models/viya-compute-context-details.model'
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -75,7 +77,7 @@ export class SasViyaService {
this.serverUrl = adapterConfig?.serverUrl || '' this.serverUrl = adapterConfig?.serverUrl || ''
//example collection request // example collection request
// this.getByCollection('jobs').subscribe((res) => { // this.getByCollection('jobs').subscribe((res) => {
// console.log('res', res) // console.log('res', res)
// }) // })
@ -95,7 +97,7 @@ export class SasViyaService {
* @returns * @returns
*/ */
getByUrl(url: string): Observable<Collection> { getByUrl(url: string): Observable<Collection> {
return this.http.get<Collection>(`${this.serverUrl}${url}`, { return this.get<Collection>(`${this.serverUrl}${url}`, {
withCredentials: true withCredentials: true
}) })
} }
@ -106,13 +108,19 @@ export class SasViyaService {
* @returns * @returns
*/ */
getByCollection(apiCollection: string): Observable<Collection> { getByCollection(apiCollection: string): Observable<Collection> {
return this.http.get<Collection>(`${this.serverUrl}${apiCollection}`, { return this.get<Collection>(`${this.serverUrl}${apiCollection}`, {
withCredentials: true withCredentials: true
}) })
} }
getComputeContexts(): Observable<any> { getComputeContexts(): Observable<ViyaComputeContexts> {
return this.http.get<any>(`${this.serverUrl}/compute/contexts`, { return this.get<ViyaComputeContexts>(`${this.serverUrl}/compute/contexts`, {
withCredentials: true
})
}
getComputeContextById(id: string): Observable<ComputeContextDetails> {
return this.get<ComputeContextDetails>(`${this.serverUrl}/compute/contexts/${id}`, {
withCredentials: true withCredentials: true
}) })
} }
@ -122,7 +130,7 @@ export class SasViyaService {
* @returns The folder info object * @returns The folder info object
*/ */
getFolderByPath(path: string): Observable<ViyaApiFolder> { getFolderByPath(path: string): Observable<ViyaApiFolder> {
return this.http.get<ViyaApiFolder>( return this.get<ViyaApiFolder>(
`${this.serverUrl}/folders/folders/@item?path=${path}`, `${this.serverUrl}/folders/folders/@item?path=${path}`,
{ {
withCredentials: true withCredentials: true
@ -131,7 +139,7 @@ export class SasViyaService {
} }
getFolderMembers(folderId: string): Observable<ViyaApiFolderMembers> { getFolderMembers(folderId: string): Observable<ViyaApiFolderMembers> {
return this.http.get<ViyaApiFolderMembers>( return this.get<ViyaApiFolderMembers>(
`${this.serverUrl}/folders/folders/${folderId}/members`, `${this.serverUrl}/folders/folders/${folderId}/members`,
{ {
withCredentials: true withCredentials: true
@ -140,7 +148,7 @@ export class SasViyaService {
} }
getAdminGroups(limit: number = 5000): Observable<ViyaApiIdentities> { getAdminGroups(limit: number = 5000): Observable<ViyaApiIdentities> {
return this.http.get<ViyaApiIdentities>( return this.get<ViyaApiIdentities>(
`${this.serverUrl}/identities/groups?sortBy=name&limit=${limit}`, `${this.serverUrl}/identities/groups?sortBy=name&limit=${limit}`,
{ {
withCredentials: true withCredentials: true
@ -149,11 +157,41 @@ export class SasViyaService {
} }
getCurrentUser(): Observable<ViyaApiCurrentUser> { getCurrentUser(): Observable<ViyaApiCurrentUser> {
return this.http.get<ViyaApiCurrentUser>( return this.get<ViyaApiCurrentUser>(
`${this.serverUrl}/identities/users/@currentUser`, `${this.serverUrl}/identities/users/@currentUser`,
{ {
withCredentials: true withCredentials: true
} }
) )
} }
get<T>(url: string, options?: {
headers?: HttpHeaders | {
[header: string]: string | string[];
};
context?: HttpContext;
observe?: 'body';
params?: HttpParams | {
[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>;
};
reportProgress?: boolean;
responseType?: 'json';
withCredentials?: boolean;
transferCache?: {
includeHeaders?: string[];
} | boolean;
}): Observable<T> {
return this.http.get<T>(url, options).pipe(
catchError((err: HttpErrorResponse) => {
console.log('url', url)
console.log('err.status', err.status)
if (err.status === 449 || err.status === 401) {
// Retry once if we got a 449
return this.http.get<T>(url, options);
}
// Otherwise propagate the error
return throwError(() => err);
})
)
}
} }

View File

@ -0,0 +1,34 @@
export interface ComputeContextDetails {
attributes?: Attributes;
createdBy: string;
creationTimeStamp: string;
description: string;
id: string;
launchContext: LaunchContext;
launchType: string;
links: Link[];
modifiedBy: string;
modifiedTimeStamp: string;
name: string;
version: number;
}
export interface Link {
method: string;
rel: string;
href: string;
uri: string;
type?: string;
responseType?: string;
itemType?: string;
}
export interface LaunchContext {
contextId: string;
}
export interface Attributes {
reuseServerProcesses: string;
runServerAs: string;
serverMinAvailable: string;
}

View File

@ -0,0 +1,36 @@
export interface ViyaComputeContexts {
accept: string;
count: number;
items: Item[];
limit: number;
links: Link2[];
name: string;
start: number;
version: number;
}
export interface Link2 {
method: string;
rel: string;
href: string;
uri: string;
type: string;
itemType: string;
}
export interface Item {
createdBy: string;
id: string;
links: Link[];
name: string;
version: number;
}
export interface Link {
method: string;
rel: string;
href: string;
uri: string;
type?: string;
responseType?: string;
}

View File

@ -3884,6 +3884,12 @@ app-header-actions {
// END OF CSP WORKAROUND // END OF CSP WORKAROUND
.clr-input-wrapper.small-mt {
.clr-form-control {
margin-top: 5px !important;
}
}
body[cds-theme="dark"] { body[cds-theme="dark"] {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: $trackColor $thumbColor; scrollbar-color: $trackColor $thumbColor;