Commit 4fa7174a authored by LAVENIER's avatar LAVENIER
Browse files

Merge branch 'release/1.6.0'

parents 3bb24f54 06f79567
......@@ -37,7 +37,8 @@
"apollo-link-logger",
"zen-observable",
"subscriptions-transport-ws",
"chart.js"
"chart.js",
"react"
],
"assets": [
{
......@@ -114,7 +115,6 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
......@@ -146,7 +146,6 @@
"aot": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": true,
"extractLicenses": true,
"vendorChunk": false
......@@ -279,5 +278,8 @@
}
}
},
"defaultProject": "app"
"defaultProject": "app",
"cli": {
"analytics": false
}
}
<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="10507" id="net.sumaris.app" version="1.5.7" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget android-versionCode="10600" id="net.sumaris.app" version="1.6.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>SUMARiS</name>
<description>Halieutic data capture</description>
<author email="contact@e-is.pro" href="http://www.e-is.pro">Environmental Information Systems</author>
......
......@@ -15,7 +15,7 @@ if [[ "_$INSTALL_DIR" == "_" ]]; then
fi
latest_version() {
echo "1.5.7" #lastest
echo "1.6.0" #lastest
}
api_release_url() {
......
{
"name": "sumaris-app",
"description": "SUMARiS app",
"version": "1.5.7",
"version": "1.6.0",
"author": "contact@e-is.pro",
"license": "AGPL-3.0",
"readmeFilename": "README.md",
......@@ -25,21 +25,18 @@
},
"dependencies": {
"@angular/animations": "^11.0.2",
"@angular/cdk": "^11.0.0",
"@angular/cdk": "^11.0.1",
"@angular/common": "^11.0.2",
"@angular/core": "^11.0.2",
"@angular/forms": "^11.0.2",
"@angular/material": "^11.0.0",
"@angular/material-moment-adapter": "^11.0.0",
"@angular/material": "^11.0.1",
"@angular/material-moment-adapter": "^11.0.1",
"@angular/platform-browser": "^11.0.2",
"@angular/platform-browser-dynamic": "^11.0.2",
"@angular/router": "^11.0.2",
"@apollo/client": "^3.2.7",
"@apollo/link-error": "^2.0.0-beta.3",
"@apollo/link-retry": "^2.0.0-beta.3",
"@apollo/link-ws": "^2.0.0-beta.3",
"@asymmetrik/ngx-leaflet": "^7.0.1",
"@e-is/ngx-material-table": "0.10.2",
"@apollo/client": "^3.3.2",
"@asymmetrik/ngx-leaflet": "^8.1.0",
"@e-is/ngx-material-table": "0.11.1",
"@ionic-native/audio-management": "^5.29.0",
"@ionic-native/camera": "^5.29.0",
"@ionic-native/core": "^5.29.0",
......@@ -61,8 +58,7 @@
"@ngx-translate/http-loader": "^6.0.0",
"angular2-text-mask": "^9.0.0",
"apollo-angular": "^2.1.0",
"apollo-angular-link-http": "^1.11.0",
"apollo-cache-inmemory": "^1.6.6",
"apollo-angular-link-http": "^2.0.0-alpha.6",
"apollo3-cache-persist": "^0.9.1",
"apollo-link-logger": "^2.0.0",
"apollo-link-queue": "^3.0.0",
......@@ -89,7 +85,7 @@
"hammer-timejs": "^1.1.0",
"integrator-cordova-plugin-downloader": "^1.1.0",
"ionic-cache": "^5.2.0",
"ionicons": "^5.1.2",
"ionicons": "^5.2.3",
"leaflet": "^1.6.0",
"localforage": "1.7.1",
"lodash.clonedeep": "^4.5.0",
......@@ -99,45 +95,37 @@
"ng2-charts": "^2.4.2",
"ng2-charts-schematics": "^0.1.7",
"ngx-color-picker": "^10.1.0",
"ngx-markdown": "^10.1.1",
"ngx-markdown": "^11.0.0",
"ngx-material-timepicker": "5.5.3",
"ngx-quicklink": "^0.2.4",
"roboto-fontface": "^0.10.0",
"scrypt-async": "^2.0.1",
"seedrandom": "^3.0.5",
"subscriptions-transport-ws": "^0.9.16",
"sw-toolbox": "3.6.0",
"tslib": "^2.0.0",
"tslib": "^2.0.3",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^3.3.3",
"zone.js": "~0.11.3"
},
"peerDependencies": {
"tar": "^5.0.5",
"rxjs": "^6.6.3"
"rxjs": "^6.6.3",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.2",
"@angular-devkit/build-webpack": "~0.1100.2",
"@angular/cli": "^11.0.2",
"@angular/compiler": "^11.0.2",
"@angular/compiler-cli": "^11.0.2",
"@angular/language-service": "^11.0.2",
"@ionic/angular-toolkit": "^3.0.0",
"@ionic/cli": "^6.12.2",
"@types/async": "^3.2.4",
"@types/graphql": "14.5.0",
"@types/jasmine": "^3.6.2",
"@types/jasminewd2": "^2.0.8",
"@types/jasmine": "~3.5.14",
"@types/jasminewd2": "~2.0.8",
"@types/leaflet": "^1.5.19",
"@types/node": "^12.19.6",
"@types/node": "^12.19.1",
"@types/uuid": "^3.4.9",
"acorn": "^7.3.1",
"codelyzer": "^6.0.0",
"codelyzer": "^6.0.1",
"cordova": "^9.0.0",
"cordova-res": "^0.15.2",
"rxjs": "^6.6.3",
"cordova-plugin-camera": "^4.1.0",
"cordova-plugin-device": "^2.0.3",
"cordova-plugin-ionic-keyboard": "^2.2.0",
......@@ -145,11 +133,10 @@
"cordova-plugin-splashscreen": "^5.0.4",
"cordova-plugin-statusbar": "^2.4.3",
"cordova-plugin-whitelist": "^1.3.4",
"eslint": "^7.4.0",
"react": "^17.0.1",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.2",
"karma": "~5.2.1",
"karma": "~5.2.3",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~4.0.1",
......@@ -160,7 +147,8 @@
"tar": "^5.0.5",
"ts-node": "^8.10.2",
"typescript": "~4.0.5",
"webpack": "^4.0.1"
"webpack": "^4.6.0",
"tslint": "^6.1.3"
},
"cordova": {
"plugins": {
......
......@@ -24,7 +24,7 @@ import {APP_LOCAL_SETTINGS_OPTIONS} from "./core/services/local-settings.service
import {LocalSettings} from "./core/services/model/settings.model";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {APP_CONFIG_OPTIONS} from "./core/services/config.service";
import {TripConfigOptions} from "./trip/services/config/trip.config";
import {TRIP_CONFIG_OPTIONS, TRIP_STORAGE_TYPE_POLICIES} from "./trip/services/config/trip.config";
import {IonicStorageModule} from "@ionic/storage";
import {InAppBrowser} from "@ionic-native/in-app-browser/ngx";
import {APP_MENU_ITEMS} from "./core/menu/menu.component";
......@@ -37,9 +37,11 @@ import {TranslateLoader, TranslateModule} from "@ngx-translate/core";
import {SharedModule} from "./shared/shared.module";
import {HttpTranslateLoaderFactory} from "./shared/translate/http-translate-loader-factory";
import {MarkdownModule, MarkedOptions} from "ngx-markdown";
import {EntitiesStorageOptions, LOCAL_ENTITIES_STORAGE_OPTIONS} from "./core/services/storage/entities-storage.service";
import {OperationService} from "./trip/services/operation.service";
import {APP_LOCAL_STORAGE_TYPE_POLICIES} from "./core/services/storage/entities-storage.service";
import {AppGestureConfig} from "./shared/gesture/gesture-config";
import {TypePolicies} from "@apollo/client/core";
import {APP_GRAPHQL_TYPE_POLICIES} from "./core/graphql/graphql.service";
import {SocialModule} from "./social/social.module";
@NgModule({
......@@ -81,6 +83,7 @@ import {AppGestureConfig} from "./shared/gesture/gesture-config";
// functional modules
CoreModule.forRoot(),
SharedModule.forRoot(),
SocialModule.forRoot(),
HammerModule,
AppRoutingModule
],
......@@ -122,7 +125,7 @@ import {AppGestureConfig} from "./shared/gesture/gesture-config";
},
// Config options (Core + trip)
{ provide: APP_CONFIG_OPTIONS, useValue: {...ConfigOptions, ...TripConfigOptions}},
{ provide: APP_CONFIG_OPTIONS, useValue: {...ConfigOptions, ...TRIP_CONFIG_OPTIONS}},
// Menu items
{ provide: APP_MENU_ITEMS, useValue: [
......@@ -189,17 +192,42 @@ import {AppGestureConfig} from "./shared/gesture/gesture-config";
]
},
// Entities options
{ provide: LOCAL_ENTITIES_STORAGE_OPTIONS, useValue: <EntitiesStorageOptions>{
'OperationVO': {
onlyLocalEntities: true,
storeById: true,
detailedAttributes: OperationService.LIGHT_EXCLUDED_ATTRIBUTES
// Entities Apollo cache options
{ provide: APP_GRAPHQL_TYPE_POLICIES, useValue: <TypePolicies>{
'MetierVO': {
keyFields: ['entityName', 'id']
},
'PmfmVO': {
keyFields: ['entityName', 'id']
},
'TaxonGroupVO': {
keyFields: ['entityName', 'id']
},
'TaxonNameVO': {
keyFields: ['entityName', 'id']
},
'LocationVO': {
keyFields: ['entityName', 'id']
},
'ReferentialVO': {
keyFields: ['entityName', 'id']
},
'MeasurementVO': {
keyFields: ['entityName', 'id']
},
'TaxonGroupStrategyVO': {
keyFields: ['__typename', 'strategyId', 'taxonGroup', ['entityName', 'id']]
},
'TaxonNameStrategyVO': {
keyFields: ['__typename', 'strategyId', 'taxonName', ['entityName', 'id']]
}
}
},
// Test pages
// Entities options
{ provide: APP_LOCAL_STORAGE_TYPE_POLICIES, useValue: { ...TRIP_STORAGE_TYPE_POLICIES}},
// Test pages link
{ provide: APP_TESTING_PAGES, useValue: <TestingPage[]>[
{label: 'Batch tree', page: '/testing/trip/batchTree'}
]},
......
......@@ -8,16 +8,17 @@ import {
FetchPolicy,
InMemoryCache,
MutationUpdaterFn,
OperationVariables,
TypePolicies,
WatchQueryFetchPolicy
} from "@apollo/client/core";
import {ErrorCodes, ServerErrorCodes, ServiceError} from "../services/errors";
import {catchError, distinctUntilChanged, filter, first, map, mergeMap, throttleTime} from "rxjs/operators";
import {environment} from '../../../environments/environment';
import {Injectable} from "@angular/core";
import {Inject, Injectable, InjectionToken, Optional} from "@angular/core";
import {ConnectionType, NetworkService} from "../services/network.service";
import {WebSocketLink} from "@apollo/link-ws";
import {MODEL_TYPES_POLICIES} from "./graphql.types";
import {WebSocketLink} from "@apollo/client/link/ws";
import {
AppWebSocket,
createTrackerLink,
......@@ -32,7 +33,6 @@ import SerializingLink from 'apollo-link-serialize';
import loggerLink from 'apollo-link-logger';
import {Platform} from "@ionic/angular";
import {EntityUtils, IEntity} from "../services/model/entity.model";
import {DataProxy} from 'apollo-cache';
import {isNil, isNotNil, toNumber} from "../../shared/functions";
import {Resolvers} from "@apollo/client/core/types";
import {HttpHeaders} from "@angular/common/http";
......@@ -40,15 +40,17 @@ import {EmptyObject} from "apollo-angular/types";
import {HttpLink, Options} from "apollo-angular/http";
import {IonicStorageWrapper, persistCache} from "apollo3-cache-persist";
import {PersistentStorage} from "apollo3-cache-persist/lib/types";
import {MutationBaseOptions} from "@apollo/client/core/watchQueryOptions";
import {Cache} from "@apollo/client/cache/core/types/Cache";
export interface WatchQueryOptions<V> {
query: any,
variables: V,
error?: ServiceError,
fetchPolicy?: WatchQueryFetchPolicy
query: any;
variables: V;
error?: ServiceError;
fetchPolicy?: WatchQueryFetchPolicy;
}
export interface MutateQueryOptions<T, V = EmptyObject> {
export interface MutateQueryOptions<T, V = OperationVariables> extends MutationBaseOptions<T, V> {
mutation: any;
variables: V;
error?: ServiceError;
......@@ -63,6 +65,9 @@ export interface MutateQueryOptions<T, V = EmptyObject> {
forceOffline?: boolean;
}
export const APP_GRAPHQL_TYPE_POLICIES = new InjectionToken<TypePolicies>('graphqlTypePolicies');
@Injectable({
providedIn: 'root'
})
......@@ -100,7 +105,8 @@ export class GraphqlService {
private apollo: Apollo,
private httpLink: HttpLink,
private network: NetworkService,
private storage: Storage
private storage: Storage,
@Optional() @Inject(APP_GRAPHQL_TYPE_POLICIES) private typePolicies: TypePolicies
) {
this._debug = !environment.production;
......@@ -130,7 +136,7 @@ export class GraphqlService {
mergeMap(() => this.network.checkPeerAlive()),
filter(alive => !alive)
)
.subscribe(() => this.network.setForceOffline(true, {displayToast: true}))
.subscribe(() => this.network.setForceOffline(true, {displayToast: true}));
}
......@@ -295,7 +301,7 @@ export class GraphqlService {
}
insertIntoQueryCache<T, V = EmptyObject>(cache: ApolloCache<any>,
opts: DataProxy.Query<V> & {
opts: Cache.ReadQueryOptions<V, any> & {
arrayFieldName: string;
totalFieldName?: string;
data: T;
......@@ -354,7 +360,7 @@ export class GraphqlService {
}
addManyToQueryCache<T = any, V = EmptyObject>(cache: ApolloCache<any>,
opts: DataProxy.Query<V> & {
opts: Cache.ReadQueryOptions<V, any> & {
arrayFieldName: string;
totalFieldName?: string;
data: T[];
......@@ -422,17 +428,17 @@ export class GraphqlService {
}
removeFromCachedQueryById<V = EmptyObject>(cache: ApolloCache<any>,
opts: DataProxy.Query<V> & {
opts: Cache.ReadQueryOptions<V, any> & {
arrayFieldName: string;
totalFieldName?: string;
id: number
id: string
}) {
cache = cache || this.apollo.client.cache;
opts.arrayFieldName = opts.arrayFieldName || 'data';
try {
let data:any = cache.readQuery({...opts, id: opts.id.toString()});
let data = cache.readQuery(opts);
if (data && data[opts.arrayFieldName]) {
// Copy because immutable
......@@ -475,7 +481,7 @@ export class GraphqlService {
}
removeFromCachedQueryByIds<V = EmptyObject>(cache: ApolloCache<any>,
opts: DataProxy.Query<V> & {
opts: Cache.ReadQueryOptions<V, any> & {
arrayFieldName: string;
totalFieldName?: string;
ids: number[]
......@@ -485,7 +491,7 @@ export class GraphqlService {
opts.arrayFieldName = opts.arrayFieldName || 'data';
try {
let data:any = cache.readQuery(opts);
let data = cache.readQuery(opts);
if (data && data[opts.arrayFieldName]) {
// Copy because immutable
......@@ -546,7 +552,7 @@ export class GraphqlService {
}
updateToQueryCache<T extends IEntity<any>, V = EmptyObject>(cache: ApolloCache<any>,
opts:DataProxy.Query<V> & {
opts: Cache.ReadQueryOptions<V, any> & {
arrayFieldName: string;
totalFieldName?: string;
data: T,
......@@ -562,7 +568,7 @@ export class GraphqlService {
// Copy because immutable
data = { ...data };
const equalsFn = opts.equalsFn || ((d1,d2) => EntityUtils.equals(d1, d2, 'id'));
const equalsFn = opts.equalsFn || ((d1, d2) => EntityUtils.equals(d1, d2, 'id'));
// Update if exists, or insert
const index = data[opts.arrayFieldName].findIndex(v => equalsFn(opts.data, v));
......@@ -600,7 +606,7 @@ export class GraphqlService {
async clearCache(client?: ApolloClient<any>): Promise<void> {
client = (client || this.apollo.client) as ApolloClient<any>;
if (client) {
let now = this._debug && Date.now();
const now = this._debug && Date.now();
console.info("[graphql] Clearing Apollo client's cache... ");
await client.cache.reset();
if (this._debug) console.debug(`[graphql] Apollo client's cache cleared, in ${Date.now() - now}ms`);
......@@ -671,7 +677,7 @@ export class GraphqlService {
// Cache
const cache = new InMemoryCache({
typePolicies: MODEL_TYPES_POLICIES
typePolicies: this.typePolicies
});
// Add cache persistence
......@@ -768,7 +774,7 @@ export class GraphqlService {
storage,
debug: true
});
} catch(err) {
} catch (err) {
console.error('[graphql] Failed to restore tracked queries from storage: ' + (err && err.message || err), err);
}
}
......@@ -839,9 +845,11 @@ export class GraphqlService {
private createAppErrorByCode(errorCode: number): any | undefined {
const message = this.getI18nErrorMessageByCode(errorCode);
if (message) return {
code: errorCode,
message: this.getI18nErrorMessageByCode(errorCode)
if (message) {
return {
code: errorCode,
message: this.getI18nErrorMessageByCode(errorCode)
};
}
return undefined;
}
......
import {TypePolicies} from "@apollo/client/core";
export const MODEL_TYPES_POLICIES: TypePolicies = {
'MetierVO': {
keyFields: ['entityName', 'id']
},
'PmfmVO': {
keyFields: ['entityName', 'id']
},
'TaxonGroupVO': {
keyFields: ['entityName', 'id']
},
'TaxonNameVO': {
keyFields: ['entityName', 'id']
},
'LocationVO': {
keyFields: ['entityName', 'id']
},
'ReferentialVO': {
keyFields: ['entityName', 'id']
},
'MeasurementVO': {
keyFields: ['entityName', 'id']
},
'TaxonGroupStrategyVO': {
keyFields: ['__typename', 'strategyId', 'taxonGroup', ['entityName', 'id']]
},
'TaxonNameStrategyVO': {
keyFields: ['__typename', 'strategyId', 'taxonName', ['entityName', 'id']]
}
};
import {defaultDataIdFromObject} from "apollo-cache-inmemory";
import {ApolloLink, NextLink, Operation} from "@apollo/client/core";
import {ApolloClient, ApolloLink, NextLink, Operation} from "@apollo/client/core";
import * as uuidv4 from "uuid/v4";
import {EventEmitter} from "@angular/core";
import {debounceTime, filter, switchMap} from "rxjs/operators";
import {BehaviorSubject, Observable} from "rxjs";
import {ApolloClient} from "@apollo/client/core";
import {environment} from "../../../environments/environment";
import {isNotNil} from "../../shared/functions";
import {getMainDefinition} from "apollo-utilities";
import {getMainDefinition} from "@apollo/client/utilities";
import {PersistentStorage} from "apollo3-cache-persist/lib/types";
declare let window: any;
......
......@@ -2,8 +2,7 @@ import {GraphqlService, MutateQueryOptions, WatchQueryOptions} from "../graphql/
import {Page} from "../../shared/services/entity-service.class";
import {EmptyObject} from "apollo-angular/types";
import {Observable} from "rxjs";
import {DataProxy} from "apollo-cache";
import {FetchResult} from "apollo-link";
import {FetchResult} from "@apollo/client/link/core";
import {environment} from "../../../environments/environment";
import {EntityUtils} from "./model/entity.model";
import {ApolloCache} from "@apollo/client/core";
......@@ -83,9 +82,10 @@ export abstract class BaseEntityService<T = any, F = any>{
mutableQuery = existingQueries[0] as MutableWatchQueryInfo<D, T, V>;
mutableQuery.counter += 1;
console.debug('[base-data-service] Find existing mutable watching query (same variables): ' + queryName);
if (mutableQuery.counter > 3) {
console.warn('[base-data-service] TODO: clean previous queries with name: ' + queryName);
}
//if (mutableQuery.counter > 3) {
// console.warn('[base-data-service] TODO: clean previous queries with name: ' + queryName);
//}
}
else {
this.registerNewMutableWatchQuery({
......@@ -144,10 +144,10 @@ export abstract class BaseEntityService<T = any, F = any>{
});
}
removeFromMutableCachedQueryByIds(proxy: ApolloCache<any>, opts: {
removeFromMutableCachedQueryByIds(cache: ApolloCache<any>, opts: {
query?: any;
queryName?: string;
ids?: number|number[];
ids: number|number[];
}){
if (!opts.query && !opts.queryName) throw Error("Missing one of 'query' or 'queryName' in the given options");
const existingQueries = opts.queryName ?
......@@ -158,7 +158,7 @@ export abstract class BaseEntityService<T = any, F = any>{
console.debug(`[base-data-service] Removing data from watching queries: `, existingQueries);
existingQueries.forEach(watchQuery => {
if (opts.ids instanceof Array) {
this.graphql.removeFromCachedQueryByIds(proxy, {
this.graphql.removeFromCachedQueryByIds(cache, {
query: watchQuery.query,
variables: watchQuery.variables,
arrayFieldName: watchQuery.arrayFieldName as string,
......@@ -166,11 +166,11 @@ export abstract class BaseEntityService<T = any, F = any>{
});
}
else {
this.graphql.removeFromCachedQueryById(proxy, {
this.graphql.removeFromCachedQueryById(cache, {
query: watchQuery.query,
variables: watchQuery.variables,
arrayFieldName: watchQuery.arrayFieldName as string,
id: opts.ids as number
id: opts.ids.toString()
});
}
});
......
......@@ -20,6 +20,7 @@ export const ErrorCodes = {
SAVE_ACCOUNT_ERROR: 8,
ACCOUNT_NOT_EXISTS: 9,
SUBSCRIBE_ACCOUNT_ERROR: 10,
ENTITY_STORAGE_MIGRATION_FAILED: 11,
// DATA errors (load error)
LOAD_PERSONS_ERROR: 100,
......@@ -34,6 +35,9 @@ export const ErrorCodes = {
LOAD_CONFIG_ERROR: 700,
SAVE_CONFIG_ERROR: 701,
LOAD_TRASH_ENTITY_ERROR: 800,
DELETE_TRASH_ENTITY_ERROR: 801
};
export const ServerErrorCodes = {
......