Unverified Commit 130e1c5f authored by bugclerk's avatar bugclerk Committed by GitHub
Browse files

WIP | NAS-126848 / 24.04-RC.1 / Export to CSV Not Working (by AlexKarpov98) (#9520)


* NAS-126848: Export to CSV Not Working

(cherry picked from commit 4ca6da389616b09d5b84c0a30309b2ff87ea655d)

* NAS-126848: Export to CSV Not Working

(cherry picked from commit 73bdd83e8b01d24e07d65b63d62dd59155131589)

* NAS-126848: PR update

(cherry picked from commit 184f520dec377ad258a545b4d86e34512a797641)

* NAS-126848: PR update

(cherry picked from commit 505845e9ca2a0345c6a6c1899f4b59e0a74ff368)

* NAS-126848: PR Update

(cherry picked from commit d3b06a6060b58f76c648604c6c0384fd399ded4c)

* NAS-126848: PR update

(cherry picked from commit 926a4a96eb52a5e7491c1b0f4903bdb500dcf10d)

* NAS-126848: Export to CSV Not Working

* NAS-126848: PR update

---------
Co-authored-by: default avatarOleksandr Karpov <Karpov4you@gmail.com>
parent ea436779
Showing with 75 additions and 40 deletions
+75 -40
......@@ -317,6 +317,7 @@ export interface ApiCallDirectory {
'audit.config': { params: void; response: AuditConfig };
'audit.query': { params: [AuditQueryParams]; response: AuditEntry[] };
'audit.update': { params: [AuditConfig]; response: AuditEntry[] };
'audit.download_report': { params: [{ report_name?: string }]; response: string[] };
// Auth
'auth.generate_token': { params: void; response: string };
......
<button
mat-button
color="accent"
ixTest="export"
[disabled]="isLoading"
(click)="onExport()"
>
{{ 'Export As CSV' | translate }}
</button>
<div class="relative">
<mat-progress-bar *ngIf="isLoading" mode="indeterminate"></mat-progress-bar>
<button
mat-button
color="accent"
ixTest="export"
[disabled]="isLoading"
(click)="onExport()"
>
{{ 'Export As {fileType}' | translate:{ fileType: fileType.toUpperCase()} }}
</button>
</div>
mat-progress-bar {
opacity: 0.6;
position: absolute;
top: -4px;
width: 100%;
z-index: 1;
}
.relative {
position: relative;
}
......@@ -13,17 +13,17 @@ import { StorageService } from 'app/services/storage.service';
import { WebSocketService } from 'app/services/ws.service';
describe('ExportButtonComponent', () => {
const method: ApiJobMethod = 'audit.export';
const jobMethod: ApiJobMethod = 'audit.export';
type EntryType = AuditEntry;
let spectator: Spectator<ExportButtonComponent<EntryType, typeof method>>;
let spectator: Spectator<ExportButtonComponent<EntryType, typeof jobMethod>>;
let loader: HarnessLoader;
const createComponent = createComponentFactory({
component: ExportButtonComponent<EntryType, typeof method>,
component: ExportButtonComponent<EntryType, typeof jobMethod>,
providers: [
mockWebsocket([
mockJob(method, { result: '/path/data.csv', state: JobState.Success } as Job<string>),
mockJob(jobMethod, { result: '/path/data.csv', state: JobState.Success } as Job<string>),
mockCall('core.download', [33456, '/_download/33456?auth_token=1234567890']),
]),
mockProvider(StorageService, {
......@@ -35,7 +35,7 @@ describe('ExportButtonComponent', () => {
beforeEach(() => {
spectator = createComponent({
props: {
method,
jobMethod,
defaultFilters: [['event', '~', '(?i)search query']],
},
});
......@@ -46,12 +46,12 @@ describe('ExportButtonComponent', () => {
const exportButton = await loader.getHarness(MatButtonHarness.with({ text: 'Export As CSV' }));
await exportButton.click();
expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith(method, [{
expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith(jobMethod, [{
export_format: 'CSV',
'query-filters': [],
'query-options': {},
}]);
expect(spectator.inject(WebSocketService).call).toHaveBeenCalledWith('core.download', [method, [{}], '/path/data.csv']);
expect(spectator.inject(WebSocketService).call).toHaveBeenCalledWith('core.download', [jobMethod, [{}], '/path/data.csv']);
expect(spectator.inject(StorageService).downloadUrl).toHaveBeenLastCalledWith(
'/_download/33456?auth_token=1234567890',
'data.csv',
......@@ -74,12 +74,12 @@ describe('ExportButtonComponent', () => {
const exportButton = await loader.getHarness(MatButtonHarness.with({ text: 'Export As CSV' }));
await exportButton.click();
expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith(method, [{
expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith(jobMethod, [{
export_format: 'CSV',
'query-filters': [['event', '~', '(?i)search query']],
'query-options': { order_by: ['-service'] },
}]);
expect(spectator.inject(WebSocketService).call).toHaveBeenCalledWith('core.download', [method, [{}], '/path/data.csv']);
expect(spectator.inject(WebSocketService).call).toHaveBeenCalledWith('core.download', [jobMethod, [{}], '/path/data.csv']);
expect(spectator.inject(StorageService).downloadUrl).toHaveBeenLastCalledWith(
'/_download/33456?auth_token=1234567890',
'data.csv',
......
......@@ -5,6 +5,7 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { catchError, EMPTY, switchMap } from 'rxjs';
import { ExportFormat } from 'app/enums/export-format.enum';
import { JobState } from 'app/enums/job-state.enum';
import { ApiCallDirectory } from 'app/interfaces/api/api-call-directory.interface';
import { ApiJobMethod, ApiJobParams } from 'app/interfaces/api/api-job-directory.interface';
import { PropertyPath } from 'app/interfaces/property-path.type';
import { QueryFilters, QueryOptions } from 'app/interfaces/query-api.interface';
......@@ -20,14 +21,19 @@ import { WebSocketService } from 'app/services/ws.service';
@Component({
selector: 'ix-export-button',
templateUrl: './export-button.component.html',
styleUrls: ['./export-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExportButtonComponent<T, M extends ApiJobMethod> {
@Input() method: M;
@Input() jobMethod: M;
@Input() searchQuery: SearchQuery<T>;
@Input() defaultFilters: QueryFilters<T>;
@Input() sorting: TableSort<T>;
@Input() filename = 'data';
@Input() fileType = 'csv';
@Input() fileMimeType = 'text/csv';
@Input() addReportNameArgument = false;
@Input() downloadMethod?: keyof ApiCallDirectory;
isLoading = false;
......@@ -41,12 +47,11 @@ export class ExportButtonComponent<T, M extends ApiJobMethod> {
onExport(): void {
this.isLoading = true;
this.ws.job(this.method, this.getExportParams(
this.ws.job(this.jobMethod, this.getExportParams(
this.getQueryFilters(this.searchQuery),
this.getQueryOptions(this.sorting),
)).pipe(
switchMap((job) => {
this.isLoading = false;
this.cdr.markForCheck();
if (job.state === JobState.Failed) {
this.dialogService.error(this.errorHandler.parseError(job));
......@@ -55,12 +60,18 @@ export class ExportButtonComponent<T, M extends ApiJobMethod> {
if (job.state !== JobState.Success) {
return EMPTY;
}
const url = job.result as string;
return this.ws.call('core.download', [this.method, [{}], url]);
}),
switchMap(([, url]) => {
return this.storage.downloadUrl(url, `${this.filename}.csv`, 'text/csv');
const customArguments = {} as { report_name?: string };
const downloadMethod = this.downloadMethod || this.jobMethod;
if (this.addReportNameArgument) {
customArguments.report_name = url;
}
return this.ws.call('core.download', [downloadMethod, [customArguments], url]);
}),
switchMap(([, url]) => this.storage.downloadUrl(url, `${this.filename}.${this.fileType}`, this.fileMimeType)),
catchError((error) => {
this.isLoading = false;
this.cdr.markForCheck();
......@@ -68,7 +79,10 @@ export class ExportButtonComponent<T, M extends ApiJobMethod> {
return EMPTY;
}),
untilDestroyed(this),
).subscribe();
).subscribe(() => {
this.isLoading = false;
this.cdr.markForCheck();
});
}
private getExportParams(
......
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { TranslateModule } from '@ngx-translate/core';
import { ExportButtonComponent } from 'app/modules/export-button/components/export-button/export-button.component';
......@@ -10,6 +11,7 @@ import { ExportButtonComponent } from 'app/modules/export-button/components/expo
CommonModule,
TranslateModule,
MatButtonModule,
MatProgressBarModule,
],
exports: [
ExportButtonComponent,
......
......@@ -82,7 +82,9 @@
<ng-template #exportButton>
<ix-export-button
*ngIf="dataProvider.totalRows"
method="audit.export"
jobMethod="audit.export"
downloadMethod="audit.download_report"
[addReportNameArgument]="true"
[searchQuery]="searchQuery"
[sorting]="dataProvider.sorting"
[defaultFilters]="basicQueryFilters"
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1401,7 +1401,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export ZFS snapshots as <a href=\"https://docs.microsoft.com/en-us/windows/desktop/vss/shadow-copies-and-shadow-copy-sets\" target=_blank>Shadow Copies</a> for VSS clients.": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
......@@ -1578,7 +1578,7 @@
"Expiration Date": "",
"Export": "",
"Export All Keys": "",
"Export As CSV": "",
"Export As {fileType}": "",
"Export Key": "",
"Export Password Secret Seed": "",
"Export Read Only": "",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment