File

src/app/streams/stream-deploy/builder/builder.component.ts

Description

TODO

Implements

OnInit OnDestroy

Example

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector app-stream-deploy-builder
styleUrls styles.scss
templateUrl builder.component.html

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(streamDeployService: StreamDeployService, changeDetector: ChangeDetectorRef, notificationService: NotificationService, bsModalService: BsModalService)
Parameters :
Name Type Optional Description
streamDeployService StreamDeployService
changeDetector ChangeDetectorRef
notificationService NotificationService
bsModalService BsModalService

Inputs

id

Stream ID

Type: string

isDeployed

Is Deployed

Default value: false

properties

Properties to load

Type: Array<string>

Outputs

copyProperties

Emit for request copy

$event type: EventEmitter
deploy

Emit for request deploy

$event type: EventEmitter
exportProperties

Emit for request export

$event type: EventEmitter
update

Emits on destroy component with the current value

$event type: EventEmitter

Methods

Private build
build(streamDeployConfig: StreamDeployConfig)

Build the Group Form

Parameters :
Name Type Optional Description
streamDeployConfig StreamDeployConfig
copyToClipboard
copyToClipboard()

Copye to clipboard

Returns : void
deployStream
deployStream()

Emit a request deploy

Returns : void
exportProps
exportProps()

Emit a request export

Returns : void
getAppProperties
getAppProperties(builderAppsProperties: literal type, appId: string)

Load the properties of an app

Parameters :
Name Type Optional Description
builderAppsProperties literal type
appId string
Returns : Array<literal type>
getDeploymentProperties
getDeploymentProperties(builderDeploymentProperties: literal type, appId?: string)

Load the deployment properties of an app or global

Parameters :
Name Type Optional Description
builderDeploymentProperties literal type
appId string true
Returns : Array<literal type>
Private getProperties
getProperties()

Return an array of properties

Returns : Array<string>
isErrorPlatform
isErrorPlatform(platforms: Array, platform: string)

Return true if the platform is on error

Parameters :
Name Type Optional Description
platforms Array<any>
platform string
Returns : boolean
isErrorVersion
isErrorVersion(app: any, version: string)

Return true if the version is not invalid

Parameters :
Name Type Optional Description
app any
version string
Returns : boolean
isInvalidPlatform
isInvalidPlatform(platforms: Array, platform: string)

Return true if the platform is invalid (invalid value)

Parameters :
Name Type Optional Description
platforms Array<any>
platform string
Returns : boolean
isSubmittable
isSubmittable(builder: )

Return true if the builder is valid

Parameters :
Name Type Optional Description
builder
Returns : boolean
ngOnDestroy
ngOnDestroy()

On Destroy

Returns : void
ngOnInit
ngOnInit()

On Init

Returns : void
openApp
openApp(builder: , app: any)

Open Application Properties modal

Parameters :
Name Type Optional Description
builder
app any
Returns : void
openDeploymentProperties
openDeploymentProperties(builder: , appId?: string)

Open Deployment Properties modal for an app or global

Parameters :
Name Type Optional Description
builder
appId string true
Returns : void
Private populate
populate(builder: )

Populate values

Parameters :
Name Type Optional Description
builder
Returns : any
Private populateApp
populateApp(builder?: any)

Populate values app

Parameters :
Name Type Optional Description
builder any true
Returns : any
removeError
removeError(value: literal type)

Remove an error property

Parameters :
Name Type Optional Description
value literal type
Returns : void
tooltip
tooltip(streamDeployConfig: , control: AbstractControl)

Generate a tooltip

Parameters :
Name Type Optional Description
streamDeployConfig
control AbstractControl
Returns : string
Private updateFormArray
updateFormArray(builder: , array: FormArray, appKey: string, key: string, value: )
Parameters :
Name Type Optional Description
builder
array FormArray
appKey string
key string
value
Returns : void

Properties

builder$
builder$: Observable<any>
Type : Observable<any>

Builder observable Contains the form and the input data

refBuilder
refBuilder:

Builder Reference

state
state: any
Type : any

States

import {
  ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit,
  Output
} from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
import { StreamDeployService } from '../stream-deploy.service';
import { map } from 'rxjs/operators';
import { StreamDeployConfig } from '../../model/stream-deploy-config';
import { Observable } from 'rxjs';
import { StreamDeployValidator } from '../stream-deploy.validator';
import { AppPropertiesSource, StreamDeployAppPropertiesComponent } from '../app-properties/app-properties.component';
import { BsModalService } from 'ngx-bootstrap';
import { Properties } from 'spring-flo';
import { NotificationService } from '../../../shared/services/notification.service';
import {
  GroupPropertiesSource, GroupPropertiesSources,
  PropertiesGroupsDialogComponent
} from '../../../shared/flo/properties-groups/properties-groups-dialog.component';

/**
 * TODO
 *
 * @author Damien Vitrac
 * @author Janne Valkealahti
 */
