Export all events matching dates (#990)
* Export eagerly pulls down all events Export does not add to rendered elements since that may cause slow down. Export is tied to the currently rendered list of events though `dirtyDates` bool * Use manual btn-submit class * Remove unnecessary method * Fix ExpressionChangedAfterItHasBeenCheckedError
This commit is contained in:
parent
744e86601f
commit
945e968e06
|
@ -4,21 +4,29 @@
|
||||||
<div class="form-inline">
|
<div class="form-inline">
|
||||||
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
|
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
|
||||||
<input type="datetime-local" class="form-control form-control-sm" id="start"
|
<input type="datetime-local" class="form-control form-control-sm" id="start"
|
||||||
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM">
|
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM"
|
||||||
|
(change)="dirtyDates = true">
|
||||||
<span class="mx-2">-</span>
|
<span class="mx-2">-</span>
|
||||||
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
|
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
|
||||||
<input type="datetime-local" class="form-control form-control-sm" id="end"
|
<input type="datetime-local" class="form-control form-control-sm" id="end"
|
||||||
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM">
|
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM"
|
||||||
|
(change)="dirtyDates = true">
|
||||||
</div>
|
</div>
|
||||||
<button #refreshBtn [appApiAction]="refreshPromise" type="button" class="btn btn-sm btn-outline-primary ml-3"
|
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
|
||||||
(click)="loadEvents(true)" [disabled]="loaded && refreshBtn.loading">
|
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
|
||||||
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
|
[disabled]="loaded && refreshForm.loading">
|
||||||
{{'refresh' | i18n}}
|
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshForm.loading}"></i>
|
||||||
</button>
|
{{'refresh' | i18n}}
|
||||||
<button #exportBtn [appApiAction]="exportPromise" type="button" class="btn btn-sm btn-outline-primary ml-3"
|
</button>
|
||||||
(click)="exportEvents()" [disabled]="loaded && refreshBtn.loading">
|
</form>
|
||||||
{{'export' | i18n}}
|
<form #exportForm [appApiAction]="exportForm" class="d-inline">
|
||||||
</button>
|
<button type="button" class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
|
||||||
|
[ngClass]="{loading:refreshForm.loading}" (click)="exportEvents()"
|
||||||
|
[disabled]="loaded && exportForm.loading || dirtyDates">
|
||||||
|
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
|
||||||
|
<span>{{'export' | i18n}}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="!loaded">
|
<ng-container *ngIf="!loaded">
|
||||||
|
|
|
@ -29,6 +29,7 @@ export class EventsComponent implements OnInit {
|
||||||
events: EventView[];
|
events: EventView[];
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
|
dirtyDates: boolean = true;
|
||||||
continuationToken: string;
|
continuationToken: string;
|
||||||
refreshPromise: Promise<any>;
|
refreshPromise: Promise<any>;
|
||||||
exportPromise: Promise<any>;
|
exportPromise: Promise<any>;
|
||||||
|
@ -69,16 +70,20 @@ export class EventsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportEvents() {
|
async exportEvents() {
|
||||||
if (this.appApiPromiseUnfulfilled()) {
|
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.exportPromise = this.exportService.getEventExport(this.events).then(data => {
|
|
||||||
const fileName = this.exportService.getFileName('org-events', 'csv');
|
const dates = this.parseDates();
|
||||||
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName);
|
if (dates == null) {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.exportPromise = this.export(dates[0], dates[1]);
|
||||||
|
|
||||||
await this.exportPromise;
|
await this.exportPromise;
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
|
@ -91,29 +96,56 @@ export class EventsComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dates: string[] = null;
|
const dates = this.parseDates();
|
||||||
try {
|
if (dates == null) {
|
||||||
dates = this.eventService.formatDateFilters(this.start, this.end);
|
|
||||||
} catch (e) {
|
|
||||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
|
||||||
this.i18nService.t('invalidDateRange'));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let response: ListResponse<EventResponse>;
|
let events: EventView[] = [];
|
||||||
try {
|
try {
|
||||||
const promise = this.apiService.getEventsOrganization(this.organizationId, dates[0], dates[1],
|
const promise = this.loadAndParseEvents(dates[0], dates[1], clearExisting ? null : this.continuationToken);
|
||||||
clearExisting ? null : this.continuationToken);
|
|
||||||
if (clearExisting) {
|
if (clearExisting) {
|
||||||
this.refreshPromise = promise;
|
this.refreshPromise = promise;
|
||||||
} else {
|
} else {
|
||||||
this.morePromise = promise;
|
this.morePromise = promise;
|
||||||
}
|
}
|
||||||
response = await promise;
|
const result = await promise;
|
||||||
|
this.continuationToken = result.continuationToken;
|
||||||
|
events = result.events;
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
this.continuationToken = response.continuationToken;
|
if (!clearExisting && this.events != null && this.events.length > 0) {
|
||||||
|
this.events = this.events.concat(events);
|
||||||
|
} else {
|
||||||
|
this.events = events;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dirtyDates = false;
|
||||||
|
this.loading = false;
|
||||||
|
this.morePromise = null;
|
||||||
|
this.refreshPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async export(start: string, end: string) {
|
||||||
|
let continuationToken = this.continuationToken;
|
||||||
|
let events = [].concat(this.events);
|
||||||
|
|
||||||
|
while (continuationToken != null) {
|
||||||
|
const result = await this.loadAndParseEvents(start, end, continuationToken);
|
||||||
|
continuationToken = result.continuationToken;
|
||||||
|
events = events.concat(result.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await this.exportService.getEventExport(events);
|
||||||
|
const fileName = this.exportService.getFileName('org-events', 'csv');
|
||||||
|
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadAndParseEvents(startDate: string, endDate: string, continuationToken: string) {
|
||||||
|
const response = await this.apiService.getEventsOrganization(this.organizationId, startDate, endDate,
|
||||||
|
continuationToken);
|
||||||
|
|
||||||
const events = await Promise.all(response.data.map(async r => {
|
const events = await Promise.all(response.data.map(async r => {
|
||||||
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
|
||||||
const eventInfo = await this.eventService.getEventInfo(r);
|
const eventInfo = await this.eventService.getEventInfo(r);
|
||||||
|
@ -132,16 +164,19 @@ export class EventsComponent implements OnInit {
|
||||||
type: r.type,
|
type: r.type,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
return { continuationToken: response.continuationToken, events: events };
|
||||||
|
}
|
||||||
|
|
||||||
if (!clearExisting && this.events != null && this.events.length > 0) {
|
private parseDates() {
|
||||||
this.events = this.events.concat(events);
|
let dates: string[] = null;
|
||||||
} else {
|
try {
|
||||||
this.events = events;
|
dates = this.eventService.formatDateFilters(this.start, this.end);
|
||||||
|
} catch (e) {
|
||||||
|
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('invalidDateRange'));
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
return dates;
|
||||||
this.loading = false;
|
|
||||||
this.morePromise = null;
|
|
||||||
this.refreshPromise = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private appApiPromiseUnfulfilled() {
|
private appApiPromiseUnfulfilled() {
|
||||||
|
|
|
@ -232,10 +232,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-primary disabled" disabled=true *ngIf="disableSend">
|
<button type="submit" class="btn btn-primary btn-submit manual" [ngClass]="{loading:form.loading}"
|
||||||
<span>{{'save' | i18n}}</span>
|
[disabled]="form.loading || disableSend">
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!disableSend">
|
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||||
<span>{{'save' | i18n}}</span>
|
<span>{{'save' | i18n}}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
Loading…
Reference in New Issue