Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use hybrid database for fetching requests #1224

Merged
merged 8 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions projects/ccf-database/src/lib/ccf-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import { CCFSpatialGraph } from './ccf-spatial-graph';
import { CCFSpatialScene, SpatialSceneNode } from './ccf-spatial-scene';
import { searchXConsortia } from './xconsortia/xconsortia-data-import';
import { AggregateResult, DatabaseStatus, Filter, OntologyTreeModel, TissueBlockResult } from './interfaces';
import { getAggregateResults, getDatasetTechnologyNames, getProviderNames } from './queries/aggregate-results-n3';
import { findIds } from './queries/find-ids-n3';
Expand All @@ -18,9 +17,10 @@
import { getTissueBlockResult } from './queries/tissue-block-result-n3';
import { FlatSpatialPlacement, SpatialEntity } from './spatial-types';
import { CCFDatabaseStatusTracker } from './util/ccf-database-status-tracker';
import { patchJsonLd } from './util/patch-jsonld';
import { enrichRuiLocations } from './util/enrich-rui-locations';
import { getBmLocatedInAs } from './util/enrich-bm-located-in-as';
import { enrichRuiLocations } from './util/enrich-rui-locations';
import { patchJsonLd } from './util/patch-jsonld';
import { searchXConsortia } from './xconsortia/xconsortia-data-import';

const { delMany, get, setMany } = idb;

