From 20a673dcb9d2599b94a0fa5bb03765a41ff9edc5 Mon Sep 17 00:00:00 2001 From: Ingvord Date: Wed, 18 Sep 2024 16:29:55 +0200 Subject: [PATCH 1/8] Progress --- .../datasets/dashboard/dashboard.component.ts | 5 +- .../dataset-table.component.html | 14 ++- .../dataset-table.component.scss | 30 +++++++ .../dataset-table/dataset-table.component.ts | 87 ++++++++++++++++++- .../reducers/datasets.reducer.ts | 37 +++++++- .../selectors/datasets.selectors.ts | 5 ++ .../state-management/state/datasets.store.ts | 4 + 7 files changed, 176 insertions(+), 6 deletions(-) diff --git a/src/app/datasets/dashboard/dashboard.component.ts b/src/app/datasets/dashboard/dashboard.component.ts index b38de06bc..2e05aa481 100644 --- a/src/app/datasets/dashboard/dashboard.component.ts +++ b/src/app/datasets/dashboard/dashboard.component.ts @@ -23,7 +23,7 @@ import { selectSelectedDatasets, selectPagination, } from "state-management/selectors/datasets.selectors"; -import { distinctUntilChanged, filter, map, take } from "rxjs/operators"; +import { distinctUntilChanged, filter, map, skip, take } from "rxjs/operators"; import { MatDialog } from "@angular/material/dialog"; import { MatSidenav } from "@angular/material/sidenav"; import { AddDatasetDialogComponent } from "datasets/add-dataset-dialog/add-dataset-dialog.component"; @@ -180,7 +180,7 @@ export class DashboardComponent implements OnInit, OnDestroy { ngOnInit() { this.store.dispatch(prefillBatchAction()); this.store.dispatch(fetchMetadataKeysAction()); - this.store.dispatch(fetchDatasetsAction()); + // this.store.dispatch(fetchDatasetsAction()); this.updateColumnSubscription(); @@ -189,6 +189,7 @@ export class DashboardComponent implements OnInit, OnDestroy { .pipe( map(([pagination, _, loggedIn]) => [pagination, loggedIn]), distinctUntilChanged(deepEqual), + skip(1), // TODO avoid this hack ) .subscribe((obj) => { this.store.dispatch(fetchDatasetsAction()); diff --git a/src/app/datasets/dataset-table/dataset-table.component.html b/src/app/datasets/dataset-table/dataset-table.component.html index def257e90..2e31cd99f 100644 --- a/src/app/datasets/dataset-table/dataset-table.component.html +++ b/src/app/datasets/dataset-table/dataset-table.component.html @@ -1,4 +1,15 @@ -
+
+ +
+
+

No data loaded. Please click the button below to load data.

+ +
+
+ +
@@ -407,3 +418,4 @@
+
diff --git a/src/app/datasets/dataset-table/dataset-table.component.scss b/src/app/datasets/dataset-table/dataset-table.component.scss index 3b81e2ad9..6bf826c65 100644 --- a/src/app/datasets/dataset-table/dataset-table.component.scss +++ b/src/app/datasets/dataset-table/dataset-table.component.scss @@ -1,3 +1,33 @@ +.dataset-container { + position: relative; /* Container to position the overlay absolutely */ + + .opaque { + opacity: 0.5; /* Make the table semi-transparent when overlay is active */ + } + + .overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.8); /* Semi-transparent background */ + display: flex; + justify-content: center; + align-items: center; + z-index: 10; /* Ensure the overlay is above the table */ + pointer-events: all; /* Make sure the overlay captures the button click */ + } + + .overlay-content { + text-align: center; + background: white; + padding: 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + border-radius: 8px; + } +} + .dataset-table { mat-table { overflow-x: scroll; diff --git a/src/app/datasets/dataset-table/dataset-table.component.ts b/src/app/datasets/dataset-table/dataset-table.component.ts index ca15bf4c2..760534278 100644 --- a/src/app/datasets/dataset-table/dataset-table.component.ts +++ b/src/app/datasets/dataset-table/dataset-table.component.ts @@ -12,7 +12,7 @@ import { import { Dataset, TableColumn } from "state-management/models"; import { MatCheckboxChange } from "@angular/material/checkbox"; import { Subscription } from "rxjs"; -import { Store } from "@ngrx/store"; +import { ActionsSubject, Store } from "@ngrx/store"; import { clearSelectionAction, selectDatasetAction, @@ -20,6 +20,9 @@ import { selectAllDatasetsAction, changePageAction, sortByColumnAction, + fetchDatasetsAction, + fetchDatasetCompleteAction, + fetchFacetCountsAction, } from "state-management/actions/datasets.actions"; import { @@ -28,6 +31,8 @@ import { selectPage, selectTotalSets, selectDatasetsInBatch, + selectLoadData, + selectCurrentDataset, } from "state-management/selectors/datasets.selectors"; import { PageChangeEvent } from "shared/modules/table/table.component"; import { @@ -37,6 +42,13 @@ import { import { get } from "lodash"; import { AppConfigService } from "app-config.service"; import { selectCurrentUser } from "state-management/selectors/user.selectors"; +import { + MatDialog, + MatDialogModule, + MatDialogRef, +} from "@angular/material/dialog"; +import { MatIconModule } from "@angular/material/icon"; +import { MatButtonModule } from "@angular/material/button"; export interface SortChangeEvent { active: string; direction: "asc" | "desc" | ""; @@ -47,6 +59,61 @@ export interface SortChangeEvent { // derivedDatasetsNum: number; // } +@Component({ + selector: "app-guard-dialog", + template: ` +

+ warning + Loading All Data +

+
+

+ You are about to load all available data. This action might take a long + time and consume significant resources. +

+

+ It's recommended to specify search or filter criteria for better + performance. +

+
+
+ + +
+ `, + styles: [ + ` + mat-dialog-content { + font-size: 16px; + line-height: 1.6; + } + + mat-dialog-actions { + padding-right: 8px; + } + + h1 mat-icon { + margin-right: 8px; + } + `, + ], + standalone: true, + imports: [MatIconModule, MatDialogModule, MatButtonModule], +}) +class GuardDialogComponent { + constructor(public dialogRef: MatDialogRef) {} + + onConfirm(): void { + this.dialogRef.close(true); // User confirmed loading all data + } + + onCancel(): void { + this.dialogRef.close(false); // User canceled, to refine search + } +} + @Component({ selector: "dataset-table", templateUrl: "dataset-table.component.html", @@ -59,6 +126,8 @@ export class DatasetTableComponent implements OnInit, OnDestroy, OnChanges { appConfig = this.appConfigService.getConfig(); + dataLoaded$ = this.store.select(selectLoadData); + lodashGet = get; currentPage$ = this.store.select(selectPage); datasetsPerPage$ = this.store.select(selectDatasetsPerPage); @@ -77,6 +146,7 @@ export class DatasetTableComponent implements OnInit, OnDestroy, OnChanges { constructor( public appConfigService: AppConfigService, + public dialog: MatDialog, private store: Store, ) {} doSettingsClick(event: MouseEvent) { @@ -268,4 +338,19 @@ export class DatasetTableComponent implements OnInit, OnDestroy, OnChanges { ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } + + loadData() { + const dialogRef = this.dialog.open(GuardDialogComponent); + + dialogRef.afterClosed().subscribe((result) => { + if (result === true) { + // User confirmed, load all data + this.store.dispatch(fetchDatasetsAction()); + this.store.dispatch(fetchFacetCountsAction()); + } else { + // User canceled, show a message or allow them to filter + console.log("User chose to refine search or filter."); + } + }); + } } diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 7c6783d88..7022f765c 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -4,7 +4,11 @@ import { DatasetState, } from "state-management/state/datasets.store"; import * as fromActions from "state-management/actions/datasets.actions"; -import { ArchViewMode, Dataset } from "state-management/models"; +import { + ArchViewMode, + Dataset, + ScientificCondition, +} from "state-management/models"; const reducer = createReducer( initialDatasetState, @@ -453,9 +457,30 @@ const reducer = createReducer( (state, { condition }): DatasetState => { const currentFilters = state.filters; const currentScientific = currentFilters.scientific; + + // Custom comparison function to check if two conditions are equal + const areConditionsEqual = ( + cond1: ScientificCondition, + cond2: ScientificCondition, + ) => { + return ( + cond1.lhs === cond2.lhs && + cond1.relation === cond2.relation && + cond1.rhs === cond2.rhs && + cond1.unit === cond2.unit + ); + }; + + // Check if the condition already exists in the scientific array + const conditionExists = currentScientific.some((existingCondition) => + areConditionsEqual(existingCondition, condition), + ); + const filters = { ...currentFilters, - scientific: [...currentScientific, condition], + scientific: conditionExists + ? currentScientific + : [...currentScientific, condition], }; return { ...state, filters }; }, @@ -471,6 +496,14 @@ const reducer = createReducer( return { ...state, filters }; }, ), + on(fromActions.fetchDatasetsCompleteAction, (state): DatasetState => { + state.dataLoaded = true; + return state; + }), + on(fromActions.fetchDatasetsFailedAction, (state): DatasetState => { + state.dataLoaded = false; + return state; + }), ); export const datasetsReducer = ( diff --git a/src/app/state-management/selectors/datasets.selectors.ts b/src/app/state-management/selectors/datasets.selectors.ts index 756f75b69..8e1341179 100644 --- a/src/app/state-management/selectors/datasets.selectors.ts +++ b/src/app/state-management/selectors/datasets.selectors.ts @@ -287,3 +287,8 @@ export const selectRelatedDatasetsPerPage = createSelector( selectRelatedDatasetsFilters, (filters) => filters.limit, ); + +export const selectLoadData = createSelector( + selectDatasetState, + (state) => state.dataLoaded, +); diff --git a/src/app/state-management/state/datasets.store.ts b/src/app/state-management/state/datasets.store.ts index ad84c0ea0..2093fd12e 100644 --- a/src/app/state-management/state/datasets.store.ts +++ b/src/app/state-management/state/datasets.store.ts @@ -46,6 +46,8 @@ export interface DatasetState { batch: Dataset[]; openwhiskResult: Record | undefined; + + dataLoaded: boolean; } export const initialDatasetState: DatasetState = { @@ -91,4 +93,6 @@ export const initialDatasetState: DatasetState = { batch: [], openwhiskResult: undefined, + + dataLoaded: false, }; From 812fec92a377da5dc0e6847420426b6fdb22f8fd Mon Sep 17 00:00:00 2001 From: ingvord Date: Thu, 19 Sep 2024 14:05:26 +0200 Subject: [PATCH 2/8] enable scientific metadata keys on FE --- src/app/state-management/effects/datasets.effects.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts index 2784b9ebf..c197504d6 100644 --- a/src/app/state-management/effects/datasets.effects.ts +++ b/src/app/state-management/effects/datasets.effects.ts @@ -89,7 +89,7 @@ export class DatasetEffects { map(([action, params]) => params), mergeMap(({ query }) => { const parsedQuery = JSON.parse(query); - parsedQuery.metadataKey = ""; + parsedQuery.scientificMetadata = { $exists: true, $ne: "" }; return this.datasetApi.metadataKeys(JSON.stringify(parsedQuery)).pipe( map((metadataKeys) => fromActions.fetchMetadataKeysCompleteAction({ metadataKeys }), From efa4eeb54e874eb4fdc91223381e57f9cc1cdb42 Mon Sep 17 00:00:00 2001 From: ingvord Date: Thu, 19 Sep 2024 15:59:00 +0200 Subject: [PATCH 3/8] metadataTypes FE infrastructure --- .../datasets/dashboard/dashboard.component.ts | 3 ++- .../datasets-filter-settings.component.ts | 6 +++++- src/app/shared/sdk/services/custom/Dataset.ts | 19 +++++++++++++++++++ .../actions/datasets.actions.ts | 15 +++++++++++++++ .../effects/datasets.effects.ts | 15 +++++++++++++++ .../reducers/datasets.reducer.ts | 8 ++++++++ .../selectors/datasets.selectors.ts | 5 +++++ .../state-management/state/datasets.store.ts | 2 ++ 8 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/app/datasets/dashboard/dashboard.component.ts b/src/app/datasets/dashboard/dashboard.component.ts index 2e05aa481..37f823f78 100644 --- a/src/app/datasets/dashboard/dashboard.component.ts +++ b/src/app/datasets/dashboard/dashboard.component.ts @@ -14,6 +14,7 @@ import { addDatasetAction, fetchDatasetCompleteAction, fetchMetadataKeysAction, + fetchMetadataTypesAction, } from "state-management/actions/datasets.actions"; import { @@ -180,7 +181,7 @@ export class DashboardComponent implements OnInit, OnDestroy { ngOnInit() { this.store.dispatch(prefillBatchAction()); this.store.dispatch(fetchMetadataKeysAction()); - // this.store.dispatch(fetchDatasetsAction()); + this.store.dispatch(fetchMetadataTypesAction()); this.updateColumnSubscription(); diff --git a/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts b/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts index f55af1307..073b0d7ab 100644 --- a/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts +++ b/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts @@ -16,7 +16,10 @@ import { selectColumnAction, } from "../../../state-management/actions/user.actions"; import { Store } from "@ngrx/store"; -import { selectMetadataKeys } from "../../../state-management/selectors/datasets.selectors"; +import { + selectMetadataKeys, + selectMetadataTypes, +} from "../../../state-management/selectors/datasets.selectors"; import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop"; import { ConditionConfig, @@ -34,6 +37,7 @@ export class DatasetsFilterSettingsComponent { protected readonly getFilterLabel = getFilterLabel; metadataKeys$ = this.store.select(selectMetadataKeys); + metadataTypes$ = this.store.select(selectMetadataTypes); appConfig = this.appConfigService.getConfig(); diff --git a/src/app/shared/sdk/services/custom/Dataset.ts b/src/app/shared/sdk/services/custom/Dataset.ts index d26e78d1e..f8cdbec9c 100644 --- a/src/app/shared/sdk/services/custom/Dataset.ts +++ b/src/app/shared/sdk/services/custom/Dataset.ts @@ -2979,6 +2979,25 @@ export class DatasetApi extends BaseLoopBackApi { return result; } + public metadataTypes(): Observable { + let _method: string = "GET"; + let _url: string = + LoopBackConfig.getPath() + + "/" + + LoopBackConfig.getApiVersion() + + "/Datasets/metadataTypes"; + let result = this.request( + _method, + _url, + undefined, + undefined, + undefined, + null, + undefined, + ); + return result; + } + /** * The name of the model represented by this $resource, * i.e. `Dataset`. diff --git a/src/app/state-management/actions/datasets.actions.ts b/src/app/state-management/actions/datasets.actions.ts index 5c6ce0d93..635f865eb 100644 --- a/src/app/state-management/actions/datasets.actions.ts +++ b/src/app/state-management/actions/datasets.actions.ts @@ -38,14 +38,29 @@ export const fetchFacetCountsFailedAction = createAction( export const fetchMetadataKeysAction = createAction( "[Dataset] Fetch Metadata Keys", ); + +export const fetchMetadataTypesAction = createAction( + "[Dataset] Fetch Metadata Types", +); + export const fetchMetadataKeysCompleteAction = createAction( "[Dataset] Fetch Metadata Keys Complete", props<{ metadataKeys: string[] }>(), ); + export const fetchMetadataKeysFailedAction = createAction( "[Dataset] Fetch Metadata Keys Failed", ); +export const fetchMetadataTypesCompleteAction = createAction( + "[Dataset] Fetch Metadata Types Complete", + props<{ metadataTypes: Record[] }>(), +); + +export const fetchMetadataTypesFailedAction = createAction( + "[Dataset] Fetch Metadata Types Failed", +); + export const fetchDatasetAction = createAction( "[Dataset] Fetch Dataset", props<{ pid: string; filters?: any }>(), diff --git a/src/app/state-management/effects/datasets.effects.ts b/src/app/state-management/effects/datasets.effects.ts index c197504d6..7db968ad3 100644 --- a/src/app/state-management/effects/datasets.effects.ts +++ b/src/app/state-management/effects/datasets.effects.ts @@ -35,6 +35,7 @@ import { loadingCompleteAction, updateUserSettingsAction, } from "state-management/actions/user.actions"; +import { fetchMetadataTypesAction } from "state-management/actions/datasets.actions"; @Injectable() export class DatasetEffects { @@ -100,6 +101,20 @@ export class DatasetEffects { ); }); + fetchMetadataTypes$ = createEffect(() => { + return this.actions$.pipe( + ofType(fromActions.fetchMetadataTypesAction), + mergeMap(() => { + return this.datasetApi.metadataTypes().pipe( + map((metadataTypes) => + fromActions.fetchMetadataTypesCompleteAction({ metadataTypes }), + ), + catchError(() => of(fromActions.fetchMetadataTypesFailedAction())), + ); + }), + ); + }); + updateUserDatasetsLimit$ = createEffect(() => { return this.actions$.pipe( ofType(fromActions.changePageAction), diff --git a/src/app/state-management/reducers/datasets.reducer.ts b/src/app/state-management/reducers/datasets.reducer.ts index 7022f765c..23c6a667a 100644 --- a/src/app/state-management/reducers/datasets.reducer.ts +++ b/src/app/state-management/reducers/datasets.reducer.ts @@ -34,6 +34,14 @@ const reducer = createReducer( (state, { metadataKeys }): DatasetState => ({ ...state, metadataKeys }), ), + on( + fromActions.fetchMetadataTypesCompleteAction, + (state, { metadataTypes }): DatasetState => ({ + ...state, + metadataTypes, + }), + ), + on( fromActions.fetchDatasetCompleteAction, (state, { dataset }): DatasetState => ({ diff --git a/src/app/state-management/selectors/datasets.selectors.ts b/src/app/state-management/selectors/datasets.selectors.ts index 8e1341179..0d74fd779 100644 --- a/src/app/state-management/selectors/datasets.selectors.ts +++ b/src/app/state-management/selectors/datasets.selectors.ts @@ -18,6 +18,11 @@ export const selectMetadataKeys = createSelector( (state) => state.metadataKeys, ); +export const selectMetadataTypes = createSelector( + selectDatasetState, + (state) => state.metadataTypes, +); + export const selectCurrentDataset = createSelector( selectDatasetState, (state) => state.currentSet, diff --git a/src/app/state-management/state/datasets.store.ts b/src/app/state-management/state/datasets.store.ts index 2093fd12e..8b4b5173d 100644 --- a/src/app/state-management/state/datasets.store.ts +++ b/src/app/state-management/state/datasets.store.ts @@ -30,6 +30,7 @@ export interface DatasetState { facetCounts: FacetCounts; metadataKeys: string[]; + metadataTypes: Record[]; hasPrefilledFilters: boolean; searchTerms: string; keywordsTerms: string; @@ -60,6 +61,7 @@ export const initialDatasetState: DatasetState = { facetCounts: {}, metadataKeys: [], + metadataTypes: [], hasPrefilledFilters: false, searchTerms: "", keywordsTerms: "", From bb51400a08992bff616d96258a540b47a867d9c8 Mon Sep 17 00:00:00 2001 From: ingvord Date: Thu, 19 Sep 2024 17:50:47 +0200 Subject: [PATCH 4/8] Progress #1141 --- .../datasets-filter-settings.component.ts | 4 +- .../search-parameters-dialog.component.html | 15 ++-- .../search-parameters-dialog.component.ts | 79 ++++++++++++++++++- 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts b/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts index 073b0d7ab..3450fae01 100644 --- a/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts +++ b/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Inject } from "@angular/core"; +import { Component, Inject } from "@angular/core"; import { MAT_DIALOG_DATA, MatDialog, @@ -26,7 +26,6 @@ import { FilterConfig, } from "../../../shared/modules/filters/filters.module"; import { getFilterLabel } from "../../../shared/modules/filters/utils"; -import { ScientificCondition } from "../../../state-management/models"; @Component({ selector: "app-type-datasets-filter-settings", @@ -55,6 +54,7 @@ export class DatasetsFilterSettingsComponent { .open(SearchParametersDialogComponent, { data: { parameterKeys: this.asyncPipe.transform(this.metadataKeys$), + parameterTypes: this.asyncPipe.transform(this.metadataTypes$), }, }) .afterClosed() diff --git a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html index 61c2149a3..82fbe289a 100644 --- a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html +++ b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html @@ -5,8 +5,8 @@

Add Characteristic

Field Add Characteristic + Operator - is greater than - is less than - is equal to (numeric) - is equal to (string) + (selectionChange)="toggleUnitField()"> + + {{ operator.label }} + diff --git a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts index d2680dc1a..80c86de48 100644 --- a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts +++ b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts @@ -1,10 +1,20 @@ -import { ChangeDetectorRef, Component, Inject } from "@angular/core"; +import { Component, Inject } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { AppConfigService } from "app-config.service"; -import { map, startWith } from "rxjs/operators"; +import { + debounceTime, + distinctUntilChanged, + filter, + map, + mergeMap, + startWith, + switchMap, + take, +} from "rxjs/operators"; import { UnitsService } from "shared/services/units.service"; import { ScientificCondition } from "../../../state-management/models"; +import { Observable, of } from "rxjs"; @Component({ selector: "search-parameters-dialog", @@ -15,6 +25,8 @@ export class SearchParametersDialogComponent { unitsEnabled = this.appConfig.scienceSearchUnitsEnabled; parameterKeys = this.data.parameterKeys; + parameterTypes = this.data.parameterTypes; + filteredOperators$: Observable<{ value: string; label: string }[]> = of([]); // TODO default set of operators units: string[] = []; parametersForm = new FormGroup({ @@ -56,6 +68,7 @@ export class SearchParametersDialogComponent { @Inject(MAT_DIALOG_DATA) public data: { parameterKeys: string[]; + parameterTypes: Record[]; condition?: ScientificCondition; }, public dialogRef: MatDialogRef, @@ -64,6 +77,68 @@ export class SearchParametersDialogComponent { if (this.data.condition?.lhs) { this.getUnits(this.data.condition.lhs); } + + // Dynamically update operators based on the field selected by the user + this.parametersForm + .get("lhs")! + .valueChanges.pipe( + debounceTime(300), + filter((selectedLhs: string) => selectedLhs && selectedLhs.length > 3), + distinctUntilChanged(), + mergeMap((selectedLhs: string) => { + return this.parameterTypes.filter((type) => + type.metadataKey.includes(selectedLhs), + ); + }), + take(1), + map((field) => { + return this.extractFieldType(field.metadataType); + }), + ) + .subscribe((type) => { + this.filteredOperators$ = of(this.getOperatorsByType(type)); + }); + } + + // Helper to extract the field type from the dataset + private extractFieldType(type: string): string { + // Assuming metadata structure contains field type info + if (type === "string") { + return "string"; + } else if (type === "int" || type === "double" /*|| type === "mixed"*/) { + // TODO what to do with mixed type + return "number"; + } else if (type === "Date") { + return "date"; + } + + return "string"; // Default to string if type can't be inferred + } + + // Dynamically fetch operators based on field type + private getOperatorsByType( + fieldType: string, + ): { value: string; label: string }[] { + switch (fieldType) { + case "string": + return [ + { value: "EQUAL_TO_STRING", label: "is equal to (string)" }, + { value: "CONTAINS", label: "contains" }, + ]; + case "number": + return [ + { value: "GREATER_THAN", label: "is greater than" }, + { value: "LESS_THAN", label: "is less than" }, + { value: "EQUAL_TO_NUMERIC", label: "is equal to (numeric)" }, + ]; + case "date": + return [ + { value: "BEFORE", label: "is before" }, + { value: "AFTER", label: "is after" }, + ]; + default: + return [{ value: "EQUAL", label: "is equal to" }]; + } } add = (): void => { From 1f18e8a2bb131e58c44b37ae20f639426465b4c2 Mon Sep 17 00:00:00 2001 From: ingvord Date: Fri, 4 Oct 2024 21:17:55 +0200 Subject: [PATCH 5/8] Progress #614 --- .../dataset-details-dashboard.component.ts | 5 ++ .../metadata-edit.component.html | 12 ++++- .../metadata-edit/metadata-edit.component.ts | 50 ++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts index e022a675b..635be1d63 100644 --- a/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts +++ b/src/app/datasets/dataset-details-dashboard/dataset-details-dashboard.component.ts @@ -23,6 +23,8 @@ import { fetchAttachmentsAction, fetchDatablocksAction, fetchDatasetAction, + fetchMetadataKeysAction, + fetchMetadataTypesAction, fetchOrigDatablocksAction, fetchRelatedDatasetsAction, } from "state-management/actions/datasets.actions"; @@ -119,6 +121,9 @@ export class DatasetDetailsDashboardComponent ) {} ngOnInit() { + this.store.dispatch(fetchMetadataKeysAction()); + this.store.dispatch(fetchMetadataTypesAction()); + this.subscriptions.push( this.route.params.pipe(pluck("id")).subscribe((id: string) => { if (id) { diff --git a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.html b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.html index 85e2b624f..66339118b 100644 --- a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.html +++ b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.html @@ -29,14 +29,24 @@ + + + {{ key }} + + Name is required diff --git a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts index 82821861e..1e8f27b3f 100644 --- a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts +++ b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts @@ -20,9 +20,14 @@ import { import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { UnitsService } from "shared/services/units.service"; import { startWith, map } from "rxjs/operators"; -import { Observable } from "rxjs"; +import { Observable, Subscription } from "rxjs"; import { ScientificMetadata } from "../scientific-metadata.module"; import { AppConfigService } from "app-config.service"; +import { Store } from "@ngrx/store"; +import { + selectMetadataKeys, + selectMetadataTypes, +} from "../../../../state-management/selectors/datasets.selectors"; @Component({ selector: "metadata-edit", @@ -38,6 +43,8 @@ export class MetadataEditComponent implements OnInit, OnChanges { appConfig = this.appConfigService.getConfig(); filteredUnits$: Observable | undefined = new Observable(); + metadataKeys$ = this.store.select(selectMetadataKeys); + metadataTypes$ = this.store.select(selectMetadataTypes); invalidUnitWarning: string[] = []; @Input() metadata: Record | undefined; @Output() save = new EventEmitter>(); @@ -46,6 +53,7 @@ export class MetadataEditComponent implements OnInit, OnChanges { private formBuilder: FormBuilder, private unitsService: UnitsService, private appConfigService: AppConfigService, + private store: Store, ) {} get formControlFields() { @@ -77,6 +85,7 @@ export class MetadataEditComponent implements OnInit, OnChanges { this.items.push(field); + this.setupFieldNameValueChangeListener(); this.setupFieldUnitValueChangeListeners(); } @@ -178,6 +187,7 @@ export class MetadataEditComponent implements OnInit, OnChanges { }); } this.items.push(field); + this.setupFieldNameValueChangeListener(); this.setupFieldUnitValueChangeListeners(); this.detectType(index); @@ -255,6 +265,43 @@ export class MetadataEditComponent implements OnInit, OnChanges { return this.metadataForm.get("items") as FormArray; } + setupFieldNameValueChangeListener() { + // Listen for changes in 'fieldName' to dynamically update 'fieldType' + this.items.controls.forEach((control, index) => { + control + .get("fieldName") + ?.valueChanges.pipe(/*debounceTime(600), distinctUntilChanged()*/) + .subscribe((selectedKey) => { + this.metadataTypes$.subscribe((types) => { + // Update the fieldType if the selectedKey exists in metadataTypes + const foundType = types.find( + (type) => type.metadataKey === selectedKey, + )?.metadataType; + if (foundType) { + this.items + .at(index) + .get("fieldType") + ?.setValue( + // TODO we should think about consistent types naming, see BE#1428 + (function (foundType, that) { + switch (foundType) { + case "mixed": + return that.typeValues[0]; // quantity + case "double": + case "int": + return that.typeValues[1]; // number + default: + return that.typeValues[2]; // string + } + })(foundType, this), + ); + this.detectType(index); + } + }); + }); + }); + } + setupFieldUnitValueChangeListeners() { this.items.controls.forEach((control, index) => { control @@ -278,6 +325,7 @@ export class MetadataEditComponent implements OnInit, OnChanges { this.addCurrentMetadata(); this.units = this.unitsService.getUnits(); + this.setupFieldNameValueChangeListener(); this.setupFieldUnitValueChangeListeners(); } From 149bbfb5c54d4a20f9d31f191d9c8bff6dc99e6a Mon Sep 17 00:00:00 2001 From: ingvord Date: Fri, 4 Oct 2024 22:50:15 +0200 Subject: [PATCH 6/8] Progress #614 --- .../metadata-edit/metadata-edit.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts index 1e8f27b3f..9c1d0fc05 100644 --- a/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts +++ b/src/app/shared/modules/scientific-metadata/metadata-edit/metadata-edit.component.ts @@ -270,7 +270,11 @@ export class MetadataEditComponent implements OnInit, OnChanges { this.items.controls.forEach((control, index) => { control .get("fieldName") - ?.valueChanges.pipe(/*debounceTime(600), distinctUntilChanged()*/) + ?.valueChanges.pipe( + debounceTime(600), + distinctUntilChanged(), + startWith(""), + ) .subscribe((selectedKey) => { this.metadataTypes$.subscribe((types) => { // Update the fieldType if the selectedKey exists in metadataTypes From 24e20e7fbe120c700a63567ad17f00ca60e48b6b Mon Sep 17 00:00:00 2001 From: ingvord Date: Fri, 4 Oct 2024 22:51:02 +0200 Subject: [PATCH 7/8] Progress #1141 --- .../datasets-filter-settings.component.ts | 1 + .../search-parameters-dialog.component.html | 4 +- .../search-parameters-dialog.component.ts | 52 +++++++++++-------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts b/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts index 3450fae01..3bd56876e 100644 --- a/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts +++ b/src/app/datasets/datasets-filter/settings/datasets-filter-settings.component.ts @@ -84,6 +84,7 @@ export class DatasetsFilterSettingsComponent { .open(SearchParametersDialogComponent, { data: { parameterKeys: this.asyncPipe.transform(this.metadataKeys$), + parameterTypes: this.asyncPipe.transform(this.metadataTypes$), condition: condition.condition, }, }) diff --git a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html index 82fbe289a..a46489066 100644 --- a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html +++ b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.html @@ -5,8 +5,8 @@

Add Characteristic

Field selectedLhs && selectedLhs.length > 3), + filter((selectedLhs: string) => selectedLhs && selectedLhs.length > 2), distinctUntilChanged(), mergeMap((selectedLhs: string) => { - return this.parameterTypes.filter((type) => - type.metadataKey.includes(selectedLhs), + return of( + this.parameterTypes.find((type) => + type.metadataKey.includes(selectedLhs), + ) || ({ metadataType: "mixed" } as Record), ); }), - take(1), + // take(1), map((field) => { return this.extractFieldType(field.metadataType); }), @@ -105,39 +105,45 @@ export class SearchParametersDialogComponent { // Assuming metadata structure contains field type info if (type === "string") { return "string"; - } else if (type === "int" || type === "double" /*|| type === "mixed"*/) { - // TODO what to do with mixed type + } else if (type === "int" || type === "double") { return "number"; } else if (type === "Date") { return "date"; } - return "string"; // Default to string if type can't be inferred + return "mixed"; // Default to string if type can't be inferred } // Dynamically fetch operators based on field type private getOperatorsByType( fieldType: string, ): { value: string; label: string }[] { + const forString = [ + { value: "EQUAL_TO_STRING", label: "is equal to (string)" }, + { value: "CONTAINS", label: "contains" }, + ]; + const forNumber = [ + { value: "GREATER_THAN", label: "is greater than" }, + { value: "LESS_THAN", label: "is less than" }, + { value: "EQUAL_TO_NUMERIC", label: "is equal to (numeric)" }, + ]; + const forDate = [ + { value: "BEFORE", label: "is before" }, + { value: "AFTER", label: "is after" }, + ]; + const all = [{ value: "EQUAL", label: "is equal to" }] + .concat(forString) + .concat(forNumber) + .concat(forDate); switch (fieldType) { case "string": - return [ - { value: "EQUAL_TO_STRING", label: "is equal to (string)" }, - { value: "CONTAINS", label: "contains" }, - ]; + return forString; case "number": - return [ - { value: "GREATER_THAN", label: "is greater than" }, - { value: "LESS_THAN", label: "is less than" }, - { value: "EQUAL_TO_NUMERIC", label: "is equal to (numeric)" }, - ]; + return forNumber; case "date": - return [ - { value: "BEFORE", label: "is before" }, - { value: "AFTER", label: "is after" }, - ]; + return forDate; default: - return [{ value: "EQUAL", label: "is equal to" }]; + return all; } } From 58350ce3d93c833a27fad11851244fab7e85ef1a Mon Sep 17 00:00:00 2001 From: ingvord Date: Fri, 4 Oct 2024 23:03:41 +0200 Subject: [PATCH 8/8] Progress #1141: workaround BE limitation on metadataKeys query (returns empty [] after first call) --- .../search-parameters-dialog.component.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts index e0acabe05..97678959b 100644 --- a/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts +++ b/src/app/shared/modules/search-parameters-dialog/search-parameters-dialog.component.ts @@ -22,7 +22,7 @@ export class SearchParametersDialogComponent { appConfig = this.appConfigService.getConfig(); unitsEnabled = this.appConfig.scienceSearchUnitsEnabled; - parameterKeys = this.data.parameterKeys; + parameterKeys = this.data.parameterTypes.map((type) => type.metadataKey); parameterTypes = this.data.parameterTypes; filteredOperators$: Observable<{ value: string; label: string }[]> = of([]); // TODO default set of operators units: string[] = []; @@ -72,8 +72,22 @@ export class SearchParametersDialogComponent { public dialogRef: MatDialogRef, private unitsService: UnitsService, ) { + function findMetadataTypeByKey(key) { + return ( + this.parameterTypes.find((type) => type.metadataKey.includes(key)) || + ({ metadataType: "mixed" } as Record) + ); + } + if (this.data.condition?.lhs) { this.getUnits(this.data.condition.lhs); + this.filteredOperators$ = of( + this.getOperatorsByType( + this.extractFieldType( + findMetadataTypeByKey.bind(this, this.data.condition.lhs), + ), + ), + ); } // Dynamically update operators based on the field selected by the user @@ -84,11 +98,7 @@ export class SearchParametersDialogComponent { filter((selectedLhs: string) => selectedLhs && selectedLhs.length > 2), distinctUntilChanged(), mergeMap((selectedLhs: string) => { - return of( - this.parameterTypes.find((type) => - type.metadataKey.includes(selectedLhs), - ) || ({ metadataType: "mixed" } as Record), - ); + return of(findMetadataTypeByKey.bind(this, selectedLhs)); }), // take(1), map((field) => {