1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-23 11:56:00 +01:00

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:
Matt Gibson 2021-06-02 07:21:57 -05:00 committed by GitHub
parent 744e86601f
commit 945e968e06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 39 deletions

View File

@ -4,21 +4,29 @@
<div class="form-inline">
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
<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>
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
<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>
<button #refreshBtn [appApiAction]="refreshPromise" type="button" class="btn btn-sm btn-outline-primary ml-3"
(click)="loadEvents(true)" [disabled]="loaded && refreshBtn.loading">
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
{{'refresh' | i18n}}
</button>
<button #exportBtn [appApiAction]="exportPromise" type="button" class="btn btn-sm btn-outline-primary ml-3"
(click)="exportEvents()" [disabled]="loaded && refreshBtn.loading">
{{'export' | i18n}}
</button>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
[disabled]="loaded && refreshForm.loading">
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshForm.loading}"></i>
{{'refresh' | i18n}}
</button>
</form>
<form #exportForm [appApiAction]="exportForm" class="d-inline">
<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>
<ng-container *ngIf="!loaded">

View File

@ -29,6 +29,7 @@ export class EventsComponent implements OnInit {
events: EventView[];
start: string;
end: string;
dirtyDates: boolean = true;
continuationToken: string;
refreshPromise: Promise<any>;
exportPromise: Promise<any>;
@ -69,16 +70,20 @@ export class EventsComponent implements OnInit {
}
async exportEvents() {
if (this.appApiPromiseUnfulfilled()) {
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
return;
}
this.loading = true;
this.exportPromise = this.exportService.getEventExport(this.events).then(data => {
const fileName = this.exportService.getFileName('org-events', 'csv');
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName);
});
const dates = this.parseDates();
if (dates == null) {
return;
}
try {
this.exportPromise = this.export(dates[0], dates[1]);
await this.exportPromise;
} catch { }
@ -91,29 +96,56 @@ export class EventsComponent implements OnInit {
return;
}
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
const dates = this.parseDates();
if (dates == null) {
return;
}
this.loading = true;
let response: ListResponse<EventResponse>;
let events: EventView[] = [];
try {
const promise = this.apiService.getEventsOrganization(this.organizationId, dates[0], dates[1],
clearExisting ? null : this.continuationToken);
const promise = this.loadAndParseEvents(dates[0], dates[1], clearExisting ? null : this.continuationToken);
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
response = await promise;
const result = await promise;
this.continuationToken = result.continuationToken;
events = result.events;
} 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 userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
@ -132,16 +164,19 @@ export class EventsComponent implements OnInit {
type: r.type,
});
}));
return { continuationToken: response.continuationToken, events: events };
}
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
private parseDates() {
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
return null;
}
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
return dates;
}
private appApiPromiseUnfulfilled() {

View File

@ -232,10 +232,8 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary disabled" disabled=true *ngIf="disableSend">
<span>{{'save' | i18n}}</span>
</button>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="!disableSend">
<button type="submit" class="btn btn-primary btn-submit manual" [ngClass]="{loading:form.loading}"
[disabled]="form.loading || disableSend">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'save' | i18n}}</span>
</button>