UNPKG

@versatiledatakit/data-pipelines

Version:

Data Pipelines help Data Engineers develop, deploy, run, and manage data processing workloads (called 'Data Job')

641 lines 120 kB
/* * Copyright 2023-2025 Broadcom * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable @typescript-eslint/member-ordering */ import { Component, Inject } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { concatMap, interval, of, Subject, timer } from 'rxjs'; import { catchError, filter, finalize, map, switchMap, take, takeUntil, takeWhile, tap } from 'rxjs/operators'; import { ClrLoadingState } from '@clr/angular'; import * as fileSaver from 'file-saver'; import { ASC, CollectionsUtil, TaurusBaseComponent, VmwToastType } from '@versatiledatakit/shared'; import { DataJobUtil, ErrorUtil } from '../../shared/utils'; import { ExtractJobStatusPipe } from '../../shared/pipes'; import { ConfirmationModalOptions, DeleteModalOptions } from '../../shared/model'; import { DATA_PIPELINES_CONFIGS, DataJobStatus, ToastDefinitions } from '../../model'; import * as i0 from "@angular/core"; import * as i1 from "@versatiledatakit/shared"; import * as i2 from "@angular/router"; import * as i3 from "../../services"; import * as i4 from "@angular/common"; import * as i5 from "@clr/angular"; import * as i6 from "../../shared/components/delete-modal/delete-modal.component"; import * as i7 from "../../shared/components/confirmation-dialog-modal/confirmation-dialog-modal.component"; function DataJobPageComponent_div_3_Template(rf, ctx) { if (rf & 1) { const _r8 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "div", 14)(1, "h2", 15)(2, "a", 16); i0.ɵɵlistener("click", function DataJobPageComponent_div_3_Template_a_click_2_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r7 = i0.ɵɵnextContext(); return ctx_r7.doNavigateBack($event); }); i0.ɵɵelement(3, "clr-icon", 17); i0.ɵɵelementEnd(); i0.ɵɵelementStart(4, "span", 18)(5, "strong"); i0.ɵɵtext(6, "Data Job:"); i0.ɵɵelementEnd(); i0.ɵɵtext(7); i0.ɵɵelementEnd()()(); } if (rf & 2) { const ctx_r0 = i0.ɵɵnextContext(); i0.ɵɵadvance(7); i0.ɵɵtextInterpolate1(" ", ctx_r0.jobName, ""); } } function DataJobPageComponent_ng_template_4_Template(rf, ctx) { if (rf & 1) { const _r10 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "div", 19)(1, "h1", 20)(2, "a", 16); i0.ɵɵlistener("click", function DataJobPageComponent_ng_template_4_Template_a_click_2_listener($event) { i0.ɵɵrestoreView(_r10); const ctx_r9 = i0.ɵɵnextContext(); return ctx_r9.doNavigateBack($event); }); i0.ɵɵelement(3, "clr-icon", 17); i0.ɵɵelementEnd(); i0.ɵɵelementStart(4, "span"); i0.ɵɵtext(5); i0.ɵɵelementEnd()()(); } if (rf & 2) { const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵadvance(5); i0.ɵɵtextInterpolate1("Data Job: ", ctx_r2.jobName, ""); } } function DataJobPageComponent_div_6_button_1_Template(rf, ctx) { if (rf & 1) { const _r15 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "button", 25); i0.ɵɵlistener("click", function DataJobPageComponent_div_6_button_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r15); const ctx_r14 = i0.ɵɵnextContext(2); return ctx_r14.cancelExecution(); }); i0.ɵɵtext(1, " Cancel Execution "); i0.ɵɵelementEnd(); } if (rf & 2) { const ctx_r11 = i0.ɵɵnextContext(2); i0.ɵɵproperty("clrLoading", ctx_r11.stopButtonsState)("disabled", ctx_r11.cancelDataJobDisabled); } } function DataJobPageComponent_div_6_button_2_Template(rf, ctx) { if (rf & 1) { const _r17 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "button", 26); i0.ɵɵlistener("click", function DataJobPageComponent_div_6_button_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r17); const ctx_r16 = i0.ɵɵnextContext(2); return ctx_r16.executeJob(); }); i0.ɵɵtext(1, " Execute "); i0.ɵɵelementEnd(); } if (rf & 2) { const ctx_r12 = i0.ɵɵnextContext(2); i0.ɵɵproperty("disabled", !ctx_r12.areJobExecutionsLoaded || ctx_r12.loadingInProgress || ctx_r12.isExecutionInProgress())("clrLoading", ctx_r12.executeButtonsState); } } function DataJobPageComponent_div_6_clr_dropdown_3_button_5_Template(rf, ctx) { if (rf & 1) { const _r20 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "button", 32); i0.ɵɵlistener("click", function DataJobPageComponent_div_6_clr_dropdown_3_button_5_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r20); const ctx_r19 = i0.ɵɵnextContext(3); return ctx_r19.downloadJobKey(); }); i0.ɵɵtext(1, " Download key "); i0.ɵɵelementEnd(); } if (rf & 2) { const ctx_r18 = i0.ɵɵnextContext(3); i0.ɵɵproperty("disabled", ctx_r18.loadingInProgress)("clrLoading", ctx_r18.downloadButtonsState); } } function DataJobPageComponent_div_6_clr_dropdown_3_Template(rf, ctx) { if (rf & 1) { const _r22 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "clr-dropdown")(1, "button", 27); i0.ɵɵtext(2, " Actions "); i0.ɵɵelement(3, "clr-icon", 28); i0.ɵɵelementEnd(); i0.ɵɵelementStart(4, "clr-dropdown-menu", 29); i0.ɵɵtemplate(5, DataJobPageComponent_div_6_clr_dropdown_3_button_5_Template, 2, 2, "button", 30); i0.ɵɵelementStart(6, "button", 31); i0.ɵɵlistener("click", function DataJobPageComponent_div_6_clr_dropdown_3_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r22); const ctx_r21 = i0.ɵɵnextContext(2); return ctx_r21.removeJob(); }); i0.ɵɵtext(7, " Delete "); i0.ɵɵelementEnd()()(); } if (rf & 2) { const ctx_r13 = i0.ɵɵnextContext(2); i0.ɵɵadvance(3); i0.ɵɵattribute("size", 15); i0.ɵɵadvance(2); i0.ɵɵproperty("ngIf", ctx_r13.isDownloadJobKeyAllowed); i0.ɵɵadvance(1); i0.ɵɵproperty("disabled", !ctx_r13.areJobExecutionsLoaded || ctx_r13.loadingInProgress || ctx_r13.isExecutionInProgress())("clrLoading", ctx_r13.deleteButtonsState); } } function DataJobPageComponent_div_6_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "div", 21); i0.ɵɵtemplate(1, DataJobPageComponent_div_6_button_1_Template, 2, 2, "button", 22); i0.ɵɵtemplate(2, DataJobPageComponent_div_6_button_2_Template, 2, 2, "button", 23); i0.ɵɵtemplate(3, DataJobPageComponent_div_6_clr_dropdown_3_Template, 8, 4, "clr-dropdown", 24); i0.ɵɵelementEnd(); } if (rf & 2) { const ctx_r3 = i0.ɵɵnextContext(); i0.ɵɵadvance(1); i0.ɵɵproperty("ngIf", ctx_r3.isDataJobRunning && ctx_r3.isJobWithRunningStatus()); i0.ɵɵadvance(1); i0.ɵɵproperty("ngIf", ctx_r3.isJobAvailable && ctx_r3.isExecuteJobAllowed && !ctx_r3.isDataJobRunning); i0.ɵɵadvance(1); i0.ɵɵproperty("ngIf", ctx_r3.isJobAvailable); } } const _c0 = function () { return ["executions"]; }; function DataJobPageComponent_li_15_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "li", 9)(1, "a", 33, 34); i0.ɵɵtext(3, "Executions"); i0.ɵɵelementEnd()(); } if (rf & 2) { const _r23 = i0.ɵɵreference(2); const ctx_r5 = i0.ɵɵnextContext(); i0.ɵɵadvance(1); i0.ɵɵproperty("routerLink", i0.ɵɵpureFunction0(3, _c0))("queryParams", ctx_r5.queryParams); i0.ɵɵattribute("aria-selected", _r23.isActive); } } const _c1 = function () { return ["lineage"]; }; function DataJobPageComponent_li_16_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "li", 9)(1, "a", 35, 36); i0.ɵɵtext(3, "Lineage "); i0.ɵɵelement(4, "clr-icon", 37); i0.ɵɵelementEnd()(); } if (rf & 2) { const _r24 = i0.ɵɵreference(2); const ctx_r6 = i0.ɵɵnextContext(); i0.ɵɵadvance(1); i0.ɵɵproperty("routerLink", i0.ɵɵpureFunction0(3, _c1))("queryParams", ctx_r6.queryParams); i0.ɵɵattribute("aria-selected", _r24.isActive); } } const _c2 = function (a0) { return { "data-pipelines-job__actions--margin-0": a0 }; }; const _c3 = function () { return ["details"]; }; var TypeButtonState; (function (TypeButtonState) { /* eslint-disable-next-line @typescript-eslint/naming-convention */ TypeButtonState[TypeButtonState["DOWNLOAD"] = 0] = "DOWNLOAD"; /* eslint-disable-next-line @typescript-eslint/naming-convention */ TypeButtonState[TypeButtonState["EXECUTE"] = 1] = "EXECUTE"; /* eslint-disable-next-line @typescript-eslint/naming-convention */ TypeButtonState[TypeButtonState["DELETE"] = 2] = "DELETE"; /* eslint-disabe-next-line @typescript-eslint/naming-convention */ TypeButtonState[TypeButtonState["STOP"] = 3] = "STOP"; })(TypeButtonState || (TypeButtonState = {})); export class DataJobPageComponent extends TaurusBaseComponent { constructor(componentService, navigationService, activatedRoute, routerService, dataJobsService, dataJobsApiService, toastService, errorHandlerService, dataPipelinesModuleConfig) { super(componentService, navigationService, activatedRoute); this.routerService = routerService; this.dataJobsService = dataJobsService; this.dataJobsApiService = dataJobsApiService; this.toastService = toastService; this.errorHandlerService = errorHandlerService; this.dataPipelinesModuleConfig = dataPipelinesModuleConfig; this.uuid = 'DataJobPageComponent'; this.teamName = ''; this.jobName = ''; this.isDataJobRunning = false; this.cancelDataJobDisabled = false; this.queryParams = {}; this.isSubpageNavigation = false; this.isJobAvailable = false; this.isJobEditable = false; this.isExecuteJobAllowed = false; this.isDownloadJobKeyAllowed = false; this.areJobExecutionsLoaded = false; this.loadingInProgress = false; this.jobExecutions = []; this.jobDeployments = []; this.deleteButtonsState = ClrLoadingState.DEFAULT; this.executeButtonsState = ClrLoadingState.DEFAULT; this.downloadButtonsState = ClrLoadingState.DEFAULT; this.stopButtonsState = ClrLoadingState.DEFAULT; this._nonExistingJobMsgShowed = false; this.isSubpageNavigation = !!activatedRoute.snapshot.data['activateSubpageNavigation']; this.deleteOptions = new DeleteModalOptions(); this.executeNowOptions = new ConfirmationModalOptions(); this.cancelNowOptions = new ConfirmationModalOptions(); } /** * ** Navigate back leveraging provided router config. */ doNavigateBack($event) { $event?.preventDefault(); // eslint-disable-next-line @typescript-eslint/no-floating-promises this.navigateBack({ '$.team': this.teamName }).then(); } /** * ** Returns if execution is in progress. */ isExecutionInProgress() { return DataJobUtil.isJobRunning(this.jobExecutions); } /** * ** Show confirmation dialog for Job execution. */ executeJob() { this.executeNowOptions.title = `Execute ${this.jobName} now?`; this.executeNowOptions.message = `Job <strong>${this.jobName}</strong> will be queued for execution.`; this.executeNowOptions.infoText = `Confirming will result in immediate data job execution.`; this.executeNowOptions.opened = true; } /** * ** On User confirm continue with Job execution. */ confirmExecuteJob() { this._submitOperationStarted(TypeButtonState.EXECUTE); this.subscriptions.push(this.dataJobsApiService .executeDataJob(this.teamName, this.jobName, this._extractJobDeployment()?.id) .pipe(finalize(() => { this._submitOperationEnded(); })) .subscribe({ next: () => { this.toastService.show(ToastDefinitions.successfullyRanJob(this.jobName)); let previousReqFinished = true; this.areJobExecutionsLoaded = false; this.subscriptions.push(interval(1250) // Send polling request on every 1.25s until execution is accepted from backend .pipe( // eslint-disable-next-line rxjs/no-unsafe-takeuntil takeUntil(timer(30000)), // Timer limit when polling to stop = 30s filter(() => previousReqFinished), tap(() => (previousReqFinished = false)), switchMap(() => this.dataJobsApiService .getJobExecutions(this.teamName, this.jobName, true, null, { property: 'startTime', direction: ASC }) .pipe(catchError((error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error)); return of([]); }), finalize(() => { previousReqFinished = true; }))), map((executions) => (executions.content ? [...executions.content] : [])), takeWhile((executions) => { if (CollectionsUtil.isArrayEmpty(executions) || executions.length <= this.jobExecutions.length) { return true; } this.jobExecutions = executions; this.areJobExecutionsLoaded = true; const lastExecution = executions[executions.length - 1]; if (!DataJobUtil.isJobRunningPredicate(lastExecution)) { return true; } this.dataJobsService.notifyForJobExecutions(executions); this.dataJobsService.notifyForRunningJobExecutionId(lastExecution.id); return false; // Stop polling if above condition is met. })) .subscribe() // eslint-disable-line rxjs/no-nested-subscribe ); }, error: (error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error), { title: error?.status === 409 ? 'Failed, Data job is already executing' : 'Failed to queue Data job for execution' }); } })); } /** * ** Download Job key. */ downloadJobKey() { this._submitOperationStarted(TypeButtonState.DOWNLOAD); this.dataJobsApiService .downloadFile(this.teamName, this.jobName) .pipe(finalize(() => { this._submitOperationEnded(); })) .subscribe({ next: (response) => { const blob = new Blob([response], { type: 'application/octet-stream' }); // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access fileSaver.saveAs(blob, `${this.jobName}.keytab`); this.toastService.show({ type: VmwToastType.INFO, title: `Download completed`, description: `Data job keytab "${this.jobName}.keytab" successfully downloaded` }); }, error: (error) => { const errorDescription = error?.status === 404 ? `Download failed. Keytab file doesn't exist for this job.` : `Download failed. Keytab file failed to download.`; this.errorHandlerService.processError(ErrorUtil.extractError(error), { description: errorDescription }); } }); } /** * ** Show confirmation dialog for Job Remove (Delete). */ removeJob() { this.deleteOptions.message = `Job <strong>${this.jobName}</strong> will be deleted. Currently executing Data Jobs will be left to finish but the credentials will be revoked.`; this.deleteOptions.infoText = `Deleting this job means that <strong> it will be permanently removed from the system</strong> including all its state (properties), source code and any deployments.`; this.deleteOptions.showOkBtn = true; this.deleteOptions.cancelBtn = 'Cancel'; this.deleteOptions.opened = true; } /** * ** On User confirm continue with Job Remove (Delete). */ confirmRemoveJob() { this._submitOperationStarted(TypeButtonState.DELETE); this.dataJobsApiService .removeJob(this.teamName, this.jobName) .pipe(finalize(() => { this._submitOperationEnded(); })) .subscribe({ next: () => { this.toastService.show({ type: VmwToastType.INFO, title: `Data job delete completed`, description: `Data job "${this.jobName}" successfully deleted` }); this.doNavigateBack(); }, error: (error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error), { title: `Data job delete failed` }); } }); } confirmCancelDataJob() { this._submitOperationStarted(TypeButtonState.STOP); this.dataJobsApiService .cancelDataJobExecution(this.teamName, this.jobName, this.lastExecution()?.id) .pipe(finalize(() => { this._submitOperationEnded(); })) .subscribe({ next: () => { this.cancelDataJobDisabled = true; this.toastService.show({ type: VmwToastType.INFO, title: `Data job execution cancellation completed`, description: `Data job "${this.jobName}" successfully canceled` }); }, error: (error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error), { title: `Data job cancellation failed` }); } }); } /** * ** Show confirmation dialog for Job execution cancellation. */ cancelExecution() { this.cancelNowOptions.title = `Cancel ${this.lastExecution()?.id} now?`; this.cancelNowOptions.message = `Execution <strong>${this.lastExecution()?.id}</strong> will be canceled.`; this.cancelNowOptions.infoText = `Confirming will result in immediate data job execution cancellation.`; this.cancelNowOptions.opened = true; } lastExecution() { return this.jobExecutions[this.jobExecutions.length - 1]; } isJobWithRunningStatus() { return this.lastExecution().status === 'RUNNING'; } /** * @inheritDoc */ onModelInit() { this.routerService .getState() .pipe(take(1)) .subscribe((state) => this._initialize(state)); } /** * @inheritDoc */ onModelError(model, _task, newErrorRecords) { newErrorRecords.forEach((errorRecord) => { const error = ErrorUtil.extractError(errorRecord.error); this.errorHandlerService.processError(error); }); } _initialize(state) { const teamParamKey = state.getData('teamParamKey'); this.teamName = state.getParam(teamParamKey); if (CollectionsUtil.isNil(teamParamKey) || CollectionsUtil.isNil(this.teamName)) { this._subscribeForImplicitTeam(); } const jobParamKey = state.getData('jobParamKey'); this.jobName = state.getParam(jobParamKey); this.isJobEditable = !!state.getData('editable'); this.queryParams = state.queryParams; this.isDownloadJobKeyAllowed = this.dataPipelinesModuleConfig.manageConfig?.allowKeyTabDownloads && this.isJobEditable; this._subscribeForTeamChange(state); this._subscribeForExecutionsChange(); this._subscribeForExecutionIdChange(); this._loadJobDetails(); this._loadJobExecutions(); } _subscribeForImplicitTeam() { this.dataJobsService .getNotifiedForTeamImplicitly() .pipe(take(1)) .subscribe((teamName) => (this.teamName = teamName)); } _subscribeForTeamChange(state) { const shouldActivateListener = !!state.getData('activateListenerForTeamChange'); if (shouldActivateListener && this.dataPipelinesModuleConfig?.manageConfig?.selectedTeamNameObservable) { this.subscriptions.push(this.dataPipelinesModuleConfig.manageConfig.selectedTeamNameObservable.subscribe((newTeam) => { if (this.teamName !== newTeam) { this.teamName = newTeam; this.doNavigateBack(); } })); } } _subscribeForExecutionsChange() { this.subscriptions.push(this.dataJobsService.getNotifiedForJobExecutions().subscribe((executions) => { this.jobExecutions = [...executions]; })); } _subscribeForExecutionIdChange() { const scheduleLastExecutionPolling = new Subject(); this.subscriptions.push(scheduleLastExecutionPolling .pipe(switchMap((id) => interval(5000).pipe(switchMap(() => this.dataJobsApiService.getJobExecution(this.teamName, this.jobName, id).pipe(map((execution) => { return { execution, error: null }; }), catchError((error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error)); return of({ execution: null, error: error }); }))), tap((data) => this._replaceRunningExecutionAndNotify(data.execution)), takeWhile((data) => { if (data.error instanceof HttpErrorResponse) { if (data.error.status === 404 || data.error.status >= 500) { this.isDataJobRunning = false; return false; } } const isRunning = CollectionsUtil.isNil(data.execution) || DataJobUtil.isJobRunningPredicate(data.execution); if (!isRunning) { this.isDataJobRunning = false; } return isRunning; })))) .subscribe()); this.subscriptions.push(this.dataJobsService .getNotifiedForRunningJobExecutionId() .pipe(concatMap((executionId) => this.dataJobsApiService.getJobExecution(this.teamName, this.jobName, executionId).pipe(map((executionDetails) => [executionId, executionDetails]), catchError((error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error)); return of([executionId]); })))) .subscribe(([executionId, executionDetails]) => { this.isDataJobRunning = true; this.cancelDataJobDisabled = false; this._replaceRunningExecutionAndNotify(executionDetails); scheduleLastExecutionPolling.next(executionId); })); } _loadJobDetails() { this.subscriptions.push(this.dataJobsApiService.getJobDetails(this.teamName, this.jobName).subscribe({ error: (error) => { if (error instanceof HttpErrorResponse) { if (error.status === 404) { this._showMessageJobNotExist(); this.doNavigateBack(); } console.error('Error loading jobDetails', error); } } })); this.subscriptions.push(this.dataJobsApiService.getJob(this.teamName, this.jobName).subscribe({ next: (job) => { if (CollectionsUtil.isDefined(job)) { this.isJobAvailable = true; this.jobDeployments = job.deployments; this.isExecuteJobAllowed = ExtractJobStatusPipe.transform(this.jobDeployments) !== DataJobStatus.NOT_DEPLOYED; return; } this._showMessageJobNotExist(); this.doNavigateBack(); }, error: (error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error), { title: `Loading Data job "${this.jobName}" failed` }); } })); } _loadJobExecutions() { this.subscriptions.push(this.dataJobsApiService .getJobExecutions(this.teamName, this.jobName, true, null, { property: 'startTime', direction: ASC }) .subscribe({ next: (value) => { if (value?.content) { this.dataJobsService.notifyForJobExecutions([...value.content]); // eslint-disable-next-line @typescript-eslint/unbound-method const runningExecution = value.content.find(DataJobUtil.isJobRunningPredicate); if (runningExecution) { this.dataJobsService.notifyForRunningJobExecutionId(runningExecution.id); } } this.areJobExecutionsLoaded = true; }, error: (error) => { this.errorHandlerService.processError(ErrorUtil.extractError(error)); } })); } _replaceRunningExecutionAndNotify(executionDetails) { if (CollectionsUtil.isNil(executionDetails)) { return; } const convertedExecution = DataJobUtil.convertFromExecutionDetailsToExecutionState(executionDetails); const foundIndex = this.jobExecutions.findIndex((ex) => ex.id === convertedExecution.id); if (foundIndex !== -1) { this.jobExecutions.splice(foundIndex, 1, convertedExecution); } else { this.jobExecutions.push(convertedExecution); } this.dataJobsService.notifyForJobExecutions(this.jobExecutions); } _submitOperationStarted(type) { switch (type) { case TypeButtonState.DELETE: this.deleteButtonsState = ClrLoadingState.LOADING; break; case TypeButtonState.DOWNLOAD: this.downloadButtonsState = ClrLoadingState.LOADING; break; case TypeButtonState.EXECUTE: this.executeButtonsState = ClrLoadingState.LOADING; break; case TypeButtonState.STOP: this.stopButtonsState = ClrLoadingState.LOADING; break; } this.loadingInProgress = true; } _submitOperationEnded() { this.deleteButtonsState = ClrLoadingState.DEFAULT; this.downloadButtonsState = ClrLoadingState.DEFAULT; this.executeButtonsState = ClrLoadingState.DEFAULT; this.stopButtonsState = ClrLoadingState.DEFAULT; this.loadingInProgress = false; } _extractJobDeployment() { if (!this.jobDeployments) { return null; } return this.jobDeployments[this.jobDeployments.length - 1]; } _showMessageJobNotExist() { if (!this._nonExistingJobMsgShowed) { this._nonExistingJobMsgShowed = true; this.toastService.show({ type: VmwToastType.FAILURE, title: `Job "${this.jobName}" doesn't exist`, description: `Data Job "${this.jobName}" for Team "${this.teamName}" doesn't exist, will load Data Jobs list` }); } } } DataJobPageComponent.ɵfac = function DataJobPageComponent_Factory(t) { return new (t || DataJobPageComponent)(i0.ɵɵdirectiveInject(i1.ComponentService), i0.ɵɵdirectiveInject(i1.NavigationService), i0.ɵɵdirectiveInject(i2.ActivatedRoute), i0.ɵɵdirectiveInject(i1.RouterService), i0.ɵɵdirectiveInject(i3.DataJobsService), i0.ɵɵdirectiveInject(i3.DataJobsApiService), i0.ɵɵdirectiveInject(i1.ToastService), i0.ɵɵdirectiveInject(i1.ErrorHandlerService), i0.ɵɵdirectiveInject(DATA_PIPELINES_CONFIGS)); }; DataJobPageComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DataJobPageComponent, selectors: [["lib-data-job-page"]], features: [i0.ɵɵInheritDefinitionFeature], decls: 19, vars: 15, consts: [["id", "data-job-page", "data-cy", "data-pipelines-job-page", 1, "page-container", "data-pipelines-job__page"], [1, "data-pipelines-job__actions", 3, "ngClass"], [1, "data-pipelines-job__actions-left"], ["class", "data-pipelines-job__subpage-navigation", 4, "ngIf", "ngIfElse"], ["rootPageNavigation", ""], ["class", "data-pipelines-job__actions-right", "data-cy", "data-pipelines-job-actions-container", 4, "ngIf"], [3, "options", "delete"], [3, "options", "changeStatus"], ["role", "tablist", "aria-owns", "dataPipelinesJobDetails dataPipelinesJobExecutions dataPipelinesJobLineage", 1, "nav", "data-pipelines-job__tabs-navigation"], ["role", "presentation", 1, "nav-item"], ["id", "dataPipelinesJobDetails", "role", "tab", "aria-hidden", "false", "data-cy", "data-pipelines-job-details-tab", "routerLinkActive", "active", 1, "nav-link", 3, "routerLink", "queryParams"], ["detailsLink", "routerLinkActive"], ["role", "presentation", "class", "nav-item", 4, "ngIf"], [1, "data-pipelines-job__router-outlet-container"], [1, "data-pipelines-job__subpage-navigation"], ["data-cy", "data-pipelines-job-main-title", 1, "m-0", "page-title"], ["href", "javascript:;", "data-cy", "data-pipelines-job-navigate-back", 1, "label-link", "data-pipelines-job__navigate-back", 3, "click"], ["shape", "redo", "flip", "horizontal", "size", "25", "role", "none", 1, "redo-icon"], ["data-cy", "dp-main-title"], [1, "data-pipelines-job__root-page-navigation"], ["data-cy", "data-pipelines-page-title", 1, "m-0", "page-title"], ["data-cy", "data-pipelines-job-actions-container", 1, "data-pipelines-job__actions-right"], ["class", "btn btn-secondary", "data-cy", "data-pipelines-job-cancel-execution-btn", 3, "clrLoading", "disabled", "click", 4, "ngIf"], ["class", "btn btn-secondary", "data-cy", "data-pipelines-job-execute-btn", "aria-label", "Execute now", 3, "disabled", "clrLoading", "click", 4, "ngIf"], [4, "ngIf"], ["data-cy", "data-pipelines-job-cancel-execution-btn", 1, "btn", "btn-secondary", 3, "clrLoading", "disabled", "click"], ["data-cy", "data-pipelines-job-execute-btn", "aria-label", "Execute now", 1, "btn", "btn-secondary", 3, "disabled", "clrLoading", "click"], ["clrDropdownTrigger", "", "data-cy", "data-pipelines-job-action-dropdown-btn", 1, "btn", "btn-secondary", "data-pipelines-job__action-dropdown-trigger"], ["title", "Actions", "shape", "caret"], ["clrPosition", "bottom-right"], ["clrDropdownItem", "", "class", "btn btn-secondary", "aria-label", "Download Key", "data-cy", "data-pipelines-job-download-btn", 3, "disabled", "clrLoading", "click", 4, "ngIf"], ["clrDropdownItem", "", "aria-label", "Delete Job", "data-cy", "data-pipelines-job-delete-btn", 1, "btn", "btn-secondary", 3, "disabled", "clrLoading", "click"], ["clrDropdownItem", "", "aria-label", "Download Key", "data-cy", "data-pipelines-job-download-btn", 1, "btn", "btn-secondary", 3, "disabled", "clrLoading", "click"], ["id", "dataPipelinesJobExecutions", "role", "tab", "aria-hidden", "false", "data-cy", "data-pipelines-job-executions-tab", "routerLinkActive", "active", 1, "nav-link", 3, "routerLink", "queryParams"], ["executionsLink", "routerLinkActive"], ["id", "dataPipelinesJobLineage", "role", "tab", "aria-hidden", "false", "data-cy", "data-pipelines-job-lineage-tab", "routerLinkActive", "active", 1, "nav-link", 3, "routerLink", "queryParams"], ["lineage", "routerLinkActive"], ["shape", "beta", 1, "beta-icon"]], template: function DataJobPageComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2); i0.ɵɵtemplate(3, DataJobPageComponent_div_3_Template, 8, 1, "div", 3); i0.ɵɵtemplate(4, DataJobPageComponent_ng_template_4_Template, 6, 1, "ng-template", null, 4, i0.ɵɵtemplateRefExtractor); i0.ɵɵelementEnd(); i0.ɵɵtemplate(6, DataJobPageComponent_div_6_Template, 4, 3, "div", 5); i0.ɵɵelementStart(7, "lib-delete-modal", 6); i0.ɵɵlistener("delete", function DataJobPageComponent_Template_lib_delete_modal_delete_7_listener() { return ctx.confirmRemoveJob(); }); i0.ɵɵelementEnd(); i0.ɵɵelementStart(8, "lib-confirmation-dialog-modal", 7); i0.ɵɵlistener("changeStatus", function DataJobPageComponent_Template_lib_confirmation_dialog_modal_changeStatus_8_listener() { return ctx.confirmExecuteJob(); }); i0.ɵɵelementEnd(); i0.ɵɵelementStart(9, "lib-confirmation-dialog-modal", 7); i0.ɵɵlistener("changeStatus", function DataJobPageComponent_Template_lib_confirmation_dialog_modal_changeStatus_9_listener() { return ctx.confirmCancelDataJob(); }); i0.ɵɵelementEnd()(); i0.ɵɵelementStart(10, "ul", 8)(11, "li", 9)(12, "a", 10, 11); i0.ɵɵtext(14, "Details"); i0.ɵɵelementEnd()(); i0.ɵɵtemplate(15, DataJobPageComponent_li_15_Template, 4, 4, "li", 12); i0.ɵɵtemplate(16, DataJobPageComponent_li_16_Template, 5, 4, "li", 12); i0.ɵɵelementEnd(); i0.ɵɵelementStart(17, "div", 13); i0.ɵɵelement(18, "router-outlet"); i0.ɵɵelementEnd()(); } if (rf & 2) { const _r1 = i0.ɵɵreference(5); const _r4 = i0.ɵɵreference(13); i0.ɵɵadvance(1); i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction1(12, _c2, !ctx.isSubpageNavigation)); i0.ɵɵadvance(2); i0.ɵɵproperty("ngIf", ctx.isSubpageNavigation)("ngIfElse", _r1); i0.ɵɵadvance(3); i0.ɵɵproperty("ngIf", ctx.isJobEditable); i0.ɵɵadvance(1); i0.ɵɵproperty("options", ctx.deleteOptions); i0.ɵɵadvance(1); i0.ɵɵproperty("options", ctx.executeNowOptions); i0.ɵɵadvance(1); i0.ɵɵproperty("options", ctx.cancelNowOptions); i0.ɵɵadvance(3); i0.ɵɵproperty("routerLink", i0.ɵɵpureFunction0(14, _c3))("queryParams", ctx.queryParams); i0.ɵɵattribute("aria-selected", _r4.isActive); i0.ɵɵadvance(3); i0.ɵɵproperty("ngIf", ctx.dataPipelinesModuleConfig.showExecutionsPage && ctx.isJobEditable); i0.ɵɵadvance(1); i0.ɵɵproperty("ngIf", ctx.dataPipelinesModuleConfig.showLineagePage); } }, directives: [i4.NgClass, i4.NgIf, i5.ClrIconCustomTag, i5.ClrLoadingButton, i5.ClrLoading, i5.ClrDropdown, i5.ClrDropdownTrigger, i5.ClrDropdownMenu, i5.ClrDropdownItem, i6.DeleteModalComponent, i7.ConfirmationDialogModalComponent, i2.RouterLinkWithHref, i2.RouterLinkActive, i2.RouterOutlet], styles: ["hr[_ngcontent-%COMP%]{border:0;height:1px;background:#8f9ba3}.label-link[_ngcontent-%COMP%]{cursor:pointer}.label-link-suppress-decoration[_ngcontent-%COMP%]:hover{text-decoration:none}.status-icon-enabled[_ngcontent-%COMP%]{color:#5aa220}.clr-col-6[_ngcontent-%COMP%]{border-right:1px solid #999999}.loading-spinner[_ngcontent-%COMP%]{margin-left:45%;margin-top:100px}[_nghost-%COMP%]{display:flex;flex-direction:column;flex:1 1 auto;height:100%}.data-pipelines-job__page[_ngcontent-%COMP%]{display:flex;flex-direction:column;flex:1 1 auto}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%]{display:inline-flex;margin-top:.8rem}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__actions-right[_ngcontent-%COMP%]{margin-left:auto}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__actions-right[_ngcontent-%COMP%] clr-dropdown[_ngcontent-%COMP%]{margin-top:var(--clr-btn-vertical-margin, .3rem);margin-bottom:var(--clr-btn-vertical-margin, .3rem);margin-left:0}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__actions-right[_ngcontent-%COMP%] .data-pipelines-job__action-dropdown-trigger[_ngcontent-%COMP%]{padding-right:1.5rem}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__actions-right[_ngcontent-%COMP%] .data-pipelines-job__action-dropdown-trigger[_ngcontent-%COMP%] clr-icon[_ngcontent-%COMP%]{transform:translateY(-50%) rotate(180deg);top:.85rem}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions.data-pipelines-job__actions--margin-0[_ngcontent-%COMP%]{margin:0}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__navigate-back[_ngcontent-%COMP%]{margin-right:10px;margin-top:-1px}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__navigate-back[_ngcontent-%COMP%] .redo-icon[_ngcontent-%COMP%]{color:var(--clr-link-color, #0072a3);height:25px;width:25px}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__info[_ngcontent-%COMP%]{display:inline-flex}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .data-pipelines-job__info[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{margin-right:.5rem}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__actions[_ngcontent-%COMP%] .page-title[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{font-size:x-large}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__tabs-navigation[_ngcontent-%COMP%]{margin-top:7px}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__tabs-navigation[_ngcontent-%COMP%] .job-details__promotion-icon[_ngcontent-%COMP%]{margin-top:-.75rem;margin-left:.25rem}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__tabs-navigation[_ngcontent-%COMP%] .beta-icon[_ngcontent-%COMP%]{vertical-align:baseline}.data-pipelines-job__page[_ngcontent-%COMP%] .data-pipelines-job__router-outlet-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;flex:1 1 auto}"] }); (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DataJobPageComponent, [{ type: Component, args: [{ selector: 'lib-data-job-page', template: "<!--\n ~ Copyright 2023-2025 Broadcom\n ~ SPDX-License-Identifier: Apache-2.0\n -->\n\n<!-- eslint-disable @angular-eslint/template/no-call-expression -->\n\n<div\n id=\"data-job-page\"\n class=\"page-container data-pipelines-job__page\"\n data-cy=\"data-pipelines-job-page\"\n>\n <div\n class=\"data-pipelines-job__actions\"\n [ngClass]=\"{\n 'data-pipelines-job__actions--margin-0': !isSubpageNavigation\n }\"\n >\n <div class=\"data-pipelines-job__actions-left\">\n <div\n *ngIf=\"isSubpageNavigation; else rootPageNavigation\"\n class=\"data-pipelines-job__subpage-navigation\"\n >\n <h2\n class=\"m-0 page-title\"\n data-cy=\"data-pipelines-job-main-title\"\n >\n <a\n class=\"label-link data-pipelines-job__navigate-back\"\n href=\"javascript:;\"\n data-cy=\"data-pipelines-job-navigate-back\"\n (click)=\"doNavigateBack($event)\"\n >\n <clr-icon\n shape=\"redo\"\n flip=\"horizontal\"\n class=\"redo-icon\"\n size=\"25\"\n role=\"none\"\n ></clr-icon>\n </a>\n <span data-cy=\"dp-main-title\"\n ><strong>Data Job:</strong> {{ jobName }}</span\n >\n </h2>\n </div>\n <ng-template #rootPageNavigation>\n <div class=\"data-pipelines-job__root-page-navigation\">\n <h1\n class=\"m-0 page-title\"\n data-cy=\"data-pipelines-page-title\"\n >\n <a\n class=\"label-link data-pipelines-job__navigate-back\"\n href=\"javascript:;\"\n data-cy=\"data-pipelines-job-navigate-back\"\n (click)=\"doNavigateBack($event)\"\n >\n <clr-icon\n shape=\"redo\"\n flip=\"horizontal\"\n class=\"redo-icon\"\n size=\"25\"\n role=\"none\"\n ></clr-icon>\n </a>\n <span>Data Job: {{ jobName }}</span>\n </h1>\n </div>\n </ng-template>\n </div>\n\n <div\n *ngIf=\"isJobEditable\"\n class=\"data-pipelines-job__actions-right\"\n data-cy=\"data-pipelines-job-actions-container\"\n >\n <button\n *ngIf=\"isDataJobRunning && isJobWithRunningStatus()\"\n class=\"btn btn-secondary\"\n data-cy=\"data-pipelines-job-cancel-execution-btn\"\n [clrLoading]=\"stopButtonsState\"\n [disabled]=\"cancelDataJobDisabled\"\n (click)=\"cancelExecution()\"\n >\n Cancel Execution\n </button>\n <button\n *ngIf=\"\n isJobAvailable && isExecuteJobAllowed && !isDataJobRunning\n \"\n class=\"btn btn-secondary\"\n data-cy=\"data-pipelines-job-execute-btn\"\n aria-label=\"Execute now\"\n [disabled]=\"\n !areJobExecutionsLoaded ||\n loadingInProgress ||\n isExecutionInProgress()\n \"\n [clrLoading]=\"executeButtonsState\"\n (click)=\"executeJob()\"\n >\n Execute\n </button>\n\n <clr-dropdown *ngIf=\"isJobAvailable\">\n <button\n clrDropdownTrigger\n class=\"btn btn-secondary data-pipelines-job__action-dropdown-trigger\"\n data-cy=\"data-pipelines-job-action-dropdown-btn\"\n >\n Actions\n <clr-icon\n title=\"Actions\"\n shape=\"caret\"\n [attr.size]=\"15\"\n ></clr-icon>\n </button>\n\n <clr-dropdown-menu clrPosition=\"bottom-right\">\n <button\n *ngIf=\"isDownloadJobKeyAllowed\"\n clrDropdownItem\n class=\"btn btn-secondary\"\n aria-label=\"Download Key\"\n data-cy=\"data-pipelines-job-download-btn\"\n [disabled]=\"loadingInProgress\"\n [clrLoading]=\"downloadButtonsState\"\n (click)=\"downloadJobKey()\"\n >\n Download key\n </button>\n\n <button\n clrDropdownItem\n class=\"btn btn-secondary\"\n aria-label=\"Delete Job\"\n data-cy=\"data-pipelines-job-delete-btn\"\n [disabled]=\"\n !areJobExecutionsLoaded ||\n loadingInProgress ||\n isExecutionInProgress()\n \"\n [clrLoading]=\"deleteButtonsState\"\n (click)=\"removeJob()\"\n >\n Delete\n </button>\n </clr-dropdown-menu>\n </clr-dropdown>\n </div>\n\n <lib-delete-modal\n [options]=\"deleteOptions\"\n (delete)=\"confirmRemoveJob()\"\n ></lib-delete-modal>\n\n <lib-confirmation-dialog-modal\n [options]=\"executeNowOptions\"\n (changeStatus)=\"confirmExecuteJob()\"\n ></lib-confirmation-dialog-modal>\n\n <lib-confirmation-dialog-modal\n [options]=\"cancelNowOptions\"\n (changeStatus)=\"confirmCancelDataJob()\"\n ></lib-confirmation-dialog-modal>\n </div>\n\n <ul\n class=\"nav data-pipelines-job__tabs-navigation\"\n role=\"tablist\"\n aria-owns=\"dataPipelinesJobDetails dataPipelinesJobExecutions dataPipelinesJobLineage\"\n >\n <li role=\"presentation\" class=\"nav-item\">\n <a\n id=\"dataPipelinesJobDetails\"\n class=\"nav-link\"\n role=\"tab\"\n aria-hidden=\"false\"\n data-cy=\"data-pipelines-job-details-tab\"\n [attr.aria-selected]=\"detailsLink.isActive\"\n [routerLink]=\"['details']\"\n [queryParams]=\"queryParams\"\n routerLinkActive=\"active\"\n #detailsLink=\"routerLinkActive\"\n >Details</a\n >\n </li>\n <li\n *ngIf=\"\n dataPipelinesModuleConfig.showExecutionsPage && isJobEditable\n \"\n role=\"presentation\"\n class=\"nav-item\"\n >\n <a\n id=\"dataPipelinesJobExecutions\"\n class=\"nav-link\"\n role=\"tab\"\n aria-hidden=\"false\"\n data-cy=\"data-pipelines-job-executions-tab\"\n [attr.aria-selected]=\"executionsLink.isActive\"\n [routerLink]=\"['executions']\"\n [queryParams]=\"queryParams\"\n routerLinkActive=\"active\"\n #executionsLink=\"routerLinkActive\"\n >Executions</a\n >\n </li>\n <li\n *ngIf=\"dataPipelinesModuleConfig.showLineagePage\"\n role=\"presentation\"\n class=\"nav-item\"\n >\n <a\n id=\"dataPipelinesJobLineage\"\n class=\"nav-link\"\n role=\"tab\"\n aria-hidden=\"false\"\n data-cy=\"data-pipelines-job-lineage-tab\"\n [attr.aria-selected]=\"lineage.isActive\"\n [routerLink]=\"['lineage']\"\n [queryParams]=\"queryParams\"\n routerLinkActive=\"active\"\n #lineage=\"routerLinkActive\"\n >Lineage\n <clr-icon class=\"beta-icon\" shape=\"beta\"></clr-icon>\n </a>\n </li>\n </ul>\n\n <div class=\"data-pipelines-job__router-outlet-container\">\n <router-outlet></router-outlet>\n </div>\n</div>\n", styles: ["/*!\n * Copyright 2023-2025 Broadcom\n * SPDX-License-Identifier: Apache-2.0\n */hr{border:0;height:1px;background:#8f9ba3}.label-link{cursor:pointer}.label-link-suppress-decoration:hover{text-decoration:none}.status-icon-enabled{color:#5aa220}.clr-col-6{border-right:1px solid #999999}.loading-spinner{margin-left:45%;margin-top:100px}:host{display:flex;flex-direction:column;flex:1 1 auto;height:100%}.data-pipelines-job__page{display:flex;flex-direction:column;flex:1 1 auto}.data-pipelines-job__page .data-pipelines-job__actions{display:inline-flex;margin-top:.8rem}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__actions-right{margin-left:auto}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__actions-right clr-dropdown{margin-top:var(--clr-btn-vertical-margin, .3rem);margin-bottom:var(--clr-btn-vertical-margin, .3rem);margin-left:0}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__actions-right .data-pipelines-job__action-dropdown-trigger{padding-right:1.5rem}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__actions-right .data-pipelines-job__action-dropdown-trigger clr-icon{transform:translateY(-50%) rotate(180deg);top:.85rem}.data-pipelines-job__page .data-pipelines-job__actions.data-pipelines-job__actions--margin-0{margin:0}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__navigate-back{margin-right:10px;margin-top:-1px}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__navigate-back .redo-icon{color:var(--clr-link-color, #0072a3);height:25px;width:25px}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__info{display:inline-flex}.data-pipelines-job__page .data-pipelines-job__actions .data-pipelines-job__info span{margin-right:.5rem}.data-pipelines-job__page .data-pipelines-job__actions .page-title span{font-size:x-large}.data-pipelines-job__page .data-pipelines-job__tabs-navigation{margin-top:7px}.data-pipelines-job__page .data-pipelines-job__tabs-navigation .job-details__promotion-icon{margin-top:-.75rem;margin-left:.25rem}.data-pipelines-job__page .data-pipelines-job__tabs-navigation .beta-icon{vertical-align:baseline}.data-pipelines-job__page .data-pipelines-job__router-outlet-container{display:flex;flex-direction:column;flex:1 1 auto}\n"] }] }], function () { return [{ type: i1.ComponentService }, { type: i1.NavigationService }, { type: i2.ActivatedRoute }, { type: i1.RouterService }, { type: i3.DataJobsService }, { type: i3.DataJobsApiService }, { type: i1.ToastService }, { type: i1.ErrorHandlerService }, { type: undefined, decorators: [{ type: Inject, args: [DATA_PIPELINES_CONFIGS] }] }]; }, null); })(); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YS1qb2ItcGFnZS5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9kYXRhLXBpcGVsaW5lcy9zcmMvbGliL2NvbXBvbmVudHMvZGF0YS1qb2IvZGF0YS1qb2ItcGFnZS5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9kYXRhLXBpcGVsaW5lcy9zcmMvbGliL2NvbXBvbmVudHMvZGF0YS1qb2IvZGF0YS1qb2ItcGFnZS5jb21w