diff --git a/.github/workflows/prod-request-merged.yml b/.github/workflows/prod-request-merged.yml new file mode 100644 index 000000000..28281452b --- /dev/null +++ b/.github/workflows/prod-request-merged.yml @@ -0,0 +1,44 @@ +name: Merged to Production + +on: + pull_request: + types: [closed] + branches: + - prod + +jobs: + OpenRequest: + runs-on: ubuntu-latest + # If a merge request triggered the push, and that request DOESN'T contain the 'bumpless' label. + # (Need to check all three, instead of 'not bumpless', because if and admin overrides the tests, + # it might not have ANY labels at that point.). + if: > + github.event.pull_request.merged && + ( + contains(github.event.pull_request.labels.*.name, 'patch') || + contains(github.event.pull_request.labels.*.name, 'minor') || + contains(github.event.pull_request.labels.*.name, 'major') + ) + steps: + - uses: actions/checkout@v2 + + - name: Save version type + # Whichever one return's true, will let their 'echo' statement run: + # Must wrap in "(*) || true" to prevent it from exiting on failure, until + # 'allow-failure' is finished getting added: https://github.com/actions/toolkit/issues/399 + run: | + (${{ contains(github.event.pull_request.labels.*.name, 'patch') }} && echo "version_type=patch" >> $GITHUB_ENV) || true + (${{ contains(github.event.pull_request.labels.*.name, 'minor') }} && echo "version_type=minor" >> $GITHUB_ENV) || true + (${{ contains(github.event.pull_request.labels.*.name, 'major') }} && echo "version_type=major" >> $GITHUB_ENV) || true + - name: Create a Release + uses: zendesk/action-create-release@v1 + env: + # NOT built in token, so this can trigger other actions: + GITHUB_TOKEN: ${{ secrets.DISCO_GITHUB_MACHINE_USER }} + with: + # version_type populated with the last job just above ^^ + auto_increment_type: "${{ env.version_type }}" + tag_schema: semantic + draft: false + prerelease: false + body: "${{ github.event.pull_request.body }}" diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e97ecd9ee..0219033e4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,8 +1,8 @@ -import {Component, OnInit, OnDestroy, AfterViewChecked, ViewChild, Inject} from '@angular/core'; +import {Component, OnInit, OnDestroy, AfterViewInit, ViewChild, Inject } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { MatSidenav } from '@angular/material/sidenav'; import { MatIconRegistry } from '@angular/material/icon'; -import {DomSanitizer, Title} from '@angular/platform-browser'; +import { DomSanitizer, Title } from '@angular/platform-browser'; import { MatDialog } from '@angular/material/dialog'; import { SubSink } from 'subsink'; import { QueueComponent } from '@components/header/queue'; @@ -30,8 +30,8 @@ import * as filtersStore from '@store/filters'; import * as services from '@services'; import * as models from './models'; import { SearchType } from './models'; -import {DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, NativeDateAdapter} from "@angular/material/core"; -import {MAT_MOMENT_DATE_FORMATS} from "@angular/material-moment-adapter"; +import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, NativeDateAdapter } from "@angular/material/core"; +import { MAT_MOMENT_DATE_FORMATS } from "@angular/material-moment-adapter"; @Component({ selector : 'app-root', @@ -46,7 +46,7 @@ import {MAT_MOMENT_DATE_FORMATS} from "@angular/material-moment-adapter"; {provide: MAT_DATE_LOCALE, useValue: 'en'}, ] }) -export class AppComponent implements OnInit, OnDestroy, AfterViewChecked { +export class AppComponent implements OnInit, OnDestroy, AfterViewInit { @ViewChild('sidenav', {static: true}) sidenav: MatSidenav; private queueStateKey = 'asf-queue-state-v1'; @@ -344,9 +344,9 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewChecked { ); this.subs.add( - combineLatest( + combineLatest([ this.store$.select(queueStore.getQueuedJobs), - this.store$.select(hyp3Store.getProcessingOptions) + this.store$.select(hyp3Store.getProcessingOptions)] ).subscribe( ([jobs, options]) => localStorage.setItem( this.customProductsQueueStateKey, JSON.stringify({jobs, options}) @@ -428,6 +428,12 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewChecked { }); } + public ngAfterViewInit(): void { + this.subs.add(this.translate.get('ASF_DATA_SEARCH_TITLE').subscribe(title => { + this.titleService.setTitle(title); + })); + } + public onLoadUrlState(): void { this.urlStateService.load(); } @@ -445,10 +451,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewChecked { this.store$.dispatch(new uiStore.CloseSidebar()); } - public ngAfterViewChecked(): void { - this.titleService.setTitle( this.translate.instant('ASF_DATA_SEARCH_TITLE') ); - } - private isEmptySearch(searchState): boolean { if (searchState.searchType === models.SearchType.LIST) { return searchState.filters.list.length < 1; diff --git a/src/app/components/filters-dropdown/list-filters/list-filters.component.ts b/src/app/components/filters-dropdown/list-filters/list-filters.component.ts index 1accf0ce3..1418ea43a 100644 --- a/src/app/components/filters-dropdown/list-filters/list-filters.component.ts +++ b/src/app/components/filters-dropdown/list-filters/list-filters.component.ts @@ -109,7 +109,7 @@ export class ListFiltersComponent implements OnInit, OnDestroy { ); this.subs.add( - combineLatest( + combineLatest([ this.actions$.pipe( ofType( searchStore.SearchActionType.SET_SEARCH_TYPE_AFTER_SAVE, @@ -117,7 +117,7 @@ export class ListFiltersComponent implements OnInit, OnDestroy { filtersStore.FiltersActionType.RESTORE_FILTERS ), withLatestFrom(this.store$.select(filtersStore.getSearchList).pipe(map(list => list.join('\n')))), - )).subscribe(([[_, listStr]]) => this.searchList = listStr + )]).subscribe(([[_, listStr]]) => this.searchList = listStr ) ); diff --git a/src/app/components/header/processing-queue/processing-queue-jobs/processing-queue-jobs.component.ts b/src/app/components/header/processing-queue/processing-queue-jobs/processing-queue-jobs.component.ts index a64340330..a8b021fd6 100644 --- a/src/app/components/header/processing-queue/processing-queue-jobs/processing-queue-jobs.component.ts +++ b/src/app/components/header/processing-queue/processing-queue-jobs/processing-queue-jobs.component.ts @@ -33,7 +33,7 @@ export class ProcessingQueueJobsComponent implements OnInit { // change detection keeps updating the view when it shouldn't causing flickering. // this observable ensures we only update the dispalyed processing queue list when the values actually change // or when the user changes the sorting order. - jobsfiltered$ = combineLatest(this.jobs$.pipe(distinctUntilChanged()), this.sortChange$).pipe( + jobsfiltered$ = combineLatest([this.jobs$.pipe(distinctUntilChanged()), this.sortChange$]).pipe( map(([jobs, _]) => jobs), filter(jobs => !!jobs), map(jobs => this.sortJobQueue(jobs))); diff --git a/src/app/components/map/map.component.ts b/src/app/components/map/map.component.ts index 13954b49a..118c75dc8 100644 --- a/src/app/components/map/map.component.ts +++ b/src/app/components/map/map.component.ts @@ -81,9 +81,9 @@ export class MapComponent implements OnInit, OnDestroy { private subs = new SubSink(); private gridlinesActive$ = this.store$.select(mapStore.getAreGridlinesActive); private isMapInitialized$ = this.store$.select(mapStore.getIsMapInitialization); - private viewType$ = combineLatest( + private viewType$ = combineLatest([ this.store$.select(mapStore.getMapView), - this.store$.select(mapStore.getMapLayerType), + this.store$.select(mapStore.getMapLayerType),] ); private sarviewsEvents: SarviewsEvent[]; @@ -131,9 +131,9 @@ export class MapComponent implements OnInit, OnDestroy { ); this.subs.add( - combineLatest( + combineLatest([ this.store$.select(uiStore.getIsResultsMenuOpen), - this.mapService.searchPolygon$ + this.mapService.searchPolygon$] ).pipe( filter(_ => !!this.overlay), map(([isResultsMenuOpen, polygon]) => !isResultsMenuOpen && !!polygon), @@ -186,10 +186,10 @@ export class MapComponent implements OnInit, OnDestroy { ); this.subs.add( - combineLatest( + combineLatest([ this.mapService.isDrawing$, this.drawMode$, - this.interactionMode$ + this.interactionMode$] ).pipe( map(([isDrawing, drawMode, interactionMode]) => { if (interactionMode === models.MapInteractionModeType.DRAW) { diff --git a/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts b/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts index 432a33442..6605455d2 100644 --- a/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts +++ b/src/app/components/results-menu/baseline-results-menu/baseline-results-menu.component.ts @@ -68,9 +68,9 @@ export class BaselineResultsMenuComponent implements OnInit, OnDestroy { ngOnInit(): void { this.subs.add( - combineLatest( + combineLatest([ this.scenesService.products$(), - this.pairService.pairs$() + this.pairService.pairs$] ).subscribe( ([products, {pairs, custom}]) => { this.products = products; @@ -97,7 +97,7 @@ export class BaselineResultsMenuComponent implements OnInit, OnDestroy { ); this.subs.add( - this.pairService.productsFromPairs$().subscribe( + this.pairService.productsFromPairs$.subscribe( products => this.sbasProducts = products ) ); diff --git a/src/app/components/results-menu/scene-files/file-contents/file-contents.component.ts b/src/app/components/results-menu/scene-files/file-contents/file-contents.component.ts index fbd19fbba..576bc45ec 100644 --- a/src/app/components/results-menu/scene-files/file-contents/file-contents.component.ts +++ b/src/app/components/results-menu/scene-files/file-contents/file-contents.component.ts @@ -60,9 +60,9 @@ export class FileContentsComponent implements OnInit, OnDestroy { ngOnInit(): void { this.subs.add( - combineLatest( + combineLatest([ this.store$.select(scenesStore.getUnzippedProducts), - this.store$.select(scenesStore.getOpenUnzippedProduct) + this.store$.select(scenesStore.getOpenUnzippedProduct)] ).pipe( tap(([_, product]) => this.product = product), map(([unzipped, product]) => unzipped[product ? product.id : null]), diff --git a/src/app/components/results-menu/scene-files/scene-files.component.ts b/src/app/components/results-menu/scene-files/scene-files.component.ts index 7b8db53c7..4ddd6abaf 100644 --- a/src/app/components/results-menu/scene-files/scene-files.component.ts +++ b/src/app/components/results-menu/scene-files/scene-files.component.ts @@ -115,10 +115,10 @@ export class SceneFilesComponent implements OnInit, OnDestroy, AfterContentInit ngOnInit() { this.subs.add( - combineLatest( + combineLatest([ this.store$.select(scenesStore.getSelectedSceneProducts), this.store$.select(scenesStore.getOpenUnzippedProduct), - this.store$.select(scenesStore.getUnzippedProducts) + this.store$.select(scenesStore.getUnzippedProducts)] ).pipe(debounceTime(0)) .subscribe( ([products, unzipped, unzippedFiles]) => { diff --git a/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts b/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts index 18ed6152b..fda0be885 100644 --- a/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts +++ b/src/app/components/results-menu/scenes-list-header/scenes-list-header.component.ts @@ -1,7 +1,7 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { saveAs } from 'file-saver'; -import { combineLatest } from 'rxjs'; +import { combineLatest, } from 'rxjs'; import { debounceTime, filter, map } from 'rxjs/operators'; import { Store } from '@ngrx/store'; @@ -31,11 +31,15 @@ import { ClipboardService } from 'ngx-clipboard'; }) export class ScenesListHeaderComponent implements OnInit, OnDestroy { public copyIcon = faCopy; - public totalResultCount$ = combineLatest( + public pairs$ = this.pairService.pairs$; + private pairProducts$ = this.pairService.productsFromPairs$; + + + public totalResultCount$ = combineLatest([ this.store$.select(searchStore.getTotalResultCount), - this.scenesService.scenes$() - ).pipe( - map(([count, scenes]) => count + scenes?.filter(scene => scene.metadata.productType === 'BURST').length) + this.scenesService.scenes$()] + ).pipe( + map(([count, scenes]) => count + scenes?.filter(scene => scene.metadata.productType === 'BURST').length) ) public currentDatasetID$ = this.store$.select(filtersStore.getSelectedDataset).pipe(map(dataset => dataset.id)); @@ -58,7 +62,7 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public isBurstStack$ = combineLatest([ this.scenesService.products$(), - this.pairService.pairs$(), + this.pairService.pairs$, this.store$.select(searchStore.getSearchType), ] ).pipe( @@ -72,7 +76,7 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { ) ) ) - public numPairs$ = this.pairService.pairs$().pipe( + public numPairs$ = this.pairService.pairs$.pipe( filter(pairs => !!pairs), map(pairs => pairs.pairs.length + pairs.custom.length) ); @@ -94,10 +98,10 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { map(products => products.filter(p => p.metadata.productType === 'BURST')) ); - public SBASburstDataProducts$ = combineLatest( + public SBASburstDataProducts$ = combineLatest([ this.burstDataProducts$, - this.pairService.productsFromPairs$() - ).pipe( + this.pairProducts$] + ).pipe( map(([products, pairs]) => { const pairNames = pairs.map(p => p.name); const output = products.filter(p => pairNames.find(name => name === p.name)); @@ -109,10 +113,10 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { map(products => products.filter(p => p.metadata.productType === 'BURST_XML')) ); - public SBASburstMetadataProducts$ = combineLatest( + public SBASburstMetadataProducts$ = combineLatest([ this.burstMetadataProducts$, - this.pairService.productsFromPairs$() - ).pipe( + this.pairProducts$] + ).pipe( map(([products, pairs]) => { const pairNames = pairs.map(p => p.name); const output = products.filter(p => pairNames.find(name => name === p.name)); @@ -120,9 +124,9 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { }) ); - public SBASBurstProductsLength$ = combineLatest( + public SBASBurstProductsLength$ = combineLatest([ this.SBASburstDataProducts$.pipe(map(products => products.length)), - this.SBASburstMetadataProducts$.pipe(map(products => products.length)) + this.SBASburstMetadataProducts$.pipe(map(products => products.length))] ).pipe(map(([data, metadata]) => data + metadata)) public pairs: models.CMRProductPair[]; @@ -169,20 +173,21 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { ngOnInit() { this.subs.add( - this.pairService.productsFromPairs$().subscribe( - products => this.sbasProducts = products - ) + this.pairProducts$.subscribe( + products => this.sbasProducts = products + ) ); this.subs.add( - combineLatest( + combineLatest([ this.scenesService.products$(), - this.pairService.pairs$() + this.pairs$ + ] ).subscribe( - ([products, {pairs, custom}]) => { + ([products, { pairs, custom }]) => { this.products = products; this.downloadableProds = this.hyp3.downloadable(products); - this.pairs = [ ...pairs, ...custom ]; + this.pairs = [...pairs, ...custom]; this.hyp3able = this.hyp3.getHyp3ableProducts([ ...this.products.map(prod => [prod]), @@ -193,10 +198,10 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { ); this.subs.add( - combineLatest( + combineLatest([ this.scenesService.scenes$(), this.store$.select(filtersStore.getProductTypes), - this.store$.select(searchStore.getSearchType), + this.store$.select(searchStore.getSearchType),] ).pipe( debounceTime(250) ).subscribe(([scenes, productTypes, searchType]) => { @@ -246,7 +251,7 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { this.subs.add( this.store$.select(queueStore.getQueuedProducts).subscribe( products => this.queuedProducts = products - ) + ) ); this.subs.add( @@ -386,15 +391,15 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public onQueueSarviewsProducts(products: models.SarviewsProduct[]): void { this.store$.dispatch(new AddItems(products.map(product => - this.eventMonitoringService.eventProductToCMRProduct(product)) - )); + this.eventMonitoringService.eventProductToCMRProduct(product)) + )); } public addOnDemandEventProducts(targetProducts: SarviewsProduct[]) { const jobs: models.QueuedHyp3Job[] = targetProducts.map(prod => ({ - granules: this.eventMonitoringService.getSourceCMRProducts(prod), + granules: this.eventMonitoringService.getSourceCMRProducts(prod), job_type: hyp3JobTypes[prod.job_type] - }) + }) ); this.store$.dispatch(new AddJobs(jobs)); @@ -423,13 +428,13 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public getProductSceneCount(products: SarviewsProduct[]) { const outputList = products.reduce((prev, product) => { - const temp = product.granules.map(granule => granule.granule_name); + const temp = product.granules.map(granule => granule.granule_name); - prev = prev.concat(temp); + prev = prev.concat(temp); - return prev; + return prev; - }, [] as string[] + }, [] as string[] ); return new Set(outputList).size; @@ -437,16 +442,16 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public getProductDownloadUrl(products: SarviewsProduct[]) { const productListStr = products.map(product => product.files.product_url); - this.clipboard.copyFromContent( productListStr.join('\n ')); + this.clipboard.copyFromContent(productListStr.join('\n ')); const lines = products.length; this.notificationService.clipboardCopyQueue(lines, false); } public onAddEventToOnDemand(product: SarviewsProduct) { const job: models.QueuedHyp3Job = { - granules: this.eventMonitoringService.getSourceCMRProducts(product), + granules: this.eventMonitoringService.getSourceCMRProducts(product), job_type: hyp3JobTypes[product.job_type] - }; + }; this.store$.dispatch(new queueStore.AddJob(job)); } @@ -454,10 +459,10 @@ export class ScenesListHeaderComponent implements OnInit, OnDestroy { public copyProductSourceScenes(products: SarviewsProduct[]) { const granuleNameList = products.reduce( (acc, curr) => acc = acc.concat(curr.granules), [] as models.SarviewProductGranule[] - ).map(gran => gran.granule_name); + ).map(gran => gran.granule_name); const granuleNameListSet = new Set(granuleNameList); - this.clipboard.copyFromContent( Array.from(granuleNameListSet).join(',')); + this.clipboard.copyFromContent(Array.from(granuleNameListSet).join(',')); this.notificationService.clipboardCopyIcon('', granuleNameListSet.size); } diff --git a/src/app/components/results-menu/scenes-list/scenes-list.component.ts b/src/app/components/results-menu/scenes-list/scenes-list.component.ts index 656d5eac1..86f4e919e 100644 --- a/src/app/components/results-menu/scenes-list/scenes-list.component.ts +++ b/src/app/components/results-menu/scenes-list/scenes-list.component.ts @@ -27,6 +27,7 @@ import { CMRProduct, QueuedHyp3Job, SarviewsEvent } from '@models'; export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit { @ViewChild(CdkVirtualScrollViewport, { static: true }) scroll: CdkVirtualScrollViewport; @Input() resize$: Observable; + private pairs$ = this.pairService.pairs$; public scenes: CMRProduct[]; public pairs; @@ -164,7 +165,7 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit ); this.subs.add( - this.pairService.pairs$().pipe(debounceTime(250)).subscribe( + this.pairs$.subscribe( pairs => { this.pairs = [...pairs.pairs, ...pairs.custom].map( pair => { @@ -201,9 +202,9 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit } ); - const queueScenes$ = combineLatest( + const queueScenes$ = combineLatest([ this.store$.select(queueStore.getQueuedProducts), - this.store$.select(scenesStore.getAllSceneProducts), + this.store$.select(scenesStore.getAllSceneProducts),] ).pipe( debounceTime(0), map(([queueProducts, searchScenes]) => { @@ -266,7 +267,7 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit this.subs.add( this.store$.select(scenesStore.getSelectedPair).pipe( - withLatestFrom(this.pairService.pairs$()), + withLatestFrom(this.pairs$), delay(20), filter(([selected, _]) => !!selected), map(([selected, pairs]) => { @@ -312,7 +313,7 @@ export class ScenesListComponent implements OnInit, OnDestroy, AfterContentInit } )); - this.subs.add(this.pairService.pairs$().pipe( + this.subs.add(this.pairs$.pipe( filter(loaded => !!loaded), withLatestFrom(this.store$.select(scenesStore.getSelectedPair)), map(([pairs, selected]) => ({ selectedPair: selected, pairs })), diff --git a/src/app/components/sbas-chart/sbas-chart.component.ts b/src/app/components/sbas-chart/sbas-chart.component.ts index 0b5c7aa49..ee50e4be1 100644 --- a/src/app/components/sbas-chart/sbas-chart.component.ts +++ b/src/app/components/sbas-chart/sbas-chart.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, OnDestroy, Input, Output, EventEmitter} from '@angular/core'; -import { combineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { Observable, combineLatest } from 'rxjs'; +import { distinctUntilChanged, map, tap, } from 'rxjs/operators'; import * as d3 from 'd3'; import { Store } from '@ngrx/store'; @@ -32,7 +32,7 @@ export class SBASChartComponent implements OnInit, OnDestroy { @Input() zoomIn$: Observable; @Input() zoomOut$: Observable; @Input() zoomToFit$: Observable; - + private pairs$: Observable = this.pairService.pairs$; @Output() isGraphDisconnected = new EventEmitter(); private hoveredLine; @@ -74,7 +74,6 @@ export class SBASChartComponent implements OnInit, OnDestroy { ngOnInit(): void { const scenes$ = this.scenesService.scenes$(); - const pairs$ = this.pairService.pairs$(); this.store$.select(scenesStore.getSelectedPair).pipe( map(pair => !!pair?.[0] && !!pair?.[1] ? pair : null) @@ -93,7 +92,7 @@ export class SBASChartComponent implements OnInit, OnDestroy { this.subs.add( combineLatest([ scenes$.pipe(distinctUntilChanged()), - pairs$.pipe(distinctUntilChanged()) + this.pairs$ ]).subscribe(([scenes, pairs]) => { this.scenes = scenes; this.pairs = pairs.pairs; diff --git a/src/app/components/shared/max-results-selector/api-link-dialog/api-link-dialog.component.ts b/src/app/components/shared/max-results-selector/api-link-dialog/api-link-dialog.component.ts index 6b2e56357..eca45802b 100644 --- a/src/app/components/shared/max-results-selector/api-link-dialog/api-link-dialog.component.ts +++ b/src/app/components/shared/max-results-selector/api-link-dialog/api-link-dialog.component.ts @@ -55,8 +55,9 @@ export class ApiLinkDialogComponent implements OnInit, OnDestroy { ngOnInit() { this.subs.add( - combineLatest( - this.amount$, this.format$ + combineLatest([ + this.amount$, + this.format$] ).pipe( tap(([amount, format]) => { this.amount = amount; diff --git a/src/app/components/shared/max-results-selector/max-results-selector.component.ts b/src/app/components/shared/max-results-selector/max-results-selector.component.ts index 532641dc1..5f95dd464 100644 --- a/src/app/components/shared/max-results-selector/max-results-selector.component.ts +++ b/src/app/components/shared/max-results-selector/max-results-selector.component.ts @@ -77,7 +77,7 @@ export class MaxResultsSelectorComponent implements OnInit, OnDestroy { ); this.subs.add( - this.pairService.productsFromPairs$().subscribe( + this.pairService.productsFromPairs$.subscribe( products => this.sbasProducts = products ) ); diff --git a/src/app/components/shared/search-button/search-button.component.ts b/src/app/components/shared/search-button/search-button.component.ts index 4fe81f51a..6acde10cd 100644 --- a/src/app/components/shared/search-button/search-button.component.ts +++ b/src/app/components/shared/search-button/search-button.component.ts @@ -106,8 +106,9 @@ export class SearchButtonComponent implements OnInit, OnDestroy { ); this.subs.add( - combineLatest(this.store$.select(getFilterMaster), - this.store$.select(uiStore.getIsFiltersMenuOpen)).subscribe(([latestFilter, isOpen]) => { + combineLatest([ + this.store$.select(getFilterMaster), + this.store$.select(uiStore.getIsFiltersMenuOpen)]).subscribe(([latestFilter, isOpen]) => { if (isOpen && this.searchType === this.searchTypes.BASELINE || this.searchType === this.searchTypes.SBAS) { this.latestReferenceScene = latestFilter; if (this.stackReferenceScene == null || '') { diff --git a/src/app/services/asf-api.service.ts b/src/app/services/asf-api.service.ts index ce5deaf5c..204260c84 100644 --- a/src/app/services/asf-api.service.ts +++ b/src/app/services/asf-api.service.ts @@ -167,7 +167,7 @@ export class AsfApiService { } public loadMissions$() { - return combineLatest( + return combineLatest([ this.missionSearch(MissionDataset.S1_BETA).pipe( map(resp => ({[MissionDataset.S1_BETA]: resp.result})) ), @@ -176,7 +176,7 @@ export class AsfApiService { ), this.missionSearch(MissionDataset.UAVSAR).pipe( map(resp => ({[MissionDataset.UAVSAR]: resp.result})) - ) + )] ).pipe( map(missions => missions.reduce( (allMissions, mission) => ({ ...allMissions, ...mission }), diff --git a/src/app/services/asf-language.service.ts b/src/app/services/asf-language.service.ts index dedd5ab64..504d3e289 100644 --- a/src/app/services/asf-language.service.ts +++ b/src/app/services/asf-language.service.ts @@ -11,6 +11,8 @@ import { DateAdapter } from "@angular/material/core"; import { AppState } from '@store'; import * as uiStore from '@store/ui'; import { Store } from "@ngrx/store"; +import {SubSink} from "subsink"; +import {Title} from "@angular/platform-browser"; const defaultLanguage = 'en'; @Injectable({ @@ -38,11 +40,15 @@ export class AsfLanguageService { 'zh' : '中文 (Chinese)', } + private subs = new SubSink(); + + constructor( public translate: TranslateService, private cookieService: CookieService, private dateAdapter: DateAdapter, private store$: Store, + private titleService: Title, ) { this.browserLang = this.translate.getBrowserLang(); this.initialize(); @@ -79,6 +85,10 @@ export class AsfLanguageService { this.translate.use(language); this.dateAdapter.setLocale(language); this.setCurrentLanguage(language); + this.subs.add(this.translate.get('ASF_DATA_SEARCH_TITLE').subscribe(title => { + this.titleService.setTitle(title); + })); + } public initialize(): void { diff --git a/src/app/services/date-extrema.service.ts b/src/app/services/date-extrema.service.ts index 3dd4e8d79..8e7a8d27c 100644 --- a/src/app/services/date-extrema.service.ts +++ b/src/app/services/date-extrema.service.ts @@ -22,8 +22,9 @@ export class DateExtremaService { map(selected => selected.date.start) ); - const startMax$ = combineLatest( - selectedDataset$, endDate$ + const startMax$ = combineLatest([ + selectedDataset$, + endDate$] ).pipe( map(([selected, userEnd]) => { if (!!userEnd) { @@ -34,9 +35,9 @@ export class DateExtremaService { }) ); - const endMin$ = combineLatest( + const endMin$ = combineLatest([ selectedDataset$, - startDate$ + startDate$] ).pipe( map(([selected, userStart]) => { if (!!userStart) { @@ -51,7 +52,7 @@ export class DateExtremaService { map(selected => selected.date.end || new Date(Date.now())) ); - return combineLatest(startMin$, startMax$, endMin$, endMax$).pipe( + return combineLatest([startMin$, startMax$, endMin$, endMax$]).pipe( map(extrema => this.buildExtrema(...extrema)), ); } @@ -73,8 +74,9 @@ export class DateExtremaService { const startMin$ = sceneMin$; - const startMax$ = combineLatest( - sceneMax$, endDate$ + const startMax$ = combineLatest([ + sceneMax$, + endDate$] ).pipe( map(([sceneMax, userEnd]) => { if (!!userEnd) { @@ -85,9 +87,9 @@ export class DateExtremaService { }) ); - const endMin$ = combineLatest( + const endMin$ = combineLatest([ sceneMin$, - startDate$ + startDate$] ).pipe( map(([sceneMin, userStart]) => { if (!!userStart) { diff --git a/src/app/services/pair.service.ts b/src/app/services/pair.service.ts index 76da63d07..9db7f31bd 100644 --- a/src/app/services/pair.service.ts +++ b/src/app/services/pair.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Observable, combineLatest } from 'rxjs'; -import { debounceTime, map, withLatestFrom } from 'rxjs/operators'; +import { map, distinctUntilChanged, shareReplay, debounceTime } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { AppState } from '@store/app.reducer'; @@ -9,28 +9,15 @@ import { getScenes, getCustomPairs } from '@store/scenes/scenes.reducer'; import { getTemporalRange, getPerpendicularRange, getDateRange, DateRangeState, getSeason, getSBASOverlapThreshold } from '@store/filters/filters.reducer'; -import { getSearchType } from '@store/search/search.reducer'; -import { CMRProduct, CMRProductPair, ColumnSortDirection, Range, SBASOverlap, SearchType } from '@models'; +import { CMRProduct, CMRProductPair, ColumnSortDirection, Range, SBASOverlap, } from '@models'; import { MapService } from './map/map.service'; import { WktService } from './wkt.service'; -import * as models from '@models'; import { Feature } from 'ol'; import Geometry from 'ol/geom/Geometry'; -export interface SBASPairParams { - scenes: any[]; - customPairs: CMRProduct[][]; - temporalRange: models.Range; - perpendicular: number; - dateRange: models.Range; - season: models.Range; - overlap: models.SBASOverlap; - polygon: Feature; - -} @Injectable({ providedIn: 'root' @@ -42,70 +29,56 @@ export class PairService { private mapService: MapService, private wktService: WktService) { } - public productsFromPairs$(): Observable { - return this.pairs$().pipe( - map(({ custom, pairs }) => { - const prods = Array.from([...custom, ...pairs].reduce((products, pair) => { - products.add(pair[0]); - products.add(pair[1]); - return products; - }, new Set())); - return prods; - }) - ); - } - - public pairs$(): Observable<{ custom: CMRProductPair[], pairs: CMRProductPair[] }> { - return combineLatest([ - this.store$.select(getScenes).pipe( - map( - scenes => this.temporalSort(scenes, ColumnSortDirection.INCREASING) - ), - ), - this.store$.select(getCustomPairs), - this.store$.select(getTemporalRange), - this.store$.select(getPerpendicularRange).pipe( - map(range => range.start) - ), - this.store$.select(getDateRange), - this.store$.select(getSeason), - this.store$.select(getSBASOverlapThreshold), - this.mapService.searchPolygon$.pipe( - map(wkt => !!wkt ? this.wktService.wktToFeature(wkt, this.mapService.epsg()) : null) + public pairs$: Observable<{ custom: CMRProductPair[], pairs: CMRProductPair[] }> = combineLatest([ + this.store$.select(getScenes).pipe( + map( + scenes => this.temporalSort(scenes, ColumnSortDirection.INCREASING) ), - ], (scenes, customPairs, temporalRange, perpendicular, dateRange, season, overlap, polygon) => - ({ - scenes, - customPairs, - temporalRange, - perpendicular, - dateRange, - season, - overlap, - polygon - } as SBASPairParams)).pipe( - debounceTime(250), - withLatestFrom(this.store$.select(getSearchType)), - map(([params, searchType]) => { - return searchType === SearchType.SBAS ? ({ - pairs: [...this.makePairs(params.scenes, - params.temporalRange, - params.perpendicular, - params.dateRange, - params.season, - params.overlap, - params.polygon)], - custom: [...params.customPairs] - }) : ({ - pairs: [], - custom: [] - }); - }) - ); - } - + ), + this.store$.select(getCustomPairs), + this.store$.select(getTemporalRange), + this.store$.select(getPerpendicularRange).pipe( + map(range => range.start) + ), + this.store$.select(getDateRange), + this.store$.select(getSeason), + this.store$.select(getSBASOverlapThreshold), + this.mapService.searchPolygon$.pipe( + map(wkt => !!wkt ? this.wktService.wktToFeature(wkt, this.mapService.epsg()) : null) + ), + ]).pipe( + debounceTime(50), + distinctUntilChanged(), + map(([scenes, customPairs, temporalRange, perpendicular, dateRange, season, overlap, polygon]) => { + const pairs = this.makePairs(scenes, + temporalRange, + perpendicular, + dateRange, + season, + overlap, + polygon + ) + return { + pairs: [...pairs], + custom: [...customPairs] + } + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ) + public productsFromPairs$: Observable = this.pairs$.pipe( + map(({ custom, pairs }) => { + const prods = Array.from([...custom, ...pairs].reduce((products, pair) => { + products.add(pair[0]); + products.add(pair[1]); + + return products; + }, new Set())); + + return prods; + }) + ); private makePairs(scenes: CMRProduct[], tempThreshold: Range, perpThreshold, dateRange: DateRangeState, season: Range, @@ -138,7 +111,6 @@ export class PairService { centroid.lat = centroid.lat / 4.0; return centroid; }; - scenes.forEach((root, index) => { if (!!aoi) { @@ -222,6 +194,7 @@ export class PairService { } }); + return pairs; } @@ -297,7 +270,7 @@ export class PairService { } public isGraphDisconnected(pairs: any[], numScenes: Number) { - if(pairs.length === 0) { + if (pairs.length === 0) { return false } let graph_model = {} @@ -322,13 +295,13 @@ export class PairService { } } - if(numScenes !== points.size) { + if (numScenes !== points.size) { return true; } let to_check = [] - let checked : Set = new Set() + let checked: Set = new Set() to_check.push(points.values().next().value) while (to_check.length > 0) { diff --git a/src/app/services/sarviews-events.service.ts b/src/app/services/sarviews-events.service.ts index 0328e9fcd..509e7b437 100644 --- a/src/app/services/sarviews-events.service.ts +++ b/src/app/services/sarviews-events.service.ts @@ -386,8 +386,10 @@ export class SarviewsEventsService { } public areEventProductsFiltered$(): Observable { - return combineLatest(this.filteredEventProducts$(), - this.store$.select(getSelectedSarviewsEventProducts)).pipe( + return combineLatest([ + this.filteredEventProducts$(), + this.store$.select(getSelectedSarviewsEventProducts)] + ).pipe( map(([filtered, unfiltered]) => { if (!!unfiltered) { return !(filtered?.length === unfiltered.length); diff --git a/src/app/services/saved-search.service.ts b/src/app/services/saved-search.service.ts index 7405fac42..b7cbd07ba 100644 --- a/src/app/services/saved-search.service.ts +++ b/src/app/services/saved-search.service.ts @@ -23,11 +23,11 @@ import { getSarviewsMagnitudeRange } from '@store/filters'; }) export class SavedSearchService { - private currentGeographicSearch$ = combineLatest( + private currentGeographicSearch$ = combineLatest([ this.store$.select(filtersStore.getGeographicSearch).pipe( map(filters => ({ ...filters, flightDirections: Array.from(filters.flightDirections) })), ), - this.mapService.searchPolygon$ + this.mapService.searchPolygon$] ).pipe( map(([filters, polygon]): models.GeographicFiltersType => ({ ...filters, @@ -36,10 +36,10 @@ export class SavedSearchService { ); private currentListSearch$ = this.store$.select(filtersStore.getListSearch); - private currentBaselineSearch$ = combineLatest( + private currentBaselineSearch$ = combineLatest([ this.store$.select(scenesStore.getFilterMaster), this.store$.select(scenesStore.getMasterName), - this.store$.select(filtersStore.getBaselineSearch), + this.store$.select(filtersStore.getBaselineSearch),] ).pipe( map(([filterMaster, reference, baselineFilters]) => ({ filterMaster, @@ -48,12 +48,12 @@ export class SavedSearchService { })) ); - private currentSbasSearch$: Observable = combineLatest( + private currentSbasSearch$: Observable = combineLatest([ this.store$.select(scenesStore.getFilterMaster), this.store$.select(scenesStore.getCustomPairIds), this.store$.select(filtersStore.getSbasSearch), this.store$.select(filtersStore.getDateRange), - this.store$.select(filtersStore.getSBASOverlapThreshold), + this.store$.select(filtersStore.getSBASOverlapThreshold),] ).pipe( map(([reference, customPairIds, sbasFilters, dateRange, thresholdOverlap]) => ({ reference, @@ -64,12 +64,12 @@ export class SavedSearchService { ...sbasFilters })) ); - private currentCustomProductSearch$ = combineLatest( + private currentCustomProductSearch$ = combineLatest([ this.store$.select(filtersStore.getJobStatuses), this.store$.select(filtersStore.getDateRange), this.store$.select(filtersStore.getProjectName), this.store$.select(filtersStore.getProductNameFilter), - this.store$.select(filtersStore.getCustomProductSearch) + this.store$.select(filtersStore.getCustomProductSearch)] ).pipe( map(([jobStatuses, dateRange, projectName, productFilterName, customProductFilters]) => ({ jobStatuses, @@ -80,7 +80,7 @@ export class SavedSearchService { })) ); - private currentSarviewsEventSearch$ = combineLatest( + private currentSarviewsEventSearch$ = combineLatest([ this.store$.select(filtersStore.getDateRange), this.store$.select(filtersStore.getSarviewsEventTypes), this.store$.select(filtersStore.getSarviewsEventNameFilter), @@ -91,7 +91,7 @@ export class SavedSearchService { this.store$.select(filtersStore.getHyp3ProductTypes).pipe( map(productTypes => productTypes.map(productType => productType.id)) ), - this.store$.select(filtersStore.getPathFrameRanges), + this.store$.select(filtersStore.getPathFrameRanges),] ).pipe( map(([dateRange, sarviewsEventTypes, sarviewsEventNameFilter, activeOnly, magnitude, pinnedProductIDs, diff --git a/src/app/services/scenes.service.ts b/src/app/services/scenes.service.ts index 01df0cf19..f817048b3 100644 --- a/src/app/services/scenes.service.ts +++ b/src/app/services/scenes.service.ts @@ -67,10 +67,10 @@ export class ScenesService { } public sortScenes$(scenes$: Observable) { - return combineLatest( + return combineLatest([ scenes$, this.store$.select(getTemporalSortDirection), - this.store$.select(getPerpendicularSortDirection) + this.store$.select(getPerpendicularSortDirection)] ).pipe( debounceTime(0), map( @@ -90,11 +90,11 @@ export class ScenesService { } private hideS1Raw$(products$: Observable) { - return combineLatest( + return combineLatest([ products$, this.store$.select(getShowS1RawData), this.store$.select(getProductTypes), - this.store$.select(getSearchType), + this.store$.select(getSearchType),] ).pipe( debounceTime(0), map(([ scenes, showS1RawData, productTypes, searchType ]) => { @@ -125,10 +125,10 @@ export class ScenesService { } private projectNameFilter$(scenes$: Observable) { - return combineLatest( + return combineLatest([ scenes$, this.store$.select(getProjectName), - this.store$.select(getSearchType), + this.store$.select(getSearchType),] ).pipe( debounceTime(0), map(([scenes, projectName, searchType]) => { @@ -154,10 +154,10 @@ export class ScenesService { } private jobStatusFilter$(scenes$: Observable) { - return combineLatest( + return combineLatest([ scenes$, this.store$.select(getJobStatuses), - this.store$.select(getSearchType), + this.store$.select(getSearchType),] ).pipe( debounceTime(0), map(([scenes, jobStatuses, searchType]) => { @@ -184,10 +184,10 @@ export class ScenesService { } private hideExpired$(scenes$: Observable) { - return combineLatest( + return combineLatest([ scenes$, this.store$.select(getShowExpiredData), - this.store$.select(getSearchType), + this.store$.select(getSearchType),] ).pipe( debounceTime(200), map(([scenes, showExpiredData, searchType]) => { @@ -209,13 +209,13 @@ export class ScenesService { } private filterBaselineValues$(scenes$: Observable) { - return combineLatest( + return combineLatest([ scenes$, this.store$.select(getTemporalRange), this.store$.select(getPerpendicularRange), this.store$.select(getDateRange), this.store$.select(getSearchType), - this.store$.select(getSeason), + this.store$.select(getSeason),] ).pipe( debounceTime(20), map(([scenes, tempRange, perpRange, dateRange, searchType, season]) => { @@ -232,10 +232,10 @@ export class ScenesService { } private filterByDate$(scenes$: Observable) { - return combineLatest( + return combineLatest([ scenes$, this.store$.select(getDateRange), - this.store$.select(getSearchType), + this.store$.select(getSearchType),] ).pipe( debounceTime(0), map( diff --git a/src/app/services/search-params.service.ts b/src/app/services/search-params.service.ts index 664c53e2a..fb89acf3f 100644 --- a/src/app/services/search-params.service.ts +++ b/src/app/services/search-params.service.ts @@ -29,9 +29,9 @@ export class SearchParamsService { ) { } public getParams(): Observable { - return combineLatest( + return combineLatest([ this.searchType$(), - this.baselineSearchParams$(), + this.baselineSearchParams$(),] ).pipe( withLatestFrom(this.listParam$()), withLatestFrom(this.filterSearchParams$()), @@ -62,11 +62,11 @@ export class SearchParamsService { } public getlatestParams(): Observable { - return combineLatest( + return combineLatest([ this.searchType$(), this.listParam$(), this.baselineSearchParams$(), - this.filterSearchParams$() + this.filterSearchParams$()] ).pipe( map( ([searchType, listParam, baselineParams, filterParams]) => { @@ -99,7 +99,7 @@ export class SearchParamsService { } private filterSearchParams$() { - return combineLatest( + return combineLatest([ this.searchPolygon$(), this.selectedDataset$(), this.dateRange$(), @@ -112,7 +112,7 @@ export class SearchParamsService { this.polarizations$(), this.maxResults$(), this.missionParam$(), - this.burstParams$(), + this.burstParams$(),] ).pipe( map((params: any[]) => params .reduce( @@ -152,10 +152,10 @@ export class SearchParamsService { } private searchPolygon$() { - return combineLatest( + return combineLatest([ this.mapService.searchPolygon$.pipe(startWith(null)), this.store$.select(filterStore.getShouldOmitSearchPolygon), - this.drawService.polygon$ + this.drawService.polygon$] ).pipe( map(([polygon, shouldOmitGeoRegion, asdf]) => shouldOmitGeoRegion ? null : { polygon: polygon, thing: asdf }), map(polygon => { @@ -198,9 +198,9 @@ export class SearchParamsService { } private selectedDataset$() { - return combineLatest( + return combineLatest([ this.store$.select(filterStore.getSelectedDataset), - this.store$.select(filterStore.getSubtypes) + this.store$.select(filterStore.getSubtypes)] ).pipe( map(([dataset, subtypes]) => { return subtypes.length > 0 ? diff --git a/src/app/store/scenes/scenes.reducer.ts b/src/app/store/scenes/scenes.reducer.ts index f61218728..9d4f405db 100644 --- a/src/app/store/scenes/scenes.reducer.ts +++ b/src/app/store/scenes/scenes.reducer.ts @@ -4,6 +4,7 @@ import { ScenesActionType, ScenesActions } from './scenes.action'; import { CMRProduct, UnzippedFolder, ColumnSortDirection, SarviewsEvent, SarviewsProduct } from '@models'; import { PinnedProduct } from '@services/browse-map.service'; +import { createSelectorFactory, defaultMemoize } from '@ngrx/store'; interface SceneEntities { [id: string]: CMRProduct; } @@ -360,7 +361,24 @@ export const allScenesWithBrowse = (scenes: {[id: string]: string[]}, products) return withBrowses; }; -export const getScenes = createSelector( +function arrayEquals(a, b) { + return Array.isArray(a) && + Array.isArray(b) && + a.length === b.length && + a.toString() === b.toString() && + a.every((value, index) => value.toString() === b[index].toString()) +} +export const createArraySelector = + createSelectorFactory( + (projectionFn) => + defaultMemoize( + projectionFn, + arrayEquals, + arrayEquals + ) + ); + +export const getScenes = createArraySelector( getScenesState, (state: ScenesState) => allScenesFrom(state.scenes, state.products) ); @@ -612,7 +630,7 @@ export const getCustomPairIds = createSelector( state => state.customPairIds ); -export const getCustomPairs = createSelector( +export const getCustomPairs = createArraySelector( getScenesState, state => state.customPairIds.map( pairIds => pairIds.map(id => state.products[id]) diff --git a/src/app/store/search/search.effect.ts b/src/app/store/search/search.effect.ts index e77dd7691..2259f72c9 100644 --- a/src/app/store/search/search.effect.ts +++ b/src/app/store/search/search.effect.ts @@ -258,9 +258,9 @@ export class SearchEffects { this.asfApiService.query(params), this.asfApiService.query(countParams) ).pipe( - withLatestFrom(combineLatest( + withLatestFrom(combineLatest([ this.store$.select(getSearchType), - this.store$.select(getIsCanceled) + this.store$.select(getIsCanceled)] )), map(([[response, totalCount], [searchType, isCanceled]]) => !isCanceled ? @@ -289,9 +289,9 @@ export class SearchEffects { switchMap( (params) => this.asfApiService.query(params).pipe( - withLatestFrom(combineLatest( + withLatestFrom(combineLatest([ this.store$.select(getSearchType), - this.store$.select(getIsCanceled) + this.store$.select(getIsCanceled)] )), map(([response, [searchType, isCanceled]]) => { const files = this.productService.fromResponse(response) diff --git a/src/app/store/user/user.effect.ts b/src/app/store/user/user.effect.ts index 82150cbc1..d66f28cc2 100644 --- a/src/app/store/user/user.effect.ts +++ b/src/app/store/user/user.effect.ts @@ -27,9 +27,9 @@ export class UserEffects { public saveUserProfile = createEffect(() => this.actions$.pipe( ofType(userActions.UserActionType.SAVE_PROFILE), withLatestFrom( - combineLatest( + combineLatest([ this.store$.select(userReducer.getUserAuth), - this.store$.select(userReducer.getUserProfile) + this.store$.select(userReducer.getUserProfile)] ) ), switchMap( @@ -62,9 +62,9 @@ export class UserEffects { public saveSavedSearches = createEffect(() => this.actions$.pipe( ofType(userActions.UserActionType.SAVE_SEARCHES), withLatestFrom( - combineLatest( + combineLatest([ this.store$.select(userReducer.getUserAuth), - this.store$.select(userReducer.getSavedSearches) + this.store$.select(userReducer.getSavedSearches)] ) ), switchMap( @@ -76,9 +76,9 @@ export class UserEffects { public saveSavedFilters = createEffect(() => this.actions$.pipe( ofType(userActions.UserActionType.SAVE_FILTERS), withLatestFrom( - combineLatest( + combineLatest([ this.store$.select(userReducer.getUserAuth), - this.store$.select(userReducer.getSavedFilters) + this.store$.select(userReducer.getSavedFilters)] ) ), switchMap( @@ -90,9 +90,9 @@ export class UserEffects { public saveSearchHistory = createEffect(() => this.actions$.pipe( ofType(userActions.UserActionType.SAVE_SEARCH_HISTORY), withLatestFrom( - combineLatest( + combineLatest([ this.store$.select(userReducer.getUserAuth), - this.store$.select(userReducer.getSearchHistory) + this.store$.select(userReducer.getSearchHistory)] ) ), switchMap(