Commit 48dd558f authored by PECQUOT's avatar PECQUOT

[enh] generic referential OK

[enh] sampling equipments OK TODO: make a module
parent 835065d5
import {Beans, isNotNil, KeysEnum, StatusIds} from "sumaris-lib";
import {Beans, isNotNil, KeysEnum} from "sumaris-lib";
export class ReferentialFilter {
export interface BaseReferentialFilter {
id?: string;
name?: string; // todo remove this, the name can be search by searchText
statusId?: number;
statusIds?: number[];
searchJoin?: string; // If search is on a sub entity (e.g. Metier can search on TaxonGroup)
searchText?: string;
searchAttribute?: string;
}
export class ReferentialFilter implements BaseReferentialFilter {
entityName: string;
id?: string;
......@@ -19,24 +33,21 @@ export class ReferentialFilter {
});
}
/**
* Clean a filter, before sending to the pod (e.g remove 'levelId', 'statusId')
* @param filter
*/
static asPodObject(filter: ReferentialFilter): any {
if (!filter) return filter;
return {
id: filter.id,
name: filter.name,
searchText: filter.searchText,
searchAttribute: filter.searchAttribute,
searchJoin: filter.searchJoin,
statusIds: isNotNil(filter.statusId) ? [filter.statusId] : (filter.statusIds)
};
}
}
export const referentialFilterKeys: KeysEnum<ReferentialFilter> = {
export function asPodObject(filter: BaseReferentialFilter) {
if (!filter) return filter;
return {
id: filter.id,
name: filter.name,
searchText: filter.searchText,
searchAttribute: filter.searchAttribute,
searchJoin: filter.searchJoin,
statusIds: isNotNil(filter.statusId) ? [filter.statusId] : (filter.statusIds)
};
}
const referentialFilterKeys: KeysEnum<ReferentialFilter> = {
entityName: true,
id: true,
name: true,
......
import {Referential} from "sumaris-lib";
import {Beans, KeysEnum, Referential} from "sumaris-lib";
import {BaseReferentialFilter} from "@app/referential/model/referential.model";
export class SamplingEquipment extends Referential<SamplingEquipment> {
......@@ -18,3 +20,33 @@ export class SamplingEquipment extends Referential<SamplingEquipment> {
this.unitId = source.unitId;
}
}
export class SamplingEquipmentFilter implements BaseReferentialFilter {
id?: string;
name?: string;
statusId?: number;
statusIds?: number[];
searchJoin?: string; // If search is on a sub entity (e.g. Metier can search on TaxonGroup)
searchText?: string;
searchAttribute?: string;
static isEmpty(f: SamplingEquipmentFilter|any): boolean {
return Beans.isEmpty<SamplingEquipmentFilter>(f, samplingEquipmentFilterKeys, {
blankStringLikeEmpty: true
});
}
}
const samplingEquipmentFilterKeys: KeysEnum<SamplingEquipmentFilter> = {
id: true,
name: true,
statusId: true,
statusIds: true,
searchJoin: true,
searchText: true,
searchAttribute: true
};
......@@ -5,19 +5,23 @@ import {ErrorCodes} from "./errors";
import {FetchPolicy, MutationUpdaterFn} from "@apollo/client/core";
import {SortDirection} from "@angular/material/sort";
import {BaseEntityService, EntitiesService, EntityUtils, GraphqlService, LoadResult, PlatformService, Referential, ReferentialUtils} from "sumaris-lib";
import {ReferentialFilter, ReferentialType} from "@app/referential/model/referential.model";
import {BaseEntityService, EntitiesService, EntityUtils, GraphqlService, LoadResult, PlatformService, Referential} from "sumaris-lib";
import {asPodObject, BaseReferentialFilter} from "@app/referential/model/referential.model";
import {Environment} from "@environments/environment.class";
import {LOAD_REFERENTIAL_TYPES_QUERY, ReferentialQueries} from "@app/referential/services/referential.queries";
import {ReferentialQueries} from "@app/referential/services/referential.queries";
export class BaseReferentialService<E extends Referential> extends BaseEntityService<E> implements EntitiesService<E, ReferentialFilter> {
export class BaseReferentialService<E extends Referential, F extends BaseReferentialFilter>
extends BaseEntityService<E>
implements EntitiesService<E, F> {
constructor(
protected graphql: GraphqlService,
protected queries: ReferentialQueries,
protected platform: PlatformService,
protected environment: Environment,
protected fromObjectFn: (object: any) => E
protected entityName: string,
protected fromObjectFn: (object: any) => E,
protected insertFilterFn?: (data: E) => boolean
) {
super(graphql, environment);
......@@ -30,6 +34,7 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
// For DEV only
this._debug = !environment.production;
}
......@@ -37,55 +42,46 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
size: number,
sortBy?: string,
sortDirection?: SortDirection,
filter?: ReferentialFilter,
filter?: F,
opts?: {
fetchPolicy?: FetchPolicy;
withTotal: boolean;
}): Observable<LoadResult<E>> {
if (!filter || !filter.entityName) {
console.error("[referential-service] Missing filter.entityName");
throw {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"};
}
const entityName = filter.entityName;
const uniqueEntityName = filter.entityName + (filter.searchJoin || '');
}
): Observable<LoadResult<E>> {
const variables: any = {
entityName,
offset: offset || 0,
size: size || 100,
sortBy: sortBy,
sortDirection: sortDirection || 'asc',
filter: ReferentialFilter.asPodObject(filter)
filter: asPodObject(filter)
};
let now = new Date();
if (this._debug) console.debug(`[referential-service] Loading ${uniqueEntityName}...`, variables);
if (this._debug) console.debug(`[referential-service] Loading ${this.entityName}...`, variables);
const withTotal = (!opts || opts.withTotal !== false);
const query = withTotal ? this.queries.loadAllWithTotalQuery() : this.queries.loadAllQuery();
return this.mutableWatchQuery<{ referentials: any[]; referentialsCount?: number }>({
return this.mutableWatchQuery<{ data: any[]; count?: number }>({
queryName: withTotal ? 'LoadAllWithTotal' : 'LoadAll',
query,
arrayFieldName: 'referentials',
totalFieldName: withTotal ? 'referentialsCount' : undefined,
insertFilterFn: (d: E) => d.entityName === entityName,
arrayFieldName: 'data',
totalFieldName: withTotal ? 'count' : undefined,
insertFilterFn: this.insertFilterFn,
variables,
error: {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"},
fetchPolicy: opts && opts.fetchPolicy || 'network-only'
})
.pipe(
map(({referentials, referentialsCount}) => {
const data = (referentials || []).map(this.fromObjectFn);
data.forEach(r => r.entityName = uniqueEntityName);
map(({data, count}) => {
const entities = (data || []).map(this.fromObjectFn);
if (now && this._debug) {
console.debug(`[referential-service] ${uniqueEntityName} loaded in ${new Date().getTime() - now.getTime()}ms`, data);
console.debug(`[referential-service] ${this.entityName} loaded in ${new Date().getTime() - now.getTime()}ms`, entities);
now = null;
}
return {
data: data,
total: referentialsCount
data: entities,
total: count
};
})
);
......@@ -95,51 +91,43 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
size: number,
sortBy?: string,
sortDirection?: SortDirection,
filter?: ReferentialFilter,
filter?: F,
opts?: {
[key: string]: any;
fetchPolicy?: FetchPolicy;
debug?: boolean;
withTotal?: boolean;
toEntity?: boolean;
}): Promise<LoadResult<E>> {
}
): Promise<LoadResult<E>> {
if (!filter || !filter.entityName) {
console.error("[referential-service] Missing filter.entityName");
throw {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"};
}
const entityName = filter.entityName;
const uniqueEntityName = filter.entityName + (filter.searchJoin || '');
const debug = this._debug && (!opts || opts.debug !== false);
const variables: any = {
entityName: entityName,
offset: offset || 0,
size: size || 100,
sortBy: sortBy || filter.searchAttribute,
sortDirection: sortDirection || 'asc',
filter: ReferentialFilter.asPodObject(filter)
filter: asPodObject(filter)
};
const now = Date.now();
if (debug) console.debug(`[referential-service] Loading ${uniqueEntityName} items...`, variables);
if (debug) console.debug(`[referential-service] Loading ${this.entityName} items...`, variables);
const query = (!opts || opts.withTotal !== false) ? this.queries.loadAllWithTotalQuery() : this.queries.loadAllQuery();
const res = await this.graphql.query<{ referentials: any[]; referentialsCount: number }>({
const res = await this.graphql.query<{ data: any[]; count?: number }>({
query,
variables,
error: {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"},
fetchPolicy: opts && opts.fetchPolicy || 'network-only'
});
const data = (!opts || opts.toEntity !== false) ?
(res && res.referentials || []).map(this.fromObjectFn) :
(res && res.referentials || []) as E[];
data.forEach(r => r.entityName = uniqueEntityName);
if (debug) console.debug(`[referential-service] ${uniqueEntityName} items loaded in ${Date.now() - now}ms`);
(res && res.data || []).map(this.fromObjectFn) :
(res && res.data || []) as E[];
if (debug) console.debug(`[referential-service] ${this.entityName} items loaded in ${Date.now() - now}ms`);
return {
data: data,
total: res.referentialsCount
total: res.count
};
}
......@@ -150,33 +138,21 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
// Nothing to save: skip
if (!entities.length) return;
const entityName = entities[0].entityName;
if (!entityName) {
console.error("[referential-service] Could not save referential: missing entityName");
throw {code: ErrorCodes.SAVE_REFERENTIALS_ERROR, message: "REFERENTIAL.ERROR.SAVE_REFERENTIALS_ERROR"};
}
if (entities.length !== entities.filter(e => e.entityName === entityName).length) {
console.error("[referential-service] Could not save referential: more than one entityName found in the array to save!");
throw {code: ErrorCodes.SAVE_REFERENTIALS_ERROR, message: "REFERENTIAL.ERROR.SAVE_REFERENTIALS_ERROR"};
}
const json = entities.map(t => t.asObject());
const now = Date.now();
if (this._debug) console.debug(`[referential-service] Saving all ${entityName}...`, json);
if (this._debug) console.debug(`[referential-service] Saving all ${this.entityName}...`, json);
await this.graphql.mutate<{ saveReferentials: E[] }>({
await this.graphql.mutate<{ data: E[] }>({
mutation: this.queries.saveAllMutation(),
variables: {
referentials: json
data: json
},
error: {code: ErrorCodes.SAVE_REFERENTIALS_ERROR, message: "REFERENTIAL.ERROR.SAVE_REFERENTIALS_ERROR"},
update: (proxy, {data}) => {
if (data && data.saveReferentials) {
if (data && data.data) {
// Update entities (id and update date)
entities.forEach(entity => {
const savedEntity = data.saveReferentials.find(e => (e.id === entity.id || e.name === entity.name));
const savedEntity = data.data.find(e => (e.id === entity.id || e.name === entity.name));
if (savedEntity !== entity) {
EntityUtils.copyIdAndUpdateDate(savedEntity, entity);
}
......@@ -185,11 +161,11 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
// Update the cache
this.insertIntoMutableCachedQuery(proxy, {
query: this.queries.loadAllQuery(),
data: data.saveReferentials
data: data.data
});
}
if (this._debug) console.debug(`[referential-service] ${entityName} saved in ${Date.now() - now}ms`, entities);
if (this._debug) console.debug(`[referential-service] ${this.entityName} saved in ${Date.now() - now}ms`, entities);
}
});
......@@ -205,11 +181,6 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
*/
async save(entity: E, options?: any): Promise<E> {
if (!entity.entityName) {
console.error("[referential-service] Missing entityName");
throw {code: ErrorCodes.SAVE_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.SAVE_REFERENTIAL_ERROR"};
}
// Transform into json
const json = entity.asObject();
const isNew = !json.id;
......@@ -217,15 +188,15 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
const now = Date.now();
if (this._debug) console.debug(`[referential-service] Saving ${entity.entityName}...`, json);
await this.graphql.mutate<{ saveReferentials: any }>({
await this.graphql.mutate<{ data: any }>({
mutation: this.queries.saveAllMutation(),
variables: {
referentials: [json]
data: [json]
},
error: {code: ErrorCodes.SAVE_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.SAVE_REFERENTIAL_ERROR"},
update: (proxy, {data}) => {
// Update entity
const savedEntity = data && data.saveReferentials && data.saveReferentials[0];
const savedEntity = data && data.data && data.data[0];
if (savedEntity === entity) {
if (this._debug) console.debug(`[referential-service] ${entity.entityName} saved in ${Date.now() - now}ms`, entity);
EntityUtils.copyIdAndUpdateDate(savedEntity, entity);
......@@ -253,28 +224,18 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
}> | any): Promise<any> {
// Filter saved entities
entities = entities && entities
.filter(e => !!e.id && !!e.entityName) || [];
entities = entities && entities.filter(e => !!e.id) || [];
// Nothing to save: skip
if (!entities.length) return;
const entityName = entities[0].entityName;
const ids = entities.filter(e => e.entityName === entityName).map(t => t.id);
// Check that all entities have the same entityName
if (entities.length > ids.length) {
console.error("[referential-service] Could not delete referentials: only one entityName is allowed");
throw {code: ErrorCodes.DELETE_REFERENTIALS_ERROR, message: "REFERENTIAL.ERROR.DELETE_REFERENTIALS_ERROR"};
}
const ids = entities.map(t => t.id);
const now = new Date();
if (this._debug) console.debug(`[referential-service] Deleting ${entityName}...`, ids);
if (this._debug) console.debug(`[referential-service] Deleting ${this.entityName}...`, ids);
await this.graphql.mutate<any>({
mutation: this.queries.deleteAllMutation(),
variables: {
entityName: entityName,
ids: ids
},
error: {code: ErrorCodes.DELETE_REFERENTIALS_ERROR, message: "REFERENTIAL.ERROR.DELETE_REFERENTIALS_ERROR"},
......@@ -289,7 +250,7 @@ export class BaseReferentialService<E extends Referential> extends BaseEntitySer
options.update(proxy);
}
if (this._debug) console.debug(`[referential-service] ${entityName} deleted in ${new Date().getTime() - now.getTime()}ms`);
if (this._debug) console.debug(`[referential-service] ${this.entityName} deleted in ${new Date().getTime() - now.getTime()}ms`);
}
});
}
......
/* eslint-disable no-throw-literal */
import {Inject, Injectable} from "@angular/core";
import {EnvironmentService, GraphqlService, PlatformService, Referential, ReferentialUtils} from "sumaris-lib";
import {BaseReferentialService} from "@app/referential/services/base-referential.service";
import {LOAD_REFERENTIAL_TYPES_QUERY, ReferentialQueries} from "@app/referential/services/referential.queries";
import {BaseEntityService, EntitiesService, EntityUtils, EnvironmentService, GraphqlService, LoadResult, PlatformService, Referential, ReferentialUtils} from "sumaris-lib";
import {ReferentialQueries} from "@app/referential/services/referential.queries";
import gql from "graphql-tag";
import {referentialFragments} from "@app/referential/services/referential.fragments";
import {Observable} from "rxjs";
import {ReferentialType} from "@app/referential/model/referential.model";
import {asPodObject, ReferentialFilter, ReferentialType} from "@app/referential/model/referential.model";
import {ErrorCodes} from "@app/referential/services/errors";
import {map} from "rxjs/operators";
import {SortDirection} from "@angular/material/sort";
import {FetchPolicy, MutationUpdaterFn} from "@apollo/client/core";
const queries: ReferentialQueries = {
countQuery(): any {
return gql`
query ReferentialsCount($entityName: String, $filter: CodeReferentialFilterVOInput){
referentialsCount(entityName: $entityName, filter: $filter)
}
`;
},
loadAllQuery(): any {
return gql`
query Referentials($entityName: String, $offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: CodeReferentialFilterVOInput){
referentials(entityName: $entityName, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
data:referentials(entityName: $entityName, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
...FullReferentialFragment
}
}
......@@ -33,10 +27,10 @@ const queries: ReferentialQueries = {
loadAllWithTotalQuery(): any {
return gql`
query ReferentialsWithTotal($entityName: String, $offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: CodeReferentialFilterVOInput){
referentials(entityName: $entityName, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
data:referentials(entityName: $entityName, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection, filter: $filter){
...FullReferentialFragment
}
referentialsCount(entityName: $entityName, filter: $filter)
count:referentialsCount(entityName: $entityName, filter: $filter)
}
${referentialFragments.fullReferential}
`;
......@@ -44,8 +38,8 @@ const queries: ReferentialQueries = {
saveAllMutation(): any {
return gql`
mutation SaveReferentials($referentials:[ReferentialVOInput]){
saveReferentials(referentials: $referentials){
mutation SaveReferentials($data:[ReferentialVOInput]){
data:saveReferentials(referentials: $data){
...FullReferentialFragment
}
}
......@@ -63,15 +57,304 @@ const queries: ReferentialQueries = {
};
const loadReferentialTypesQuery: any = gql`
query ReferentialTypes{
referentialTypes {
name
idIsString
idIsComposite
labelPresent
descriptionPresent
commentPresent
creationDatePresent
__typename
}
}
`;
@Injectable({providedIn: 'root'})
export class ReferentialGenericService extends BaseReferentialService<Referential> {
export class ReferentialGenericService extends BaseEntityService<Referential>
implements EntitiesService<Referential, ReferentialFilter> {
constructor(
protected graphql: GraphqlService,
protected platform: PlatformService,
@Inject(EnvironmentService) protected environment
) {
super(graphql, queries, platform, environment, ReferentialUtils.fromObject);
super(graphql, environment);
platform.ready().then(() => {
// No limit for updatable watch queries, if desktop
if (!platform.mobile) {
this._mutableWatchQueriesMaxCount = -1;
}
});
// For DEV only
this._debug = !environment.production;
}
watchAll(offset: number,
size: number,
sortBy?: string,
sortDirection?: SortDirection,
filter?: ReferentialFilter,
opts?: {
fetchPolicy?: FetchPolicy;
withTotal: boolean;
}
): Observable<LoadResult<Referential>> {
if (!filter || !filter.entityName) {
console.error("[referential-service] Missing filter.entityName");
throw {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"};
}
const entityName = filter.entityName;
const uniqueEntityName = filter.entityName + (filter.searchJoin || '');
const variables: any = {
entityName,
offset: offset || 0,
size: size || 100,
sortBy: sortBy,
sortDirection: sortDirection || 'asc',
filter: asPodObject(filter)
};
let now = new Date();
if (this._debug) console.debug(`[referential-service] Loading ${uniqueEntityName}...`, variables);
const withTotal = (!opts || opts.withTotal !== false);
const query = withTotal ? queries.loadAllWithTotalQuery() : queries.loadAllQuery();
return this.mutableWatchQuery<{ data: any[]; count?: number }>({
queryName: withTotal ? 'LoadAllWithTotal' : 'LoadAll',
query,
arrayFieldName: 'data',
totalFieldName: withTotal ? 'count' : undefined,
insertFilterFn: (d: Referential) => d.entityName === entityName,
variables,
error: {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"},
fetchPolicy: opts && opts.fetchPolicy || 'network-only'
})
.pipe(
map(({data, count}) => {
const entities = (data || []).map(ReferentialUtils.fromObject);
entities.forEach(r => r.entityName = uniqueEntityName);
if (now && this._debug) {
console.debug(`[referential-service] ${uniqueEntityName} loaded in ${new Date().getTime() - now.getTime()}ms`, entities);
now = null;
}
return {
data: entities,
total: count
};
})
);
}
async loadAll(offset: number,
size: number,
sortBy?: string,
sortDirection?: SortDirection,
filter?: ReferentialFilter,
opts?: {
[key: string]: any;
fetchPolicy?: FetchPolicy;
debug?: boolean;
withTotal?: boolean;
toEntity?: boolean;
}
): Promise<LoadResult<Referential>> {
if (!filter || !filter.entityName) {
console.error("[referential-service] Missing filter.entityName");
throw {code: ErrorCodes.LOAD_REFERENTIAL_ERROR, message: "REFERENTIAL.ERROR.LOAD_REFERENTIAL_ERROR"};
}
const entityName = filter.entityName;
const uniqueEntityName = filter.entityName + (filter.searchJoin || '');
const debug = this._debug && (!opts || opts.debug !== false);
<