From b2021f695e917dbeff16044bfe8c858e0fa9125b Mon Sep 17 00:00:00 2001 From: Samuel Imolorhe Date: Sat, 16 Dec 2023 22:55:09 +0100 Subject: [PATCH] updated to make multiple HTTP requests simultaneously --- .../modules/altair/effects/query.effect.ts | 38 +++++------- .../altair/services/gql/gql.service.spec.ts | 18 ++++-- .../altair/services/gql/gql.service.ts | 58 ++++++------------- 3 files changed, 44 insertions(+), 70 deletions(-) diff --git a/packages/altair-app/src/app/modules/altair/effects/query.effect.ts b/packages/altair-app/src/app/modules/altair/effects/query.effect.ts index 15a709b26b..f10baf7d64 100644 --- a/packages/altair-app/src/app/modules/altair/effects/query.effect.ts +++ b/packages/altair-app/src/app/modules/altair/effects/query.effect.ts @@ -1,27 +1,15 @@ -import { - of as observableOf, - EMPTY, - Observable, - iif, - Subscriber, - of, - from, - combineLatest, - zip, -} from 'rxjs'; +import { of as observableOf, EMPTY, of, from, combineLatest } from 'rxjs'; import { - tap, catchError, withLatestFrom, switchMap, map, takeUntil, - distinct, mergeMap, } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { Store, Action } from '@ngrx/store'; +import { Store } from '@ngrx/store'; import { Actions, ofType, createEffect } from '@ngrx/effects'; import { @@ -31,11 +19,9 @@ import { DonationService, ElectronAppService, EnvironmentService, - PreRequestService, SubscriptionProviderRegistryService, QueryService, } from '../services'; -import * as fromRoot from '../store'; import * as queryActions from '../store/query/query.action'; import * as variablesActions from '../store/variables/variables.action'; @@ -60,7 +46,6 @@ import { debug } from '../utils/logger'; import { generateCurl } from '../utils/curl'; import { OperationDefinitionNode } from 'graphql'; import { IDictionary, UnknownError } from '../interfaces/shared'; -import { SendRequestResponse } from '../services/gql/gql.service'; import { RootState } from 'altair-graphql-core/build/types/state/state.interfaces'; import { WEBSOCKET_PROVIDER_ID } from 'altair-graphql-core/build/subscriptions'; import { SubscriptionProvider } from 'altair-graphql-core/build/subscriptions/subscription-provider'; @@ -72,6 +57,7 @@ import { RequestType } from '../services/pre-request/helpers'; export class QueryEffects { // Sends the query request to the specified URL // with the specified headers and variables + // NOTE: Should use mergeMap instead of switchMap, because switchMap cancels the previous request sendQueryRequest$ = createEffect( () => { return this.actions$.pipe( @@ -90,7 +76,7 @@ export class QueryEffects { }; } ), - switchMap((response) => { + mergeMap((response) => { if (response.action.type === queryActions.CANCEL_QUERY_REQUEST) { this.store.dispatch( new layoutActions.StopLoadingAction(response.windowId) @@ -98,14 +84,14 @@ export class QueryEffects { return EMPTY; } - const query = (response.data?.query.query || '').trim(); + const query = (response.data?.query.query ?? '').trim(); if (!query) { return EMPTY; } return observableOf(response); }), - switchMap((response) => { + mergeMap((response) => { return combineLatest([ of(response), from( @@ -117,13 +103,13 @@ export class QueryEffects { }) ); }), - switchMap((returnedData) => { + mergeMap((returnedData) => { if (!returnedData) { return EMPTY; } return observableOf(returnedData).pipe( - switchMap((_returnedData) => { + mergeMap((_returnedData) => { const { response, transformedData } = _returnedData; if (!response.data) { @@ -263,7 +249,8 @@ export class QueryEffects { debug.log('Sending..'); return this.gqlService - .sendRequest(url, { + .sendRequest({ + url, query, variables, headers, @@ -555,7 +542,8 @@ export class QueryEffects { new docsAction.StartLoadingDocsAction(response.windowId) ); return this.gqlService - .getIntrospectionRequest(url, { + .getIntrospectionRequest({ + url, method: response.data.query.httpVerb, headers, withCredentials: @@ -939,7 +927,7 @@ export class QueryEffects { } const subscriptionProviderId = - response.data.query.subscriptionProviderId || WEBSOCKET_PROVIDER_ID; + response.data.query.subscriptionProviderId ?? WEBSOCKET_PROVIDER_ID; const { getProviderClass } = this.subscriptionProviderRegistryService.getProviderData( subscriptionProviderId diff --git a/packages/altair-app/src/app/modules/altair/services/gql/gql.service.spec.ts b/packages/altair-app/src/app/modules/altair/services/gql/gql.service.spec.ts index 4d43ff4741..8df321c583 100644 --- a/packages/altair-app/src/app/modules/altair/services/gql/gql.service.spec.ts +++ b/packages/altair-app/src/app/modules/altair/services/gql/gql.service.spec.ts @@ -139,7 +139,8 @@ describe('GqlService', () => { it('should call HttpClient with expected parameters', inject( [GqlService], (service: GqlService) => { - service.sendRequest('http://test.com', { + service.sendRequest({ + url: 'http://test.com', method: 'post', query: '{}', }); @@ -162,7 +163,8 @@ describe('GqlService', () => { it('should call HttpClient with correct content type when file is included', inject( [GqlService], (service: GqlService) => { - service.sendRequest('http://test.com', { + service.sendRequest({ + url: 'http://test.com', method: 'post', query: '{}', files: [ @@ -197,7 +199,8 @@ describe('GqlService', () => { it('should call HttpClient with default json content type if file passed to it is not valid', inject( [GqlService], (service: GqlService) => { - service.sendRequest('http://test.com', { + service.sendRequest({ + url: 'http://test.com', method: 'post', query: '{}', files: [ @@ -254,7 +257,8 @@ describe('GqlService', () => { } }; const res = await service - .getIntrospectionRequest('http://test.com', { + .getIntrospectionRequest({ + url: 'http://test.com', method: 'GET', }) .pipe(take(1)) @@ -298,7 +302,8 @@ describe('GqlService', () => { } }; const res = await service - .getIntrospectionRequest('http://test.com', { + .getIntrospectionRequest({ + url: 'http://test.com', method: 'GET', }) .pipe(take(1)) @@ -342,7 +347,8 @@ describe('GqlService', () => { try { const res = await service - .getIntrospectionRequest('http://test.com', { + .getIntrospectionRequest({ + url: 'http://test.com', method: 'GET', }) .pipe(take(1)) diff --git a/packages/altair-app/src/app/modules/altair/services/gql/gql.service.ts b/packages/altair-app/src/app/modules/altair/services/gql/gql.service.ts index 6ec15009eb..866e164584 100644 --- a/packages/altair-app/src/app/modules/altair/services/gql/gql.service.ts +++ b/packages/altair-app/src/app/modules/altair/services/gql/gql.service.ts @@ -56,6 +56,7 @@ import { ElectronAppService } from '../electron-app/electron-app.service'; import { ELECTRON_ALLOWED_FORBIDDEN_HEADERS } from '@altairgraphql/electron-interop/build/constants'; interface SendRequestOptions { + url: string; query: string; method: string; withCredentials?: boolean; @@ -91,9 +92,6 @@ export class GqlService { headers = new HttpHeaders(); introspectionData = {}; - private api_url = localStorage.getItem('altair:url'); - private method = 'POST'; - constructor( private http: HttpClient, private notifyService: NotifyService, @@ -103,28 +101,17 @@ export class GqlService { this.setHeaders(); } - sendRequest( - url: string, - opts: SendRequestOptions - ): Observable { + sendRequest(opts: SendRequestOptions): Observable { // Only need resolvedFiles to know if valid files exist at this point const { resolvedFiles } = this.normalizeFiles(opts.files); - this.setUrl(url) - .setHTTPMethod(opts.method) - // Skip json default headers for files - .setHeaders(opts.headers, { - skipDefaults: this.isGETRequest(opts.method) || !!resolvedFiles.length, - }); + // Skip json default headers for files + this.setHeaders(opts.headers, { + skipDefaults: this.isGETRequest(opts.method) || !!resolvedFiles.length, + }); const requestStartTime = new Date().getTime(); - return this._send({ - query: opts.query, - variables: opts.variables, - selectedOperation: opts.selectedOperation, - files: opts.files, - withCredentials: opts.withCredentials, - }).pipe( + return this._send(opts).pipe( map((response) => { const requestEndTime = new Date().getTime(); const requestElapsedTime = requestEndTime - requestStartTime; @@ -147,7 +134,7 @@ export class GqlService { ); } - isGETRequest(method = this.method) { + private isGETRequest(method: string) { return method.toLowerCase() === 'get'; } @@ -190,18 +177,9 @@ export class GqlService { }, new HttpParams()); } - setUrl(url: string) { - this.api_url = url; - return this; - } - - setHTTPMethod(httpVerb: string) { - this.method = httpVerb; - return this; - } - - getIntrospectionRequest(url: string, opts: IntrospectionRequestOptions) { + getIntrospectionRequest(opts: IntrospectionRequestOptions) { const requestOpts: SendRequestOptions = { + url: opts.url, query: getIntrospectionQuery(), headers: opts.headers, method: opts.method, @@ -209,7 +187,7 @@ export class GqlService { variables: '{}', selectedOperation: 'IntrospectionQuery', }; - return this.sendRequest(url, requestOpts).pipe( + return this.sendRequest(requestOpts).pipe( map((data) => { debug.log('introspection', data.response); if (!data.response.ok) { @@ -223,7 +201,7 @@ export class GqlService { debug.log('Error from first introspection query.', err); // Try the old introspection query - return this.sendRequest(url, { + return this.sendRequest({ ...requestOpts, query: oldIntrospectionQuery, }).pipe( @@ -624,12 +602,14 @@ export class GqlService { * @param vars */ private _send({ + url, + method, query, variables, selectedOperation, files, withCredentials, - }: Omit) { + }: SendRequestOptions) { const data = { query, variables: {}, @@ -654,7 +634,7 @@ export class GqlService { } } - if (!this.isGETRequest()) { + if (!this.isGETRequest(method)) { const { resolvedFiles } = this.normalizeFiles(files); if (resolvedFiles && resolvedFiles.length) { // https://github.com/jaydenseric/graphql-multipart-request-spec#multipart-form-field-structure @@ -678,13 +658,13 @@ export class GqlService { } else { params = this.getParamsFromData(data); } - if (!this.api_url) { + if (!url) { throw new Error('You need to have a URL for the request!'); } return this.http - .request(this.method, this.api_url, { + .request(method, url, { // GET method uses params, while the other methods use body - ...(!this.isGETRequest() && { body }), + ...(!this.isGETRequest(method) && { body }), params, headers, observe: 'response',