@Component({
  selector: 'app-stream-deploy-builder',
  templateUrl: 'builder.component.html',
  styleUrls: ['styles.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class StreamDeployBuilderComponent implements OnInit, OnDestroy {

  /**
   * Stream ID
   */
  @Input() id: string;

  /**
   * Emits on destroy component with the current value
   */
  @Output() update = new EventEmitter();

  /**
   * Emit for request export
   */
  @Output() exportProperties = new EventEmitter();

  /**
   * Emit for request deploy
   */
  @Output() deploy = new EventEmitter();

  /**
   * Emit for request copy
   */
  @Output() copyProperties = new EventEmitter();

  /**
   * Properties to load
   */
  @Input() properties: Array<string> = [];

  /**
   * Is Deployed
   */
  @Input() isDeployed = false;

  /**
   * Builder observable
   * Contains the form and the input data
   */
  builder$: Observable<any>;

  /**
   * Builder Reference
   */
  refBuilder;

  /**
   * States
   */
  state: any = {
    platform: true,
    deployer: true,
    app: true,
    specificPlatform: true
  };

  constructor(private streamDeployService: StreamDeployService,
              private changeDetector: ChangeDetectorRef,
              private notificationService: NotificationService,
              private bsModalService: BsModalService) {
  }

  /**
   * On Init
   */
  ngOnInit() {
    this.builder$ = this.streamDeployService
      .config(this.id)
      .pipe(map((streamDeployConfig) => this.build(streamDeployConfig)))
      .pipe(map((builder) => this.populate(builder)))
      .pipe(map((builder) => this.populateApp(builder)));
  }

  /**
   * On Destroy
   */
  ngOnDestroy() {
    if (this.refBuilder) {
      this.update.emit(this.getProperties());
    }
  }

  /**
   * Return an array of properties
   * @returns {Array<string>}
   */
  private getProperties(): Array<string> {
    const result: Array<string> = [];
    const isEmpty = (control: AbstractControl) => !control || (control.value === '' || control.value === null);
    const deployers: FormArray = this.refBuilder.formGroup.get('deployers') as FormArray;
    const appsVersion: FormGroup = this.refBuilder.formGroup.get('appsVersion') as FormGroup;
    const global: FormArray = this.refBuilder.formGroup.get('global') as FormArray;
    const specificPlatform: FormArray = this.refBuilder.formGroup.get('specificPlatform') as FormArray;

    // Platform
    if (!isEmpty(this.refBuilder.formGroup.get('platform'))) {
      result.push(`spring.cloud.dataflow.skipper.platformName=${this.refBuilder.formGroup.get('platform').value}`);
    }

    // Deployers
    this.refBuilder.streamDeployConfig.deployers.forEach((deployer, index) => {
      if (!isEmpty(deployers.controls[index].get('global'))) {
        result.push(`deployer.*.${deployer.id}=${deployers.controls[index].get('global').value}`);
      }
      this.refBuilder.streamDeployConfig.apps.forEach((app) => {
        if (!isEmpty(deployers.controls[index].get(app.name))) {
          result.push(`deployer.${app.name}.${deployer.id}=${deployers.controls[index].get(app.name).value}`);
        }
      });
    });

    // Dynamic Form
    [specificPlatform, global].forEach((arr, index) => {
      const keyStart = (!index) ? 'deployer' : 'app';
      arr.controls.forEach((line: FormGroup) => {
        if (!isEmpty(line.get('property'))) {
          const key = line.get('property').value;
          if (!isEmpty(line.get('global'))) {
            result.push(`${keyStart}.*.${key}=${line.get('global').value}`);
          }
          this.refBuilder.streamDeployConfig.apps.forEach((app) => {
            if (!isEmpty(line.get(app.name))) {
              result.push(`${keyStart}.${app.name}.${key}=${line.get(app.name).value}`);
            }
          });
        }
      });
    });

    // Apps Version (appsVersion)
    this.refBuilder.streamDeployConfig.apps.forEach((app) => {
      if (!isEmpty(appsVersion.get(app.name))) {
        result.push(`version.${app.name}=${appsVersion.get(app.name).value}`);
      }
      // App deployment props set via modal
      this.getDeploymentProperties(this.refBuilder.builderDeploymentProperties, app.name).forEach((keyValue) => {
        result.push(`deployer.${app.name}.${keyValue.key.replace(/spring.cloud.deployer./, '')}=${keyValue.value}`);
      });
    });

    // Apps Properties
    Object.keys(this.refBuilder.builderAppsProperties).forEach((key: string) => {
      this.getAppProperties(this.refBuilder.builderAppsProperties, key).forEach((keyValue) => {
        result.push(`app.${key}.${keyValue.key}=${keyValue.value}`);
      });
    });

    // Global deployment props set via modal
    this.getDeploymentProperties(this.refBuilder.builderDeploymentProperties).forEach((keyValue) => {
      result.push(`deployer.*.${keyValue.key.replace(/spring.cloud.deployer./, '')}=${keyValue.value}`);
    });

    // Errors
    this.refBuilder.errors.global.forEach((error) => {
      result.push(error);
    });
    this.refBuilder.errors.app.forEach((error) => {
      result.push(error);
    });

    return result;
  }

  private updateFormArray(builder, array: FormArray, appKey: string, key: string, value) {
    let group: FormGroup;
    const lines = array.controls
      .filter((formGroup: FormGroup) => key === formGroup.get('property').value);

    if (lines.length > 0) {
      group = lines[0] as FormGroup;
    } else {
      group = new FormGroup({
        'property': new FormControl('', [StreamDeployValidator.key]),
        'global': new FormControl('')
      }, { validators: StreamDeployValidator.keyRequired });
      builder.streamDeployConfig.apps.forEach((app) => {
        group.addControl(app.name, new FormControl(''));
      });
      array.push(group);
    }
    group.get('property').setValue(key);
    group.get(appKey === '*' ? 'global' : appKey).setValue(value);
  }

  /**
   * Populate values app
   */
  private populateApp(builder?: any) {
    builder = builder || this.refBuilder;
    if (!builder) {
      return false;
    }
    builder.formGroup.get('global').controls = [];
    builder.errors.app = [];
    const appNames: Array<string> = builder.streamDeployConfig.apps.map((app, index) => {
      const appInfo = builder.streamDeployConfig.apps[index];
      builder.builderAppsProperties[app.name] = (appInfo && appInfo.options)
        ? builder.builderAppsProperties[app.name] = appInfo.options
          .map((property) => Object.assign({}, property))
        : [];
      return app.name;
    });
    const add = (array: FormArray) => {
      const group = new FormGroup({
        'property': new FormControl('', [StreamDeployValidator.key]),
        'global': new FormControl('')
      }, { validators: StreamDeployValidator.keyRequired });

      builder.streamDeployConfig.apps.forEach((app) => {
        group.addControl(app.name, new FormControl(''));
      });
      array.push(group);
    };
    this.properties.forEach((line: string) => {
      const arr = line.split(/=(.*)/);
      const key = arr[0] as string;
      const value = arr[1] as string;
      const appKey = key.split('.')[1];
      if (StreamDeployService.app.is(key)) {
        const keyReduce = StreamDeployService.app.extract(key);
        if ((appKey !== '*' && appNames.indexOf(appKey) === -1) || keyReduce === '') {
          // Error: app not found
          builder.errors.app.push(line);
        } else {
          let free = true;
          if (appNames.indexOf(appKey) > -1) {
            const appProperties = builder.streamDeployConfig.apps[appNames.indexOf(appKey)];
            if (appProperties.options && !appProperties.optionsState.isInvalid) {
              const option = builder.builderAppsProperties[appKey].find(opt => {
                return opt.name === keyReduce || opt.id === keyReduce;
              });
              if (option) {
                option.value = value;
                free = false;
              }
            }
          }
          if (free) {
            this.updateFormArray(builder, builder.formGroup.get('global') as FormArray, appKey, keyReduce, value);
          }
        }
      }
    });
    add(builder.formGroup.get('global'));
    return builder;
  }

  /**
   * Populate values
   */
  private populate(builder) {
    this.refBuilder = builder;

    const appNames: Array<string> = builder.streamDeployConfig.apps.map(app => app.name);
    const deployerKeys: Array<string> = builder.streamDeployConfig.deployers.map(deployer => deployer.name);
    builder.errors.global = [];

    // we need to iterate twice to get a possible platform name set
    // as it's needed later and we don't control order of these properties
    this.properties.some((line: string) => {
      const arr = line.split(/=(.*)/);
      const key = arr[0] as string;
      const value = arr[1] as string;
      if (StreamDeployService.platform.is(key)) {
        builder.formGroup.get('platform').setValue(value);
        const platform = builder.streamDeployConfig.platform.values.find(p => p.name === value);
        builder.builderDeploymentProperties.global = [];
        builder.streamDeployConfig.apps.forEach((app: any) => {
          builder.builderDeploymentProperties.apps[app.name] = [];
        });
        if (platform) {
          platform.options.forEach(o => {
            builder.builderDeploymentProperties.global.push(Object.assign({isSemantic: true}, o));
            builder.streamDeployConfig.apps.forEach((app: any) => {
              builder.builderDeploymentProperties.apps[app.name].push(Object.assign({isSemantic: true}, o));
            });
          });
        }
        // got it, break from loop
        return true;
      }
      return false;
    });

    this.properties.forEach((line: string) => {
      const arr = line.split(/=(.*)/);
      const key = arr[0] as string;
      const value = arr[1] as string;
      const appKey = key.split('.')[1];
      if (StreamDeployService.platform.is(key)) {
        // consume but don't do anything, otherwise error is added
        // platform value is set in first iteration
      } else if (StreamDeployService.deployer.is(key)) {
        // Deployer
        const keyReduce = StreamDeployService.deployer.extract(key);
        if ((appKey !== '*' && appNames.indexOf(appKey) === -1) || keyReduce === '') {
          // Error: app not found / * not found
          builder.errors.global.push(line);
        } else {
          if (deployerKeys.indexOf(keyReduce) > -1) {
            builder.formGroup.get('deployers')
              .controls[deployerKeys.indexOf(keyReduce)]
              .get(appKey === '*' ? 'global' : appKey).setValue(value);
          } else {
            const keymatch = 'spring.cloud.deployer.' + keyReduce;
            // go through deployment properties for global and apps and
            // mark match if it looks property is handled by modal, otherwise
            // it belong to form array
            let match = false;
            if (key.indexOf('deployer.*.') > -1) {
              builder.builderDeploymentProperties.global.forEach(p => {
                if (keymatch === p.id) {
                  match = true;
                  p.value = value;
                }
              });
            } else if (key.indexOf('deployer.' + appKey + '.') > -1) {
              builder.builderDeploymentProperties.apps[appKey].forEach(p => {
                if (keymatch === p.id) {
                  match = true;
                  p.value = value;
                }
              });
            }

            if (!match) {
              this.updateFormArray(builder, builder.formGroup.get('specificPlatform') as FormArray, appKey,
                keyReduce, value);
            }
          }
        }
      } else if (StreamDeployService.version.is(key)) {
        // Version
        if (appNames.indexOf(appKey) === -1) {
          // Error: app not found
          builder.errors.global.push(line);
        } else {
          const app = this.refBuilder.streamDeployConfig.apps.find((app_: any) => app_.name === appKey);
          // Populate if it's not the default version
          if (!app || app.version !== value) {
            builder.formGroup.get('appsVersion').get(appKey).setValue(value);
          }
        }
      } else if (!StreamDeployService.app.is(key)) {
        // Invalid Key
        builder.errors.global.push(line);
      }
    });
    return builder;
  }

  /**
   * Build the Group Form
   */
  private build(streamDeployConfig: StreamDeployConfig) {
    const formGroup: FormGroup = new FormGroup({});

    const getValue = (defaultValue) => !defaultValue ? '' : defaultValue;
    const builderAppsProperties = {};
    const builderDeploymentProperties = {global: [], apps: {}};

    // Platform
    const platformControl = new FormControl(getValue(streamDeployConfig.platform.defaultValue),
      (formControl: FormControl) => {
        if (this.isErrorPlatform(streamDeployConfig.platform.values, formControl.value)) {
          return { invalid: true };
        }
        if (streamDeployConfig.platform.values.length > 1 && !formControl.value) {
          return { invalid: true };
        }
        return null;
      });

    const defaultPlatform = streamDeployConfig.platform.values.length === 1 ?
      streamDeployConfig.platform.values[0] : null;

    const populateDeployerProperties = (value) => {
      builderDeploymentProperties.global = [];
      streamDeployConfig.apps.forEach((app: any) => {
        builderDeploymentProperties.apps[app.name] = [];
      });
      const platform = streamDeployConfig.platform.values.find(p => p.name === value);
      if (platform) {
        platform.options.forEach(o => {
          builderDeploymentProperties.global.push(Object.assign({isSemantic: true}, o));
          streamDeployConfig.apps.forEach((app: any) => {
            builderDeploymentProperties.apps[app.name].push(Object.assign({isSemantic: true}, o));
          });
        });
      }
    };

    if (defaultPlatform) {
      populateDeployerProperties(defaultPlatform.name);
    }

    platformControl.valueChanges.subscribe((value) => {
      if (!defaultPlatform) {
        populateDeployerProperties(value);
      }
    });

    formGroup.addControl('platform', platformControl);

    // Deployers
    const deployers = new FormArray([]);
    streamDeployConfig.deployers.forEach((deployer: any) => {
      const groupDeployer: FormGroup = new FormGroup({});
      const validators = [];
      if (deployer.type === 'java.lang.Integer') {
        validators.push(StreamDeployValidator.number);
      }
      groupDeployer.addControl('global', new FormControl(getValue(deployer.defaultValue), validators));
      streamDeployConfig.apps.forEach((app: any) => {
        groupDeployer.addControl(app.name, new FormControl('', validators));
      });
      deployers.push(groupDeployer);
    });

    // Applications
    const appsVersion = new FormGroup({});
    streamDeployConfig.apps.forEach((app: any) => {
      builderAppsProperties[app.name] = [];
      const control = new FormControl(null,
        (formControl: FormControl) => {
          if (this.isErrorVersion(app, formControl.value)) {
            return { invalid: true };
          }
          return null;
        });
      control.valueChanges.subscribe((value) => {
        builderAppsProperties[app.name] = [];
        if (this.isErrorVersion(app, value)) {
          app.optionsState.isInvalid = true;
          app.options = null;
          this.changeDetector.markForCheck();
        } else {
          app.optionsState.isInvalid = false;
          app.optionsState.isOnError = false;
          app.optionsState.isLoading = true;
          this.streamDeployService.appDetails(app.type, app.origin, value).subscribe((options) => {
            app.options = options;
          }, (error) => {
            app.options = [];
            app.optionsState.isOnError = true;
          }, () => {
            app.optionsState.isLoading = false;
            this.changeDetector.markForCheck();
            this.populateApp();
          });
        }
      });
      control.setValue(getValue(app.defaultValue));
      appsVersion.addControl(app.name, control);
    });

    // Useful methods for FormArray
    const add = (array: FormArray) => {
      const group = new FormGroup({
        'property': new FormControl('', [StreamDeployValidator.key]),
        'global': new FormControl('')
      }, { validators: StreamDeployValidator.keyRequired });

      streamDeployConfig.apps.forEach((app) => {
        group.addControl(app.name, new FormControl(''));
      });
      array.push(group);
    };
    const isEmpty = (dictionary): boolean => Object.entries(dictionary).every((a) => a[1] === '');
    const clean = (val: Array<any>, array: FormArray) => {
      const toRemove = val.map((a, index) =>
        ((index < (val.length - 1) && isEmpty(a)) ? index : null)).filter((a) => a != null);

      toRemove.reverse().forEach((a) => {
        array.removeAt(a);
      });
      if (!isEmpty(val[val.length - 1])) {
        return add(array);
      }
    };

    // Dynamic App properties
    const globalControls: FormArray = new FormArray([]);
    add(globalControls);
    globalControls.valueChanges.subscribe((val: Array<any>) => {
      clean(val, globalControls);
    });

    // Dynamic Platform properties
    const specificPlatformControls: FormArray = new FormArray([]);
    add(specificPlatformControls);
    specificPlatformControls.valueChanges.subscribe((val: Array<any>) => {
      clean(val, specificPlatformControls);
    });

    formGroup.addControl('deployers', deployers);
    formGroup.addControl('appsVersion', appsVersion);
    formGroup.addControl('global', globalControls);
    formGroup.addControl('specificPlatform', specificPlatformControls);

    return {
      formGroup: formGroup,
      builderAppsProperties: builderAppsProperties,
      builderDeploymentProperties: builderDeploymentProperties,
      streamDeployConfig: streamDeployConfig,
      errors: {
        global: [],
        app: []
      }
    };
  }

  /**
   * Return true if the version is not invalid
   *
   * @param {any} app
   * @param {string} version
   * @returns {boolean}
   */
  isErrorVersion(app: any, version: string): boolean {
    return version !== '' && !app.versions.find(v => v.version === version);
  }

  /**
   * Return true if the builder is valid
   * @param builder
   * @returns {boolean}
   */
  isSubmittable(builder): boolean {
    if (!builder) {
      return false;
    }
    return builder.formGroup.valid;
  }

  /**
   * Return true if the platform is on error
   *
   * @param {Array<any>} platforms
   * @param {string} platform
   * @returns {boolean}
   */
  isErrorPlatform(platforms: Array<any>, platform: string): boolean {
    return (platform !== '' && !platforms.find(p => p.key === platform))
      || (platform === '' && platforms.length > 1);
  }

  /**
   * Return true if the platform is invalid (invalid value)
   *
   * @param {Array<any>} platforms
   * @param {string} platform
   * @returns {boolean}
   */
  isInvalidPlatform(platforms: Array<any>, platform: string): boolean {
    return (platform !== '' && !platforms.find(p => p.key === platform));
  }

  /**
   * Generate a tooltip
   *
   * @param streamDeployConfig
   * @param control
   * @returns {string}
   */
  tooltip(streamDeployConfig, control: AbstractControl): string {
    const arr = [];
    if (control instanceof FormGroup) {
      if (control.get('property')) {
        if (control.get('property') && control.get('property').invalid) {
          arr.push(`The field "property" is not valid.`);
        }
      }
      if (control.get('global') && control.get('global').invalid) {
        arr.push(`The field "global" is not valid.`);
      }
      streamDeployConfig.apps.forEach((app) => {
        if (control.get(app.name).invalid) {
          arr.push(`The field "${app.name}" is not valid.`);
        }
      });
    } else {

    }
    return arr.join('<br />');
  }

  /**
   * Load the deployment properties of an app or global
   *
   * @param {{}} builderDeploymentProperties
   * @param {string} appId
   * @returns {Array}
   */
  getDeploymentProperties(builderDeploymentProperties: {global: any[], apps: any}, appId?: string): Array<{ key: string, value: any }> {
    const deploymentProperties = appId ? builderDeploymentProperties.apps[appId] : builderDeploymentProperties.global;
    if (!deploymentProperties) {
      return [];
    }

    return deploymentProperties.map((property: Properties.Property) => {
      if (property.value !== null && property.value !== undefined && property.value.toString() !== ''
        && property.value !== property.defaultValue) {
        return {
          key: `${property.id}`,
          value: property.value
        };
      }
      return null;
    }).filter((app) => app !== null);
  }

  /**
   * Open Deployment Properties modal for an app or global
   *
   * @param builder
   * @param {string} appId
   * @param app
   */
  openDeploymentProperties(builder, appId?: string) {
    const modal = this.bsModalService.show(PropertiesGroupsDialogComponent);
    const options = appId ? builder.builderDeploymentProperties.apps[appId] : builder.builderDeploymentProperties.global;
    modal.content.title = `Deployment properties for platform`;

    // jee.foo.bar-xxx -> jee.foo
    const deduceKey = (key) => {
      return key.substring(0, key.lastIndexOf('.'));
    };

    // grouping all properties by a deduced key
    const groupBy = (items, key) => items.reduce(
      (result, item) => {
        const groupKey = deduceKey(item[key]);
        return ({
          ...result,
          [groupKey]: [...(result[groupKey] || []), item],
        });
      }, {}
    );

    // setup groups and sort alphabetically by group titles
    let groupedPropertiesSources: Array<GroupPropertiesSource> = [];
    const groupedEntries: { [s: string]: Array<any>; } = groupBy(options, 'id');
    Object.entries(groupedEntries).forEach(v => {
      const groupedPropertiesSource = new GroupPropertiesSource(Object.assign([], v[1]
        .map((property) => Object.assign({}, property))), v[0]);
      groupedPropertiesSources.push(groupedPropertiesSource);
    });
    groupedPropertiesSources = groupedPropertiesSources.sort(((a, b) => {
      return a.title === b.title ? 0 : a.title < b.title ? -1 : 1;
    }));
    const groupPropertiesSources = new GroupPropertiesSources(groupedPropertiesSources);

    // get new props from modal
    groupPropertiesSources.confirm.subscribe((properties: Array<any>) => {
      if (appId) {
        builder.builderDeploymentProperties.apps[appId] = properties;
      } else {
        builder.builderDeploymentProperties.global = properties;
      }
      this.changeDetector.markForCheck();
    });

    modal.content.setData(groupPropertiesSources);
  }

  /**
   * Load the properties of an app
   *
   * @param {{}} builderAppsProperties
   * @param {string} appId
   * @returns {Array}
   */
  getAppProperties(builderAppsProperties: {}, appId: string): Array<{ key: string, value: any }> {
    const appProperties = builderAppsProperties[appId];
    if (!appProperties) {
      return [];
    }
    return appProperties.map((property: Properties.Property) => {
      if (property.value !== null && property.value !== undefined && property.value.toString() !== ''
        && property.value !== property.defaultValue) {
        if (property.id.startsWith(`${appId}.`)) {
          return {
            key: `${property.name}`,
            value: property.value
          };
        } else {
          return {
            key: `${property.id}`,
            value: property.value
          };
        }
      }
      return null;
    }).filter((app) => app !== null);
  }

  /**
   * Open Application Properties modal
   *
   * @param builder
   * @param app
   */
  openApp(builder, app: any) {
    const version = builder.formGroup.get('appsVersion').get(app.name).value || app.version;
    const options = builder.builderAppsProperties[app.name] ? builder.builderAppsProperties[app.name] : app.options;
    const modal = this.bsModalService.show(StreamDeployAppPropertiesComponent);

    modal.content.title = `Properties for ${app.name}`;
    if (version) {
      modal.content.title += ` (${version})`;
    }
    const appPropertiesSource = new AppPropertiesSource(Object.assign([], options
      .map((property) => Object.assign({}, property))));

    appPropertiesSource.confirm.subscribe((properties: Array<any>) => {
      builder.builderAppsProperties[app.name] = properties;
      this.changeDetector.markForCheck();
    });
    modal.content.setData(appPropertiesSource);
  }

  /**
   * Remove an error property
   */
  removeError(value: { type: string, index: number }) {
    const errors = this.refBuilder.errors[value.type];
    if (errors[value.index]) {
      errors.splice(value.index, 1);
    }
    this.properties = this.getProperties();
  }

  /**
   * Emit a request deploy
   */
  deployStream() {
    if (!this.isSubmittable(this.refBuilder)) {
      this.notificationService.error('Some field(s) are invalid.');
    } else {
      this.deploy.emit(this.getProperties());
    }
  }

  /**
   * Emit a request export
   */
  exportProps() {
    this.exportProperties.emit(this.getProperties());
  }

  /**
   * Copye to clipboard
   */
  copyToClipboard() {
    this.copyProperties.emit(this.getProperties());
  }

}
<div *ngIf="builder$ | async as builder; else loading">
  <form class="form-horizontal" novalidate (ngSubmit)="deployStream()">
    <div>
      <div class="tab-pane">
        <app-stream-deploy-builder-errors [errors]="builder.errors" (removeError)="removeError($event)">
        </app-stream-deploy-builder-errors>
        <div class="builder" [formGroup]="builder.formGroup">

          <div class="col-fixed col-key">

            <!-- 0 -->
            <div class="line-head">
            </div>

            <!-- 1 -->
            <div class="line-toggle">

              <a class="toggle" (click)="state.platform = !state.platform">
                <span class="fa" [class.fa-chevron-down]="state.platform"
                      [class.fa-chevron-right]="!state.platform"></span>
                Platform
              </a>
            </div>

            <!-- 2 -->
            <div class="line-body" *ngIf="state.platform">
              <div class="status" *ngIf="builder.formGroup.get('platform').invalid">
                <div tooltipPlacement="right"
                     [tooltip]="tooltipTemplate"
                     class="fa fa-warning"></div>
                <ng-template #tooltipTemplate>
                  <div [innerHtml]="'The define platform is not valid (unknown)'"></div>
                </ng-template>
              </div>
              <div class="form-control form-control-label">
                Platform
              </div>
            </div>

            <!-- 3 -->
            <div class="line-toggle">
              <a class="toggle" (click)="state.deployer = !state.deployer">
                <span class="fa" [class.fa-chevron-down]="state.deployer"
                      [class.fa-chevron-right]="!state.deployer"></span>
                Generic Deployer Properties
              </a>
            </div>

            <!-- 4 -->
            <div *ngIf="state.deployer">
              <div class="line-body" *ngFor="let deployer of builder.streamDeployConfig.deployers;let i = index">

                <div class="status" *ngIf="builder.formGroup.get('deployers').controls[i].invalid">
                  <div tooltipPlacement="right"
                       [tooltip]="tooltipTemplate"
                       class="fa fa-warning"></div>

                  <ng-template #tooltipTemplate>
                    <div
                      [innerHtml]="tooltip(builder.streamDeployConfig, builder.formGroup.get('deployers').controls[i])"></div>
                  </ng-template>
                </div>

                <div class="form-control form-control-label">
                  {{ deployer.name }}
                </div>
              </div>
            </div>

            <!-- 5 -->
            <div class="line-toggle">
              <div class="help">
                <span class="fa fa-question-circle" tooltipPlacement="right" [tooltip]="tooltipTemplate"></span>
                <ng-template #tooltipTemplate>
                  <div>
                    <div><strong>Example:</strong></div>
                    <div>Key: <strong>cloudfoundry.services</strong></div>
                    <div>Value (application <strong>jdbc</strong>): <strong>mysqlcups</strong></div>
                    <div>Generated property: <strong>deployer.jdbc.cloudfoundry.services=mysqlcups</strong></div>
                  </div>
                </ng-template>
              </div>


              <a class="toggle" (click)="state.specificPlatform = !state.specificPlatform">
              <span class="fa" [class.fa-chevron-down]="state.specificPlatform"
                    [class.fa-chevron-right]="!state.specificPlatform"></span>
                Deployment Platform
                <!--
                <span *ngIf="builder.formGroup.get('platform').value">
                  ({{ builder.formGroup.get('platform').value }})
                </span>
                -->
              </a>
            </div>

            <!-- 6 -->
            <div *ngIf="state.specificPlatform">
              <div *ngIf="builder.builderDeploymentProperties.global.length > 0"
                   class="line-body">
                <div class="form-control form-control-label">
                  Properties
                </div>
              </div>
            </div>
            <div *ngIf="state.specificPlatform"
                 formArrayName="specificPlatform">
              <div class="line-body" *ngFor="let f of builder.formGroup.get('specificPlatform').controls;let i = index"
                   [formGroupName]="i"
                   [class.has-error]="builder.formGroup.get('specificPlatform').controls[i].get('property').invalid">
                <div class="status" *ngIf="builder.formGroup.get('specificPlatform').controls[i].invalid">
                  <div tooltipPlacement="right"
                       [tooltip]="tooltipTemplate"
                       class="fa fa-warning"></div>

                  <ng-template #tooltipTemplate>
                    <div
                      [innerHtml]="tooltip(builder.streamDeployConfig, builder.formGroup.get('specificPlatform').controls[i])"></div>
                  </ng-template>
                </div>

                <input tabindex="{{ 50 + i }}" placeholder="Enter a value" class="form-control"
                       formControlName="property" type="text"/>
              </div>
            </div>

            <!-- 7 -->
            <div class="line-toggle">
              <a class="toggle" (click)="state.app = !state.app">
                <span class="fa" [class.fa-chevron-down]="state.app" [class.fa-chevron-right]="!state.app"></span>
                Applications Properties
              </a>
            </div>

            <!-- 8 -->
            <div *ngIf="state.app">
              <div class="line-body">

                <div class="status" *ngIf="builder.formGroup.get('appsVersion').invalid">
                  <div tooltipPlacement="right"
                       [tooltip]="tooltipTemplate"
                       class="fa fa-warning"></div>
                  <ng-template #tooltipTemplate>
                    <div
                      [innerHtml]="tooltip(builder.streamDeployConfig, builder.formGroup.get('appsVersion'))"></div>
                  </ng-template>
                </div>

                <div class="form-control form-control-label">
                  Version
                </div>
              </div>
              <div class="line-body">
                <div class="form-control form-control-label">
                  Properties
                </div>
              </div>
              <div formArrayName="global">
                <div class="line-body" *ngFor="let f of builder.formGroup.get('global').controls;let i = index"
                     [formGroupName]="i"
                     [class.has-error]="builder.formGroup.get('global').controls[i].get('property').invalid">

                  <div class="status" *ngIf="builder.formGroup.get('global').controls[i].invalid">

                    <div tooltipPlacement="right"
                         [tooltip]="tooltipTemplate"
                         class="fa fa-warning"></div>

                    <ng-template #tooltipTemplate>
                      <div
                        [innerHtml]="tooltip(builder.streamDeployConfig, builder.formGroup.get('global').controls[i])"></div>
                    </ng-template>
                  </div>

                  <input tabindex="{{ 102 + i }}" placeholder="Enter a value" class="form-control"
                         formControlName="property" type="text"/>
                </div>
              </div>
            </div>

          </div>

          <div class="col-fixed global">

            <!-- 0 -->
            <div class="line-head">
              Global
            </div>

            <!-- 1 -->
            <div class="line-toggle"></div>

            <!-- 2 -->
            <div class="line-body" *ngIf="state.platform"
                 [class.has-error]="isErrorPlatform(builder.streamDeployConfig.platform.values, builder.formGroup.get('platform').value)">
              <select tabindex="1" formControlName="platform">
                <option value="">Select a value</option>
                <option *ngFor="let platform of builder.streamDeployConfig.platform.values" [value]="platform.key">
                  {{ platform.name }}
                  ({{ platform.type }})
                </option>
                <option
                  *ngIf="isInvalidPlatform(builder.streamDeployConfig.platform.values, builder.formGroup.get('platform').value)"
                  [value]="builder.formGroup.get('platform').value">
                  {{ builder.formGroup.get('platform').value }} (invalid)
                </option>
              </select>
            </div>

            <!-- 3 -->
            <div class="line-toggle"></div>

            <!-- 4 -->
            <div formArrayName="deployers" *ngIf="state.deployer">
              <div [formGroupName]="i" class="line-body"
                   *ngFor="let deployer of builder.streamDeployConfig.deployers; let i = index"
                   [class.has-error]="builder.formGroup.get('deployers').controls[i].get('global').invalid">

                <input tabindex="{{ 2 + i }}" placeholder="Enter a number" formControlName="global" class="form-control"
                       [class.form-control-number]="deployer.suffix" type="text"/>
                <span class="placeholder-unit">{{ deployer.suffix }}</span>
              </div>
            </div>

            <!-- 5 -->
            <div class="line-toggle"></div>

            <div class="line-body"
                 *ngIf="state.specificPlatform && builder.builderDeploymentProperties.global.length > 0">
              <div class="form-control form-control-label">
                <strong>{{ getDeploymentProperties(builder.builderDeploymentProperties).length }}</strong>
                <span> /</span> {{ builder.builderDeploymentProperties.global.length }} properties
                <button tabindex="{{ 101 }}" type="button" (click)="openDeploymentProperties(builder)"
                        class="btn btn-primary">
                  <span class="fa fa-pencil"></span>
                </button>
              </div>
            </div>

            <!-- 6 -->
            <div *ngIf="state.specificPlatform"
                 formArrayName="specificPlatform">
              <div class="line-body" *ngFor="let f of builder.formGroup.get('specificPlatform').controls;let i = index"
                   [formGroupName]="i"
                   [class.has-error]="builder.formGroup.get('specificPlatform').controls[i].get('global').invalid">
                <input tabindex="{{ 50 + i }}" placeholder="Enter a value" class="form-control" formControlName="global"
                       type="text"/>
              </div>
            </div>

            <!-- 7 -->
            <div class="line-toggle"></div>

            <!-- 8 -->
            <div *ngIf="state.app">
              <div class="line-body"></div>
              <div class="line-body"></div>
              <div formArrayName="global">
                <div class="line-body" *ngFor="let f of builder.formGroup.get('global').controls;let i = index"
                     [formGroupName]="i"
                     [class.has-error]="builder.formGroup.get('global').controls[i].get('global').invalid">
                  <input tabindex="{{ 102 + i }}" placeholder="Enter a value" class="form-control"
                         formControlName="global" type="text"/>
                </div>
              </div>
            </div>

          </div>

          <div class="scroll">

            <div class="col" *ngFor="let app of builder.streamDeployConfig.apps">

              <!-- 0 -->
              <div class="line-head">
                <span *ngIf="app.origin != app.name">
                  <strong>{{ app.name }}</strong>
                  ({{ app.origin }})
                </span>
                <strong *ngIf="app.origin == app.name">{{ app.name }}</strong>
                <br/>
                <app-type [application]="app"></app-type>
              </div>

              <!-- 1 -->
              <div class="line-toggle"></div>

              <!-- 2 -->
              <div class="line-body" *ngIf="state.platform"></div>

              <!-- 3 -->
              <div class="line-toggle"></div>

              <!-- 4 -->
              <div formArrayName="deployers" *ngIf="state.deployer">
                <div [formGroupName]="i" class="line-body"
                     *ngFor="let deployer of builder.streamDeployConfig.deployers; let i = index"
                     [class.has-error]="builder.formGroup.get('deployers').controls[i].get(app.name).invalid">
                  <input tabindex="{{ 2 + i }}" placeholder="Enter a number" [formControlName]="app.name"
                         class="form-control"
                         [class.form-control-number]="deployer.suffix" type="text"/>
                  <span class="placeholder-unit">{{ deployer.suffix }}</span>
                </div>
              </div>

              <!-- 5 -->
              <div class="line-toggle"></div>

              <!-- 6 -->
              <!-- track visibility from global as those are same as per apps -->
              <div class="line-body" *ngIf="state.specificPlatform && builder.builderDeploymentProperties.global.length > 0">
                <div class="form-control form-control-label">
                  <strong>{{ getDeploymentProperties(builder.builderDeploymentProperties, app.name).length }}</strong>
                  <span> /</span> {{ builder.builderDeploymentProperties.apps[app.name].length }} properties
                  <button tabindex="{{ 101 }}" type="button" (click)="openDeploymentProperties(builder, app.name)"
                      class="btn btn-primary">
                    <span class="fa fa-pencil"></span>
                  </button>
                </div>
              </div>

              <div *ngIf="state.specificPlatform"
                   formArrayName="specificPlatform">
                <div class="line-body"
                     *ngFor="let f of builder.formGroup.get('specificPlatform').controls;let i = index"
                     [formGroupName]="i"
                     [class.has-error]="builder.formGroup.get('specificPlatform').controls[i].get(app.name).invalid">
                  <input tabindex="{{ 50 + i }}" placeholder="Enter a value" class="form-control"
                         [formControlName]="app.name" type="text"/>
                </div>
              </div>

              <!-- 7 -->
              <div class="line-toggle"></div>

              <!-- 8 -->
              <div *ngIf="state.app">
                <div class="line-body" [class.has-error]="app.optionsState.isInvalid">
                  <div formGroupName="appsVersion">
                    <div class="cell">
                      <select tabindex="{{ 100 }}" [formControlName]="app.name">
                        <option value="">Default version ({{ app.version }})</option>
                        <option *ngFor="let version of app.versions" [value]="version.version">
                          {{ version.version }}
                        </option>
                        <option *ngIf="app.optionsState.isInvalid"
                                [value]="builder.formGroup.get('appsVersion').get(app.name).value">
                          {{ builder.formGroup.get('appsVersion').get(app.name).value }} (invalid)
                        </option>
                      </select>
                    </div>
                  </div>
                </div>
                <div class="line-body" [class.has-error]="app.optionsState.isInvalid">
                  <div
                    *ngIf="app.options && !app.optionsState.isLoading && !app.optionsState.isOnError && !app.optionsState.isInvalid"
                    class="form-control form-control-label">
                    <div *ngIf="app.options.length > 0">
                      <strong>{{ getAppProperties(builder.builderAppsProperties, app.name).length }}</strong>
                      <span> /</span> {{ app.options.length }} properties
                      <button tabindex="{{ 101 }}" type="button" (click)="openApp(builder, app)"
                              class="btn btn-primary">
                        <span class="fa fa-pencil"></span>
                      </button>
                    </div>
                    <div *ngIf="app.options.length == 0">
                      <strong>No properties</strong>
                    </div>
                  </div>
                  <div *ngIf="app.optionsState.isLoading" class="form-control form-control-label">
                    Loading ...
                  </div>
                  <div *ngIf="app.optionsState.isOnError" class="form-control form-control-label">
                    Error loading
                  </div>
                  <div *ngIf="app.optionsState.isInvalid" class="form-control form-control-label">
                    Invalid version
                  </div>
                </div>
                <div formArrayName="global">
                  <div class="line-body" *ngFor="let f of builder.formGroup.get('global').controls;let i = index"
                       [formGroupName]="i"
                       [class.has-error]="builder.formGroup.get('global').controls[i].get(app.name).invalid">
                    <input tabindex="{{ 102 + i }}" placeholder="Enter a value" class="form-control"
                           [formControlName]="app.name" type="text"/>
                  </div>
                </div>
              </div>

            </div>
          </div>
        </div>
        <app-page-actions>
          <a tabindex="200" id="btn-cancel" class="btn btn-default" routerLink="/streams">Cancel</a>
          <button tabindex="200" id="btn-export" type="button" class="btn btn-default" (click)="exportProps()">
            Export
          </button>
          <button tabindex="200" id="btn-copy" type="button" class="btn btn-default" (click)="copyToClipboard()">
            Copy to Clipboard
          </button>
          <button tabindex="200" id="btn-deploy-builder" type="submit" class="btn btn-primary">
            <span *ngIf="!isDeployed">Deploy stream</span>
            <span *ngIf="isDeployed">Update stream</span>
          </button>
        </app-page-actions>
      </div>
    </div>
  </form>
</div>
<ng-template #loading>
  <app-loader></app-loader>
</ng-template>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""