[Trunk | Monitor Dashboard]:

1. indicators.component.ts:
	a. Added fields "import_loading" and "export_loading" and set "editing", to show loading icon and disable edit options while export or import process is ongoing.
	b. Validate schema of indicators' file when importing it (type (chart or number), url, and jsonPath (for number indicators) are requested).
2. indicators.component.html: 
	a. Instead of import or export icon (next to buttons), show loading icon when "import_loading" or "export_loading" is true.
	b. Disable edit options for indicators when "import_loading" or "export_loading" is true and show tooltip.


git-svn-id: https://svn.driver.research-infrastructures.eu/driver/dnet40/modules/uoa-monitor-portal/trunk/monitor_dashboard@61433 d315682c-612b-4755-9ff5-7f18f6832af3
This commit is contained in:
Konstantina Galouni 2021-07-19 09:50:08 +00:00
parent 94fe302965
commit f51ca5f7ed
2 changed files with 96 additions and 28 deletions

View File

@ -1,12 +1,14 @@
<div *ngIf="stakeholder && canEdit"> <div *ngIf="stakeholder && canEdit">
<div class="uk-text-right uk-width-expand"> <div class="uk-text-right uk-width-expand">
<a class="uk-margin-bottom uk-margin-right " uk-icon="icon:download" title="Export indicators (JSON file)" <a class="uk-margin-bottom uk-margin-right " [attr.uk-icon]="export_loading ? null : 'icon:download'" title="Export indicators (JSON file)"
(click)="export_indicators()"> (click)="export_indicators()">
<span class="uk-text-small">Export</span> <span class="uk-text-small">Export</span>
<span *ngIf="export_loading" class="uk-margin-small-left uk-icon icon-button"><loading [top_margin]="false"></loading></span>
</a> </a>
<div uk-form-custom class="js-upload uk-margin-top"> <div uk-form-custom class="js-upload uk-margin-top">
<input id="exampleInputFile" type="file" class="uk-width-medium" (change)="fileChangeEvent($event)"/> <input id="exampleInputFile" type="file" class="uk-width-medium" (change)="fileChangeEvent($event)"/>
<a class=" uk-margin-bottom" uk-icon="icon:upload" title="Import indicators (JSON file)"> Import</a> <a class=" uk-margin-bottom" [attr.uk-icon]="import_loading ? null : 'icon:upload'" title="Import indicators (JSON file)"> Import</a>
<span *ngIf="import_loading" class="uk-margin-small-left uk-icon icon-button"><loading [top_margin]="false"></loading></span>
</div> </div>
</div> </div>
@ -60,8 +62,9 @@
<div class="uk-card uk-card-default uk-card-body uk-position-relative" [class.uk-card-hover]="canReorder"> <div class="uk-card uk-card-default uk-card-body uk-position-relative" [class.uk-card-hover]="canReorder">
<ng-container <ng-container
*ngTemplateOutlet="visibilityOptions; context:{indicator: indicator, sectionId: number._id}"></ng-container> *ngTemplateOutlet="visibilityOptions; context:{indicator: indicator, sectionId: number._id}"></ng-container>
<div class="uk-position-top-right uk-margin-small-right uk-margin-small-top clickable"> <div [attr.uk-tooltip]="export_loading ? 'title:Edit is disabled, while exporting indicators; cls:uk-active' : 'cls: uk-invisible'"
<i uk-icon="more-vertical" (click)="$event.stopPropagation();$event.preventDefault()"></i> class="uk-position-top-right uk-margin-small-right uk-margin-small-top clickable">
<i [class]="export_loading ? 'uk-disabled ' : ' '" uk-icon="more-vertical" (click)="$event.stopPropagation();$event.preventDefault()"></i>
<div #element uk-dropdown="mode: click; pos: bottom-right; delay-hide: 0; flip: false"> <div #element uk-dropdown="mode: click; pos: bottom-right; delay-hide: 0; flip: false">
<ul class="uk-nav uk-dropdown-nav"> <ul class="uk-nav uk-dropdown-nav">
<li *ngIf="!editing"><a <li *ngIf="!editing"><a
@ -164,8 +167,9 @@
<div class="uk-card uk-card-default uk-card-body uk-position-relative" [class.uk-card-hover]="canReorder"> <div class="uk-card uk-card-default uk-card-body uk-position-relative" [class.uk-card-hover]="canReorder">
<ng-container <ng-container
*ngTemplateOutlet="visibilityOptions; context:{indicator: indicator, sectionId: chart._id}"></ng-container> *ngTemplateOutlet="visibilityOptions; context:{indicator: indicator, sectionId: chart._id}"></ng-container>
<div class="uk-position-top-right uk-margin-small-right uk-margin-small-top clickable"> <div [attr.uk-tooltip]="export_loading ? 'title:Edit is disabled, while exporting indicators; cls:uk-active' : 'cls: uk-invisible'"
<i uk-icon="more-vertical" (click)="$event.stopPropagation();$event.preventDefault()"></i> class="uk-position-top-right uk-margin-small-right uk-margin-small-top clickable">
<i [class]="export_loading ? 'uk-disabled ' : ' '" uk-icon="more-vertical" (click)="$event.stopPropagation();$event.preventDefault()"></i>
<div #element uk-dropdown="mode: click; pos: bottom-right; delay-hide: 0; flip: false"> <div #element uk-dropdown="mode: click; pos: bottom-right; delay-hide: 0; flip: false">
<ul class="uk-nav uk-dropdown-nav"> <ul class="uk-nav uk-dropdown-nav">
<li *ngIf="!editing"><a <li *ngIf="!editing"><a
@ -543,8 +547,9 @@
Are you sure you want to proceed? Are you sure you want to proceed?
</modal-alert>--> </modal-alert>-->
<ng-template #visibilityOptions let-indicator="indicator" let-sectionId="sectionId"> <ng-template #visibilityOptions let-indicator="indicator" let-sectionId="sectionId">
<span class="uk-position-top-left uk-margin-small-left uk-margin-small-top clickable visibility"> <span [attr.uk-tooltip]="export_loading ? 'title:Edit is disabled, while exporting indicators; cls:uk-active' : 'cls: uk-invisible'"
<span class="clickable color"> class="uk-position-top-left uk-margin-small-left uk-margin-small-top visibility" [class]="export_loading ? '' : ' clickable '">
<span [class]="export_loading ? 'uk-disabled ' : ' '" class="clickable color">
<icon [name]="stakeholderUtils.visibilityIcon.get(indicator.visibility)"></icon> <icon [name]="stakeholderUtils.visibilityIcon.get(indicator.visibility)"></icon>
</span> </span>
<div #element uk-dropdown="mode: click; pos: bottom-left; delay-hide: 0; flip: false"> <div #element uk-dropdown="mode: click; pos: bottom-left; delay-hide: 0; flip: false">

