From 7599a57e1b0d5041a900d5cf13ba1bcd1ccc226f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr> Date: Thu, 17 Mar 2022 17:00:03 +0100 Subject: [PATCH] #34 => WIP --- client/src/app/app-config.service.ts | 1 + client/src/app/app-init.ts | 6 +- client/src/app/app.module.ts | 2 + client/src/app/instance/instance.reducer.ts | 3 - .../result/datatable-actions.component.html | 2 +- .../result/datatable-actions.component.ts | 14 +- .../result/datatable-tab.component.ts | 2 +- .../components/result/download.component.html | 2 +- .../components/result/download.component.ts | 13 +- .../search/components/result/index.ts | 4 +- .../components/result/samp.component.html | 32 --- .../components/result/samp.component.spec.ts | 183 ------------------ .../components/result/samp.component.ts | 57 ------ .../search/containers/result.component.html | 19 +- .../search/containers/result.component.ts | 13 +- .../store/actions/download-file.actions.ts | 6 +- .../store/effects/download-file.effects.ts | 29 ++- .../src/app/instance/store/effects/index.ts | 2 - .../src/app/instance/store/services/index.ts | 2 - .../store/actions => samp}/samp.actions.ts | 0 .../effects => samp}/samp.effects.spec.ts | 4 +- .../store/effects => samp}/samp.effects.ts | 27 ++- client/src/app/samp/samp.module.ts | 28 +++ .../reducers => samp}/samp.reducer.spec.ts | 2 +- .../store/reducers => samp}/samp.reducer.ts | 2 +- .../selectors => samp}/samp.selector.spec.ts | 2 +- .../store/selectors => samp}/samp.selector.ts | 12 +- .../store/services => samp}/samp.service.ts | 16 +- docker-compose.yml | 3 +- server/app/settings.php | 1 + server/src/Action/ClientSettingsAction.php | 1 + 31 files changed, 147 insertions(+), 343 deletions(-) delete mode 100644 client/src/app/instance/search/components/result/samp.component.html delete mode 100644 client/src/app/instance/search/components/result/samp.component.spec.ts delete mode 100644 client/src/app/instance/search/components/result/samp.component.ts rename client/src/app/{instance/store/actions => samp}/samp.actions.ts (100%) rename client/src/app/{instance/store/effects => samp}/samp.effects.spec.ts (97%) rename client/src/app/{instance/store/effects => samp}/samp.effects.ts (69%) create mode 100644 client/src/app/samp/samp.module.ts rename client/src/app/{instance/store/reducers => samp}/samp.reducer.spec.ts (96%) rename client/src/app/{instance/store/reducers => samp}/samp.reducer.ts (93%) rename client/src/app/{instance/store/selectors => samp}/samp.selector.spec.ts (84%) rename client/src/app/{instance/store/selectors => samp}/samp.selector.ts (52%) rename client/src/app/{instance/store/services => samp}/samp.service.ts (97%) diff --git a/client/src/app/app-config.service.ts b/client/src/app/app-config.service.ts index 604ad74d..5c27615b 100644 --- a/client/src/app/app-config.service.ts +++ b/client/src/app/app-config.service.ts @@ -5,6 +5,7 @@ export class AppConfigService { public apiUrl: string; public servicesUrl: string; public baseHref: string; + public sampEnabled: string; public authenticationEnabled: boolean; public ssoAuthUrl: string; public ssoRealm: string; diff --git a/client/src/app/app-init.ts b/client/src/app/app-init.ts index af48b11b..630038a9 100644 --- a/client/src/app/app-init.ts +++ b/client/src/app/app-init.ts @@ -8,11 +8,13 @@ import { AppConfigService } from './app-config.service'; import { initializeKeycloak } from 'src/app/auth/init.keycloak'; import { environment } from 'src/environments/environment'; +import { firstValueFrom } from 'rxjs'; function appInit(http: HttpClient, appConfigService: AppConfigService, keycloak: KeycloakService, store: Store<{ }>) { return () => { - return http.get(getClientSettingsUrl()) - .toPromise() + const source$ = http.get(getClientSettingsUrl()); + + return firstValueFrom(source$) .then(data => { Object.assign(appConfigService, data); appConfigService.apiUrl = environment.apiUrl; diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index cca4aece..4552174d 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -24,6 +24,7 @@ import { CustomSerializer } from './custom-route-serializer'; import { CoreModule } from './core/core.module'; import { AuthModule } from './auth/auth.module'; import { MetamodelModule } from './metamodel/metamodel.module'; +import { SampModule } from './samp/samp.module'; import { AppComponent } from './core/containers/app.component'; import { AppConfigService } from './app-config.service'; import { appInitializer } from './app-init'; @@ -36,6 +37,7 @@ import { appInitializer } from './app-init'; CoreModule, AuthModule, MetamodelModule, + SampModule, AppRoutingModule, StoreModule.forRoot(reducers, { metaReducers, diff --git a/client/src/app/instance/instance.reducer.ts b/client/src/app/instance/instance.reducer.ts index ed942d2b..3be0935a 100644 --- a/client/src/app/instance/instance.reducer.ts +++ b/client/src/app/instance/instance.reducer.ts @@ -12,7 +12,6 @@ import { combineReducers, createFeatureSelector } from '@ngrx/store'; import { RouterReducerState } from 'src/app/custom-route-serializer'; import * as search from './store/reducers/search.reducer'; import * as searchMultiple from './store/reducers/search-multiple.reducer'; -import * as samp from './store/reducers/samp.reducer'; import * as coneSearch from './store/reducers/cone-search.reducer'; import * as detail from './store/reducers/detail.reducer'; import * as svomJsonKw from './store/reducers/svom-json-kw.reducer'; @@ -26,7 +25,6 @@ import * as downloadFile from './store/reducers/download-file.reducer'; export interface State { search: search.State, searchMultiple: searchMultiple.State, - samp: samp.State, coneSearch: coneSearch.State detail: detail.State, svomJsonKw: svomJsonKw.State, @@ -36,7 +34,6 @@ export interface State { const reducers = { search: search.searchReducer, searchMultiple: searchMultiple.searchMultipleReducer, - samp: samp.sampReducer, coneSearch: coneSearch.coneSearchReducer, detail: detail.detailReducer, svomJsonKw: svomJsonKw.svomJsonKwReducer, diff --git a/client/src/app/instance/search/components/result/datatable-actions.component.html b/client/src/app/instance/search/components/result/datatable-actions.component.html index a9a9a6e3..f5403152 100644 --- a/client/src/app/instance/search/components/result/datatable-actions.component.html +++ b/client/src/app/instance/search/components/result/datatable-actions.component.html @@ -19,7 +19,7 @@ </a> </li> <li *ngIf="getConfigDownloadResultFormat('download_vo')" role="menuitem" [class.disabled]="!sampRegistered"> - <a class="dropdown-item" [class.disabled]="!sampRegistered" (click)="broadcast.emit()"> + <a class="dropdown-item" [class.disabled]="!sampRegistered" (click)="broadcastResult()"> <span class="fas fa-broadcast-tower"></span> Broadcast VOtable </a> </li> diff --git a/client/src/app/instance/search/components/result/datatable-actions.component.ts b/client/src/app/instance/search/components/result/datatable-actions.component.ts index 8b3b7ef2..58331ce1 100644 --- a/client/src/app/instance/search/components/result/datatable-actions.component.ts +++ b/client/src/app/instance/search/components/result/datatable-actions.component.ts @@ -17,8 +17,7 @@ export class DatatableActionsComponent { @Input() coneSearch: ConeSearch; @Input() dataLength: number; @Input() sampRegistered: boolean; - @Output() broadcast: EventEmitter<string> = new EventEmitter(); - @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean }> = new EventEmitter(); + @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean, broadcastVo: boolean }> = new EventEmitter(); @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter(); /** @@ -44,10 +43,19 @@ export class DatatableActionsComponent { downloadResult(format: string) { this.startTaskCreateResult.emit({ format, - selectedData: true + selectedData: true, + broadcastVo: false }); } + broadcastResult() { + this.startTaskCreateResult.emit({ + format: 'votable', + selectedData: true, + broadcastVo: true + }) + } + downloadArchive() { this.startTaskCreateArchive.emit({ selectedData: true diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.ts b/client/src/app/instance/search/components/result/datatable-tab.component.ts index 2f4e3475..97335f09 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.ts +++ b/client/src/app/instance/search/components/result/datatable-tab.component.ts @@ -40,7 +40,7 @@ export class DatatableTabComponent { @Output() addSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter(); @Output() broadcast: EventEmitter<string> = new EventEmitter(); - @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean }> = new EventEmitter(); + @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean, broadcastVo: boolean }> = new EventEmitter(); @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter(); @Output() downloadFile: EventEmitter<{url: string, fileId: string, datasetName: string, filename: string}> = new EventEmitter(); } diff --git a/client/src/app/instance/search/components/result/download.component.html b/client/src/app/instance/search/components/result/download.component.html index 0c9c15a5..cf29bc57 100644 --- a/client/src/app/instance/search/components/result/download.component.html +++ b/client/src/app/instance/search/components/result/download.component.html @@ -30,7 +30,7 @@ <span class="fas fa-file"></span> VOtable </a> - <button *ngIf="getConfigDownloadResultFormat('download_vo')" [disabled]="!sampRegistered" (click)="downloadResult('votable')" class="btn btn-outline-primary" title="Broadcast samp votable"> + <button *ngIf="getConfigDownloadResultFormat('download_vo')" [disabled]="!sampRegistered" (click)="broadcastResult()" class="btn btn-outline-primary" title="Broadcast samp votable"> <span class="fas fa-broadcast-tower"></span> Broadcast VOtable </button> </div> diff --git a/client/src/app/instance/search/components/result/download.component.ts b/client/src/app/instance/search/components/result/download.component.ts index d5a16313..42f3584a 100644 --- a/client/src/app/instance/search/components/result/download.component.ts +++ b/client/src/app/instance/search/components/result/download.component.ts @@ -29,7 +29,7 @@ export class DownloadComponent { @Input() dataLength: number; @Input() sampRegistered: boolean; @Output() broadcast: EventEmitter<string> = new EventEmitter(); - @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean }> = new EventEmitter(); + @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean, broadcastVo: boolean }> = new EventEmitter(); @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter(); /** @@ -64,10 +64,19 @@ export class DownloadComponent { downloadResult(format: string) { this.startTaskCreateResult.emit({ format, - selectedData: false + selectedData: false, + broadcastVo: false }); } + broadcastResult() { + this.startTaskCreateResult.emit({ + format: 'votable', + selectedData: false, + broadcastVo: true + }) + } + downloadArchive() { this.startTaskCreateArchive.emit({ selectedData: false diff --git a/client/src/app/instance/search/components/result/index.ts b/client/src/app/instance/search/components/result/index.ts index 4fa8d96a..b39de14f 100644 --- a/client/src/app/instance/search/components/result/index.ts +++ b/client/src/app/instance/search/components/result/index.ts @@ -1,9 +1,8 @@ import { DatatableTabComponent } from './datatable-tab.component'; import { DownloadComponent } from './download.component'; import { ReminderComponent } from './reminder.component'; -import { SampComponent } from './samp.component'; import { UrlDisplayComponent } from './url-display.component'; -import {Â DatatableComponent } from './datatable.component'; +import { DatatableComponent } from './datatable.component'; import { DatatableActionsComponent } from './datatable-actions.component'; import { DownloadFileTabComponent } from './download-file-tab.component'; import { rendererComponents } from './renderer'; @@ -12,7 +11,6 @@ export const resultComponents = [ DatatableTabComponent, DownloadComponent, ReminderComponent, - SampComponent, UrlDisplayComponent, DatatableComponent, DatatableActionsComponent, diff --git a/client/src/app/instance/search/components/result/samp.component.html b/client/src/app/instance/search/components/result/samp.component.html deleted file mode 100644 index 9970bd3d..00000000 --- a/client/src/app/instance/search/components/result/samp.component.html +++ /dev/null @@ -1,32 +0,0 @@ -<accordion *ngIf="isSampActivated()" [isAnimated]="true"> - <accordion-group #ag [isOpen]="isSampOpened()" [panelClass]="'custom-accordion'" class="my-2"> - <button class="btn btn-link btn-block clearfix" accordion-heading> - <span class="pull-left float-left"> - <span [style.color]="getColor()"> - <span class="fas fa-circle"></span> - </span> SAMP access - - <span *ngIf="ag.isOpen"> - <span class="fas fa-chevron-up"></span> - </span> - <span *ngIf="!ag.isOpen"> - <span class="fas fa-chevron-down"></span> - </span> - </span> - </button> - <div> - <div class="row"> - <p *ngIf="!sampRegistered" class="lead"> - You are not connected to a SAMP-hub - </p> - <p *ngIf="sampRegistered" class="lead"> - You are connected to a SAMP-hub - </p> - </div> - <div class="row"> - <button *ngIf="!sampRegistered" (click)="sampRegister.emit()" class="btn btn-outline-primary">Try to register</button> - <button *ngIf="sampRegistered" (click)="sampUnregister.emit()" class="btn btn-outline-primary">Unregister</button> - </div> - </div> - </accordion-group> -</accordion> diff --git a/client/src/app/instance/search/components/result/samp.component.spec.ts b/client/src/app/instance/search/components/result/samp.component.spec.ts deleted file mode 100644 index fd6f128f..00000000 --- a/client/src/app/instance/search/components/result/samp.component.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - -import { AccordionModule } from 'ngx-bootstrap/accordion'; - -import { SampComponent } from './samp.component'; - -describe('[Instance][Search][Component][Result] SampComponent', () => { - let component: SampComponent; - let fixture: ComponentFixture<SampComponent>; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [SampComponent], - imports: [ - AccordionModule.forRoot(), - BrowserAnimationsModule - ] - }); - fixture = TestBed.createComponent(SampComponent); - component = fixture.componentInstance; - }); - - it('should create the component', () => { - expect(component).toBeTruthy(); - }); - - it('#isSampActivated() should return if SAMP has to be enabled or not', () => { - component.datasetList = [ - { - name: 'myDataset', - table_ref: 'table', - label: 'my dataset', - description: 'This is my dataset', - display: 1, - data_path: '/path', - survey_name: 'mySurvey', - id_dataset_family: 1, - public: true, - full_data_path: '/data/path', - info_survey_enabled: true, - info_survey_label: 'More about this survey', - cone_search_enabled: true, - cone_search_opened: true, - cone_search_column_ra: 1, - cone_search_column_dec: 2, - download_enabled: true, - download_opened: true, - download_csv: true, - download_ascii: true, - download_vo: true, - download_archive: true, - summary_enabled: true, - summary_opened: true, - server_link_enabled: false, - server_link_opened: true, - samp_enabled: false, - samp_opened: false, - datatable_enabled: true, - datatable_opened: true, - datatable_selectable_rows: true - }, - { - name: 'anotherDataset', - table_ref: 'table', - label: 'another dataset', - description: 'This is another dataset', - display: 1, - data_path: '/path', - survey_name: 'mySurvey', - id_dataset_family: 1, - public: true, - full_data_path: '/data/path', - info_survey_enabled: true, - info_survey_label: 'More about this survey', - cone_search_enabled: true, - cone_search_opened: true, - cone_search_column_ra: 1, - cone_search_column_dec: 2, - download_enabled: true, - download_opened: true, - download_csv: true, - download_ascii: true, - download_vo: true, - download_archive: true, - summary_enabled: true, - summary_opened: true, - server_link_enabled: true, - server_link_opened: true, - samp_enabled: true, - samp_opened: true, - datatable_enabled: true, - datatable_opened: true, - datatable_selectable_rows: true - } - ]; - component.datasetSelected = 'myDataset'; - expect(component.isSampActivated()).toBeFalsy(); - component.datasetSelected = 'anotherDataset'; - expect(component.isSampActivated()).toBeTruthy(); - }); - - it('#isSampOpened() should return if URL tab has to be opened or not', () => { - component.datasetList = [ - { - name: 'myDataset', - table_ref: 'table', - label: 'my dataset', - description: 'This is my dataset', - display: 1, - data_path: '/path', - survey_name: 'mySurvey', - id_dataset_family: 1, - public: true, - full_data_path: '/data/path', - info_survey_enabled: true, - info_survey_label: 'More about this survey', - cone_search_enabled: true, - cone_search_opened: true, - cone_search_column_ra: 1, - cone_search_column_dec: 2, - download_enabled: true, - download_opened: true, - download_csv: true, - download_ascii: true, - download_vo: true, - download_archive: true, - summary_enabled: true, - summary_opened: true, - server_link_enabled: false, - server_link_opened: false, - samp_enabled: false, - samp_opened: false, - datatable_enabled: true, - datatable_opened: true, - datatable_selectable_rows: true - }, - { - name: 'anotherDataset', - table_ref: 'table', - label: 'another dataset', - description: 'This is another dataset', - display: 1, - data_path: '/path', - survey_name: 'mySurvey', - id_dataset_family: 1, - public: true, - full_data_path: '/data/path', - info_survey_enabled: true, - info_survey_label: 'More about this survey', - cone_search_enabled: true, - cone_search_opened: true, - cone_search_column_ra: 1, - cone_search_column_dec: 2, - download_enabled: true, - download_opened: true, - download_csv: true, - download_ascii: true, - download_vo: true, - download_archive: true, - summary_enabled: true, - summary_opened: true, - server_link_enabled: true, - server_link_opened: true, - samp_enabled: true, - samp_opened: true, - datatable_enabled: true, - datatable_opened: true, - datatable_selectable_rows: true - } - ]; - component.datasetSelected = 'myDataset'; - expect(component.isSampOpened()).toBeFalsy(); - component.datasetSelected = 'anotherDataset'; - expect(component.isSampOpened()).toBeTruthy(); - }); - - it('#getColor() should return color button depending on SAMP registered or not', () => { - expect(component.getColor()).toEqual('red'); - component.sampRegistered = true; - expect(component.getColor()).toEqual('green'); - }); -}); diff --git a/client/src/app/instance/search/components/result/samp.component.ts b/client/src/app/instance/search/components/result/samp.component.ts deleted file mode 100644 index be61e1c9..00000000 --- a/client/src/app/instance/search/components/result/samp.component.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core'; - -import { Dataset } from 'src/app/metamodel/models'; - -/** - * @class - * @classdesc Samp component. - */ -@Component({ - selector: 'app-samp', - templateUrl: 'samp.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SampComponent { - @Input() datasetSelected: string; - @Input() datasetList: Dataset[]; - @Input() sampRegistered: boolean; - @Output() sampRegister: EventEmitter<{}> = new EventEmitter(); - @Output() sampUnregister: EventEmitter<{}> = new EventEmitter(); - - /** - * Checks if SAMP has to be display. - * - * @return boolean - */ - isSampActivated(): boolean { - return this.datasetList.find(d => d.name === this.datasetSelected).samp_enabled; - } - - /** - * Checks if SAMP tab has to be opened. - * - * @return boolean - */ - isSampOpened(): boolean { - return this.datasetList.find(d => d.name === this.datasetSelected).samp_opened; - } - - /** - * Returns SAMP color button. - * - * @return boolean - */ - getColor(): string { - if (this.sampRegistered) return 'green'; - return 'red'; - } -} diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index d06e9f7f..3a1eabae 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -25,6 +25,18 @@ selected with <span class="font-weight-bold">{{ dataLength | async }}</span> objects found. </div> </div> + <div *ngIf="sampEnabled()" class="jumbotron mb-4 py-4"> + <div class="lead"> + <ng-container *ngIf="!(sampRegistered | async)"> + You are not connected to a SAMP-hub + <button (click)="sampRegister()" class="btn btn-outline-primary">Try to register</button> + </ng-container> + <ng-container *ngIf="(sampRegistered | async)"> + You are connected to a SAMP-hub + <button (click)="sampUnregister()" class="btn btn-outline-primary">Unregister</button> + </ng-container> + </div> + </div> <app-download-file-tab *ngIf="(downloadedFiles | async).length > 0" [downloadedFiles]="downloadedFiles | async"> @@ -52,13 +64,6 @@ [coneSearch]="coneSearch | async" [outputList]="outputList | async"> </app-reminder> - <app-samp - [datasetSelected]="datasetSelected | async" - [datasetList]="datasetList | async" - [sampRegistered]="sampRegistered | async" - (sampRegister)="sampRegister()" - (sampUnregister)="sampUnregister()"> - </app-samp> <app-url-display [datasetSelected]="datasetSelected | async" [datasetList]="datasetList | async" diff --git a/client/src/app/instance/search/containers/result.component.ts b/client/src/app/instance/search/containers/result.component.ts index cc058ea4..8d93cdba 100644 --- a/client/src/app/instance/search/containers/result.component.ts +++ b/client/src/app/instance/search/containers/result.component.ts @@ -12,14 +12,15 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; +import { AppConfigService } from 'src/app/app-config.service'; import { AbstractSearchComponent } from './abstract-search.component'; import { Pagination, DownloadFile } from '../../store/models'; import { Instance } from 'src/app/metamodel/models'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; import * as searchActions from '../../store/actions/search.actions'; import * as searchSelector from '../../store/selectors/search.selector'; -import * as sampActions from '../../store/actions/samp.actions'; -import * as sampSelector from '../../store/selectors/samp.selector'; +import * as sampActions from 'src/app/samp/samp.actions'; +import * as sampSelector from 'src/app/samp/samp.selector'; import * as downloadFileActions from '../../store/actions/download-file.actions'; import * as downloadFileSelector from '../../store/selectors/download-file.selector'; @@ -47,7 +48,7 @@ export class ResultComponent extends AbstractSearchComponent { public downloadedFiles: Observable<DownloadFile[]>; public pristineSubscription: Subscription; - constructor(protected store: Store<{ }>) { + constructor(protected store: Store<{ }>, private appConfig: AppConfigService) { super(store); this.instance = store.select(instanceSelector.selectInstanceByRouteName); this.dataLength = this.store.select(searchSelector.selectDataLength); @@ -74,6 +75,10 @@ export class ResultComponent extends AbstractSearchComponent { }); } + sampEnabled() { + return this.appConfig.sampEnabled; + } + /** * Dispatches action to register to SAMP. */ @@ -138,7 +143,7 @@ export class ResultComponent extends AbstractSearchComponent { * * @param string format - Info about result format */ - startTaskCreateResult(event: { format: string, selectedData: boolean }) { + startTaskCreateResult(event: { format: string, selectedData: boolean, broadcastVo: boolean }) { this.store.dispatch(downloadFileActions.startTaskCreateResult(event)); } diff --git a/client/src/app/instance/store/actions/download-file.actions.ts b/client/src/app/instance/store/actions/download-file.actions.ts index 8368ed92..60fefb6f 100644 --- a/client/src/app/instance/store/actions/download-file.actions.ts +++ b/client/src/app/instance/store/actions/download-file.actions.ts @@ -9,10 +9,10 @@ import { createAction, props } from '@ngrx/store'; -export const startTaskCreateResult = createAction('[File] Start Task Create Result', props<{ format: string, selectedData: boolean }>()); -export const startTaskCreateResultSuccess = createAction('[File] Start Task Create Result Success', props<{ fileId: string, datasetName: string, filename: string }>()); +export const startTaskCreateResult = createAction('[File] Start Task Create Result', props<{ format: string, selectedData: boolean, broadcastVo: boolean }>()); +export const startTaskCreateResultSuccess = createAction('[File] Start Task Create Result Success', props<{ fileId: string, datasetName: string, filename: string, broadcastVo: boolean }>()); export const startTaskCreateResultFail = createAction('[File] Start Task Create Result Fail'); -export const isResultAvailable = createAction('[File] Is Result Available', props<{ fileId: string, datasetName: string, filename: string }>()); +export const isResultAvailable = createAction('[File] Is Result Available', props<{ fileId: string, datasetName: string, filename: string, broadcastVo: boolean }>()); export const isResultAvailableFail = createAction('[File] Is Result Available Fail'); export const startTaskCreateArchive = createAction('[File] Start Task Create Archive', props<{ selectedData: boolean }>()); diff --git a/client/src/app/instance/store/effects/download-file.effects.ts b/client/src/app/instance/store/effects/download-file.effects.ts index b7caa911..1cb58cd4 100644 --- a/client/src/app/instance/store/effects/download-file.effects.ts +++ b/client/src/app/instance/store/effects/download-file.effects.ts @@ -19,6 +19,7 @@ import { ToastrService } from 'ngx-toastr'; import { AppConfigService } from 'src/app/app-config.service'; import { DownloadFileService } from '../services/download-file.service'; import * as downloadFileActions from '../actions/download-file.actions'; +import * as sampActions from 'src/app/samp/samp.actions'; import * as searchSelector from '../selectors/search.selector'; import * as attributeSelector from 'src/app/metamodel/selectors/attribute.selector'; import * as coneSearchSelector from '../selectors/cone-search.selector'; @@ -61,7 +62,8 @@ export class DownloadFileEffects { map((response) => downloadFileActions.startTaskCreateResultSuccess({ fileId: response.file_id, filename: response.file_name, - datasetName: currentDataset + datasetName: currentDataset, + broadcastVo: action.broadcastVo })), catchError(() => of(downloadFileActions.startTaskCreateResultFail())) ) @@ -94,17 +96,28 @@ export class DownloadFileEffects { ofType(downloadFileActions.isResultAvailable), switchMap(action => this.downloadFileService.isResultAvailable(action.fileId) .pipe( - map(result => { + switchMap(result => { if (result.file_is_available) { this.kill$[action.fileId].next({}); this.kill$[action.fileId].unsubscribe(); - return downloadFileActions.startsDownloadingFile({ - fileId: action.fileId, - filename: action.filename, - url: `${this.config.apiUrl}/download-result/${action.datasetName}/${action.fileId}` - }); + if (action.broadcastVo) { + return [ + sampActions.broadcastVotable({ + url: `${this.config.apiUrl}/download-result/${action.datasetName}/${action.fileId}` + }), + downloadFileActions.fileDownloaded({ fileId: action.fileId }) + ]; + } else { + return [ + downloadFileActions.startsDownloadingFile({ + fileId: action.fileId, + filename: action.filename, + url: `${this.config.apiUrl}/download-result/${action.datasetName}/${action.fileId}` + }) + ]; + } } else { - return { type: '[No Action] Is Result Available' }; + return [{ type: '[No Action] Is Result Available' }]; } }), catchError(() => of(downloadFileActions.isResultAvailableFail())) diff --git a/client/src/app/instance/store/effects/index.ts b/client/src/app/instance/store/effects/index.ts index 18cb99de..ddaa3d86 100644 --- a/client/src/app/instance/store/effects/index.ts +++ b/client/src/app/instance/store/effects/index.ts @@ -1,4 +1,3 @@ -import { SampEffects } from './samp.effects'; import { SearchEffects } from './search.effects'; import { SearchMultipleEffects } from './search-multiple.effects'; import { ConeSearchEffects } from './cone-search.effects'; @@ -7,7 +6,6 @@ import { SvomJsonKwEffects } from './svom-json-kw.effects'; import { DownloadFileEffects } from './download-file.effects'; export const instanceEffects = [ - SampEffects, SearchEffects, SearchMultipleEffects, ConeSearchEffects, diff --git a/client/src/app/instance/store/services/index.ts b/client/src/app/instance/store/services/index.ts index d70ddbf8..4f4c2847 100644 --- a/client/src/app/instance/store/services/index.ts +++ b/client/src/app/instance/store/services/index.ts @@ -1,5 +1,4 @@ import { SearchService } from './search.service'; -import { SampService } from './samp.service'; import { ConeSearchService } from './cone-search.service'; import { DetailService } from './detail.service'; import { SvomJsonKwService } from './svom-json-kw.service'; @@ -7,7 +6,6 @@ import { DownloadFileService } from './download-file.service'; export const instanceServices = [ SearchService, - SampService, ConeSearchService, DetailService, SvomJsonKwService, diff --git a/client/src/app/instance/store/actions/samp.actions.ts b/client/src/app/samp/samp.actions.ts similarity index 100% rename from client/src/app/instance/store/actions/samp.actions.ts rename to client/src/app/samp/samp.actions.ts diff --git a/client/src/app/instance/store/effects/samp.effects.spec.ts b/client/src/app/samp/samp.effects.spec.ts similarity index 97% rename from client/src/app/instance/store/effects/samp.effects.spec.ts rename to client/src/app/samp/samp.effects.spec.ts index 1220db80..b2aa420b 100644 --- a/client/src/app/instance/store/effects/samp.effects.spec.ts +++ b/client/src/app/samp/samp.effects.spec.ts @@ -7,8 +7,8 @@ import { cold, hot } from 'jasmine-marbles'; import { ToastrService } from 'ngx-toastr'; import { SampEffects } from './samp.effects'; -import { SampService } from '../services/samp.service'; -import * as sampActions from '../actions/samp.actions'; +import { SampService } from './samp.service'; +import * as sampActions from './samp.actions'; describe('[Instance][Store] SampEffects', () => { let actions = new Observable(); diff --git a/client/src/app/instance/store/effects/samp.effects.ts b/client/src/app/samp/samp.effects.ts similarity index 69% rename from client/src/app/instance/store/effects/samp.effects.ts rename to client/src/app/samp/samp.effects.ts index 6d685853..7ddb3e71 100644 --- a/client/src/app/instance/store/effects/samp.effects.ts +++ b/client/src/app/samp/samp.effects.ts @@ -9,13 +9,17 @@ import { Injectable } from '@angular/core'; -import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { of } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects'; +import { of, from } from 'rxjs'; import { map, tap, mergeMap, catchError } from 'rxjs/operators'; + +import { KeycloakService } from 'keycloak-angular'; import { ToastrService } from 'ngx-toastr'; -import { SampService } from '../services/samp.service'; -import * as sampActions from '../actions/samp.actions'; +import { SampService } from './samp.service'; +import * as sampActions from './samp.actions'; +import * as authSelector from 'src/app/auth/auth.selector'; /** * @class @@ -23,7 +27,6 @@ import * as sampActions from '../actions/samp.actions'; */ @Injectable() export class SampEffects { - /** * Calls actions to register. */ @@ -80,8 +83,16 @@ export class SampEffects { broadcastVotable$ = createEffect(() => this.actions$.pipe( ofType(sampActions.broadcastVotable), - tap(action => { - this.sampService.broadcast('table.load.votable', action.url) + concatLatestFrom(() => this.store.select(authSelector.selectIsAuthenticated)), + tap(([action, isAuthenticated]) => { + if (isAuthenticated) { + console.log('Authenticated'); + this.keycloak.getToken().then(token => { + this.sampService.broadcast('table.load.votable', `${action.url}?token=${token}`); + }); + } else { + this.sampService.broadcast('table.load.votable', action.url); + } }) ), { dispatch: false } @@ -90,6 +101,8 @@ export class SampEffects { constructor( private actions$: Actions, private sampService: SampService, + private store: Store<{ }>, + private keycloak: KeycloakService, private toastr: ToastrService ) {} } diff --git a/client/src/app/samp/samp.module.ts b/client/src/app/samp/samp.module.ts new file mode 100644 index 00000000..59f9f585 --- /dev/null +++ b/client/src/app/samp/samp.module.ts @@ -0,0 +1,28 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { NgModule } from '@angular/core'; + +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { sampReducer } from './samp.reducer'; +import { SampEffects } from './samp.effects'; +import { SampService } from './samp.service'; + +@NgModule({ + imports: [ + StoreModule.forFeature('samp', sampReducer), + EffectsModule.forFeature([ SampEffects ]) + ], + providers: [ + SampService + ] +}) +export class SampModule { } diff --git a/client/src/app/instance/store/reducers/samp.reducer.spec.ts b/client/src/app/samp/samp.reducer.spec.ts similarity index 96% rename from client/src/app/instance/store/reducers/samp.reducer.spec.ts rename to client/src/app/samp/samp.reducer.spec.ts index d8cec00d..0fab3a66 100644 --- a/client/src/app/instance/store/reducers/samp.reducer.spec.ts +++ b/client/src/app/samp/samp.reducer.spec.ts @@ -1,7 +1,7 @@ import { Action } from '@ngrx/store'; import * as fromSamp from './samp.reducer'; -import * as sampActions from '../actions/samp.actions'; +import * as sampActions from './samp.actions'; describe('[Instance][Store] Samp reducer', () => { it('unknown action should return the default state', () => { diff --git a/client/src/app/instance/store/reducers/samp.reducer.ts b/client/src/app/samp/samp.reducer.ts similarity index 93% rename from client/src/app/instance/store/reducers/samp.reducer.ts rename to client/src/app/samp/samp.reducer.ts index f16136d2..96c54318 100644 --- a/client/src/app/instance/store/reducers/samp.reducer.ts +++ b/client/src/app/samp/samp.reducer.ts @@ -9,7 +9,7 @@ import { createReducer, on } from '@ngrx/store'; -import * as sampActions from '../actions/samp.actions'; +import * as sampActions from './samp.actions'; /** * Interface for samp state. diff --git a/client/src/app/instance/store/selectors/samp.selector.spec.ts b/client/src/app/samp/samp.selector.spec.ts similarity index 84% rename from client/src/app/instance/store/selectors/samp.selector.spec.ts rename to client/src/app/samp/samp.selector.spec.ts index ab8f44b3..32d0572c 100644 --- a/client/src/app/instance/store/selectors/samp.selector.spec.ts +++ b/client/src/app/samp/samp.selector.spec.ts @@ -1,5 +1,5 @@ import * as sampSelector from './samp.selector'; -import * as fromSamp from '../reducers/samp.reducer'; +import * as fromSamp from './samp.reducer'; describe('[Instance][Store] Samp selector', () => { it('should get registered', () => { diff --git a/client/src/app/instance/store/selectors/samp.selector.ts b/client/src/app/samp/samp.selector.ts similarity index 52% rename from client/src/app/instance/store/selectors/samp.selector.ts rename to client/src/app/samp/samp.selector.ts index d485b19f..410c723c 100644 --- a/client/src/app/instance/store/selectors/samp.selector.ts +++ b/client/src/app/samp/samp.selector.ts @@ -7,17 +7,13 @@ * file that was distributed with this source code. */ -import { createSelector } from '@ngrx/store'; +import { createSelector, createFeatureSelector } from '@ngrx/store'; -import * as reducer from '../../instance.reducer'; -import * as fromSamp from '../reducers/samp.reducer'; +import * as fromSamp from './samp.reducer'; -export const selectSampState = createSelector( - reducer.getInstanceState, - (state: reducer.State) => state.samp -); +export const selectAuthState = createFeatureSelector<fromSamp.State>('samp'); export const selectRegistered = createSelector( - selectSampState, + selectAuthState, fromSamp.selectRegistered ); diff --git a/client/src/app/instance/store/services/samp.service.ts b/client/src/app/samp/samp.service.ts similarity index 97% rename from client/src/app/instance/store/services/samp.service.ts rename to client/src/app/samp/samp.service.ts index 3dea465b..a8ecc579 100644 --- a/client/src/app/instance/store/services/samp.service.ts +++ b/client/src/app/samp/samp.service.ts @@ -23,7 +23,14 @@ declare var samp: any; export class SampService { private connector = null; - constructor(private config: AppConfigService) { + constructor(private config: AppConfigService) { } + + /** + * Register to Samp. + * + * @return Observable<any> + */ + register(): Observable<any> { let baseUrl = `${window.location.protocol}//${window.location.host}`; if (this.config.baseHref !== '/') { baseUrl += this.config.baseHref; @@ -37,14 +44,7 @@ export class SampService { "samp.icon.url": `${baseUrl}/assets/cesam_anis40.png` }; this.connector = new samp.Connector("anis-client", meta); - } - /** - * Register to Samp. - * - * @return Observable<any> - */ - register(): Observable<any> { return new Observable(observer => { samp.register(this.connector.name, (conn) => { this.connector.setConnection(conn); diff --git a/docker-compose.yml b/docker-compose.yml index 06b8074d..eb381ed7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,10 +33,11 @@ services: LOGGER_LEVEL: "debug" SERVICES_URL: "http://localhost:5000" BASE_HREF: "/" + SAMP_ENABLED: 1 SSO_AUTH_URL: "http://localhost:8180/auth" SSO_REALM: "anis" SSO_CLIENT_ID: "anis-client" - TOKEN_ENABLED: 0 + TOKEN_ENABLED: 1 TOKEN_JWKS_URL: "http://keycloak:8180/auth/realms/anis/protocol/openid-connect/certs" TOKEN_ADMIN_ROLES: anis_admin,superuser RMQ_HOST: rmq diff --git a/server/app/settings.php b/server/app/settings.php index e062e3ef..5ceab1a8 100644 --- a/server/app/settings.php +++ b/server/app/settings.php @@ -35,6 +35,7 @@ return [ ], 'services_url' => getenv('SERVICES_URL'), 'base_href' => getenv('BASE_HREF'), + 'samp_enabled' => getenv('SAMP_ENABLED'), 'sso' => [ 'auth_url' => getenv('SSO_AUTH_URL'), 'realm' => getenv('SSO_REALM'), diff --git a/server/src/Action/ClientSettingsAction.php b/server/src/Action/ClientSettingsAction.php index 9ccc6f48..5b84798e 100644 --- a/server/src/Action/ClientSettingsAction.php +++ b/server/src/Action/ClientSettingsAction.php @@ -52,6 +52,7 @@ final class ClientSettingsAction $payload = json_encode(array( 'servicesUrl' => $this->settings['services_url'], 'baseHref' => $this->settings['base_href'], + 'sampEnabled' => boolval($this->settings['samp_enabled']), 'authenticationEnabled' => boolval($this->settings['token']['enabled']), 'ssoAuthUrl' => $this->settings['sso']['auth_url'], 'ssoRealm' => $this->settings['sso']['realm'], -- GitLab