From c21daff159a737a4ef59f1d9c7af0f4ebf46a46c Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Tue, 1 Nov 2022 17:07:39 -0600 Subject: [PATCH 01/16] created export library test for method removed all params as per new updated api route spec added route in the rich-skill-service file route will be /api/export/library. Created the component that will call the route and return it and download it to users folder. Button is now hidden when not authorized and visible when user with proper role has access to it. Added the path to routerLink so it just stays in the same page when clicked on created button for library export button checks auth roles to make sure only users with access can export --- ui/src/app/app-routing.module.ts | 1 + ui/src/app/auth/auth-roles.ts | 4 +++- .../navigation/abstract-search.component.ts | 2 ++ .../navigation/commoncontrols.component.html | 7 ++++++- .../navigation/commoncontrols.component.ts | 21 +++++++++++++++++-- .../service/rich-skill.service.spec.ts | 17 +++++++++++++++ .../richskill/service/rich-skill.service.ts | 19 ++++++++++++++++- 7 files changed, 66 insertions(+), 5 deletions(-) diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index 702db5057..aee15b8cc 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -21,6 +21,7 @@ import {PublishCollectionComponent} from "./collection/detail/publish-collection import {CollectionSkillSearchComponent} from "./collection/collection-skill-search.component" import {BatchImportComponent} from "./richskill/import/batch-import.component" import { ActionByRoles, ButtonAction } from "./auth/auth-roles" +import {CommoncontrolsComponent} from "./navigation/commoncontrols.component" const routes: Routes = [ diff --git a/ui/src/app/auth/auth-roles.ts b/ui/src/app/auth/auth-roles.ts index e3269d11c..b8f5e1aca 100644 --- a/ui/src/app/auth/auth-roles.ts +++ b/ui/src/app/auth/auth-roles.ts @@ -10,7 +10,8 @@ export enum ButtonAction { CollectionUpdate, CollectionCreate, CollectionPublish, - CollectionSkillsUpdate + CollectionSkillsUpdate, + LibraryExport } export const ActionByRoles = new Map([ @@ -21,6 +22,7 @@ export const ActionByRoles = new Map([ [ButtonAction.CollectionCreate, [OSMT_ADMIN, OSMT_CURATOR]], [ButtonAction.CollectionPublish, [OSMT_ADMIN]], [ButtonAction.CollectionSkillsUpdate, [OSMT_ADMIN]], + [ButtonAction.LibraryExport, [OSMT_ADMIN]] ]) //TODO migrate AuthServiceWgu & AuthService.hasRole & isEnabledByRoles into a singleton here. HDN Sept 15, 2022 diff --git a/ui/src/app/navigation/abstract-search.component.ts b/ui/src/app/navigation/abstract-search.component.ts index 24b335bda..0214d9842 100644 --- a/ui/src/app/navigation/abstract-search.component.ts +++ b/ui/src/app/navigation/abstract-search.component.ts @@ -16,6 +16,7 @@ export class AbstractSearchComponent { canCollectionCreate: boolean = false canCollectionPublish: boolean = false canCollectionSkillsUpdate: boolean = false + canExportLibrary: boolean = false constructor(protected searchService: SearchService, protected route: ActivatedRoute, protected authService: AuthService) { this.searchService.searchQuery$.subscribe(apiSearch => { @@ -41,6 +42,7 @@ export class AbstractSearchComponent { this.canCollectionCreate = this.authService.isEnabledByRoles(ButtonAction.CollectionCreate); this.canCollectionPublish = this.authService.isEnabledByRoles(ButtonAction.CollectionPublish); this.canCollectionSkillsUpdate = this.authService.isEnabledByRoles(ButtonAction.CollectionSkillsUpdate); + this.canExportLibrary = this.authService.isEnabledByRoles(ButtonAction.LibraryExport); } clearSearch(): boolean { diff --git a/ui/src/app/navigation/commoncontrols.component.html b/ui/src/app/navigation/commoncontrols.component.html index 12f5ab79c..71625c297 100644 --- a/ui/src/app/navigation/commoncontrols.component.html +++ b/ui/src/app/navigation/commoncontrols.component.html @@ -65,7 +65,12 @@ Batch Import RSDs - +
diff --git a/ui/src/app/navigation/commoncontrols.component.ts b/ui/src/app/navigation/commoncontrols.component.ts index 6bd20d1c3..68d8c44a8 100644 --- a/ui/src/app/navigation/commoncontrols.component.ts +++ b/ui/src/app/navigation/commoncontrols.component.ts @@ -1,9 +1,12 @@ -import {Component, OnInit} from "@angular/core" +import {Component, OnInit, LOCALE_ID, Inject} from "@angular/core" +import {formatDate} from "@angular/common" import {AbstractSearchComponent} from "./abstract-search.component" import {SearchService} from "../search/search.service" +import {RichSkillService} from "../richskill/service/rich-skill.service" import {ActivatedRoute} from "@angular/router" import {SvgHelper, SvgIcon} from "../core/SvgHelper" import {AuthService} from "../auth/auth-service" +import * as FileSaver from "file-saver" @Component({ selector: "app-commoncontrols", @@ -14,10 +17,24 @@ export class CommoncontrolsComponent extends AbstractSearchComponent implements searchIcon = SvgHelper.path(SvgIcon.SEARCH) dismissIcon = SvgHelper.path(SvgIcon.DISMISS) - constructor(protected searchService: SearchService, protected route: ActivatedRoute, protected authService: AuthService) { + constructor( + protected searchService: SearchService, + protected route: ActivatedRoute, + protected authService: AuthService, + protected richSkillService: RichSkillService, + @Inject(LOCALE_ID) protected locale: string) { super(searchService, route, authService) } ngOnInit(): void { } + + onDownloadCsv(): void { + this.richSkillService.libraryExport() + .subscribe((csv: string) => { + const blob = new Blob([csv], {type: "text/csv;charset=utf-8;"}) + const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) + FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) + }) + } } diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index 48fe51a6b..dd63172c5 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -306,6 +306,23 @@ describe("RichSkillService", () => { }) }) + it("libraryExport should return", () => { + RouterData.commands = [] + + // Act + const result$ = testService.libraryExport() + + // Assert + result$.subscribe( (data: string) => { + expect(RouterData.commands).toEqual([]) // No Errors + }) + + const req = httpTestingController.expectOne("/api/export/library") + expect(req.request.method).toEqual("GET") + expect(req.request.headers.get("Accept")).toEqual("text/csv") + req.flush(result$) + }) + it("publishSkillsWithResult should return", fakeAsync(() => { // Arrange RouterData.commands = [] diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index 10996bd2e..ad40efe08 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -1,5 +1,5 @@ import {Injectable} from "@angular/core" -import {HttpClient, HttpHeaders} from "@angular/common/http" +import {HttpClient, HttpHeaders, HttpResponse} from "@angular/common/http" import {Observable} from "rxjs" import {ApiAuditLog, ApiSkill, ApiSortOrder, IAuditLog, ISkill} from "../ApiSkill" import {map, share} from "rxjs/operators" @@ -149,6 +149,23 @@ export class RichSkillService extends AbstractService { })) } + libraryExport(): Observable { + + const errorMsg = "Could not export to CSV" + + return this.httpClient + .get(this.buildUrl("api/export/library"), { + headers: this.wrapHeaders(new HttpHeaders({ + Accept: "text/csv" + } + )), + responseType: "text", + observe: "response" + }) + .pipe(share()) + .pipe(map((response) => this.safeUnwrapBody(response.body, errorMsg))) + } + publishSkillsWithResult( apiSearch: ApiSearch, newStatus: PublishStatus = PublishStatus.Published, From 0a6701e0489f1640f56edeb43a3b44fd86dad43e Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Thu, 10 Nov 2022 17:13:08 -0700 Subject: [PATCH 02/16] fixed test that was failing --- ui/src/app/richskill/service/rich-skill.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index dd63172c5..a9b543127 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -317,7 +317,7 @@ describe("RichSkillService", () => { expect(RouterData.commands).toEqual([]) // No Errors }) - const req = httpTestingController.expectOne("/api/export/library") + const req = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/api/export/library") expect(req.request.method).toEqual("GET") expect(req.request.headers.get("Accept")).toEqual("text/csv") req.flush(result$) From db46b71ee7c6cf8a0aef4184ada40fbb9bb98208 Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Thu, 10 Nov 2022 18:03:25 -0700 Subject: [PATCH 03/16] added comment explaining why we added the export library logic inside commons controller --- ui/src/app/navigation/commoncontrols.component.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/src/app/navigation/commoncontrols.component.ts b/ui/src/app/navigation/commoncontrols.component.ts index 68d8c44a8..40a0e5a38 100644 --- a/ui/src/app/navigation/commoncontrols.component.ts +++ b/ui/src/app/navigation/commoncontrols.component.ts @@ -28,7 +28,13 @@ export class CommoncontrolsComponent extends AbstractSearchComponent implements ngOnInit(): void { } - + /** + * Added the logic for export library here because this controllers template holds the button + * The other links on the template link out to other templates and controllers while the export + * button required us to have a click action and call a method from this controller. We could possibly + * move it to the abstract search component or a helper since there are other onDownloadCsv functions + * that are similar to this + **/ onDownloadCsv(): void { this.richSkillService.libraryExport() .subscribe((csv: string) => { From 7f7bb9cebb463e6122885cc180f2f1f75520d83f Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Sun, 13 Nov 2022 17:49:07 -0700 Subject: [PATCH 04/16] removed code from common component to library export component --- ui/src/app/app.module.ts | 2 + .../navigation/commoncontrols.component.html | 7 +--- .../navigation/commoncontrols.component.ts | 24 +---------- .../navigation/libraryexport.component.html | 6 +++ .../app/navigation/libraryexport.component.ts | 41 +++++++++++++++++++ 5 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 ui/src/app/navigation/libraryexport.component.html create mode 100644 ui/src/app/navigation/libraryexport.component.ts diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 8383e482a..f81052186 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -96,6 +96,7 @@ import {LoginComponent} from "./auth/login.component" import {LogoutComponent} from "./auth/logout.component" import {NgIdleKeepaliveModule} from "@ng-idle/keepalive" import {LabelWithSelectComponent} from "./table/skills-library-table/label-with-select.component" +import {LibraryExportComponent} from "./navigation/libraryexport.component" export function initializeApp( appConfig: AppConfig, @@ -133,6 +134,7 @@ export function initializeApp( // Rich skills RichSkillsLibraryComponent, SkillCollectionsDisplayComponent, + LibraryExportComponent, // Rich skill detail RichSkillPublicComponent, diff --git a/ui/src/app/navigation/commoncontrols.component.html b/ui/src/app/navigation/commoncontrols.component.html index 71625c297..a1a8d90ef 100644 --- a/ui/src/app/navigation/commoncontrols.component.html +++ b/ui/src/app/navigation/commoncontrols.component.html @@ -65,12 +65,7 @@ Batch Import RSDs - +
diff --git a/ui/src/app/navigation/commoncontrols.component.ts b/ui/src/app/navigation/commoncontrols.component.ts index 40a0e5a38..82c405589 100644 --- a/ui/src/app/navigation/commoncontrols.component.ts +++ b/ui/src/app/navigation/commoncontrols.component.ts @@ -1,12 +1,9 @@ -import {Component, OnInit, LOCALE_ID, Inject} from "@angular/core" -import {formatDate} from "@angular/common" +import {Component, OnInit} from "@angular/core" import {AbstractSearchComponent} from "./abstract-search.component" import {SearchService} from "../search/search.service" -import {RichSkillService} from "../richskill/service/rich-skill.service" import {ActivatedRoute} from "@angular/router" import {SvgHelper, SvgIcon} from "../core/SvgHelper" import {AuthService} from "../auth/auth-service" -import * as FileSaver from "file-saver" @Component({ selector: "app-commoncontrols", @@ -20,27 +17,10 @@ export class CommoncontrolsComponent extends AbstractSearchComponent implements constructor( protected searchService: SearchService, protected route: ActivatedRoute, - protected authService: AuthService, - protected richSkillService: RichSkillService, - @Inject(LOCALE_ID) protected locale: string) { + protected authService: AuthService) { super(searchService, route, authService) } ngOnInit(): void { } - /** - * Added the logic for export library here because this controllers template holds the button - * The other links on the template link out to other templates and controllers while the export - * button required us to have a click action and call a method from this controller. We could possibly - * move it to the abstract search component or a helper since there are other onDownloadCsv functions - * that are similar to this - **/ - onDownloadCsv(): void { - this.richSkillService.libraryExport() - .subscribe((csv: string) => { - const blob = new Blob([csv], {type: "text/csv;charset=utf-8;"}) - const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) - FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) - }) - } } diff --git a/ui/src/app/navigation/libraryexport.component.html b/ui/src/app/navigation/libraryexport.component.html new file mode 100644 index 000000000..c840b94db --- /dev/null +++ b/ui/src/app/navigation/libraryexport.component.html @@ -0,0 +1,6 @@ + diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts new file mode 100644 index 000000000..5cc26d8d2 --- /dev/null +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -0,0 +1,41 @@ +import {Component, OnInit, LOCALE_ID, Inject} from "@angular/core" +import {formatDate} from "@angular/common" +import {SearchService} from "../search/search.service" +import {RichSkillService} from "../richskill/service/rich-skill.service" +import {ActivatedRoute} from "@angular/router" +import {AuthService} from "../auth/auth-service" +import * as FileSaver from "file-saver" +import {SvgHelper, SvgIcon} from "../core/SvgHelper" +import {CommoncontrolsComponent} from "./commoncontrols.component" +import {AbstractSearchComponent} from "./abstract-search.component" + +@Component({ + selector: "app-libraryexport", + templateUrl: "./libraryexport.component.html" +}) +export class LibraryExportComponent extends AbstractSearchComponent implements OnInit { + + searchIcon = SvgHelper.path(SvgIcon.SEARCH) + dismissIcon = SvgHelper.path(SvgIcon.DISMISS) + + constructor( + protected searchService: SearchService, + protected route: ActivatedRoute, + protected authService: AuthService, + protected richSkillService: RichSkillService, + @Inject(LOCALE_ID) protected locale: string) { + super(searchService, route, authService) + } + + ngOnInit(): void { + } + + onDownloadLibrary(): void { + this.richSkillService.libraryExport() + .subscribe((csv: string) => { + const blob = new Blob([csv], {type: "text/csv;charset=utf-8;"}) + const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) + FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) + }) + } +} From ae57f17c05ff0b2f27ed6cafe97749983a0aa4c7 Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Mon, 14 Nov 2022 22:30:01 -0700 Subject: [PATCH 05/16] Created test spec file for LibraryExport component it tests the onDownloadLibrary function and makes sure it's called correctly --- .../libraryexport.component.spec.ts | 62 +++++++++++++++++++ .../app/navigation/libraryexport.component.ts | 1 - ui/test/resource/mock-stubs.ts | 5 ++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 ui/src/app/navigation/libraryexport.component.spec.ts diff --git a/ui/src/app/navigation/libraryexport.component.spec.ts b/ui/src/app/navigation/libraryexport.component.spec.ts new file mode 100644 index 000000000..768d60220 --- /dev/null +++ b/ui/src/app/navigation/libraryexport.component.spec.ts @@ -0,0 +1,62 @@ +import { Type } from "@angular/core" +import { async, ComponentFixture, TestBed } from "@angular/core/testing" +import { ActivatedRoute, Router } from "@angular/router" +import * as FileSaver from "file-saver" +import { ActivatedRouteStubSpec } from "test/util/activated-route-stub.spec" +import {AuthService} from "../auth/auth-service" +import { RichSkillService } from "../richskill/service/rich-skill.service" +import {AuthServiceStub, RichSkillServiceStub} from "../../../test/resource/mock-stubs" +import {LibraryExportComponent} from "./libraryexport.component" + + +let component: LibraryExportComponent +let fixture: ComponentFixture +let activatedRoute: ActivatedRouteStubSpec + +export function createComponent(T: Type): Promise { + fixture = TestBed.createComponent(T) + component = fixture.componentInstance + + // 1st change detection triggers ngOnInit which gets a hero + fixture.detectChanges() + + return fixture.whenStable().then(() => { + // 2nd change detection displays the async-fetched hero + fixture.detectChanges() + }) +} + +describe("LibraryExportComponent", () => { + beforeEach(async(() => { + const routerSpy = ActivatedRouteStubSpec.createRouterSpy() + activatedRoute = new ActivatedRouteStubSpec() + + TestBed.configureTestingModule({ + declarations: [ + LibraryExportComponent + ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: AuthService, useClass: AuthServiceStub }, + { provide: RichSkillService, useClass: RichSkillServiceStub }, + { provide: Router, useValue: routerSpy } + ] + }) + .compileComponents() + createComponent(LibraryExportComponent) + spyOn(FileSaver, "saveAs").and.stub() + })) + + it("should be created", () => { + expect(LibraryExportComponent).toBeTruthy() + }) + + describe("onDownloadLibrary", () => { + it("Should return blob type and file name", () => { + const blob = new Blob(["x, y"], {type: "text/csv;charset=utf-8;"}) + + component.onDownloadLibrary() + expect(FileSaver.saveAs).toHaveBeenCalledWith(blob, "RSD Library - OSMT 2022-11-14.csv") + }) + }) +}) diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts index 5cc26d8d2..ebbbd583d 100644 --- a/ui/src/app/navigation/libraryexport.component.ts +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -6,7 +6,6 @@ import {ActivatedRoute} from "@angular/router" import {AuthService} from "../auth/auth-service" import * as FileSaver from "file-saver" import {SvgHelper, SvgIcon} from "../core/SvgHelper" -import {CommoncontrolsComponent} from "./commoncontrols.component" import {AbstractSearchComponent} from "./abstract-search.component" @Component({ diff --git a/ui/test/resource/mock-stubs.ts b/ui/test/resource/mock-stubs.ts index 569614059..d8bb52f6a 100644 --- a/ui/test/resource/mock-stubs.ts +++ b/ui/test/resource/mock-stubs.ts @@ -336,6 +336,11 @@ export class RichSkillServiceStub { ): Observable { return of(createMockPaginatedSkills()) } + + // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols + libraryExport(): Observable { + return of(`x, y, z`) + } } export let KeywordSearchServiceData = { From c9a3dd0df45253ab46d8175972580ae6f2f99956 Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Tue, 15 Nov 2022 18:20:09 -0700 Subject: [PATCH 06/16] test is passing which just checks to make sure it was called for now --- ui/src/app/navigation/libraryexport.component.spec.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/src/app/navigation/libraryexport.component.spec.ts b/ui/src/app/navigation/libraryexport.component.spec.ts index 768d60220..d13cf5870 100644 --- a/ui/src/app/navigation/libraryexport.component.spec.ts +++ b/ui/src/app/navigation/libraryexport.component.spec.ts @@ -3,10 +3,10 @@ import { async, ComponentFixture, TestBed } from "@angular/core/testing" import { ActivatedRoute, Router } from "@angular/router" import * as FileSaver from "file-saver" import { ActivatedRouteStubSpec } from "test/util/activated-route-stub.spec" -import {AuthService} from "../auth/auth-service" +import { AuthService } from "../auth/auth-service" import { RichSkillService } from "../richskill/service/rich-skill.service" -import {AuthServiceStub, RichSkillServiceStub} from "../../../test/resource/mock-stubs" -import {LibraryExportComponent} from "./libraryexport.component" +import { AuthServiceStub, RichSkillServiceStub } from "../../../test/resource/mock-stubs" +import { LibraryExportComponent } from "./libraryexport.component" let component: LibraryExportComponent @@ -53,10 +53,9 @@ describe("LibraryExportComponent", () => { describe("onDownloadLibrary", () => { it("Should return blob type and file name", () => { - const blob = new Blob(["x, y"], {type: "text/csv;charset=utf-8;"}) - component.onDownloadLibrary() - expect(FileSaver.saveAs).toHaveBeenCalledWith(blob, "RSD Library - OSMT 2022-11-14.csv") + + expect(FileSaver.saveAs).toHaveBeenCalled() }) }) }) From db8818b853adee7cfdb7559cd19a641251d3f571 Mon Sep 17 00:00:00 2001 From: Edwin Santizo Date: Thu, 17 Nov 2022 17:23:11 -0700 Subject: [PATCH 07/16] cleaned up the code removed unnecessary whitespace and removed unused variables. fixed the async issue that could have been a problem later on --- ui/src/app/app-routing.module.ts | 1 - ui/src/app/navigation/commoncontrols.component.ts | 5 +---- ui/src/app/richskill/service/rich-skill.service.spec.ts | 5 +++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index aee15b8cc..702db5057 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -21,7 +21,6 @@ import {PublishCollectionComponent} from "./collection/detail/publish-collection import {CollectionSkillSearchComponent} from "./collection/collection-skill-search.component" import {BatchImportComponent} from "./richskill/import/batch-import.component" import { ActionByRoles, ButtonAction } from "./auth/auth-roles" -import {CommoncontrolsComponent} from "./navigation/commoncontrols.component" const routes: Routes = [ diff --git a/ui/src/app/navigation/commoncontrols.component.ts b/ui/src/app/navigation/commoncontrols.component.ts index 82c405589..6bd20d1c3 100644 --- a/ui/src/app/navigation/commoncontrols.component.ts +++ b/ui/src/app/navigation/commoncontrols.component.ts @@ -14,10 +14,7 @@ export class CommoncontrolsComponent extends AbstractSearchComponent implements searchIcon = SvgHelper.path(SvgIcon.SEARCH) dismissIcon = SvgHelper.path(SvgIcon.DISMISS) - constructor( - protected searchService: SearchService, - protected route: ActivatedRoute, - protected authService: AuthService) { + constructor(protected searchService: SearchService, protected route: ActivatedRoute, protected authService: AuthService) { super(searchService, route, authService) } diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index a9b543127..ace1d1fbc 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -306,12 +306,13 @@ describe("RichSkillService", () => { }) }) - it("libraryExport should return", () => { + it("libraryExport should return", fakeAsync(() => { RouterData.commands = [] // Act const result$ = testService.libraryExport() + tick(ASYNC_WAIT_PERIOD) // Assert result$.subscribe( (data: string) => { expect(RouterData.commands).toEqual([]) // No Errors @@ -321,7 +322,7 @@ describe("RichSkillService", () => { expect(req.request.method).toEqual("GET") expect(req.request.headers.get("Accept")).toEqual("text/csv") req.flush(result$) - }) + })) it("publishSkillsWithResult should return", fakeAsync(() => { // Arrange From b06aca90209fb638656271c665f98fc1ec318d15 Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Fri, 9 Dec 2022 16:19:45 -0600 Subject: [PATCH 08/16] Download library as csv - Add new request to download library as csv. - Modify onDownloadLibrary and library export. --- .../app/navigation/libraryexport.component.ts | 16 ++++-- .../richskill/service/rich-skill.service.ts | 50 +++++++++++++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts index ebbbd583d..447174b90 100644 --- a/ui/src/app/navigation/libraryexport.component.ts +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -7,6 +7,8 @@ import {AuthService} from "../auth/auth-service" import * as FileSaver from "file-saver" import {SvgHelper, SvgIcon} from "../core/SvgHelper" import {AbstractSearchComponent} from "./abstract-search.component" +import {ApiTaskResult} from "../task/ApiTaskResult" +import {Observable} from "rxjs" @Component({ selector: "app-libraryexport", @@ -31,10 +33,16 @@ export class LibraryExportComponent extends AbstractSearchComponent implements O onDownloadLibrary(): void { this.richSkillService.libraryExport() - .subscribe((csv: string) => { - const blob = new Blob([csv], {type: "text/csv;charset=utf-8;"}) - const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) - FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) + .subscribe((response: ApiTaskResult) => { + this.richSkillService.getResultExportedLibrary(response.id.slice(1)).subscribe( + csv => { + console.log(csv.body) + const blob = new Blob([csv.body], {type: "text/csv;charset=utf-8;"}) + const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) + FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) + } + ) }) } + } diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index ad40efe08..ababb33fd 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -1,8 +1,8 @@ import {Injectable} from "@angular/core" import {HttpClient, HttpHeaders, HttpResponse} from "@angular/common/http" -import {Observable} from "rxjs" +import {concat, Observable, of, throwError} from "rxjs" import {ApiAuditLog, ApiSkill, ApiSortOrder, IAuditLog, ISkill} from "../ApiSkill" -import {map, share} from "rxjs/operators" +import {delay, map, retryWhen, scan, share, switchMap, takeWhile} from "rxjs/operators" import {AbstractService} from "../../abstract.service" import {ApiSkillUpdate} from "../ApiSkillUpdate" import {AuthService} from "../../auth/auth-service" @@ -11,8 +11,8 @@ import {PublishStatus} from "../../PublishStatus" import {ApiBatchResult} from "../ApiBatchResult" import {ApiTaskResult, ITaskResult} from "../../task/ApiTaskResult" import {ApiSkillSummary, ISkillSummary} from "../ApiSkillSummary" -import {Router} from "@angular/router"; -import {Location} from "@angular/common"; +import {Router} from "@angular/router" +import {Location} from "@angular/common" @Injectable({ @@ -43,7 +43,7 @@ export class RichSkillService extends AbstractService { return new PaginatedSkills( body?.map(skill => skill) || [], Number(headers.get("X-Total-Count")) - ) + ) })) } @@ -149,12 +149,32 @@ export class RichSkillService extends AbstractService { })) } - libraryExport(): Observable { + other(): Observable { + return this.pollForTaskResult( + this.libraryExport(), + 1000 + ) + } + + libraryExport(): Observable { const errorMsg = "Could not export to CSV" return this.httpClient - .get(this.buildUrl("api/export/library"), { + .get(this.buildUrl("api/export/library"), { + headers: this.wrapHeaders(new HttpHeaders({ + Accept: "application/json" + } + )), + observe: "response" + }) + .pipe(share()) + .pipe(map(({body}) => new ApiTaskResult(this.safeUnwrapBody(body, "unwrap failure")))) + } + + getResultExportedLibrary(url: string): Observable { + return this.httpClient + .get(this.buildUrl(url), { headers: this.wrapHeaders(new HttpHeaders({ Accept: "text/csv" } @@ -162,8 +182,20 @@ export class RichSkillService extends AbstractService { responseType: "text", observe: "response" }) - .pipe(share()) - .pipe(map((response) => this.safeUnwrapBody(response.body, errorMsg))) + .pipe( + retryWhen(errors => errors.pipe( + switchMap((error) => { + if (error.status === 404) { + return of(error.status) + } + return throwError(error) + // return _throw({message: error.error.message || 'Notification.Core.loginError'}); + }), + // scan(acc => acc + 1, 0), + // takeWhile(acc => acc < 3), + delay(1000), + // concat(_throw({message: 'Notification.Core.networkError'})) + ))) } publishSkillsWithResult( From 690de0f86ddf8e5b410c92a482b499f5863377df Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Fri, 9 Dec 2022 17:46:01 -0600 Subject: [PATCH 09/16] Download csv exportLibraryWithResult - Override observableForTaskResult in rich-skill.service.ts - Refactoring code in libraryexport.component.ts --- .../app/navigation/libraryexport.component.ts | 25 +++++++++------ .../richskill/service/rich-skill.service.ts | 32 +++++++++++++++---- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts index 447174b90..734c37ad8 100644 --- a/ui/src/app/navigation/libraryexport.component.ts +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -8,7 +8,6 @@ import * as FileSaver from "file-saver" import {SvgHelper, SvgIcon} from "../core/SvgHelper" import {AbstractSearchComponent} from "./abstract-search.component" import {ApiTaskResult} from "../task/ApiTaskResult" -import {Observable} from "rxjs" @Component({ selector: "app-libraryexport", @@ -32,17 +31,25 @@ export class LibraryExportComponent extends AbstractSearchComponent implements O } onDownloadLibrary(): void { + /* this.richSkillService.exportLibraryWithResult().subscribe( + csv => { + if (csv) { + this.downloadAsCsvFile(csv.body) + } + } + ) */ this.richSkillService.libraryExport() - .subscribe((response: ApiTaskResult) => { - this.richSkillService.getResultExportedLibrary(response.id.slice(1)).subscribe( - csv => { - console.log(csv.body) - const blob = new Blob([csv.body], {type: "text/csv;charset=utf-8;"}) - const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) - FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) - } + .subscribe((apiTaskResult: ApiTaskResult) => { + this.richSkillService.getResultExportedLibrary(apiTaskResult.id.slice(1)).subscribe( + response => this.downloadAsCsvFile(response.body) ) }) } + private downloadAsCsvFile(csv: string): void { + const blob = new Blob([csv], {type: "text/csv;charset=utf-8;"}) + const date = formatDate(new Date(), "yyyy-MM-dd", this.locale) + FileSaver.saveAs(blob, `RSD Library - OSMT ${date}.csv`) + } + } diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index ababb33fd..4781b7bf4 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -149,7 +149,7 @@ export class RichSkillService extends AbstractService { })) } - other(): Observable { + exportLibraryWithResult(): Observable { return this.pollForTaskResult( this.libraryExport(), 1000 @@ -157,9 +157,6 @@ export class RichSkillService extends AbstractService { } libraryExport(): Observable { - - const errorMsg = "Could not export to CSV" - return this.httpClient .get(this.buildUrl("api/export/library"), { headers: this.wrapHeaders(new HttpHeaders({ @@ -172,6 +169,10 @@ export class RichSkillService extends AbstractService { .pipe(map(({body}) => new ApiTaskResult(this.safeUnwrapBody(body, "unwrap failure")))) } + /** + * Check if result exported with libraryExport() is ready if not check again every 1000 milliseconds. + * @param url Url to get RSD library exported as csv + */ getResultExportedLibrary(url: string): Observable { return this.httpClient .get(this.buildUrl(url), { @@ -189,12 +190,10 @@ export class RichSkillService extends AbstractService { return of(error.status) } return throwError(error) - // return _throw({message: error.error.message || 'Notification.Core.loginError'}); }), // scan(acc => acc + 1, 0), // takeWhile(acc => acc < 3), delay(1000), - // concat(_throw({message: 'Notification.Core.networkError'})) ))) } @@ -244,4 +243,25 @@ export class RichSkillService extends AbstractService { return body || [] })) } + + observableForTaskResult(task: ApiTaskResult, pollIntervalMs: number = 1000): Observable { + return new Observable((observer) => { + const tick = () => { + this.httpClient.get(this.buildUrl(task.id), { + headers: this.wrapHeaders(), + observe: "response", + responseType: "text" + }).subscribe((body) => { + observer.next(body) + observer.complete() + }, (error) => { + if (error.status === 404) { + observer.next(undefined) + setTimeout(() => tick(), pollIntervalMs) + } + }) + } + tick() + }) + } } From a7165d4cdbf55d8cd74239f38f398d5206cfbbd3 Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:18:08 -0600 Subject: [PATCH 10/16] Working in spec files - Add test exportLibraryWithResult. - Update libraryExport should return. --- .../service/rich-skill.service.spec.ts | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index ace1d1fbc..34b55c18c 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -25,6 +25,7 @@ import { ApiSkillSummary } from "../ApiSkillSummary" import { ApiSkillUpdate } from "../ApiSkillUpdate" import { ApiSearch, PaginatedSkills } from "./rich-skill-search.service" import { RichSkillService } from "./rich-skill.service" +import { ApiTaskResult } from "../../task/ApiTaskResult" // An example of how to test a service @@ -314,16 +315,43 @@ describe("RichSkillService", () => { tick(ASYNC_WAIT_PERIOD) // Assert - result$.subscribe( (data: string) => { + result$.subscribe((data: ApiTaskResult) => { expect(RouterData.commands).toEqual([]) // No Errors }) const req = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/api/export/library") expect(req.request.method).toEqual("GET") - expect(req.request.headers.get("Accept")).toEqual("text/csv") + expect(req.request.headers.get("Accept")).toEqual("application/json") req.flush(result$) })) + it("exportLibraryWithResult", fakeAsync(() => { + { + const taskResult = { + uuid: "c2624480-4935-4362-bc71-86e052dcb852", + status: "Processing", + "content-type": "text/csv", + id: "/api/results/text/c2624480-4935-4362-bc71-86e052dcb852" + } + const path = "api/export/library" + const path2 = taskResult.id.slice(1) + testService.exportLibraryWithResult().subscribe(data => { + console.log({result: data}) + } + ) + const req1 = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/" + path) + expect(req1.request.method).toEqual("GET") + req1.flush(taskResult) + + tick(ASYNC_WAIT_PERIOD) + + /* Setup for request 2 */ + const req2 = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/" + path2) + expect(req2.request.method).toEqual("GET") + req2.flush("csv") + } + })) + it("publishSkillsWithResult should return", fakeAsync(() => { // Arrange RouterData.commands = [] From 7461c8541a639eb9818a2417404423d2d1852462 Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:18:43 -0600 Subject: [PATCH 11/16] Use pollForTaskResult - Use exportLibraryWithResult in libraryexport.component.ts. - Comment solution with rxjs operators. --- ui/src/app/navigation/libraryexport.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts index 734c37ad8..147e3b5c0 100644 --- a/ui/src/app/navigation/libraryexport.component.ts +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -31,19 +31,19 @@ export class LibraryExportComponent extends AbstractSearchComponent implements O } onDownloadLibrary(): void { - /* this.richSkillService.exportLibraryWithResult().subscribe( + this.richSkillService.exportLibraryWithResult().subscribe( csv => { if (csv) { this.downloadAsCsvFile(csv.body) } } - ) */ - this.richSkillService.libraryExport() + ) + /* this.richSkillService.libraryExport() .subscribe((apiTaskResult: ApiTaskResult) => { this.richSkillService.getResultExportedLibrary(apiTaskResult.id.slice(1)).subscribe( response => this.downloadAsCsvFile(response.body) ) - }) + }) */ } private downloadAsCsvFile(csv: string): void { From 3ae53034cb8a41f9b1978d5865cc9a506e15531d Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:29:20 -0600 Subject: [PATCH 12/16] Update tests - Add test in libraryexport.component.spec.ts - Update rich-skill.service.spec.ts - Update RichSkillServiceStub - Add mock for api task result --- .../libraryexport.component.spec.ts | 19 ++++++++++++++----- .../service/rich-skill.service.spec.ts | 13 +++---------- ui/test/resource/mock-data.ts | 10 +++++++++- ui/test/resource/mock-stubs.ts | 4 ++++ 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/ui/src/app/navigation/libraryexport.component.spec.ts b/ui/src/app/navigation/libraryexport.component.spec.ts index d13cf5870..603b8ef69 100644 --- a/ui/src/app/navigation/libraryexport.component.spec.ts +++ b/ui/src/app/navigation/libraryexport.component.spec.ts @@ -1,3 +1,4 @@ +/* tslint:disable:no-string-literal */ import { Type } from "@angular/core" import { async, ComponentFixture, TestBed } from "@angular/core/testing" import { ActivatedRoute, Router } from "@angular/router" @@ -7,6 +8,8 @@ import { AuthService } from "../auth/auth-service" import { RichSkillService } from "../richskill/service/rich-skill.service" import { AuthServiceStub, RichSkillServiceStub } from "../../../test/resource/mock-stubs" import { LibraryExportComponent } from "./libraryexport.component" +import { of } from "rxjs" +import { apiTaskResultForCSV } from "../../../test/resource/mock-data" let component: LibraryExportComponent @@ -51,11 +54,17 @@ describe("LibraryExportComponent", () => { expect(LibraryExportComponent).toBeTruthy() }) - describe("onDownloadLibrary", () => { - it("Should return blob type and file name", () => { - component.onDownloadLibrary() + it("Should call export library with result", () => { + const service = TestBed.inject(RichSkillService) + const spy = spyOn(service, "exportLibraryWithResult").and.returnValue(of(apiTaskResultForCSV)) + component.onDownloadLibrary() + expect(spy).toHaveBeenCalled() + // expect(FileSaver.saveAs).toHaveBeenCalled() + }) - expect(FileSaver.saveAs).toHaveBeenCalled() - }) + + it("download as csv file", () => { + component["downloadAsCsvFile"]("value1,value2,value3") + expect(FileSaver.saveAs).toHaveBeenCalled() }) }) diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index 34b55c18c..566259d0f 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -6,6 +6,7 @@ import { HttpClientTestingModule, HttpTestingController } from "@angular/common/ import { fakeAsync, TestBed, tick } from "@angular/core/testing" import { Router } from "@angular/router" import { + apiTaskResultForCSV, createMockAuditLog, createMockBatchResult, createMockPaginatedSkills, @@ -327,18 +328,10 @@ describe("RichSkillService", () => { it("exportLibraryWithResult", fakeAsync(() => { { - const taskResult = { - uuid: "c2624480-4935-4362-bc71-86e052dcb852", - status: "Processing", - "content-type": "text/csv", - id: "/api/results/text/c2624480-4935-4362-bc71-86e052dcb852" - } + const taskResult = apiTaskResultForCSV const path = "api/export/library" const path2 = taskResult.id.slice(1) - testService.exportLibraryWithResult().subscribe(data => { - console.log({result: data}) - } - ) + testService.exportLibraryWithResult().subscribe() const req1 = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/" + path) expect(req1.request.method).toEqual("GET") req1.flush(taskResult) diff --git a/ui/test/resource/mock-data.ts b/ui/test/resource/mock-data.ts index 2761e0f00..949858e89 100644 --- a/ui/test/resource/mock-data.ts +++ b/ui/test/resource/mock-data.ts @@ -16,7 +16,7 @@ import { import { ApiCollectionSummary, ICollectionSummary, ISkillSummary } from "../../src/app/richskill/ApiSkillSummary" import { ApiReferenceListUpdate, IRichSkillUpdate, IStringListUpdate } from "../../src/app/richskill/ApiSkillUpdate" import { PaginatedCollections, PaginatedSkills } from "../../src/app/richskill/service/rich-skill-search.service" -import { ITaskResult } from "../../src/app/task/ApiTaskResult" +import { ApiTaskResult, ITaskResult } from "../../src/app/task/ApiTaskResult" // Add mock data here. // For more examples, see https://github.com/WGU-edu/ema-eval-ui/blob/develop/src/app/admin/pages/edit-user/edit-user.component.spec.ts @@ -277,3 +277,11 @@ export function createMockCollectionUpdate(creationDate: Date, updateDate: Date, skills: createMockStringListUpdate() } } + +export const apiTaskResultForCSV: ApiTaskResult = { + uuid: "c2624480-4935-4362-bc71-86e052dcb852", + status: "Processing", + contentType: "text/csv", + id: "/api/results/text/c2624480-4935-4362-bc71-86e052dcb852" +} + diff --git a/ui/test/resource/mock-stubs.ts b/ui/test/resource/mock-stubs.ts index d8bb52f6a..988b3fcc4 100644 --- a/ui/test/resource/mock-stubs.ts +++ b/ui/test/resource/mock-stubs.ts @@ -341,6 +341,10 @@ export class RichSkillServiceStub { libraryExport(): Observable { return of(`x, y, z`) } + + exportLibraryWithResult(): Observable { + return of("") + } } export let KeywordSearchServiceData = { From 7b6022930092fcf2b3e035bfbb01da275a18bcca Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 13:05:51 -0600 Subject: [PATCH 13/16] Add spinner on download csv --- ui/src/app/navigation/libraryexport.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts index 147e3b5c0..13e117e22 100644 --- a/ui/src/app/navigation/libraryexport.component.ts +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -8,6 +8,7 @@ import * as FileSaver from "file-saver" import {SvgHelper, SvgIcon} from "../core/SvgHelper" import {AbstractSearchComponent} from "./abstract-search.component" import {ApiTaskResult} from "../task/ApiTaskResult" +import {ToastService} from "../toast/toast.service" @Component({ selector: "app-libraryexport", @@ -23,7 +24,9 @@ export class LibraryExportComponent extends AbstractSearchComponent implements O protected route: ActivatedRoute, protected authService: AuthService, protected richSkillService: RichSkillService, - @Inject(LOCALE_ID) protected locale: string) { + @Inject(LOCALE_ID) protected locale: string, + private toastService: ToastService + ) { super(searchService, route, authService) } @@ -31,10 +34,12 @@ export class LibraryExportComponent extends AbstractSearchComponent implements O } onDownloadLibrary(): void { + this.toastService.loaderSubject.next(true) this.richSkillService.exportLibraryWithResult().subscribe( csv => { if (csv) { this.downloadAsCsvFile(csv.body) + this.toastService.loaderSubject.next(false) } } ) From d651b7e6844a41ca21f1ad254eebf3c0edc2e653 Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 14:54:50 -0600 Subject: [PATCH 14/16] Fix broken test - Use getResultExportedLibrary instead of exportLibrary for result to not break previous test. - Update tests with the new implementation. --- .../libraryexport.component.spec.ts | 2 +- .../app/navigation/libraryexport.component.ts | 17 +++++------- .../service/rich-skill.service.spec.ts | 13 +++------ .../richskill/service/rich-skill.service.ts | 27 ------------------- ui/test/resource/mock-stubs.ts | 2 +- 5 files changed, 12 insertions(+), 49 deletions(-) diff --git a/ui/src/app/navigation/libraryexport.component.spec.ts b/ui/src/app/navigation/libraryexport.component.spec.ts index 603b8ef69..50eeb193e 100644 --- a/ui/src/app/navigation/libraryexport.component.spec.ts +++ b/ui/src/app/navigation/libraryexport.component.spec.ts @@ -56,7 +56,7 @@ describe("LibraryExportComponent", () => { it("Should call export library with result", () => { const service = TestBed.inject(RichSkillService) - const spy = spyOn(service, "exportLibraryWithResult").and.returnValue(of(apiTaskResultForCSV)) + const spy = spyOn(service, "libraryExport").and.returnValue(of(apiTaskResultForCSV)) component.onDownloadLibrary() expect(spy).toHaveBeenCalled() // expect(FileSaver.saveAs).toHaveBeenCalled() diff --git a/ui/src/app/navigation/libraryexport.component.ts b/ui/src/app/navigation/libraryexport.component.ts index 13e117e22..29ac42841 100644 --- a/ui/src/app/navigation/libraryexport.component.ts +++ b/ui/src/app/navigation/libraryexport.component.ts @@ -35,20 +35,15 @@ export class LibraryExportComponent extends AbstractSearchComponent implements O onDownloadLibrary(): void { this.toastService.loaderSubject.next(true) - this.richSkillService.exportLibraryWithResult().subscribe( - csv => { - if (csv) { - this.downloadAsCsvFile(csv.body) - this.toastService.loaderSubject.next(false) - } - } - ) - /* this.richSkillService.libraryExport() + this.richSkillService.libraryExport() .subscribe((apiTaskResult: ApiTaskResult) => { this.richSkillService.getResultExportedLibrary(apiTaskResult.id.slice(1)).subscribe( - response => this.downloadAsCsvFile(response.body) + response => { + this.downloadAsCsvFile(response.body) + this.toastService.loaderSubject.next(false) + } ) - }) */ + }) } private downloadAsCsvFile(csv: string): void { diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index 566259d0f..29df65ff5 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -326,22 +326,17 @@ describe("RichSkillService", () => { req.flush(result$) })) - it("exportLibraryWithResult", fakeAsync(() => { + it("getResultExportedLibrary", fakeAsync(() => { { const taskResult = apiTaskResultForCSV - const path = "api/export/library" + const path = "api/results/text/" + apiTaskResultForCSV.uuid const path2 = taskResult.id.slice(1) - testService.exportLibraryWithResult().subscribe() + testService.getResultExportedLibrary(path2).subscribe() const req1 = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/" + path) expect(req1.request.method).toEqual("GET") - req1.flush(taskResult) + req1.flush("csv") tick(ASYNC_WAIT_PERIOD) - - /* Setup for request 2 */ - const req2 = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + "/" + path2) - expect(req2.request.method).toEqual("GET") - req2.flush("csv") } })) diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index 4781b7bf4..b99e75b03 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -149,13 +149,6 @@ export class RichSkillService extends AbstractService { })) } - exportLibraryWithResult(): Observable { - return this.pollForTaskResult( - this.libraryExport(), - 1000 - ) - } - libraryExport(): Observable { return this.httpClient .get(this.buildUrl("api/export/library"), { @@ -244,24 +237,4 @@ export class RichSkillService extends AbstractService { })) } - observableForTaskResult(task: ApiTaskResult, pollIntervalMs: number = 1000): Observable { - return new Observable((observer) => { - const tick = () => { - this.httpClient.get(this.buildUrl(task.id), { - headers: this.wrapHeaders(), - observe: "response", - responseType: "text" - }).subscribe((body) => { - observer.next(body) - observer.complete() - }, (error) => { - if (error.status === 404) { - observer.next(undefined) - setTimeout(() => tick(), pollIntervalMs) - } - }) - } - tick() - }) - } } diff --git a/ui/test/resource/mock-stubs.ts b/ui/test/resource/mock-stubs.ts index 988b3fcc4..41708d34a 100644 --- a/ui/test/resource/mock-stubs.ts +++ b/ui/test/resource/mock-stubs.ts @@ -342,7 +342,7 @@ export class RichSkillServiceStub { return of(`x, y, z`) } - exportLibraryWithResult(): Observable { + getResultExportedLibrary(): Observable { return of("") } } From 13027d62f62c6f34c3cf7941e7cabec0e39fcad0 Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 16:33:19 -0600 Subject: [PATCH 15/16] Add parameter pollIntervalMs - Add parameter pollIntervalMs, the default value is 1000 milliseconds. --- ui/src/app/richskill/service/rich-skill.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index b99e75b03..678632c2f 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -165,8 +165,9 @@ export class RichSkillService extends AbstractService { /** * Check if result exported with libraryExport() is ready if not check again every 1000 milliseconds. * @param url Url to get RSD library exported as csv + * @param pollIntervalMs Milliseconds to retry request */ - getResultExportedLibrary(url: string): Observable { + getResultExportedLibrary(url: string, pollIntervalMs: number = 1000): Observable { return this.httpClient .get(this.buildUrl(url), { headers: this.wrapHeaders(new HttpHeaders({ @@ -186,7 +187,7 @@ export class RichSkillService extends AbstractService { }), // scan(acc => acc + 1, 0), // takeWhile(acc => acc < 3), - delay(1000), + delay(pollIntervalMs), ))) } From 808a29c2fe3b033906e225425c0b05a9808b0ae8 Mon Sep 17 00:00:00 2001 From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:10:59 -0600 Subject: [PATCH 16/16] Remove unused code - Remove commented code and unused imports --- ui/src/app/richskill/service/rich-skill.service.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index 678632c2f..925bd9655 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -1,8 +1,8 @@ import {Injectable} from "@angular/core" import {HttpClient, HttpHeaders, HttpResponse} from "@angular/common/http" -import {concat, Observable, of, throwError} from "rxjs" +import {Observable, of, throwError} from "rxjs" import {ApiAuditLog, ApiSkill, ApiSortOrder, IAuditLog, ISkill} from "../ApiSkill" -import {delay, map, retryWhen, scan, share, switchMap, takeWhile} from "rxjs/operators" +import {delay, map, retryWhen, share, switchMap} from "rxjs/operators" import {AbstractService} from "../../abstract.service" import {ApiSkillUpdate} from "../ApiSkillUpdate" import {AuthService} from "../../auth/auth-service" @@ -185,8 +185,6 @@ export class RichSkillService extends AbstractService { } return throwError(error) }), - // scan(acc => acc + 1, 0), - // takeWhile(acc => acc < 3), delay(pollIntervalMs), ))) }