From ba99c42bd724fd82ad6459a1f7a0aaee3206013a Mon Sep 17 00:00:00 2001 From: Stan Lewis Date: Wed, 9 Aug 2017 15:23:43 -0400 Subject: [PATCH] feat(connections): Engage sledgehammer Apply hacksaw to current connection service relates to #556 --- .../configure-fields.component.ts | 26 +-- .../connection-basics.component.ts | 15 +- .../create-page/create-page.component.ts | 51 +++++- .../create-page/current-connection.ts | 156 ++++++++++++------ .../create-page/review/review.component.ts | 21 ++- src/app/store/connector/connector.service.ts | 45 +++-- src/app/store/entity/entity.store.ts | 3 +- 7 files changed, 235 insertions(+), 82 deletions(-) diff --git a/src/app/connections/create-page/configure-fields/configure-fields.component.ts b/src/app/connections/create-page/configure-fields/configure-fields.component.ts index 3d7139b2f..b351c0930 100644 --- a/src/app/connections/create-page/configure-fields/configure-fields.component.ts +++ b/src/app/connections/create-page/configure-fields/configure-fields.component.ts @@ -1,7 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { RouterStateSnapshot } from '@angular/router'; -import { CurrentConnectionService } from '../current-connection'; +import { + CurrentConnectionService, + ConnectionEvent, +} from '../current-connection'; import { Connection } from '../../../model'; import { log } from '../../../logging'; import { CanComponentDeactivate } from '../../../common/can-deactivate-guard.service'; @@ -11,7 +14,8 @@ import { ModalService } from '../../../common/modal/modal.service'; selector: 'syndesis-connections-configure-fields', templateUrl: 'configure-fields.component.html', }) -export class ConnectionsConfigureFieldsComponent implements OnInit, CanComponentDeactivate { +export class ConnectionsConfigureFieldsComponent + implements OnInit, CanComponentDeactivate { constructor( private current: CurrentConnectionService, private modalService: ModalService, @@ -22,6 +26,9 @@ export class ConnectionsConfigureFieldsComponent implements OnInit, CanComponent } set connection(connection: Connection) { + if (!connection.connectorId) { + return; + } this.current.connection = connection; } @@ -33,15 +40,14 @@ export class ConnectionsConfigureFieldsComponent implements OnInit, CanComponent this.current.acquireCredentials(); } - ngOnInit() { - log.infoc(() => 'Credentials: ' + JSON.stringify(this.current.credentials)); - log.infoc(() => 'hasCredentials: ' + this.current.hasCredentials()); - } + ngOnInit() {} canDeactivate(nextState: RouterStateSnapshot) { - return nextState.url === '/connections/create/cancel' || - nextState.url === '/connections/create/connection-basics' || - nextState.url === '/connections/create/review' || - this.modalService.show(); + return ( + nextState.url === '/connections/create/cancel' || + nextState.url === '/connections/create/connection-basics' || + nextState.url === '/connections/create/review' || + this.modalService.show() + ); } } diff --git a/src/app/connections/create-page/connection-basics/connection-basics.component.ts b/src/app/connections/create-page/connection-basics/connection-basics.component.ts index 1fc5b8613..026a7e44d 100644 --- a/src/app/connections/create-page/connection-basics/connection-basics.component.ts +++ b/src/app/connections/create-page/connection-basics/connection-basics.component.ts @@ -1,5 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router, RouterStateSnapshot } from '@angular/router'; +import { Subscription } from 'rxjs/Subscription'; import { CurrentConnectionService, @@ -11,7 +12,8 @@ import { Connection } from '../../../model'; selector: 'syndesis-connections-connection-basics', templateUrl: 'connection-basics.component.html', }) -export class ConnectionsConnectionBasicsComponent implements OnInit { +export class ConnectionsConnectionBasicsComponent implements OnInit, OnDestroy { + private subscription: Subscription; constructor( private current: CurrentConnectionService, @@ -24,11 +26,14 @@ export class ConnectionsConnectionBasicsComponent implements OnInit { } set connection(connection: Connection) { + if (!connection.connectorId) { + return; + } this.current.connection = connection; } ngOnInit() { - const subscription = this.current.events.subscribe( + this.subscription = this.current.events.subscribe( (event: ConnectionEvent) => { switch (event.kind) { case 'connection-set-connection': @@ -37,9 +42,11 @@ export class ConnectionsConnectionBasicsComponent implements OnInit { }); return; } - subscription.unsubscribe(); }, ); } + ngOnDestroy() { + this.subscription.unsubscribe(); + } } diff --git a/src/app/connections/create-page/create-page.component.ts b/src/app/connections/create-page/create-page.component.ts index 5a500b637..e73ab9c19 100644 --- a/src/app/connections/create-page/create-page.component.ts +++ b/src/app/connections/create-page/create-page.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; import { NavigationService } from '../../common/navigation.service'; -import { CurrentConnectionService } from './current-connection'; +import { CurrentConnectionService, ConnectionEvent } from './current-connection'; import { Connection, TypeFactory } from '../../model'; import { log, getCategory } from '../../logging'; import { CanComponentDeactivate } from '../../common/can-deactivate-guard.service'; @@ -150,12 +150,51 @@ export class ConnectionsCreatePage implements OnInit, OnDestroy { } } - ngOnInit() { - this.current.connection = TypeFactory.createConnection(); - // we always want to start at the beginning of the wizard on a refresh - if (this.getCurrentPage() !== 'connection-basics') { - this.router.navigate(['connection-basics'], { relativeTo: this.route }); + handleEvent(event: ConnectionEvent) { + switch (event.kind) { + case 'connection-set-connection': + log.infoc( + () => 'Credentials: ' + JSON.stringify(this.current.credentials), + ); + log.infoc(() => 'hasCredentials: ' + this.current.hasCredentials()); + if ( + !this.current.connection.connector || + !this.current.connection.connectorId + ) { + this.router.navigate(['connection-basics'], { + relativeTo: this.route, + }); + return; + } + /* + TODO + if (document.cookie) { + const cookie = document.cookie; + if (cookie.indexOf('cred-') !== -1) { + this.router.navigate(['review'], { relativeTo: this.route }); + return; + } + } + */ + break; } + + } + + ngOnInit() { + this.current.events.subscribe(event => { + this.handleEvent(event); + }); + this.route.queryParams.subscribe((params) => { + if (this.current.connection) { + // if the connection has started to be configured we don't want this sub to reset it + return; + } + const connection = TypeFactory.createConnection(); + // detect if there's a selected connection ID already or not + connection.connectorId = params.connectorId; + this.current.connection = connection; + }); this.nav.hide(); this.routerEventsSubscription = this.router.events.subscribe(event => { this.detector.detectChanges(); diff --git a/src/app/connections/create-page/current-connection.ts b/src/app/connections/create-page/current-connection.ts index d66975d65..605ea18ca 100644 --- a/src/app/connections/create-page/current-connection.ts +++ b/src/app/connections/create-page/current-connection.ts @@ -32,8 +32,103 @@ export class CurrentConnectionService { ); } + + private checkCredentials() { + const connectorId = this._connection.connectorId; + if (!connectorId) { + return false; + } + if (!this._credentials || this._credentials.connectorId !== connectorId) { + // fetch any credentials for the connector + const sub = this.fetchCredentials().subscribe( + () => { + sub.unsubscribe(); + this.events.emit({ + kind: 'connection-set-connection', + connection: this._connection, + }); + }, + error => { + log.infoc( + () => + 'Failed to fetch connector credentials: ' + JSON.stringify(error), + category, + ); + sub.unsubscribe(); + this.events.emit({ + kind: 'connection-set-connection', + connection: this._connection, + }); + }, + ); + return true; + } else { + return false; + } + + } + + private fetchConnector(connectorId: string) { + if (connectorId && !this._connection.connector) { + const sub = this.connectorStore.load(connectorId).subscribe( + connector => { + if (!connector.id) { + return; + } + this._connection.connector = connector; + this._connection.icon = connector.icon; + this.events.emit({ + kind: 'connection-check-credentials', + connection: this._connection, + }); + sub.unsubscribe(); + }, + error => { + try { + log.infoc( + () => + 'Failed to fetch connector: ' + + JSON.stringify(error), + category, + ); + } catch (err) { + log.infoc( + () => 'Failed to fetch connector: ' + error, + category, + ); + } + this.events.emit({ + kind: 'connection-check-credentials', + error: error, + connection: this._connection, + }); + sub.unsubscribe(); + }, + ); + return true; + } + return false; + } + handleEvent(event: ConnectionEvent) { + log.infoc(() => 'connection event: ' + JSON.stringify(event), category); switch (event.kind) { + case 'connection-check-connector': + if (!this.fetchConnector(this._connection.connectorId)) { + this.events.emit({ + kind: 'connection-check-credentials', + connectorId: this._connection.connectorId, + }); + } + break; + case 'connection-check-credentials': + if (!this.checkCredentials()) { + this.events.emit({ + kind: 'connection-set-connection', + connection: this._connection, + }); + } + break; case 'connection-set-connection': break; // TODO not sure if these next 3 cases are needed really @@ -60,14 +155,12 @@ export class CurrentConnectionService { } const connectorId = this._connection.connectorId; return Observable.create(observer => { - this.connectorStore - .credentials(connectorId) - .subscribe((resp: any) => { - // enrich the response with the connectorId - this._credentials = { ...resp, ...{ connectorId: connectorId } }; - observer.next(this._credentials); - observer.complete(); - }); + this.connectorStore.credentials(connectorId).subscribe((resp: any) => { + // enrich the response with the connectorId + this._credentials = { ...resp, ...{ connectorId: connectorId } }; + observer.next(this._credentials); + observer.complete(); + }); }); } @@ -77,9 +170,11 @@ export class CurrentConnectionService { return Observable.empty(); } const connectorId = this._connection.connectorId; - this.connectorStore.acquireCredentials(connectorId).subscribe((resp: any) => { - log.infoc(() => 'Got response: ' + JSON.stringify(resp)); - }); + this.connectorStore + .acquireCredentials(connectorId) + .subscribe((resp: any) => { + log.infoc(() => 'Got response: ' + JSON.stringify(resp)); + }); } private saveConnection(event: ConnectionEvent) { @@ -129,7 +224,6 @@ export class CurrentConnectionService { return this._credentials && this._credentials.type !== undefined; } - get connection(): Connection { return this._connection; } @@ -137,39 +231,9 @@ export class CurrentConnectionService { set connection(connection: Connection) { this._connection = connection; const connectorId = connection.connectorId; - // only query for credentials if the stored ones don't match the passed in connector - if ( - !connection || - !connectorId || - (this._credentials && this._credentials.connectorId === connectorId) - ) { - this.events.emit({ - kind: 'connection-set-connection', - connection: this._connection, - }); - return; - } - // fetch any credentials for the connector - const sub = this.fetchCredentials().subscribe( - () => { - sub.unsubscribe(); - this.events.emit({ - kind: 'connection-set-connection', - connection: this._connection, - }); - }, - error => { - log.infoc( - () => - 'Failed to fetch connector credentials: ' + JSON.stringify(error), - category, - ); - sub.unsubscribe(); - this.events.emit({ - kind: 'connection-set-connection', - connection: this._connection, - }); - }, - ); + this.events.emit({ + kind: 'connection-check-connector', + connection: this._connection, + }); } } diff --git a/src/app/connections/create-page/review/review.component.ts b/src/app/connections/create-page/review/review.component.ts index 75f330231..d7a0e4850 100644 --- a/src/app/connections/create-page/review/review.component.ts +++ b/src/app/connections/create-page/review/review.component.ts @@ -1,5 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router, RouterStateSnapshot } from '@angular/router'; +import { Subscription } from 'rxjs/Subscription'; import { CurrentConnectionService, ConnectionEvent } from '../current-connection'; import { Connection } from '../../../model'; @@ -10,9 +11,11 @@ import { ModalService } from '../../../common/modal/modal.service'; selector: 'syndesis-connections-review', templateUrl: 'review.component.html', }) -export class ConnectionsReviewComponent implements OnInit, CanComponentDeactivate { +export class ConnectionsReviewComponent implements OnInit, OnDestroy, CanComponentDeactivate { + private subscription: Subscription; private saved = false; + private loaded = false; constructor( private current: CurrentConnectionService, @@ -24,20 +27,30 @@ export class ConnectionsReviewComponent implements OnInit, CanComponentDeactivat } set connection(connection: Connection) { + if (!connection.connectorId) { + return; + } this.current.connection = connection; } ngOnInit() { - const subscription = this.current.events.subscribe((event: ConnectionEvent) => { + this.subscription = this.current.events.subscribe((event: ConnectionEvent) => { + if (event.kind === 'connection-set-connection') { + this.loaded = true; + } if (event.kind === 'connection-save-connection') { this.saved = true; - subscription.unsubscribe(); } }); } + ngOnDestroy() { + this.subscription.unsubscribe(); + } + canDeactivate(nextState: RouterStateSnapshot) { return this.saved || + nextState.url === '/connections/create/connection-basics' || nextState.url === '/connections/create/cancel' || nextState.url === '/connections/create/configure-fields' || this.modalService.show(); diff --git a/src/app/store/connector/connector.service.ts b/src/app/store/connector/connector.service.ts index 68e64f366..eb5bf64e3 100644 --- a/src/app/store/connector/connector.service.ts +++ b/src/app/store/connector/connector.service.ts @@ -9,13 +9,13 @@ import { Connector, Connectors } from '../../model'; interface AcquisitionResponseState { persist: string; spec: string; -}; +} interface AcquisitionResponse { redirectUrl: string; type: string; state: AcquisitionResponseState; -}; +} @Injectable() export class ConnectorService extends RESTService { @@ -35,16 +35,39 @@ export class ConnectorService extends RESTService { // need to save the state of the app a bit since this // will navigate to a new page and then come back return Observable.create(observer => { - this.restangularService - .one(id) - .post('credentials', { - returnUrl: window.location.pathname + '?state=create-connection&connectorId=' + id, - }).subscribe((resp: AcquisitionResponse) => { - document.cookie = resp.state.spec; - window.location.href = resp.redirectUrl; - observer.next(resp); - observer.complete(); + // TODO we probably don't need all these nested setTimeouts, but... + setTimeout(() => { + // Try and clear any stale cookies, though we can't touch HttpOnly ones + document.cookie.split(';').forEach(function(c) { + if (c.startsWith('cred-')) { + const newCookie = c + .replace(/^ +/, '') + .replace( + /=.*/, + '=;expires=' + new Date().toUTCString() + ';path=/', + ); + } }); + + setTimeout(() => { + this.restangularService + .one(id) + .post('credentials', { + returnUrl: + window.location.pathname + + '?state=create-connection&connectorId=' + + id, + }) + .subscribe((resp: AcquisitionResponse) => { + document.cookie = resp.state.spec; + setTimeout(() => { + window.location.href = resp.redirectUrl; + observer.next(resp); + observer.complete(); + }, 30); + }); + }, 30); + }, 30); }); } } diff --git a/src/app/store/entity/entity.store.ts b/src/app/store/entity/entity.store.ts index 657bf5d16..9eb20aadc 100644 --- a/src/app/store/entity/entity.store.ts +++ b/src/app/store/entity/entity.store.ts @@ -116,7 +116,7 @@ export abstract class AbstractStore< this._loading.next(true); } - load(id: string, retries = 0) { + load(id: string, retries = 0): Observable { this._loading.next(true); this.service.get(id).subscribe( entity => { @@ -138,6 +138,7 @@ export abstract class AbstractStore< } }, ); + return this._current.asObservable(); } private massageError(error: any) {