import classNames from 'classnames'; import type { FormikErrors, FormikProps } from 'formik'; import React from 'react'; import type { Observable } from 'rxjs'; import { combineLatest as observableCombineLatest, from as observableFrom, Subject } from 'rxjs'; import { distinctUntilChanged, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators'; import type { Application, IAccount, IRegion, IWizardPageComponent } from '@spinnaker/core'; import { AccountService, CheckboxInput, FormikFormField, FormValidator, HelpField, ReactSelectInput, TextInput, } from '@spinnaker/core'; import { s3BucketNameValidator } from '../../aws.validators'; import type { IAmazonFunction } from '../../domain'; import type { IAmazonFunctionUpsertCommand } from '../../index'; import { availableRuntimes } from '../../pipeline/stages/deployLambda/components/function.constants'; export interface IFunctionProps { app: Application; formik: FormikProps; isNew?: boolean; functionDef: IAmazonFunction; } export interface IFunctionState { existingFunctionNames: string[]; accounts: IAccount[]; regions: IRegion[]; } export class FunctionBasicInformation extends React.Component implements IWizardPageComponent { public state: IFunctionState = { accounts: [], existingFunctionNames: [], regions: [], }; private props$ = new Subject(); private destroy$ = new Subject(); public validate(values: IAmazonFunctionUpsertCommand): FormikErrors { const validator = new FormValidator(values); validator.field('s3bucket', 'S3 Bucket Name').optional().withValidators(s3BucketNameValidator); const errors = validator.validateForm(); if ( this.props.isNew && this.state.existingFunctionNames.includes(this.props.app.name.concat('-').concat(values.functionName)) ) { errors.functionName = `There is already a function in ${values.credentials}:${values.region} with that name.`; } return errors; } public componentDidUpdate() { this.props$.next(this.props); } public componentWillUnmount(): void { this.destroy$.next(); } public componentDidMount(): void { const formValues$ = this.props$.pipe(map((props) => props.formik.values)); const form = { account$: formValues$.pipe( map((x) => x.credentials), distinctUntilChanged(), ), region$: formValues$.pipe( map((x) => x.region), distinctUntilChanged(), ), functionName$: formValues$.pipe( map((x) => x.functionName), distinctUntilChanged(), ), runtime$: formValues$.pipe( map((x) => x.runtime), distinctUntilChanged(), ), s3bucket$: formValues$.pipe( map((x) => x.s3bucket), distinctUntilChanged(), ), s3key$: formValues$.pipe( map((x) => x.s3key), distinctUntilChanged(), ), handler$: formValues$.pipe( map((x) => x.handler), distinctUntilChanged(), ), }; const allAccounts$ = observableFrom(AccountService.listAccounts('aws')).pipe(shareReplay(1)); // combineLatest with allAccounts to wait for accounts to load and be cached const accountRegions$ = observableCombineLatest([form.account$, allAccounts$]).pipe( switchMap(([currentAccount, _allAccounts]) => AccountService.getRegionsForAccount(currentAccount)), shareReplay(1), ); const allFunctions$ = this.props.app.getDataSource('functions').data$ as Observable; const regionfunctions$ = observableCombineLatest([allFunctions$, form.account$, form.region$]).pipe( map(([allFunctions, currentAccount, currentRegion]) => { return allFunctions .filter((fn) => fn.account === currentAccount && fn.region === currentRegion) .map((fn) => fn.functionName); }), shareReplay(1), ); accountRegions$ .pipe(withLatestFrom(form.region$), takeUntil(this.destroy$)) .subscribe(([accountRegions, selectedRegion]) => { // If the selected region doesn't exist in the new list of regions (for a new acct), select the first region. if (!accountRegions.some((x) => x.name === selectedRegion)) { this.props.formik.setFieldValue('region', accountRegions[0] && accountRegions[0].name); } }); observableCombineLatest([allAccounts$, accountRegions$, regionfunctions$]) .pipe(takeUntil(this.destroy$)) .subscribe(([accounts, regions, existingFunctionNames]) => { return this.setState({ accounts, regions, existingFunctionNames }); }); } public render() { const { isNew } = this.props; const { errors, values } = this.props.formik; const { accounts, regions } = this.state; const className = classNames({ well: true, 'alert-danger': !!errors.functionName, 'alert-info': !errors.functionName, }); return (
{isNew && (
Your function will be named: {this.props.app.name}-{values.functionName} null} />
)} ( acc.name)} clearable={true} /> )} /> ( reg.name)} clearable={true} /> )} /> } input={(props) => } /> } input={(props) => } /> } input={(props) => } /> } input={(props) => } /> } input={(props) => } /> } />
); } }