added error handling for ngx-data-apollo

added handle wrapper
This commit is contained in:
Mathias Beaulieu-Duncan 2019-12-10 16:54:49 -06:00
parent e5133b2f95
commit ab99d5f076
6 changed files with 158 additions and 20 deletions

View File

@ -8,8 +8,10 @@
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"build-lib": "ng build @poweredsoft/ngx-data", "build-data": "ng build @poweredsoft/ngx-data",
"publish-lib": "npm publish dist/poweredsoft/ngx-data" "build-apollo": "ng build @poweredsoft/ngx-data-apollo",
"publish-data": "npm publish dist/poweredsoft/ngx-data",
"publish-apollo": "npm publish dist/poweredsoft/ngx-data-apollo"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@poweredsoft/ngx-data-apollo", "name": "@poweredsoft/ngx-data-apollo",
"version": "0.0.2", "version": "0.0.3",
"peerDependencies": { "peerDependencies": {
"@poweredsoft/data": "^0.0.26", "@poweredsoft/data": "^0.0.26",
"@angular/common": "^8.2.4", "@angular/common": "^8.2.4",

View File

@ -1,24 +1,32 @@
import { IDataSourceOptions, IQueryCriteria, IDataSourceTransportOptions, IDataSourceQueryAdapterOptions, IDataSourceCommandAdapterOptions, IAdvanceQueryAdapter, IFilter, IAggregate, ISort, IGroup, ISimpleFilter, ICompositeFilter, IQueryExecutionGroupResult, IQueryExecutionResult, IAggregateResult, IGroupQueryResult } from '@poweredsoft/data'; import { IDataSourceOptions, IQueryCriteria, IDataSourceTransportOptions, IDataSourceQueryAdapterOptions, IDataSourceCommandAdapterOptions, IAdvanceQueryAdapter, IFilter, IAggregate, ISort, IGroup, ISimpleFilter, ICompositeFilter, IQueryExecutionGroupResult, IQueryExecutionResult, IAggregateResult, IGroupQueryResult, IResolveCommandModelEvent, IDataSourceErrorMessage, IDataSourceValidationError } from '@poweredsoft/data';
import { Apollo } from 'apollo-angular'; import { Apollo } from 'apollo-angular';
import { IGraphQLAdvanceQueryResult, IGraphQLAdvanceQueryInput, IGraphQLAdvanceQueryFilterInput, IGraphQLAdvanceQueryAggregateInput, IGraphQLAdvanceQuerySortInput, IGraphQLAdvanceQueryGroupInput, FilterType, IGraphQLAdvanceQueryAggregateResult, IGraphQLVariantResult, AggregateType, IGraphQLAdvanceGroupResult } from './models'; import { IGraphQLAdvanceQueryResult, IGraphQLAdvanceQueryInput, IGraphQLAdvanceQueryFilterInput, IGraphQLAdvanceQueryAggregateInput, IGraphQLAdvanceQuerySortInput, IGraphQLAdvanceQueryGroupInput, FilterType, IGraphQLAdvanceQueryAggregateResult, IGraphQLVariantResult, AggregateType, IGraphQLAdvanceGroupResult } from './models';
import gql from 'graphql-tag'; import gql from 'graphql-tag';
import { DocumentNode } from 'graphql'; import { DocumentNode, GraphQLError } from 'graphql';
import { map, catchError } from 'rxjs/operators'; import { map, catchError, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { FetchResult } from 'apollo-link';
import { of } from 'zen-observable';
import { ApolloError } from 'apollo-client';
export class GraphQLDataSourceOptionsBuilder<TModel, TKey> { export class GraphQLDataSourceOptionsBuilder<TModel, TKey> {
querySelect: string;
private _commands: { [key: string] : IDataSourceCommandAdapterOptions<any> }; private _commands: { [key: string] : IDataSourceCommandAdapterOptions<any> } = {};
constructor(private apollo: Apollo, constructor(private apollo: Apollo,
private queryName: string, private queryName: string,
private queryInputName: string, private queryInputName: string,
private querySelect: string, querySelect: string | string[],
private keyResolver: (model: TModel) => TKey, private keyResolver: (model: TModel) => TKey,
private defaultCriteria: IQueryCriteria, private defaultCriteria: IQueryCriteria,
private manageNotificationMessage: boolean) private manageNotificationMessage: boolean)
{ {
if (Array.isArray(querySelect))
this.querySelect = querySelect.join(' ');
else
this.querySelect = querySelect;
} }
create(): IDataSourceOptions<TModel> { create(): IDataSourceOptions<TModel> {
let ret: IDataSourceOptions<TModel> = { let ret: IDataSourceOptions<TModel> = {
resolveIdField: this.keyResolver, resolveIdField: this.keyResolver,
@ -28,6 +36,7 @@ export class GraphQLDataSourceOptionsBuilder<TModel, TKey> {
}; };
return ret; return ret;
} }
protected createTransport(): IDataSourceTransportOptions<TModel> { protected createTransport(): IDataSourceTransportOptions<TModel> {
let ret: IDataSourceTransportOptions<TModel> = { let ret: IDataSourceTransportOptions<TModel> = {
query: this.createQuery(), query: this.createQuery(),
@ -122,6 +131,7 @@ export class GraphQLDataSourceOptionsBuilder<TModel, TKey> {
return null; return null;
} }
private createGraphQLQuery(query: IQueryCriteria): DocumentNode { private createGraphQLQuery(query: IQueryCriteria): DocumentNode {
return gql` return gql`
query getAll($criteria: ${this.queryInputName}) { query getAll($criteria: ${this.queryInputName}) {
@ -163,14 +173,63 @@ export class GraphQLDataSourceOptionsBuilder<TModel, TKey> {
groups: query.groups ? query.groups.map(this.convertGroup.bind(this)) : null groups: query.groups ? query.groups.map(this.convertGroup.bind(this)) : null
}; };
return ret; return ret;
} }
public addMutation<TMutation, TMutationResult>(name: string, handle: (command: TMutation) => Observable<TMutationResult>) { /*public addMutationTest<TMutation, TMutationResult>(name: string, mutationName: string, mutationSelect?: string, resolveCommandModel?: (event: IResolveCommandModelEvent<TModel>) => Observable<TMutation & any>) {
this._commands[name] = <IDataSourceCommandAdapterOptions<TModel>> {
this._commands[name] = <IDataSourceCommandAdapterOptions<TMutation>> {
adapter: { adapter: {
handle: handle handle: this.apollo.use()
} },
resolveCommandModel: resolveCommandModel
};
return this;
}*/
public addMutation<TMutation, TMutationResult>(name: string, mutationName: string, handle: (command: TMutation) => Observable<FetchResult<TMutationResult>>, resolveCommandModel?: (event: IResolveCommandModelEvent<TModel>) => Observable<TMutation & any>) {
const handleWrapper = command => {
return handle(command)
.pipe(
map(result => {
return result.data[mutationName];
}),
catchError((error: any) => {
// should handle bad request with exception
// should handle bad request with validation
// should handle forbidden result 403
// should handle not authorized result 401
const apolloError : ApolloError = error;
if (!apolloError.networkError) {
const validationError = apolloError.graphQLErrors.find(t => t.extensions.code == 'ValidationError');
if (validationError) {
const extensions = validationError.extensions;
const result = Object.keys(extensions).filter(t => t != 'code').reduce((prev, attributeName) => {
prev[attributeName] = extensions[attributeName];
return prev;
}, {});
return throwError(<IDataSourceValidationError>{
type: 'validation',
errors: result
});
}
}
return throwError(<IDataSourceErrorMessage>{
type: 'message',
message: apolloError.message
});
})
);
};
this._commands[name] = <IDataSourceCommandAdapterOptions<TModel>> {
adapter: {
handle: handleWrapper
},
resolveCommandModel: resolveCommandModel
}; };
return this; return this;

View File

@ -8,4 +8,8 @@
<button (click)="testGraphQL()"> <button (click)="testGraphQL()">
Test GraphQL query Test GraphQL query
</button>
<button (click)="testGraphQLMutation()">
Test GraphQL mutation
</button> </button>

View File

@ -1,8 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { GenericRestDataSourceService } from 'projects/poweredsoft/ngx-data/src/public-api'; import { GenericRestDataSourceService } from 'projects/poweredsoft/ngx-data/src/public-api';
import { of } from 'rxjs'; import { of, Observable } from 'rxjs';
import { DataSource } from '@poweredsoft/data'; import { DataSource, IResolveCommandModelEvent } from '@poweredsoft/data';
import { GraphQLDataSourceService } from 'projects/poweredsoft/ngx-data-apollo/src/public-api'; import { GraphQLDataSourceService } from 'projects/poweredsoft/ngx-data-apollo/src/public-api';
import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';
import { map } from 'rxjs/operators';
import { DocumentNode } from 'graphql';
export interface IContact { export interface IContact {
@ -17,6 +21,11 @@ export interface ICustomerModel {
lastName: string; lastName: string;
} }
export interface IFooCommand {
amount: number;
comment: string;
}
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
@ -26,7 +35,7 @@ export class AppComponent implements OnInit {
title = 'ngx-data'; title = 'ngx-data';
dataSource: DataSource<ICustomerModel>; dataSource: DataSource<ICustomerModel>;
constructor(genericService: GenericRestDataSourceService, private graphQLService: GraphQLDataSourceService) { constructor(genericService: GenericRestDataSourceService, private apollo: Apollo, private graphQLService: GraphQLDataSourceService) {
const keyResolver = (model: ICustomerModel) => model.id; const keyResolver = (model: ICustomerModel) => model.id;
const transportOptions = genericService.createStandardRestTransportOptions('api/customer', keyResolver); const transportOptions = genericService.createStandardRestTransportOptions('api/customer', keyResolver);
@ -77,6 +86,70 @@ export class AppComponent implements OnInit {
}); });
} }
testGraphQLMutation() {
const builder = this.graphQLService.createDataSourceOptionsBuilder<IContact, number>(
'contacts',
'GraphQLAdvanceQueryOfContactModelInput',
'id firstName lastName',
(m) => m.id,
{
groups: [
{
path: 'sex'
}
],
aggregates: [
{
path: 'id',
type: 'Max'
}
]
}
);
builder.addMutation<IFooCommand, string>('create', 'foo', (command) => {
return this.apollo.mutate<string>({
mutation: gql`mutation executeFoo($command: FooCommandInput) {
foo(params: $command)
}`,
variables: {
command: command
}
});
},
(event) => {
console.log(event);
if (event.model.id)
return of({
firstName: 'hello world'
});
});
const dataSourceOptions = builder.create();
const dataSource = new DataSource<IContact>(dataSourceOptions);
let event: IResolveCommandModelEvent<IContact> = {
command: 'create',
model: {
id: 1,
firstName: 'hello',
lastName: 'world'
}
};
dataSource.resolveCommandModelByName(event)
.subscribe((result) => {
console.log('resolve result', result);
});
dataSource.executeCommandByName('create', {
amount: 0,
//comment: "hello"
}).subscribe((result) => {
console.log(result);
})
}
testGraphQL() { testGraphQL() {
const builder = this.graphQLService.createDataSourceOptionsBuilder<IContact, number>( const builder = this.graphQLService.createDataSourceOptionsBuilder<IContact, number>(
'contacts', 'contacts',

View File

@ -3,7 +3,7 @@ import {ApolloModule, APOLLO_OPTIONS} from 'apollo-angular';
import {HttpLinkModule, HttpLink} from 'apollo-angular-link-http'; import {HttpLinkModule, HttpLink} from 'apollo-angular-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory'; import {InMemoryCache} from 'apollo-cache-inmemory';
const uri = 'https://localhost:5001/graphql'; // <-- add the URL of the GraphQL server here const uri = 'http://localhost:5000/graphql'; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink) { export function createApollo(httpLink: HttpLink) {
return { return {
link: httpLink.create({uri}), link: httpLink.create({uri}),