Expand Down Expand Up @@ -152,7 +152,7 @@
this.store = deserializeN3Store(ccfOwlUrl, DataFactory);
} else if (ccfOwlUrl.endsWith('.n3store.json')) {
const storeString = await fetch(ccfOwlUrl).then(r => r.text())
.catch(() => console.log('Couldn\'t locate serialized store.'));

Check warning on line 155 in projects/ccf-database/src/lib/ccf-database.ts

View workflow job for this annotation

GitHub Actions / Tests (18.x)

Unexpected console statement
if (storeString) {
this.store = deserializeN3Store(storeString, DataFactory);
}
Expand Down Expand Up @@ -194,7 +194,7 @@
if ((source.startsWith('http') || source.startsWith('assets/')) && source.includes('jsonld')) {
const sourceUrl = source;
source = await fetch(sourceUrl).then(r => r.text()).catch((err) => {
console.log(`Error fetching ${sourceUrl}`, err);

Check warning on line 197 in projects/ccf-database/src/lib/ccf-database.ts

View workflow job for this annotation

GitHub Actions / Tests (18.x)

Unexpected console statement
return '[]';
});
source = patchJsonLd(source);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Injectable, ProviderToken } from '@angular/core';
import {
ApiEndpointDataSourceService, CCFDatabaseDataSourceService, DataSourceLike, InjectorDelegateDataSourceService,
ApiEndpointDataSourceService, CCFDatabaseDataSourceService, DataSourceLike, InjectorDelegateDataSourceService, MixedCCfDatabaseDatasourceService
} from 'ccf-shared';

import { environment } from '../../../../environments/environment';
import { WorkerDataSourceService } from './worker-data-source.service';


export interface DelegateDataSourceOptions {
dataSources?: [];
useRemoteApi?: boolean;
remoteApiEndpoint?: string;
}
Expand All @@ -18,10 +19,14 @@ export interface DelegateDataSourceOptions {
})
export class DelegateDataSourceService extends InjectorDelegateDataSourceService<DelegateDataSourceOptions> {
protected selectToken(config: DelegateDataSourceOptions): ProviderToken<DataSourceLike> {
const { useRemoteApi, remoteApiEndpoint } = config;
const { dataSources, useRemoteApi, remoteApiEndpoint } = config;

if (useRemoteApi && !!remoteApiEndpoint) {
return ApiEndpointDataSourceService;
if (dataSources && dataSources.length > 0) {
return MixedCCfDatabaseDatasourceService;
} else {
return ApiEndpointDataSourceService;
}
} else if (typeof Worker !== 'undefined' && !environment.disableDbWorker) {
return WorkerDataSourceService;
} else {
Expand Down
4 changes: 2 additions & 2 deletions projects/ccf-eui/src/app/core/store/data/data.state.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { TestBed } from '@angular/core/testing';
import { NgxsDataPluginModule } from '@angular-ru/ngxs';
import { NgxsModule } from '@ngxs/store';
import { CCFDatabaseDataSourceService, DataSourceService, GlobalConfigState } from 'ccf-shared';
import { CCFDatabaseDataSourceService, DataSourceService, GlobalConfigState, DEFAULT_FILTER } from 'ccf-shared';

import { DataState, DEFAULT_FILTER } from './data.state';
import { DataState } from './data.state';

describe('DataState', () => {
let dataState: DataState;
Expand Down
23 changes: 4 additions & 19 deletions projects/ccf-eui/src/app/core/store/data/data.state.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
/* eslint-disable no-underscore-dangle */
/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable @typescript-eslint/naming-convention */
import { Injectable } from '@angular/core';
import { DataAction, Payload, StateRepository } from '@angular-ru/ngxs/decorators';
import { NgxsDataRepository } from '@angular-ru/ngxs/repositories';
import { Injectable } from '@angular/core';
import { Action, NgxsOnInit, State } from '@ngxs/store';
import { bind } from 'bind-decorator';
import { AggregateResult, DatabaseStatus, Filter, OntologyTreeModel, SpatialSceneNode, TissueBlockResult } from 'ccf-database';
import { DataSourceService } from 'ccf-shared';
import { combineLatest, defer, ObservableInput, ObservedValueOf, OperatorFunction, ReplaySubject, Subject } from 'rxjs';
import { delay, distinct, filter as rxjsFilter, map, publishReplay, refCount, repeat, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { DEFAULT_FILTER, DataSourceService } from 'ccf-shared';
import { ObservableInput, ObservedValueOf, OperatorFunction, ReplaySubject, Subject, combineLatest, defer } from 'rxjs';
import { delay, distinct, map, publishReplay, refCount, repeat, filter as rxjsFilter, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { UpdateFilter } from './data.actions';


/** Default values for filters. */
export const DEFAULT_FILTER: Filter = {
sex: 'Both',
ageRange: [1, 110],
bmiRange: [13, 83],
consortiums: [],
tmc: [],
technologies: [],
ontologyTerms: ['http://purl.obolibrary.org/obo/UBERON_0013702'],
cellTypeTerms: ['http://purl.obolibrary.org/obo/CL_0000000'],
biomarkerTerms: ['http://purl.org/ccf/biomarkers'],
spatialSearches: []
};

/** Current state of data queries. */
// eslint-disable-next-line no-shadow
export enum DataQueryState {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { GoogleAnalyticsService } from 'ngx-google-analytics';

import { DEFAULT_FILTER } from '../../../core/store/data/data.state';
import { DEFAULT_FILTER } from 'ccf-shared';
import { SpatialSearchFilterItem } from '../../../core/store/spatial-search-filter/spatial-search-filter.state';
import { Sex } from '../../../shared/components/spatial-search-config/spatial-search-config.component';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import { Matrix4 } from '@math.gl/core';
import {
AggregateResult, Filter, OntologyTreeModel, OntologyTreeNode, SpatialEntity, SpatialSceneNode, TissueBlockResult,
AggregateResult, Filter, OntologyTreeModel,
SpatialEntity, SpatialSceneNode, TissueBlockResult
} from 'ccf-database';
import { DatabaseStatus, DefaultService, MinMax, SpatialSearch, SpatialSceneNode as RawSpatialSceneNode } from 'ccf-openapi/angular-client';
import { combineLatest, Observable, of, Subject } from 'rxjs';
import { DatabaseStatus, DefaultService, MinMax, SpatialSceneNode as RawSpatialSceneNode, SpatialSearch } from 'ccf-openapi/angular-client';
import { Observable, Subject, combineLatest } from 'rxjs';
import { map, switchMap, take, tap } from 'rxjs/operators';
import { Cacheable } from 'ts-cacheable';

Expand All @@ -20,14 +21,14 @@ export interface ApiEndpointDataSourceOptions {
// Not exported from ts-cacheable!?
type IObservableCacheConfig = NonNullable<Parameters<typeof Cacheable>[0]>;

type RequestMethod<P, T> = (params: P) => Observable<T>;
type DataReviver<T, U> = (data: T) => U;
export type RequestMethod<P, T> = (params: P) => Observable<T>;
axdanbol marked this conversation as resolved.
Show resolved Hide resolved
export type DataReviver<T, U> = (data: T) => U;

interface DefaultParams {
export interface DefaultParams {
token?: string;
}

interface FilterParams {
export interface FilterParams {
age?: MinMax;
ageRange?: string;
bmi?: MinMax;
Expand Down Expand Up @@ -61,7 +62,7 @@ function cast<T>(): (data: unknown) => T {
return data => data as T;
}

function rangeToMinMax(
export function rangeToMinMax(
range: [number, number] | undefined,
low: number, high: number
): MinMax | undefined {
Expand All @@ -71,7 +72,7 @@ function rangeToMinMax(
} : undefined;
}

function filterToParams(filter?: Filter): FilterParams {
export function filterToParams(filter?: Filter): FilterParams {
return {
age: rangeToMinMax(filter?.ageRange, 1, 110),
bmi: rangeToMinMax(filter?.bmiRange, 13, 83),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { Injectable, isDevMode } from '@angular/core';
import { CCFDatabase, CCFDatabaseOptions } from 'ccf-database';
import { releaseProxy, Remote, wrap } from 'comlink';
import { Observable, Unsubscribable, using } from 'rxjs';
import { CCFDatabase, CCFDatabaseOptions, Filter } from 'ccf-database';
import { Remote, releaseProxy, wrap } from 'comlink';
import { Observable, ObservableInput, Unsubscribable, using } from 'rxjs';
import { filter, map, shareReplay, switchMap } from 'rxjs/operators';

import { GlobalConfigState } from '../../config/global-config.state';
import { DataSourceLike, DelegateDataSource } from './data-source';

import { ApiEndpointDataSourceService } from './api-endpoint.service';
import { DataSource, DataSourceDataType, DataSourceLike, DataSourceMethod, DelegateDataSource, ForwardingDataSource } from './data-source';
import { isEqual } from 'lodash';

/** Default values for filters. */
export const DEFAULT_FILTER: Filter = {
axdanbol marked this conversation as resolved.
Show resolved Hide resolved
sex: 'Both',
ageRange: [1, 110],
bmiRange: [13, 83],
consortiums: [],
tmc: [],
technologies: [],
ontologyTerms: ['http://purl.obolibrary.org/obo/UBERON_0013702'],
cellTypeTerms: ['http://purl.obolibrary.org/obo/CL_0000000'],
biomarkerTerms: ['http://purl.org/ccf/biomarkers'],
spatialSearches: []
};

interface CCFDatabaseManager extends Unsubscribable {
database: CCFDatabase | Remote<CCFDatabase>;
Expand Down Expand Up @@ -75,3 +89,58 @@ export abstract class WorkerCCFDatabaseDataSourceService extends CCFDatabaseData
};
}
}

const REMOTE_METHODS: (keyof DataSource)[] = [
'getProviderNames',
axdanbol marked this conversation as resolved.
Show resolved Hide resolved
'getDatasetTechnologyNames',
'getOntologyTreeModel',
'getCellTypeTreeModel',
'getBiomarkerTreeModel',
'getReferenceOrgans',
];
const FILTER_METHODS_ARG_0: (keyof DataSource)[] = [
'getTissueBlockResults',
'getAggregateResults',
'getOntologyTermOccurences',
'getCellTypeTermOccurences',
'getBiomarkerTermOccurences',
'getScene',
];
const FILTER_METHODS_ARG_1: (keyof DataSource)[] = [
'getReferenceOrganScene',
];

@Injectable({
providedIn: 'root'
})
export class MixedCCfDatabaseDatasourceService extends ForwardingDataSource {
axdanbol marked this conversation as resolved.
Show resolved Hide resolved
constructor(
private readonly remote: ApiEndpointDataSourceService,
private readonly local: CCFDatabaseDataSourceService,
) {
super();
}

protected forwardCall<K extends keyof DataSource>(
method: K, ...args: Parameters<DataSourceMethod<K>>
): Observable<DataSourceDataType<K>> {
type AnyFunction = (...rest: unknown[]) => ObservableInput<unknown>;
type Res = Observable<DataSourceDataType<K>>;
const source = this.isRemoteCall(method, args) ? this.remote : this.local;
return (source[method] as AnyFunction)(...args) as Res;
}

private isRemoteCall(method: keyof DataSource, args: unknown[]): boolean {
return (
REMOTE_METHODS.includes(method) ||
(FILTER_METHODS_ARG_0.includes(method) && this.isDefaultFilter(args[0] as Filter)) ||
axdanbol marked this conversation as resolved.
Show resolved Hide resolved
(FILTER_METHODS_ARG_1.includes(method) && this.isDefaultFilter(args[1] as Filter))
);
}

private isDefaultFilter(value?: Filter): boolean {
axdanbol marked this conversation as resolved.
Show resolved Hide resolved
return isEqual(value, DEFAULT_FILTER);
}
}


Loading