diff --git a/client/src/app/app-config.service.ts b/client/src/app/app-config.service.ts index 604ad74d6184fe35542b8220aa14559210f06cce..5c27615b80d28b2ceb482d880379fcbe06f8b4ff 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 af48b11b16bbd43a264d5afd5618b7aa210335e8..630038a94684e61be0ee34a5a36241b631fa6800 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 cca4aece56fc59af597c3db12456c7952748f33d..4552174d0de81d1dc4e4107ec5279653161565e8 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 ed942d2b4006a1d7be4b27325465d2089b8d506e..3be0935a92e04797c5cd669a3721253f0bf81a81 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 a9a9a6e3e0832fd10dcd5ca89b56f1ee32797a87..f54031521f73c94cb57c0d2f0f02a0c63a475e85 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 8b3b7ef203949153afbbb251fed913f9adf6face..58331ce19f0053cab31fb29de583b7c9775b91b3 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 2f4e3475b628d1e746d03f88ec93c27d5eef1cd5..97335f09355bdbb67eacec67bdd813809d9bcde3 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 0c9c15a510fa3b62b7405b882a5d30a4068808dd..cf29bc5791b3e7269884b54e8d9abafbe68ca7a2 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 d5a16313c639a946103d4752bddaf6f2993ca21c..42f3584a1a361104e1ed9032a2899fa6fdcfc3f0 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 4fa8d96abe8417aebb5a490ea60e9744f7616cc6..b39de14f79a0d38682f33ddfb074ded06e17955a 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 9970bd3d96b083b4fd957ec3d7970fb8c28c9c9c..0000000000000000000000000000000000000000 --- 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 fd6f128fe38eb7a1600e348950a5a45001da36ee..0000000000000000000000000000000000000000 --- 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 be61e1c985d4eff3f07d4019dec2a7cb195d5a98..0000000000000000000000000000000000000000 --- 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 d06e9f7f5d38fe71eb6878a08d112f32a7426ef1..3a1eabae5af2f0acd198072acf6a0cb04f590037 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 cc058ea450f4445bd295363a7fe2b2a25dee5f34..8d93cdbae7950f00c8b9e10c1b9fe5e0f054b688 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 8368ed9207b145ecd1970f5d63569c06afd35270..60fefb6f1cadd470066f5b081b2afcf553d23a93 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 b7caa91165f2ff0a6bc6144714109e2b8c1fe140..1cb58cd4d6cbd3b7d37cb60a7567f9990e503e24 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 18cb99de4d7126d13194355146ddacf39b3c8ae5..ddaa3d86df6a14dc0dababa0aa9fafafd4dad401 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 d70ddbf82870dc1059472f410111eb4979b00052..4f4c2847d6d9b98de06b4daef5509ffb8e82a3ba 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 1220db80bd3e8f6d74e2532069cc8e20df1dba87..b2aa420b00418a72d7638d7d539ef9b7439a4e7a 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 6d685853788c586e1bbc8845d8e794c48c0087fa..7ddb3e71ffc356011548ecc6b29a3de4e32d90de 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 0000000000000000000000000000000000000000..59f9f5850eec66df1ddc47e610c0b24ae2547bc6 --- /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 d8cec00d47fae1f75481f50c7bffbfeafeaade34..0fab3a66b4750934f88c3cf9ad26ff86aea9d9bd 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 f16136d25d96e7789d838d8da2462a14424c37ec..96c5431864fe11a8e3271c7621290a3d1f9c0e55 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 ab8f44b37c53981db9be4ce9c76c9d99a74a1f58..32d0572c6207f10354c41e7fcf8b12d21f31bb12 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 d485b19f91a13a881a78ae04896e117a2b9dc7f5..410c723c8bdea8d2e5062c1633a8ca5d9d9050aa 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 3dea465b139563c25674109a46b0d6cc5b179324..a8ecc5796aa3c2a0dca2233499bb73fec763f060 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 06b8074d7e6be4d1320d0781535d17d99d006895..eb381ed7d8dee10fa27881a1aa850ecef5d7f806 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 e062e3ef440da113b8ce0268cb6f3f1faa6b1cd3..5ceab1a828e734839f433086ea9b782ac2001a03 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 9ccc6f48de67f3a928bdc060c7cd128010246f6e..5b84798e34a73373b5ab6dd75a0c194c04b92dfe 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'],