From 7338f7daffe876c8688a860121748790f20e76e6 Mon Sep 17 00:00:00 2001 From: Jammy Louie Date: Wed, 10 Apr 2019 13:57:26 -0400 Subject: [PATCH] refactor(cf): use Observable to set state to prevent memory leaks (#6832) --- .../configure/loadBalancerDetails.tsx | 45 ++++++++++++------- ...loudfoundryCreateServiceKeyStageConfig.tsx | 30 ++++++++----- .../CloudfoundryDeployServiceStageConfig.tsx | 35 +++++++-------- .../CreateServiceInstanceDirectInput.tsx | 16 ++++--- .../CloudfoundryDestroyAsgStageConfig.tsx | 28 ++++++++---- .../CloudfoundryDestroyServiceStageConfig.tsx | 33 +++++++++----- .../CloudfoundryDisableAsgStageConfig.tsx | 29 ++++++++---- .../CloudfoundryEnableAsgStageConfig.tsx | 29 ++++++++---- ...loudfoundryMapLoadBalancersStageConfig.tsx | 17 ++++--- .../CloudfoundryResizeAsgStageConfig.tsx | 29 ++++++++---- ...CloudfoundryRollbackClusterStageConfig.tsx | 19 +++++--- .../CloudfoundryShareServiceStageConfig.tsx | 38 ++++++++++------ ...udfoundryUnmapLoadBalancersStageConfig.tsx | 17 ++++--- .../CloudfoundryUnshareServiceStageConfig.tsx | 30 ++++++++----- .../AccountRegionClusterSelector.tsx | 32 ++++++++----- .../FormikAccountRegionClusterSelector.tsx | 32 ++++++++----- .../basicSettings/BasicSettings.cf.tsx | 23 +++++++--- .../cloneSettings/CloneSettings.cf.tsx | 13 ++++-- 18 files changed, 329 insertions(+), 166 deletions(-) diff --git a/app/scripts/modules/cloudfoundry/src/loadBalancer/configure/loadBalancerDetails.tsx b/app/scripts/modules/cloudfoundry/src/loadBalancer/configure/loadBalancerDetails.tsx index ab8d7c6cf50..8e1155e1066 100644 --- a/app/scripts/modules/cloudfoundry/src/loadBalancer/configure/loadBalancerDetails.tsx +++ b/app/scripts/modules/cloudfoundry/src/loadBalancer/configure/loadBalancerDetails.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import Select, { Option } from 'react-select'; import { FormikErrors, FormikProps } from 'formik'; @@ -25,6 +27,7 @@ export interface ILoadBalancerDetailsState { export class LoadBalancerDetails extends React.Component implements IWizardPageComponent { + private destroy$ = new Subject(); public state: ILoadBalancerDetailsState = { accounts: undefined, availabilityZones: [], @@ -60,11 +63,17 @@ export class LoadBalancerDetails extends React.Component { - this.setState({ accounts }); - this.loadDomainsAndRegions(); - }); + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + this.loadDomainsAndRegions(); + }); } private accountUpdated = (option: Option): void => { @@ -76,12 +85,12 @@ export class LoadBalancerDetails extends React.Component { - this.setState({ domains: accountDetails.domains }); - }); - AccountService.getRegionsForAccount(account).then(regions => { - this.setState({ regions }); - }); + Observable.fromPromise(AccountService.getAccountDetails(account)) + .takeUntil(this.destroy$) + .subscribe((accountDetails: ICloudFoundryAccount) => this.setState({ domains: accountDetails.domains })); + Observable.fromPromise(AccountService.getRegionsForAccount(account)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } } @@ -90,14 +99,16 @@ export class LoadBalancerDetails extends React.Component { - const { domains } = accountDetails; - this.setState({ - domains: domains.filter( - domain => domain.organization === undefined || region.match('^' + domain.organization.name), - ), + Observable.fromPromise(AccountService.getAccountDetails(credentials)) + .takeUntil(this.destroy$) + .subscribe((accountDetails: ICloudFoundryAccount) => { + const { domains } = accountDetails; + this.setState({ + domains: domains.filter( + domain => domain.organization === undefined || region.match('^' + domain.organization.name), + ), + }); }); - }); } }; diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/createServiceKey/CloudfoundryCreateServiceKeyStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/createServiceKey/CloudfoundryCreateServiceKeyStageConfig.tsx index 9b91d030c50..2089c8e7f02 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/createServiceKey/CloudfoundryCreateServiceKeyStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/createServiceKey/CloudfoundryCreateServiceKeyStageConfig.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { Option } from 'react-select'; import { @@ -21,6 +23,8 @@ export class CloudfoundryCreateServiceKeyStageConfig extends React.Component< IStageConfigProps, ICloudfoundryCreateServiceKeyStageConfigState > { + private destroy$ = new Subject(); + constructor(props: IStageConfigProps) { super(props); props.stage.cloudProvider = 'cloudfoundry'; @@ -30,22 +34,28 @@ export class CloudfoundryCreateServiceKeyStageConfig extends React.Component< }; } - public componentDidMount = () => { - AccountService.listAccounts('cloudfoundry').then((rawAccounts: IAccount[]) => { - this.setState({ accounts: rawAccounts.map(it => it.name) }); - }); + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe((rawAccounts: IAccount[]) => this.setState({ accounts: rawAccounts.map(it => it.name) })); if (this.props.stage.credentials) { this.clearAndReloadRegions(); } - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private clearAndReloadRegions = () => { this.setState({ regions: [] }); - AccountService.getRegionsForAccount(this.props.stage.credentials).then((regionList: IRegion[]) => { - const regions = regionList.map(r => r.name); - regions.sort((a, b) => a.localeCompare(b)); - this.setState({ regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(this.props.stage.credentials)) + .takeUntil(this.destroy$) + .subscribe((regionList: IRegion[]) => { + const regions = regionList.map(r => r.name); + regions.sort((a, b) => a.localeCompare(b)); + this.setState({ regions }); + }); }; private serviceInstanceNameUpdated = (event: React.ChangeEvent) => { diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CloudfoundryDeployServiceStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CloudfoundryDeployServiceStageConfig.tsx index fcc01bd57d9..4d6e3220190 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CloudfoundryDeployServiceStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CloudfoundryDeployServiceStageConfig.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; + import { Option } from 'react-select'; +import { Observable, Subject } from 'rxjs'; + import { AccountService, IAccount, @@ -38,10 +41,7 @@ export class CloudfoundryDeployServiceStageConfig extends React.Component< updatable: true, }, }; - - // Hack necessary probably because of stage.module.js manually unmounting the component. - // There is no apparent way to cancel IPromises generated by AccountService. - private mounted = true; + private destroy$ = new Subject(); constructor(props: IStageConfigProps) { super(props); @@ -62,24 +62,23 @@ export class CloudfoundryDeployServiceStageConfig extends React.Component< this.reloadRegions(); }; - public componentWillUnmount(): void { - this.mounted = false; + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); + this.reloadRegions(); } - public componentDidMount = (): void => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - if (this.mounted) { - this.setState({ accounts: accounts }); - } - }); - this.reloadRegions(); - }; + public componentWillUnmount(): void { + this.destroy$.next(); + } private reloadRegions = () => { - if (this.props.stage.credentials) { - if (this.mounted) { - AccountService.getRegionsForAccount(this.props.stage.credentials).then(regions => this.setState({ regions })); - } + const { credentials } = this.props.stage; + if (credentials) { + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } }; diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CreateServiceInstanceDirectInput.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CreateServiceInstanceDirectInput.tsx index 5675b486d78..fe0b0c903ee 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CreateServiceInstanceDirectInput.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/deployService/CreateServiceInstanceDirectInput.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Option } from 'react-select'; +import { Observable, Subject } from 'rxjs'; import { IService, @@ -30,6 +31,7 @@ export class CreateServiceInstanceDirectInput extends React.Component< ICreateServiceInstanceDirectInputProps, ICreateServiceInstanceDirectInputState > { + private destroy$ = new Subject(); constructor(props: ICreateServiceInstanceDirectInputProps) { super(props); this.state = { serviceNamesAndPlans: [] }; @@ -42,15 +44,19 @@ export class CreateServiceInstanceDirectInput extends React.Component< } } - public componentDidMount = () => { + public componentDidMount(): void { this.loadServices(this.props.credentials, this.props.region); - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private loadServices(credentials: string, region: string) { if (credentials && region) { - ServicesReader.getServices(credentials, region).then(serviceNamesAndPlans => { - this.setState({ serviceNamesAndPlans }); - }); + Observable.fromPromise(ServicesReader.getServices(credentials, region)) + .takeUntil(this.destroy$) + .subscribe(serviceNamesAndPlans => this.setState({ serviceNamesAndPlans })); } } diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyAsg/CloudfoundryDestroyAsgStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyAsg/CloudfoundryDestroyAsgStageConfig.tsx index 6d51307ee05..3a08d33406f 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyAsg/CloudfoundryDestroyAsgStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyAsg/CloudfoundryDestroyAsgStageConfig.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; + +import { Observable, Subject } from 'rxjs'; + import { AccountService, Application, @@ -33,6 +36,7 @@ export class CloudfoundryDestroyAsgStageConfig extends React.Component< ICloudfoundryDestroyAsgStageProps, ICloudfoundryDestroyAsgStageConfigState > { + private destroy$ = new Subject(); constructor(props: ICloudfoundryDestroyAsgStageProps) { super(props); props.stage.cloudProvider = 'cloudfoundry'; @@ -49,20 +53,26 @@ export class CloudfoundryDestroyAsgStageConfig extends React.Component< }; } - public componentDidMount = (): void => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts }); - this.accountUpdated(); - }); + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + this.accountUpdated(); + }); this.props.stageFieldUpdated(); - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private accountUpdated = (): void => { const { credentials } = this.props.stage; if (credentials) { - AccountService.getRegionsForAccount(credentials).then(regions => { - this.setState({ regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } }; diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyService/CloudfoundryDestroyServiceStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyService/CloudfoundryDestroyServiceStageConfig.tsx index 7872c806b54..b3bb29d1a1a 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyService/CloudfoundryDestroyServiceStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/destroyService/CloudfoundryDestroyServiceStageConfig.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import Select, { Option } from 'react-select'; +import { Observable, Subject } from 'rxjs'; import { AccountService, IAccount, IRegion, IStageConfigProps, StageConfigField } from '@spinnaker/core'; @@ -13,6 +14,7 @@ export class CloudfoundryDestroyServiceStageConfig extends React.Component< IStageConfigProps, ICloudfoundryDestroyServiceStageConfigState > { + private destroy$ = new Subject(); constructor(props: IStageConfigProps) { super(props); this.props.updateStageField({ cloudProvider: 'cloudfoundry' }); @@ -22,20 +24,29 @@ export class CloudfoundryDestroyServiceStageConfig extends React.Component< }; } - public componentDidMount = () => { - AccountService.listAccounts('cloudfoundry').then((accounts: IAccount[]) => { - this.setState({ accounts }); - if (this.props.stage.credentials) { - this.clearAndReloadRegions(); - } - }); - }; + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + if (this.props.stage.credentials) { + this.clearAndReloadRegions(); + } + }); + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private clearAndReloadRegions = () => { this.setState({ regions: [] }); - AccountService.getRegionsForAccount(this.props.stage.credentials).then((regions: IRegion[]) => - this.setState({ regions }), - ); + const { credentials } = this.props.stage; + if (credentials) { + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); + } }; private accountUpdated = (option: Option) => { diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/disableAsg/CloudfoundryDisableAsgStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/disableAsg/CloudfoundryDisableAsgStageConfig.tsx index 4a68679a4e9..9169742e589 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/disableAsg/CloudfoundryDisableAsgStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/disableAsg/CloudfoundryDisableAsgStageConfig.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; + +import { Observable, Subject } from 'rxjs'; + import { AccountService, Application, @@ -32,6 +35,8 @@ export class CloudfoundryDisableAsgStageConfig extends React.Component< ICloudfoundryDisableAsgStageProps, ICloudfoundryDisableAsgStageConfigState > { + private destroy$ = new Subject(); + constructor(props: ICloudfoundryDisableAsgStageProps) { super(props); props.stage.cloudProvider = 'cloudfoundry'; @@ -47,20 +52,26 @@ export class CloudfoundryDisableAsgStageConfig extends React.Component< }; } - public componentDidMount = (): void => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts: accounts }); - this.accountUpdated(); - }); + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + this.accountUpdated(); + }); this.props.stageFieldUpdated(); - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private accountUpdated = (): void => { const { credentials } = this.props.stage; if (credentials) { - AccountService.getRegionsForAccount(credentials).then(regions => { - this.setState({ regions: regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } }; diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/enableAsg/CloudfoundryEnableAsgStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/enableAsg/CloudfoundryEnableAsgStageConfig.tsx index 5feb45cda61..8b5413063c1 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/enableAsg/CloudfoundryEnableAsgStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/enableAsg/CloudfoundryEnableAsgStageConfig.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; + +import { Observable, Subject } from 'rxjs'; + import { AccountService, Application, @@ -32,6 +35,8 @@ export class CloudfoundryEnableAsgStageConfig extends React.Component< ICloudfoundryEnableAsgStageProps, ICloudfoundryEnableAsgStageConfigState > { + private destroy$ = new Subject(); + constructor(props: ICloudfoundryEnableAsgStageProps) { super(props); props.stage.cloudProvider = 'cloudfoundry'; @@ -47,20 +52,26 @@ export class CloudfoundryEnableAsgStageConfig extends React.Component< }; } - public componentDidMount = (): void => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts: accounts }); - this.accountUpdated(); - }); + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + this.accountUpdated(); + }); this.props.stageFieldUpdated(); - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private accountUpdated = (): void => { const { credentials } = this.props.stage; if (credentials) { - AccountService.getRegionsForAccount(credentials).then(regions => { - this.setState({ regions: regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } }; diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/mapLoadBalancers/CloudfoundryMapLoadBalancersStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/mapLoadBalancers/CloudfoundryMapLoadBalancersStageConfig.tsx index fe6c191b669..e55ebdd0cfa 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/mapLoadBalancers/CloudfoundryMapLoadBalancersStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/mapLoadBalancers/CloudfoundryMapLoadBalancersStageConfig.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { AccountService, Application, @@ -34,6 +36,7 @@ export class CloudfoundryMapLoadBalancersStageConfig extends React.Component< ICloudfoundryMapLoadBalancersStageConfigState > { private formikRef = React.createRef>(); + private destroy$ = new Subject(); constructor(props: ICloudfoundryLoadBalancerStageConfigProps) { super(props); @@ -53,11 +56,15 @@ export class CloudfoundryMapLoadBalancersStageConfig extends React.Component< }; } - public componentDidMount = () => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts }); - }); - }; + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private targetUpdated = (target: string) => { this.props.updateStageField({ target }); diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/resizeAsg/CloudfoundryResizeAsgStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/resizeAsg/CloudfoundryResizeAsgStageConfig.tsx index b4930afc12b..6089f77e103 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/resizeAsg/CloudfoundryResizeAsgStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/resizeAsg/CloudfoundryResizeAsgStageConfig.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; + +import { Observable, Subject } from 'rxjs'; + import { AccountService, Application, @@ -42,6 +45,8 @@ export class CloudfoundryResizeAsgStageConfig extends React.Component< ICloudfoundryResizeAsgStageProps, ICloudfoundryResizeAsgStageConfigState > { + private destroy$ = new Subject(); + constructor(props: ICloudfoundryResizeAsgStageProps) { super(props); let interestingHealthProviderNames; @@ -80,20 +85,26 @@ export class CloudfoundryResizeAsgStageConfig extends React.Component< Object.assign(this.state, initStage); } - public componentDidMount = (): void => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts: accounts }); - this.accountUpdated(); - }); + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + this.accountUpdated(); + }); this.props.stageFieldUpdated(); - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private accountUpdated = (): void => { const { credentials } = this.props.stage; if (credentials) { - AccountService.getRegionsForAccount(credentials).then(regions => { - this.setState({ regions: regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } }; diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/rollbackCluster/CloudfoundryRollbackClusterStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/rollbackCluster/CloudfoundryRollbackClusterStageConfig.tsx index ec8fa63894b..57f91c4b5ab 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/rollbackCluster/CloudfoundryRollbackClusterStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/rollbackCluster/CloudfoundryRollbackClusterStageConfig.tsx @@ -1,4 +1,7 @@ import * as React from 'react'; + +import { Observable, Subject } from 'rxjs'; + import { AccountService, IAccount, IPipeline, IStageConfigProps, StageConfigField } from '@spinnaker/core'; import { AccountRegionClusterSelector } from 'cloudfoundry/presentation'; @@ -15,6 +18,8 @@ export class CloudfoundryRollbackClusterStageConfig extends React.Component< ICloudfoundryRollbackClusterStageProps, ICloudfoundryRollbackClusterStageConfigState > { + private destroy$ = new Subject(); + constructor(props: ICloudfoundryRollbackClusterStageProps) { super(props); @@ -27,11 +32,15 @@ export class CloudfoundryRollbackClusterStageConfig extends React.Component< this.state = { accounts: [] }; } - public componentDidMount = (): void => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts }); - }); - }; + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private waitTimeBetweenRegionsUpdated = (event: React.ChangeEvent): void => { const time = parseInt(event.target.value || '0', 10); diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/shareService/CloudfoundryShareServiceStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/shareService/CloudfoundryShareServiceStageConfig.tsx index f4fd2bf4384..c16fa0fa1d5 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/shareService/CloudfoundryShareServiceStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/shareService/CloudfoundryShareServiceStageConfig.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import Select, { Option } from 'react-select'; import { @@ -22,6 +24,8 @@ export class CloudfoundryShareServiceStageConfig extends React.Component< IStageConfigProps, ICloudfoundryShareServiceStageConfigState > { + private destroy$ = new Subject(); + constructor(props: IStageConfigProps) { super(props); props.stage.cloudProvider = 'cloudfoundry'; @@ -32,26 +36,32 @@ export class CloudfoundryShareServiceStageConfig extends React.Component< }; } - public componentDidMount = () => { - AccountService.listAccounts('cloudfoundry').then((accounts: IAccount[]) => { - this.setState({ accounts }); - }); + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); if (this.props.stage.credentials) { this.clearAndReloadRegions(); } - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private clearAndReloadRegions = () => { this.setState({ regions: [] }); - AccountService.getRegionsForAccount(this.props.stage.credentials).then((regionList: IRegion[]) => { - const { region } = this.props.stage; - const regions = regionList.map(r => r.name); - regions.sort((a, b) => a.localeCompare(b)); - this.setState({ regions }); - if (region) { - this.clearAndResetShareToRegionList(region, regions); - } - }); + Observable.fromPromise(AccountService.getRegionsForAccount(this.props.stage.credentials)) + .takeUntil(this.destroy$) + .subscribe((regionList: IRegion[]) => { + const { region } = this.props.stage; + const regions = regionList.map(r => r.name); + regions.sort((a, b) => a.localeCompare(b)); + this.setState({ regions }); + if (region) { + this.clearAndResetShareToRegionList(region, regions); + } + }); }; private serviceInstanceNameUpdated = (event: React.ChangeEvent) => { diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/unmapLoadBalancers/CloudfoundryUnmapLoadBalancersStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/unmapLoadBalancers/CloudfoundryUnmapLoadBalancersStageConfig.tsx index ade930d9d59..60cb40e266e 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/unmapLoadBalancers/CloudfoundryUnmapLoadBalancersStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/unmapLoadBalancers/CloudfoundryUnmapLoadBalancersStageConfig.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { AccountService, Application, @@ -33,6 +35,7 @@ export class CloudfoundryUnmapLoadBalancersStageConfig extends React.Component< ICloudfoundryLoadBalancerStageConfigProps, ICloudfoundryUnmapLoadBalancersStageConfigState > { + private destroy$ = new Subject(); private formikRef = React.createRef>(); constructor(props: ICloudfoundryLoadBalancerStageConfigProps) { @@ -53,11 +56,15 @@ export class CloudfoundryUnmapLoadBalancersStageConfig extends React.Component< }; } - public componentDidMount = () => { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts }); - }); - }; + public componentDidMount(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private targetUpdated = (target: string) => { this.props.updateStageField({ target }); diff --git a/app/scripts/modules/cloudfoundry/src/pipeline/stages/unshareService/CloudfoundryUnshareServiceStageConfig.tsx b/app/scripts/modules/cloudfoundry/src/pipeline/stages/unshareService/CloudfoundryUnshareServiceStageConfig.tsx index b83ad12b862..e957139bdd2 100644 --- a/app/scripts/modules/cloudfoundry/src/pipeline/stages/unshareService/CloudfoundryUnshareServiceStageConfig.tsx +++ b/app/scripts/modules/cloudfoundry/src/pipeline/stages/unshareService/CloudfoundryUnshareServiceStageConfig.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import Select, { Option } from 'react-select'; import { @@ -21,6 +23,8 @@ export class CloudfoundryUnshareServiceStageConfig extends React.Component< IStageConfigProps, ICloudfoundryShareServiceStageConfigState > { + private destroy$ = new Subject(); + constructor(props: IStageConfigProps) { super(props); props.stage.cloudProvider = 'cloudfoundry'; @@ -30,22 +34,28 @@ export class CloudfoundryUnshareServiceStageConfig extends React.Component< }; } - public componentDidMount = () => { - AccountService.listAccounts('cloudfoundry').then((accounts: IAccount[]) => { - this.setState({ accounts }); - }); + public componentDidMoun(): void { + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); if (this.props.stage.credentials) { this.clearAndReloadRegions(); } - }; + } + + public componentWillUnmount(): void { + this.destroy$.next(); + } private clearAndReloadRegions = () => { this.setState({ regions: [] }); - AccountService.getRegionsForAccount(this.props.stage.credentials).then((regionList: IRegion[]) => { - const regions = regionList.map(r => r.name); - regions.sort((a, b) => a.localeCompare(b)); - this.setState({ regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(this.props.stage.credentials)) + .takeUntil(this.destroy$) + .subscribe((regionList: IRegion[]) => { + const regions = regionList.map(r => r.name); + regions.sort((a, b) => a.localeCompare(b)); + this.setState({ regions }); + }); }; private serviceInstanceNameUpdated = (event: React.ChangeEvent) => { diff --git a/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/AccountRegionClusterSelector.tsx b/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/AccountRegionClusterSelector.tsx index 1e76f61612e..ef2ddb44249 100644 --- a/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/AccountRegionClusterSelector.tsx +++ b/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/AccountRegionClusterSelector.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { first, isNil, uniq } from 'lodash'; import Select, { Option } from 'react-select'; @@ -38,6 +40,8 @@ export class AccountRegionClusterSelector extends React.Component< IAccountRegionClusterSelectorProps, IAccountRegionClusterSelectorState > { + private destroy$ = new Subject(); + constructor(props: IAccountRegionClusterSelectorProps) { super(props); const clusterField = props.clusterField || 'cluster'; @@ -59,24 +63,32 @@ export class AccountRegionClusterSelector extends React.Component< ); } + public componentWillUnmount(): void { + this.destroy$.next(); + } + private setRegionList = (credentials: string): void => { const { application } = this.props; const accountFilter: IServerGroupFilter = (serverGroup: IServerGroup) => serverGroup ? serverGroup.account === credentials : true; - application.ready().then(() => { - const availableRegions = AppListExtractor.getRegions([application], accountFilter); - availableRegions.sort(); - this.setState({ availableRegions }); - }); + Observable.fromPromise(application.ready()) + .takeUntil(this.destroy$) + .subscribe(() => { + const availableRegions = AppListExtractor.getRegions([application], accountFilter); + availableRegions.sort(); + this.setState({ availableRegions }); + }); }; private setClusterList = (credentials: string, regions: string[]): void => { const { application } = this.props; - application.ready().then(() => { - const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(credentials, regions); - const clusters = AppListExtractor.getClusters([application], clusterFilter); - this.setState({ clusters }); - }); + Observable.fromPromise(application.ready()) + .takeUntil(this.destroy$) + .subscribe(() => { + const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(credentials, regions); + const clusters = AppListExtractor.getClusters([application], clusterFilter); + this.setState({ clusters }); + }); }; public onAccountUpdate = (option: Option): void => { diff --git a/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/FormikAccountRegionClusterSelector.tsx b/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/FormikAccountRegionClusterSelector.tsx index 56ce4ba8286..db4f9e601f3 100644 --- a/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/FormikAccountRegionClusterSelector.tsx +++ b/app/scripts/modules/cloudfoundry/src/presentation/widgets/accountRegionClusterSelector/FormikAccountRegionClusterSelector.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { get } from 'lodash'; import { @@ -35,6 +37,8 @@ export class FormikAccountRegionClusterSelector extends React.Component< IFormikAccountRegionClusterSelectorProps, IFormikAccountRegionClusterSelectorState > { + private destroy$ = new Subject(); + constructor(props: IFormikAccountRegionClusterSelectorProps) { super(props); const clusterField = props.clusterField || 'cluster'; @@ -55,24 +59,32 @@ export class FormikAccountRegionClusterSelector extends React.Component< this.setClusterList(credentials, [region]); } + public componentWillUnmount(): void { + this.destroy$.next(); + } + private setRegionList = (credentials: string): void => { const { application } = this.props; const accountFilter: IServerGroupFilter = (serverGroup: IServerGroup) => serverGroup ? serverGroup.account === credentials : true; - application.ready().then(() => { - const availableRegions = AppListExtractor.getRegions([application], accountFilter); - availableRegions.sort(); - this.setState({ availableRegions }); - }); + Observable.fromPromise(application.ready()) + .takeUntil(this.destroy$) + .subscribe(() => { + const availableRegions = AppListExtractor.getRegions([application], accountFilter); + availableRegions.sort(); + this.setState({ availableRegions }); + }); }; private setClusterList = (credentials: string, regions: string[]): void => { const { application } = this.props; - application.ready().then(() => { - const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(credentials, regions); - const clusters = AppListExtractor.getClusters([application], clusterFilter); - this.setState({ clusters }); - }); + Observable.fromPromise(application.ready()) + .takeUntil(this.destroy$) + .subscribe(() => { + const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(credentials, regions); + const clusters = AppListExtractor.getClusters([application], clusterFilter); + this.setState({ clusters }); + }); }; public accountChanged = (credentials: string): void => { diff --git a/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/basicSettings/BasicSettings.cf.tsx b/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/basicSettings/BasicSettings.cf.tsx index d46de4b1e8d..0e73e4bf70d 100644 --- a/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/basicSettings/BasicSettings.cf.tsx +++ b/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/basicSettings/BasicSettings.cf.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { get } from 'lodash'; import { FormikErrors, FormikProps } from 'formik'; @@ -34,16 +36,23 @@ export interface ICloudFoundryServerGroupLocationSettingsState { export class CloudFoundryServerGroupBasicSettings extends React.Component implements IWizardPageComponent { + private destroy$ = new Subject(); public state: ICloudFoundryServerGroupLocationSettingsState = { accounts: [], regions: [], }; public componentDidMount(): void { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts }); - this.updateRegionList(); - }); + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => { + this.setState({ accounts }); + this.updateRegionList(); + }); + } + + public componentWillUnmount(): void { + this.destroy$.next(); } private accountChanged = (): void => { @@ -56,9 +65,9 @@ export class CloudFoundryServerGroupBasicSettings const accountField = this.props.isClone ? 'account' : 'credentials'; const credentials = get(this.props.formik.values, accountField, undefined); if (credentials) { - AccountService.getRegionsForAccount(credentials).then(regions => { - this.setState({ regions: regions }); - }); + Observable.fromPromise(AccountService.getRegionsForAccount(credentials)) + .takeUntil(this.destroy$) + .subscribe(regions => this.setState({ regions })); } }; diff --git a/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/cloneSettings/CloneSettings.cf.tsx b/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/cloneSettings/CloneSettings.cf.tsx index 1e46d9f4595..983a4904b9f 100644 --- a/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/cloneSettings/CloneSettings.cf.tsx +++ b/app/scripts/modules/cloudfoundry/src/serverGroup/configure/wizard/sections/cloneSettings/CloneSettings.cf.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { Observable, Subject } from 'rxjs'; + import { FormikErrors, FormikProps } from 'formik'; import { @@ -31,14 +33,19 @@ export interface ICloudFoundryCloneSettingsState { export class CloudFoundryServerGroupCloneSettings extends React.Component implements IWizardPageComponent { + private destroy$ = new Subject(); public state: ICloudFoundryCloneSettingsState = { accounts: [], }; public componentDidMount(): void { - AccountService.listAccounts('cloudfoundry').then(accounts => { - this.setState({ accounts }); - }); + Observable.fromPromise(AccountService.listAccounts('cloudfoundry')) + .takeUntil(this.destroy$) + .subscribe(accounts => this.setState({ accounts })); + } + + public componentWillUnmount(): void { + this.destroy$.next(); } private strategyOptionRenderer = (option: IDeploymentStrategy) => {