View File

@ -45,7 +45,8 @@ declare var UIkit;
export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
filesToUpload: Array<File>; filesToUpload: Array<File>;
errorMessage = ""; errorMessage = "";
enableUpload: boolean = true; import_loading: boolean = false;
export_loading: boolean = false;
@Input() @Input()
public properties: EnvProperties = null; public properties: EnvProperties = null;
@ -910,6 +911,7 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
this.filterCharts(); this.filterCharts();
this.filterNumbers(); this.filterNumbers();
this.editing = false; this.editing = false;
this.import_loading = false;
UIkit.notification("Success! Indicated were imported", { UIkit.notification("Success! Indicated were imported", {
status: 'success', status: 'success',
timeout: 6000, timeout: 6000,
@ -923,6 +925,7 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
pos: 'bottom-right' pos: 'bottom-right'
}); });
this.editing = false; this.editing = false;
this.import_loading = false;
})); }));
@ -1233,6 +1236,30 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
for(let chart of charts) { for(let chart of charts) {
let exists = false; let exists = false;
let indicatorPath; let indicatorPath;
// validate indicators' schema from file
let invalid_file_message = "";
if (!chart.type) {
invalid_file_message = "No indicator type is specified. Type should be chart or number.";
} else if(chart.type != "chart" || chart.type != "number") {
invalid_file_message = "Invalid indicator type. Type should be chart or number.";
} else if(chart.type == "number" && !chart.jsonPath) {
invalid_file_message = "No jsonPath is specified for number indicator."
} else if(!chart.url) {
invalid_file_message = "No indicator url is specified.";
}
if(invalid_file_message) {
UIkit.notification("Error on indicators' schema. Type should be chart or number, url is needed and for number indicators, jsonPath is needed as well.", {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.editing = false;
this.import_loading = false;
break;
}
if (chart.type == "chart") { if (chart.type == "chart") {
indicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.statisticsService.getChartSource(chart.url), chart.url, chart.type, this.stakeholder); indicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.statisticsService.getChartSource(chart.url), chart.url, chart.type, this.stakeholder);
for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].charts) { for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].charts) {
@ -1283,6 +1310,8 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
timeout: 6000, timeout: 6000,
pos: 'bottom-right' pos: 'bottom-right'
}); });
this.editing = false;
this.import_loading = false;
}else if(indicators.length > 0){ }else if(indicators.length > 0){
console.log(indicators); console.log(indicators);
this.saveIndicators(indicators) this.saveIndicators(indicators)
@ -1293,12 +1322,17 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
timeout: 6000, timeout: 6000,
pos: 'bottom-right' pos: 'bottom-right'
}); });
this.editing = false;
this.import_loading = false;
} }
} }
public export_indicators() { public export_indicators() {
console.debug("Export indicators"); console.debug("Export indicators");
this.editing = true;
this.export_loading = true;
let indicators = []; let indicators = [];
let index: number = 0; let index: number = 0;
@ -1307,10 +1341,11 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
indicator.indicatorPaths.forEach(indicatorPath => { indicator.indicatorPaths.forEach(indicatorPath => {
console.debug("export number: ", this.statisticsService.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath))); console.debug("export number: ", this.statisticsService.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)));
indicators[index] = { indicators[index] = {
"type": indicator.type, "name": indicator.name, "jsonPath":indicatorPath.jsonPath, "type": indicator.type, "name": indicator.name, "jsonPath": indicatorPath.jsonPath,
"description": indicator.description, "additionalDescription": indicator.additionalDescription, "description": indicator.description, "additionalDescription": indicator.additionalDescription,
"visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height,
"url": this.statisticsService.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath))}; "url": this.statisticsService.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath))
};
index++; index++;
}); });
}); });
@ -1319,12 +1354,13 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
this.displayCharts.forEach(section => { this.displayCharts.forEach(section => {
section.indicators.forEach(indicator => { section.indicators.forEach(indicator => {
indicator.indicatorPaths.forEach(indicatorPath => { indicator.indicatorPaths.forEach(indicatorPath => {
console.debug("export chart: "+this.getUrlByStakeHolder(indicatorPath)); console.debug("export chart: " + this.getUrlByStakeHolder(indicatorPath));
indicators[index] = { indicators[index] = {
"type": indicator.type, "name": indicator.name, "type": indicator.type, "name": indicator.name,
"description": indicator.description, "additionalDescription": indicator.additionalDescription, "description": indicator.description, "additionalDescription": indicator.additionalDescription,
"visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height,
"url": this.getUrlByStakeHolder(indicatorPath)}; "url": this.getUrlByStakeHolder(indicatorPath)
};
index++; index++;
}); });
}); });
@ -1339,29 +1375,45 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
window.document.body.appendChild(a); window.document.body.appendChild(a);
a.setAttribute('style', 'display: none'); a.setAttribute('style', 'display: none');
a.href = jsonFileUrl; a.href = jsonFileUrl;
a.download = this.stakeholder.alias + "_"+topic.alias + "_" + category.alias + "_" + subCategory.alias + ".json"; a.download = this.stakeholder.alias + "_" + topic.alias + "_" + category.alias + "_" + subCategory.alias + ".json";
a.click(); a.click();
window.URL.revokeObjectURL(jsonFileUrl); window.URL.revokeObjectURL(jsonFileUrl);
a.remove(); // remove the element a.remove(); // remove the element
this.editing = false;
this.export_loading = false;
} }
fileChangeEvent(fileInput: any) { fileChangeEvent(fileInput: any) {
this.editing = true;
this.import_loading = true;
this.filesToUpload = <Array<File>>fileInput.target.files; this.filesToUpload = <Array<File>>fileInput.target.files;
this.upload(); this.upload();
} }
upload() { upload() {
this.enableUpload = false;
// this.errorMessage = "";
console.debug(this.filesToUpload); console.debug(this.filesToUpload);
if (this.filesToUpload.length == 0) { if (this.filesToUpload.length == 0) {
console.error("There is no selected file to upload."); console.error("There is no selected file to upload.");
// this.errorMessage = "There is no selected file to upload."; UIkit.notification("There is no selected file to upload.", {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.editing = false;
this.import_loading = false;
return; return;
} else { } else {
if (this.filesToUpload[0].name.indexOf(".json") == -1 || (this.filesToUpload[0].type != "application/json")) { if (this.filesToUpload[0].name.indexOf(".json") == -1 || (this.filesToUpload[0].type != "application/json")) {
// this.errorMessage = "No valid file type. The required type is JSON";
console.error("No valid file type. The required type is JSON"); console.error("No valid file type. The required type is JSON");
UIkit.notification("No valid file type. The required type is JSON", {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.editing = false;
this.import_loading = false;
return; return;
} }
} }
@ -1370,11 +1422,28 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
this.makeFileRequest(this.properties.utilsService + '/upload?type=json', [], this.filesToUpload).then(async (result: string) => { this.makeFileRequest(this.properties.utilsService + '/upload?type=json', [], this.filesToUpload).then(async (result: string) => {
let json_result = JSON.parse(result); let json_result = JSON.parse(result);
this.importIndicatorsAndSave(json_result);
this.endOfFetching(); // validate file
if(!json_result || json_result.length == 0) {
UIkit.notification("Importing file is empty", {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.editing = false;
this.import_loading = false;
} else {
this.importIndicatorsAndSave(json_result);
}
}, (error) => { }, (error) => {
this.enableUpload = true; console.error("Error importing files", error);
console.error("An error occurred"); UIkit.notification("Error importing files", {
status: 'danger',
timeout: 6000,
pos: 'bottom-right'
});
this.editing = false;
this.import_loading = false;
}); });
} }
@ -1398,10 +1467,4 @@ export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterV
xhr.send(formData); xhr.send(formData);
}); });
} }
endOfFetching() {
// this.showReport = true;
this.enableUpload = true;
}
} }