text:
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
  Output,
  ViewChild
} from '@angular/core';
import {AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup} from '@angular/forms';
import {map, tap} from 'rxjs/operators';
import {Observable, of, Subscription} from 'rxjs';
import {Properties} from 'spring-flo';
import PropertiesSource = Properties.PropertiesSource;
import {TaskLaunchService} from '../task-launch.service';
import {TaskLaunchValidator} from '../task-launch.validator';
import {NotificationService} from '../../../../shared/service/notification.service';
import {Task, TaskLaunchConfig} from '../../../../shared/model/task.model';
import {
  GroupPropertiesSource,
  GroupPropertiesSources,
  PropertiesGroupsDialogComponent
} from '../../../../flo/shared/properties-groups/properties-groups-dialog.component';
import {App} from '../../../../shared/model/app.model';
import {PropertiesDialogComponent} from '../../../../flo/shared/properties/properties-dialog.component';
import {StreamAppPropertiesSource, StreamHead} from '../../../../flo/stream/properties/stream-properties-source';
import {TaskPropertiesDialogComponent} from '../../../../flo/task/properties/task-properties-dialog-component';
import {ValuedConfigurationMetadataProperty} from '../../../../shared/model/detailed-app.model';
import {get} from 'lodash';
import {TranslateService} from '@ngx-translate/core';

export class AppPropertiesSource implements StreamAppPropertiesSource {
  private options: Array<any>;
  public confirm = new EventEmitter();

  constructor(options: Array<any>) {
    this.options = options;
  }

  getStreamHead(): StreamHead {
    return {presentStreamNames: []};
  }

  getProperties(): Promise<Properties.Property[]> {
    return of(this.options).toPromise();
  }

  applyChanges(properties: Properties.Property[]): void {
    this.confirm.emit(properties);
  }
}

export class CtrPropertiesSource implements PropertiesSource {
  private options: Array<any>;
  public confirm = new EventEmitter();

  constructor(options: Array<any>) {
    this.options = options;
  }

  getProperties(): Promise<Properties.Property[]> {
    return of(this.options).toPromise();
  }

  applyChanges(properties: Properties.Property[]): void {
    this.confirm.emit(properties);
  }
}

export interface Builder {
  taskLaunchConfig: TaskLaunchConfig;
  formGroup: UntypedFormGroup;
  builderAppsProperties: any;

  // additional arrays and groups exposed directly so that we
  // get proper typing in a template as getting these from a
  // formGroup would return AbstractControl
  deployers: UntypedFormArray;
  appsVersion: UntypedFormGroup;
  globalControls: UntypedFormArray;
  specificPlatformControls: UntypedFormArray;
  argumentsControls: UntypedFormArray;

  builderDeploymentProperties: {
    global: Array<any>;
    apps: any;
  };

  ctrProperties: ValuedConfigurationMetadataProperty[];
  ctrPropertiesState: {
    isLoading: boolean;
    isOnError: boolean;
  };

  // args for global and task apps
  arguments: {
    global: string[];
    apps: {[name: string]: string[]};
  };

  errors: {
    global: string[];
    app: string[];
  };
}

export interface Migrations {
  migratedNomatch: string[];
  migratedMatch: RegExpExecArray[];
}

@Component({
  selector: 'app-task-launch-builder',
  templateUrl: 'builder.component.html',
  styleUrls: ['builder.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BuilderComponent implements OnInit, OnDestroy {
  @ViewChild('appPropertiesModal', {static: true}) appPropertiesModal: PropertiesDialogComponent;
  @ViewChild('groupsPropertiesModal', {static: true}) groupsPropertiesModal: PropertiesGroupsDialogComponent;
  @ViewChild('ctrPropertiesModal', {static: true}) ctrPropertiesModal: TaskPropertiesDialogComponent;

  @Input() task: Task;
  @Input() properties: Array<string> = [];
  @Input() arguments: Array<string> = [];
  @Output() updateProperties = new EventEmitter();
  @Output() updateArguments = new EventEmitter();
  @Output() exportProperties = new EventEmitter();
  @Output() launch = new EventEmitter<{props: string[]; args: string[]}>();
  @Output() copyProperties = new EventEmitter();

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

  /**
   * Internal builder reference populated during init when an actual
   * builder is constructed asyncronously when observable is consumed.
   */
  private refBuilder: Builder;

  private crtOptionsSub: Subscription;

  /**
   * The ones we originally get with page load and which
   * we use to fill enough info in structures to know
   * when property needs to send back as empty value.
   */
  private originalProperties: Array<string> = [];

  /**
   * States for UI to i.e. keep collap section state.
   */
  state: any = {
    platform: true,
    deployer: true,
    app: true,
    specificPlatform: true,
    ctr: true,
    arguments: true
  };

  private migrations: Migrations;

  constructor(
    private taskLaunchService: TaskLaunchService,
    private changeDetector: ChangeDetectorRef,
    private notificationService: NotificationService,
    private translate: TranslateService
  ) {}

  /**
   * Build needed structures what's needed after init.
   */
  ngOnInit() {
    this.builder$ = this.taskLaunchService
      .config(this.task.name)
      .pipe(map(taskLaunchConfig => this.build(taskLaunchConfig)))
      .pipe(map(builder => this.populate(builder)))
      .pipe(map(builder => this.populateApp(builder)))
      .pipe(map(builder => this.populateAppArgs(builder)))
      .pipe(
        tap(builder => {
          if (this.task.composed) {
            // if ctr, fire up load for its options separately
            // so that we get page loaded faster and user gets
            // loading text in a ctr form part
            this.crtOptionsSub = this.taskLaunchService.ctrOptions().subscribe(
              options => {
                if (Array.isArray(options)) {
                  options.forEach((o: any) => {
                    builder.ctrProperties.push(Object.assign({isSemantic: true}, o));
                  });
                }

                // set 'original' values so we know when user cleared an option
                this.originalProperties.forEach((line: string) => {
                  const arr = line.split(/=(.*)/);
                  const key = arr[0] as string;
                  const value = arr[1] as string;
                  if (TaskLaunchService.ctr.is(key)) {
                    const ctrKey = TaskLaunchService.ctr.extract(key);
                    builder.ctrProperties.forEach(p => {
                      if (p.id === ctrKey) {
                        p.originalValue = value;
                      }
                    });
                  }
                });
                // need to set values here instead of init time
                this.properties.forEach((line: string) => {
                  const arr = line.split(/=(.*)/);
                  const key = arr[0] as string;
                  const value = arr[1] as string;
                  if (TaskLaunchService.ctr.is(key)) {
                    const ctrKey = TaskLaunchService.ctr.extract(key);
                    builder.ctrProperties.forEach(p => {
                      if (p.id === ctrKey) {
                        p.value = value;
                      }
                    });
                  }
                });
                this.migrations = this.calculateMigrations();
              },
              () => {
                this.refBuilder.ctrPropertiesState.isOnError = true;
              },
              () => {
                builder.ctrPropertiesState.isLoading = false;
                this.changeDetector.markForCheck();
              }
            );
          }
        })
      );
  }

  /**
   * What's needed when this component goes bye bye.
   */
  ngOnDestroy() {
    if (this.refBuilder) {
      this.updateProperties.emit(this.getProperties());
      this.updateArguments.emit(this.getArguments());
    }
    if (this.crtOptionsSub) {
      this.crtOptionsSub.unsubscribe();
    }
  }

  /**
   * Return what's known as current properties. This is a list of a raw array
   * of properties what's "known" to a builder. Essentially as key/values
   * separated by '='.
   */
  private getProperties(): Array<string> {
    const result: Array<string> = [];
    const isEmpty = (control: AbstractControl) => !control || control.value === '' || control.value === null;
    const deployers: UntypedFormArray = this.refBuilder.formGroup.get('deployers') as UntypedFormArray;
    const appsVersion: UntypedFormGroup = this.refBuilder.formGroup.get('appsVersion') as UntypedFormGroup;
    const global: UntypedFormArray = this.refBuilder.formGroup.get('global') as UntypedFormArray;
    const specificPlatform: UntypedFormArray = this.refBuilder.formGroup.get('specificPlatform') as UntypedFormArray;

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

    // Deployers
    this.refBuilder.taskLaunchConfig.deployers.forEach((deployer, index) => {
      if (!isEmpty(deployers.controls[index].get('global'))) {
        result.push(`deployer.*.${deployer.id}=${deployers.controls[index].get('global').value}`);
      }
      this.refBuilder.taskLaunchConfig.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: UntypedFormGroup) => {
        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.taskLaunchConfig.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.taskLaunchConfig.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}`);
    });

    // Ctr Properties
    this.refBuilder.ctrProperties.forEach(x => {
      if (x.value !== null && x.value !== undefined && x.value !== '' && x.value !== x.defaultValue) {
        result.push(`app.composed-task-runner.${x.id}=${x.value}`);
      } else if (x.originalValue !== undefined) {
        // user cleared property, need to give 'empty' value so that
        // server will clear it from manifest
        result.push(`app.composed-task-runner.${x.id}=`);
      }
    });

    // Errors
    this.refBuilder.errors.global.forEach(error => {
      result.push(error);
    });
    // TODO: causing push to app.composed-task-runner
    // this.refBuilder.errors.app.forEach((error) => {
    //   result.push(error);
    // });

    return result;
  }

  private getArguments(): Array<string> {
    const isEmpty = (control: AbstractControl) => !control || control.value === '' || control.value === null;
    const result: Array<string> = [];
    // indexMap for having property postfix per app
    const indexMap = new Map<string, number>();
    (this.refBuilder.argumentsControls.controls as UntypedFormGroup[]).forEach((g, i) => {
      for (const field in g.controls) {
        if (g.controls.hasOwnProperty(field)) {
          const control = g.get(field);
          if (!isEmpty(control)) {
            // we start from 0, like app.t1.0
            let index = 0;
            if (indexMap.has(field)) {
              index = indexMap.get(field);
            } else {
              indexMap.set(field, index);
            }

            if (field === 'global') {
              result.push(`app.*.${index}=${control.value}`);
            } else {
              result.push(`app.${field}.${index}=${control.value}`);
            }
            // next index for app
            indexMap.set(field, ++index);
          }
        }
      }
    });
    return result;
  }

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

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

  /**
   * Populate values app
   */
  private populateApp(builder: Builder): Builder {
    builder.globalControls.controls = [];
    builder.errors.app = [];
    const appNames: Array<string> = builder.taskLaunchConfig.apps.map((app, index) => {
      const appInfo = builder.taskLaunchConfig.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: UntypedFormArray) => {
      const group = new UntypedFormGroup(
        {
          property: new UntypedFormControl('', [TaskLaunchValidator.key]),
          global: new UntypedFormControl('')
        },
        {validators: TaskLaunchValidator.keyRequired}
      );

      builder.taskLaunchConfig.apps.forEach(app => {
        group.addControl(app.name, new UntypedFormControl(''));
      });
      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 (!TaskLaunchService.ctr.is(key) && TaskLaunchService.app.is(key)) {
        const keyReduce = TaskLaunchService.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.taskLaunchConfig.apps[appNames.indexOf(appKey)];
            if (appProperties.options && !appProperties.optionsState.isInvalid) {
              const option = builder.builderAppsProperties[appKey].find(
                opt => opt.name === keyReduce || opt.id === keyReduce
              );
              if (option) {
                option.value = value;
                free = false;
              }
            }
          }
          if (free) {
            this.updateFormArray(
              builder,
              builder.formGroup.get('global') as UntypedFormArray,
              appKey,
              keyReduce,
              value
            );
          }
        }
      }
    });
    add(builder.globalControls);
    return builder;
  }

  private populateAppArgs(builder: Builder): Builder {
    const argumentsControls = builder.formGroup.get('argumentsControls') as UntypedFormArray;

    let maxRows = 0;
    const argsMap = this.arguments.reduce((m, line) => {
      const arr = line.split(/=(.*)/);
      const key = arr[0] as string;
      const appKey = key.split('.')[1];
      if (m.has(appKey)) {
        m.set(appKey, [...m.get(appKey), line]);
        maxRows = Math.max(maxRows, m.get(appKey).length);
      } else {
        m.set(appKey, [line]);
        maxRows = Math.max(maxRows, 1);
      }
      return m;
    }, new Map<string, string[]>());

    for (let index = 0; index < maxRows; index++) {
      const gControl = new UntypedFormControl('');
      if (argsMap.has('*') && argsMap.get('*').length > index) {
        gControl.setValue(TaskLaunchService.ctr.value(argsMap.get('*')[index]));
      }
      const group = new UntypedFormGroup({
        global: gControl
      });
      builder.taskLaunchConfig.apps.forEach(app => {
        const aControl = new UntypedFormControl('');
        if (argsMap.has(app.name) && argsMap.get(app.name).length > index) {
          aControl.setValue(TaskLaunchService.ctr.value(argsMap.get(app.name)[index]));
        }
        group.addControl(app.name, aControl);
      });
      argumentsControls.push(group);
    }
    return builder;
  }

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

    const appNames: Array<string> = builder.taskLaunchConfig.apps.map(app => app.name);
    const deployerKeys: Array<string> = builder.taskLaunchConfig.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 (TaskLaunchService.platform.is(key)) {
        builder.formGroup.get('platform').setValue(value);
        const platform = builder.taskLaunchConfig.platform.values.find(p => p.name === value);
        builder.builderDeploymentProperties.global = [];
        builder.taskLaunchConfig.apps.forEach((app: any) => {
          builder.builderDeploymentProperties.apps[app.name] = [];
        });
        if (platform) {
          platform.options.forEach((o: any) => {
            const obj = Object.assign({isSemantic: true}, o);
            builder.builderDeploymentProperties.global.push(obj);
            builder.taskLaunchConfig.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 (TaskLaunchService.platform.is(key)) {
        // consume but don't do anything, otherwise error is added
        // platform value is set in first iteration
      } else if (TaskLaunchService.deployer.is(key)) {
        // Deployer
        const keyReduce = TaskLaunchService.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') as UntypedFormArray).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 UntypedFormArray,
                appKey,
                keyReduce,
                value
              );
            }
          }
        }
      } else if (TaskLaunchService.version.is(key)) {
        // Version
        if (appNames.indexOf(appKey) === -1) {
          // Error: app not found
          builder.errors.global.push(line);
        } else {
          const app = this.refBuilder.taskLaunchConfig.apps.find((appConfig: any) => appConfig.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 (!TaskLaunchService.app.is(key)) {
        // Invalid Key
        builder.errors.global.push(line);
      }
    });
    return builder;
  }

  /**
   * Build the Group Form
   */
  private build(taskLaunchConfig: TaskLaunchConfig): Builder {
    const formGroup: UntypedFormGroup = new UntypedFormGroup({});

    // we dehydrate only when coming into builder, not
    // when switching between build and free-text.
    // dehydrated values are then expected to come back from free-text
    if (this.properties.length === 0 && taskLaunchConfig.deploymentProperties.length > 0) {
      this.properties = taskLaunchConfig.deploymentProperties;
    }
    // stash original given properties
    this.originalProperties = taskLaunchConfig.deploymentProperties;

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

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

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

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

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

    // ctr
    const ctrPropertiesState = {
      isLoading: taskLaunchConfig.ctr.optionsState.isLoading,
      isOnError: taskLaunchConfig.ctr.optionsState.isOnError
    };

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

    formGroup.addControl('platform', platformControl);

    // Deployers
    const deployers = new UntypedFormArray([]);
    taskLaunchConfig.deployers.forEach((deployer: any) => {
      const groupDeployer: UntypedFormGroup = new UntypedFormGroup({});
      const validators = [];
      if (deployer.type === 'java.lang.Integer') {
        validators.push(TaskLaunchValidator.number);
      }
      groupDeployer.addControl('global', new UntypedFormControl(getValue(deployer.defaultValue), validators));
      taskLaunchConfig.apps.forEach((app: any) => {
        groupDeployer.addControl(app.name, new UntypedFormControl('', validators));
      });
      deployers.push(groupDeployer);
    });

    // Applications
    const appsVersion = new UntypedFormGroup({});
    taskLaunchConfig.apps.forEach((app: any) => {
      builderAppsProperties[app.name] = [];
      const control = new UntypedFormControl(null, (formControl: UntypedFormControl) => {
        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.taskLaunchService.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();
              if (this.refBuilder) {
                this.populateApp(this.refBuilder);
              }
            }
          );
        }
      });

      control.setValue(getValue(app.defaultValue));
      appsVersion.addControl(app.name, control);
    });

    // Useful methods for FormArray
    const addProperty = (array: UntypedFormArray) => {
      const group = new UntypedFormGroup(
        {
          property: new UntypedFormControl('', [TaskLaunchValidator.key]),
          global: new UntypedFormControl('')
        },
        {validators: TaskLaunchValidator.keyRequired}
      );

      taskLaunchConfig.apps.forEach(app => {
        group.addControl(app.name, new UntypedFormControl(''));
      });
      array.push(group);
    };

    const addArgument = (array: UntypedFormArray) => {
      const group = new UntypedFormGroup({
        global: new UntypedFormControl('')
      });

      taskLaunchConfig.apps.forEach(app => {
        group.addControl(app.name, new UntypedFormControl(''));
      });
      array.push(group);
    };

    const isEmpty = (dictionary): boolean => Object.entries(dictionary).every(a => a[1] === '');
    const clean = (val: Array<any>, array: UntypedFormArray, addField: (array: UntypedFormArray) => void) => {
      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 addField(array);
      }
    };

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

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

    // Dynamic Arguments
    const argumentsControls: UntypedFormArray = new UntypedFormArray([]);
    addArgument(argumentsControls);
    argumentsControls.valueChanges.subscribe((val: Array<any>) => {
      clean(val, argumentsControls, addArgument);
    });

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

    return {
      formGroup,
      builderAppsProperties,
      builderDeploymentProperties,
      taskLaunchConfig,

      deployers,
      appsVersion,
      globalControls,
      specificPlatformControls,
      argumentsControls,

      ctrProperties,
      ctrPropertiesState,

      arguments: {
        global: [],
        apps: {}
      },

      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: 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 taskLaunchConfig
   * @param control
   * @returns {string}
   */
  tooltip(taskLaunchConfig, control: AbstractControl): string {
    const arr = [];
    if (control instanceof UntypedFormGroup) {
      if (control.get('property')) {
        if (control.get('property') && control.get('property').invalid) {
          arr.push(this.translate.instant('tasks.launch.builder.tooltip.invalidProperty'));
        }
      }
      if (control.get('global') && control.get('global').invalid) {
        arr.push(this.translate.instant('tasks.launch.builder.tooltip.invalidGlobal'));
      }
      taskLaunchConfig.apps.forEach(app => {
        if (control.get(app.name).invalid) {
          arr.push(this.translate.instant('tasks.launch.builder.tooltip.invalidField', {name: app.name}));
        }
      });
    } 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 options = appId
      ? builder.builderDeploymentProperties.apps[appId]
      : builder.builderDeploymentProperties.global;

    // jee.foo.bar-xxx -> jee.foo
    const deduceKey = key => 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) =>
      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();
    });
    this.groupsPropertiesModal.setData(groupPropertiesSources);
    this.groupsPropertiesModal.title = this.translate.instant('tasks.launch.builder.deploymentPropertiesTitle');
    this.groupsPropertiesModal.isOpen = true;
  }

  getCtrProperties(ctrProperties: any[]): Array<{key: string; value: any}> {
    if (!ctrProperties) {
      return [];
    }

    return ctrProperties
      .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(p => p !== null);
  }

  openCtrProperties(builder: Builder) {
    const options = Object.assign(
      [],
      builder.ctrProperties.map(property => Object.assign({}, property))
    );
    const propertiesSource = new CtrPropertiesSource(options);

    propertiesSource.confirm.subscribe((properties: Array<any>) => {
      builder.ctrProperties = properties;
      this.changeDetector.markForCheck();
    });

    this.ctrPropertiesModal.setData(propertiesSource);
    this.ctrPropertiesModal.title = this.translate.instant('tasks.launch.builder.ctrPropertiesTitle');
    this.ctrPropertiesModal.isOpen = true;
  }

  /**
   * Load the properties of an app
   *
   * @param {{}} builderAppsProperties
   * @param {string} appId
   * @returns {Array}
   */
  getAppProperties(builderAppsProperties: any, 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 Task application properties modal.
   */
  openApp(builder: 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 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();
    });
    this.appPropertiesModal.app = new App();
    this.appPropertiesModal.app.name = app.name;
    this.appPropertiesModal.app.type = app.type;
    this.appPropertiesModal.app.version = version;
    this.appPropertiesModal.setData(appPropertiesSource);
    this.appPropertiesModal.isOpen = true;
  }

  /**
   * 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 properties to launch Output.
   */
  launchTask() {
    if (!this.isSubmittable(this.refBuilder)) {
      this.notificationService.error(
        this.translate.instant('commons.message.invalidFieldsTitle'),
        this.translate.instant('commons.message.invalidFieldsContent')
      );
    } else {
      this.launch.emit({props: this.getProperties(), args: this.getArguments()});
    }
  }

  getLabelAppVersion(taskLaunchConfig, app) {
    const lastVersion = get(taskLaunchConfig?.lastExecution?.deploymentProperties, `version.${app.name}`);
    if (lastVersion && app.version !== lastVersion) {
      return this.translate.instant('tasks.launch.builder.lastVersionLabel', {version: lastVersion});
    }
    return this.translate.instant('tasks.launch.builder.defaultVersionLabel', {version: app.version});
  }

  hasMigrations(): boolean {
    return this.migrations && this.migrations.migratedMatch && this.migrations.migratedMatch.length > 0;
  }

  migrate() {
    if (this.migrations) {
      const ctp = this.refBuilder.ctrProperties.find(prop => prop.id === 'composed-task-properties' && prop.value);
      if (ctp) {
        this.migrations.migratedMatch.forEach(m => {
          if (m[1]) {
            const prop: ValuedConfigurationMetadataProperty[] = this.refBuilder.builderAppsProperties[m[1]];
            if (prop) {
              prop.forEach(v => {
                if (v.id === m[3]) {
                  v.value = m[4];
                }
              });
            }
          }
        });
        ctp.value = this.migrations.migratedNomatch.join(',');
      }
    }
    this.migrations = this.calculateMigrations();
  }

  private calculateMigrations(): Migrations {
    const migratedNomatch: string[] = [];
    const migratedMatch: RegExpExecArray[] = [];
    if (this.task.composed) {
      const ctp = this.refBuilder.ctrProperties.find(prop => prop.id === 'composed-task-properties' && prop.value);
      if (ctp) {
        ctp.value.split(',').forEach(p => {
          const r = new RegExp(`app\\.${this.task.name}-(\\w*)\\.app\\.(\\w*)\\.(.*)=(.*)`);
          const match = r.exec(p);
          if (match) {
            migratedMatch.push(match);
          } else {
            migratedNomatch.push(p);
          }
        });
      }
    }
    return {
      migratedMatch,
      migratedNomatch
    };
  }
}

redpajama_set_name: RedPajamaGithub


text:
Jay Ajayi breaks through in Week 6, leads RB touches
*Welcome to "Committee Meetings," the running back portion of our weekly Targets/Touches column! Every Monday we'll recap the week that was among running backs and preview the outlook for the upcoming slate. *
With Week 6 nearly in the books, it's time to dive into some backfield breakdowns. We saw some familiar names among the top running back performers including LeSean McCoy, Jonathan Stewart and a breakout game from Jay Ajayi while guys like Duke Johnson and James White surprisingly made their way into the top 15 for the week. Some of the bigger disappointments of the week include Carlos Hyde and Isaiah Crowell, and below we'll try to figure out how to value these running backs going forward. That's enough small talk for now ... more on those backs and every other team's backfield below.
Note: The Opportunity Report is a living document and will be updated with the results of every game until after the Monday night game concludes. If you're looking for backfield touches data on a team and they aren't here yet, check back later.
This just in: David Johnson is still really, really good at football. He absolutely dominated the Jets defense in this game and single-handedly saved some fantasy matchups for owners who were in a hole going into Monday night. Andre Ellington is no threat to his workload, as he didn't get touches until the fourth quarter when this game was basically in the books. Johnson is an elite talent and arguably the best running back in the NFL, and fantasy. He'll face Seattle in Week 7.
We sort of had a feeling coming into Sunday that this was not a great week to mess around with Devonta Freeman and Tevin Coleman, but the way they've been playing made it hard to bench either. Unfortunately, the matchup played out here, and our worst fears became reality. The Freeman/Coleman tandem combined for a mere 67 yards against Seattle, the worst outing of the season by far. We're going to chalk this one up as simply a bad matchup and move on. The Falcons face the Chargers next, in Atlanta, so both Freeman and Coleman will be viable fantasy plays.
Fantasy owners who have stuck with Terrance West during his resurgence this season enjoyed two touchdowns and 87 yards against the Giants on Sunday. That was enough to put West in the top five among running backs in standard leagues by Sunday evening, and it was his second double-digit fantasy outing in the last three weeks. He benefitted from the offensive coordinator change, logging 23 carries after seeing just 11 last week. It's becoming more evident that Kenneth Dixon is not going to be a factor, at least not in the near future, and that this is West's starting gig to lose. He's a weekly RB2 starter but may find it tough sledding against the Jets in Week 7, so temper expectations some.
This game was set up perfectly for LeSean McCoy to erupt, and he did just that. Despite an injury scare in the first half, McCoy enjoyed a cake matchup against the 49ers and absolutely went off with three touchdowns and 140 rushing yards, averaging 7.4 yards per carry and helped the Bills cruise to a 45-16 win. He is having a special season thus far and has another ideal matchup next week against the Dolphins who have allowed tons of yards to running backs. McCoy might not post another three touchdowns, but he should be locked into lineups as an elite RB1.
Jonathan Stewart return to the Panthers backfield following a few weeks off with an injury, and took full advantage of a favorable matchup against the Saints defense. He looked 100 percent healthy averaging 4.5 yards per carry on his 19 rush attempts and punched in two short-yardage scores. Of course Cam Newton stole a rushing touchdown in the fourth quarter as he's known to do, but Stewart's owners can't really complain. JStew will have a week to rest with a bye up next, so his fantasy owners will need to find other options.
Rookie Jordan Howard has been dominating the snaps in Chicago's backfield the past two weeks, so we sort of knew a downtick in his workload was on the horizon. And while Howard did lead the team with 15 carries and scored a touchdown, Ka'Deem Carey rushed nine times and had more yardage than his rookie teammate. Howard is still the guy fantasy owners will want to start every week, but his 95 percent snap share from last week dropped to 68 percent in Week 6, though we knew that insane workload was not going to hold up for long. The Bears have a quick turnaround as they face the Packers on Thursday night in Week 7, and Howard should be a high-end RB2 in that game.
Despite his chest injury, Jeremy Hill had 13 carries against the Patriots, but managed just 38 yards on the ground. His status as a touchdown-dependent fantasy back was further solidified here. Giovani Bernard, who has now been the starter for two straight weeks totaled 19 touches in the game and fell just shy of 100 scrimmage yards. Furthermore, Bernard far out-snapped Hill by a count of 44 to 18 (68 percent to 29 percent). At one point, the Bengals tried punching the rock into the end zone from a few yards out three times with Bernard, but to no avail. Still, given his ability to gain yardage both through the air and on the ground, Bernard will be the upside play going forward. The Bengals backfield will enjoy a great matchup against the Cleveland Browns in Week 7, where Bernard should be considered an upside flex play and a must-start in PPR formats.
At halftime, the Browns as a team had eight total rushing yards. EIGHT. Isaiah Crowell struggled to get anything going, and it seemed like Cleveland made a real effort to get Duke Johnson involved early. That effort paid off for Johnson owners as he managed to pound in a 1-yard touchdown run very late in the fourth quarter. This marks two slow games in a row for Crowell and he'll be tough to start next week against the Bengals. One decent game from Johnson (on eight touches) isn't enough to sell us just yet, but if this keeps up, he may become a legitimate weekly flex option going forward.
The Broncos offense was broken on Thursday night which didn't help C.J. Anderson's production. In fact, he was out-rushed by Devontae Booker on fewer carries, which is not a good look. To his credit, Anderson had an impressive touchdown catch-and-run in the fourth quarter that was called back on a holding penalty. He's still the team's primary back working in on 76 percent of the team's snaps and while Booker has seen his workload increase due to game script the last two weeks, it's tough to advocate benching Anderson unless this turns into more of a timeshare. Either way, Anderson has a tough matchup against the Texans defensive front in Week 7.
Ezekiel Elliott keeps one-upping himself, this time collecting 174 yards from scrimmage following last week's 171-yard performance. He good. Any Zeke fantasy owner who was afraid of a matchup against a Packers defense that, coming into Sunday, had been one of the toughest against running backs should now be reassured that the Cowboys' stud rookie is matchup proof. He's got a comfortable lead as the NFL's leading rusher with 703 yards so far, with the next closest back, LeSean McCoy, at 587 rush yards. If this keeps up, Elliott could push for 2,000 yards by the end of the season. The Cowboys have a Week 7 bye so Zeke's fantasy owners will have to make do without him.
With Theo Riddick ruled out (ankle), and Dwayne Washington still not healthy enough to play, the Lions leaned on Zach Zenner to lead the way against the Rams. Zenner managed 58 rushing yards on his 14 carries and added two receptions for 19 yards but failed to get into the end zone. The newly acquired Justin Forsett is worth mentioning because he did play and had five carries, but they went for just five total yards. Until Theo Riddick gets back, this backfield will be tough to predict. Hopefully, we receive some clarity this week since the Lions face one of the worst run defenses in the NFL in the Washington Redskins next. If Zenner is projected to be the primary back again, he'll be a sneaky waiver add.
The Packers rolled Eddie Lacy out on a bum ankle, because quite simply, they had no other choice. James Starks was ruled out with a knee injury leaving Lacy as the only real option. He did some rumbling, and some hurdling, but for the most part was inefficient and as has been the case all season, has struggled due to the Packers' overall struggles on offense. He still hasn't scored a touchdown this year and now has just one double-digit fantasy outing in five games. He'll be hard to trust on a short week against the Bears Thursday night.
Ladies and gentlemen, the Lamar Miller game we've all been waiting for … FINALLY Miller broke though on Sunday night in front of a national television audience. If he didn't get into the end zone in this game, against a pathetic Colts run defense, it might have never happened. He actually scored twice in the game, once rushing and once on a reception in which he broke several tackles and looked like the Miller we grew to love last year in Miami during his hot streak. Miller ranks second in the NFL in rush attempts with 125 behind only Ezekiel Elliott, and his fantasy owners have to hope this is the start of a run. The Texans face the Broncos next week on Monday night.
Frank Gore dropped 100 rushing yards for the first time this season, and as is the case every week, he just keeps going. Even with the Colts offense looking like a broken unit, Gore is the only reliable option out of the backfield, and the team keeps handing him the rock. There's virtually no upside with Gore, but his consistent floor is the kind of flex running back that dreams are made of.
There was a report late in the week that T.J. Yeldon had apparently earned himself feature back duties in Jacksonville. Then the game started on Sunday and Chris Ivory led the backfield with 11 carries and scored a touchdown. Welp. It was the first game all season where Ivory actually had an impact for fantasy owners, so maybe the Jaguars changed something during their bye week, or maybe Ivory is finally healthy enough to serve the primary back. Either way, the overall production here was not encouraging. Ivory will be a touchdown-dependent RB3 going forward while Yeldon, who had just 26 yards, will be nothing more than a matchup-based flex. The Jaguars face a struggling Raiders defense in Week 7, so Ivory should be considered as a low-end fantasy starter.
This was the game where Jamaal Charles' fantasy owners finally felt safe starting him given all the hype leading up to the game that he was ready to rock. Well, Charles did get a touchdown here, but Spencer Ware was the real star, with 163 yards from scrimmage and a touchdown on his 26 touches. Charles played just 15 snaps in the game compared to Ware's 40. In a strange twist of fantasy cruelty, Charles became Ware's touchdown vulture. Hopefully, Charles' playing time increases as the season goes on, and as we've seen his entire career, Charles is not the kind of player that needs a huge workload to produce elite fantasy numbers. But we have to face the fact that Spencer Ware is a real thing, and he's not going away. The Chiefs face the Saints at home in Week 7.
Given Todd Gurley's poor efficiency on the ground this season, he played well on Sunday with 4.1 yards per carry on his 14 rush attempts and added four receptions for 39 yards. It seems like the Rams have finally figured out that one of the most athletic players in the NFL actually can catch the ball since Gurley now has 124 receiving yards in his last three games. That receiving production will help boost his floor when he's unable to get into the end zone and today was a perfect example of that as he finished with nine fantasy points despite zero touchdowns. In Week 7 Gurley faces a Giants defense that has had a tough time stopping running backs the last few games.
Nobody saw this coming. The Dolphins rode the Jay-Train on Sunday, as Jay Ajayi shredded the Steelers defense for 204 yards on 25 carries and scored twice. On his last carry of the game, he took a 62-yard scamper to the house for his second score, putting him over the 200-yard mark for the day. We thought things would look different with Arian Foster back on the field, but this was Ajayi's breakthrough week. Ajayi stated his case as to why the team should feature him on a weekly basis and has now led the team's running backs in touches for three weeks in a row. The Dolphins face Buffalo in Week 7, so while it's not a great matchup, Ajayi will still have flex value at worst given his uptick in volume and the fact that he may be finally finding a groove in this offense.
We knew LeGarrette Blount's volume would dip with Brady back, and it has, but he's still finding the end zone, which is great for his fantasy owners. He scored again on Sunday and now has scored in all but one game this season. Fantasy owners need to keep rolling him out despite his diminished role as he's a touchdown threat each week. James White may be the biggest beneficiary of Brady's return as he had two receiving touchdowns, his first scores of the season, against Cincinnati. The young pass-catching back out-snapped Blount 37 to 24 (61 percent to 39 percent) and tied for the team lead in targets on Sunday with nine, hauling in eight of them. On the season, White is tied for second on the team in targets (33) with Martellus Bennett and is making a case to be an every-week flex play.
After watching Jacquizz Rodgers gain over 100 rushing yards against the Panthers last week, you'd think Mark Ingram would have had a better day. But that wasn't the case as he averaged just 3.2 yards per rush on his 16 attempts. He still lead the team in rushing but that isn't saying much. His 52 yards was his second-lowest rushing total of the season. And in an ironic twist of fate, tight end Coby Fleener scored the Saints' only rushing touchdown in a game where the offense put up 41 points. Ingram and the Saints head to Kansas City in Week 7, where he will be a touchdown-dependent RB2.
There is not much to say about the Giants backfield production against the Ravens other than the fact that it was worse than anyone could have imagined. Even with Rashad Jennings back in the mix, the Giants running back trio combined for just 38 rush yards the entire game. They'll face the Rams in Week 7 so fantasy owners should steer clear for another week.
It feels like an eternity ago when Matt Forte was a top-five fantasy running back, all the way back in Week 1 and Week 2. Life comes at you fast in the NFL, and the Jets offense is not helping his cause. There's virtually no upside in starting him, or Bilal Powell. Even in a PPR league, Powell is a low-end flex at best. Until the Jets get right, which may not happen with Geno Smith under center, it will be hard to trust this backfield going forward. Sad!
For the second week in a row, the Raiders backfield was without Latavius Murray, and for the second week in a row this was a backfield that fantasy owners should have avoided. DeAnrdre Washington was efficient but had just 10 carries in the game as the Raiders were playing from behind. It's odd then, that Jalen Richard only had six touches. Either way, until Murray gets back, we want nothing to do with this backfield; it's one of the most muddled situations in the NFL.
The Eagles backfield failed to take advantage of a great matchup against one of the worst run defenses in the league. Philly's backs combined for 92 rushing yards with zero touchdowns. Ryan Mathews, whose nine carries led the team, did averaged 6.7 yards per carry which was nice to see considering he's running at a 3.3 yards per carry clip this season. There's really not much else to say about this backfield other than the fact that it was an extremely disappointing week. Philadelphia has a rough matchup against the Vikings in Week 7, so it will be difficult to recommend starting any of these guys in fantasy.
Technically, Le'Veon Bell led the Steelers backfield with 53 rushing yards, but wideout Darrius Heyward-Bey gained more rushing yards than Bell on a single, 60-yard touchdown run in the first half. Still, Bell's 10 carries led the team. Bell also led the team in receiving with six catches for 55 yards. His usage makes sense given that he was on the field for 96 percent of the Steelers snaps. For Bell's standards it was a disappointing game, but he still had over 100 yards from scrimmage for the third straight game. He has yet to score a touchdown this year, but his fantasy owners can't complain about three-straight double-digit performances. He'll look to keep it going against New England next week.
Hyde was ineffective in a blowout loss in Buffalo, and even worse, suffered a shoulder injury in the third quarter. He returned to the game which is a good sign, but his practice participation will need to be monitored all week. A shoulder injury could be devastating for Hyde's fantasy owners especially seeing as he's scored four touchdowns in his previous three games and has six on the season. For that reason, as long as he suits up, he's a must-start in fantasy. There's also a chance that Colin Kaepernick saps some of Hyde's value going forward (Kap out-rushed Hyde on Sunday), but if the San Francisco running back is healthy, he's nearly impossible to bench in a great matchup against Tampa Bay in Week 7.
Melvin Gordon ran well on Thursday night but for the first time all season, he failed to find pay dirt. He did log 27 carries, a season-high, but considering the volume, his fantasy owners would have liked to see more than just 94 yards on the ground. Gordon remains a weekly must-start and still leads the NFL in red-zone rush attempts (27). He'll face the Falcons, who are frequently gashed by opposing running backs, in Week 7.
The 2016 Awakening Tour featuring Christine Michael continued on Sunday as the resurgent running back dropped two touchdowns and 83 total yards on the Falcons. C-Woke now has five touchdowns in his last three games and his elite athletic prowess is on display every time he touches the ball. He did have an injury scare in the second quarter and was on the sideline being examined when rookie Alex Collins fell into the end zone from two yards out, that could have been Michael's third score of the day. But hey, we'll take two. C.J. Spiller got a bit of work but it was nothing to write home about. The 2016 Awakening Tour rolls into Arizona in Week 7, where Michael needs to be started but fantasy expectations should be tempered, just a bit.
DeMarco Murray struggled with just 65 rush yards on 21 carries, which is surprising considering the cushy matchup against the Browns. We'll consider this an outlier for Murray who has looked simply unstoppable this season. He'll be a top-tier RB1 in Week 7 against the Colts, so make sure you keep him locked into your lineups.
We're starting to sense a trend with Matt Jones. He tends to go off every other game, so after a slow Week 5, he posted 135 rush yards and a score against the Eagles this week. He'll be hard to leave on your bench against the Lions in Week 7, despite the trend. Rob Kelley got more work than he's had all season with 59 yards on five carries, but Jones was the clear lead back. Chris Thompson's 66 all-purpose yards were respectable for PPR owners, but as long as the Redskins keep feeding Jones 15-20 carries a game, he'll be the only member of this backfield worth starting.
You can rely on NFL.com's Touches and Targets column for great fantasy advice. Just like you can rely on FedEx Ground for fast and affordable shipping.
Matt Franciscovich is an associate fantasy writer/editor for NFL.com. Follow him on Twitter @MattFranchise
Fantasy targets & touches: The 2017 year in review
With the 2017 regular season nearly over, Matt Harmon looks back on the year that was from a targets and touches perspective, going team-by-team highlighting the top volume getters for each squad.
Fantasy Touches & Targets: Todd Gurley dominates
We recap all of the relevant targets and touches highlights from Week 16 fantasy football action. From Todd Gurley's dominant performance to Antonio Gates' unlikely eruption, catch up here.
Fantasy Touches & Targets: Todd Gurley for the win
We recap all of the Week 15 fantasy football action including Rams running back Todd Gurley's four-touchdown eruption and Giants receiver Sterling Shepard's big day.
Fantasy Touches & Targets: Kareem Hunt rebounds
The name of the game in fantasy football is volume. We recap all of the running back touches and wide receiver targets from Week 14 here, including Kansas City Chiefs rookie Kareem Hunt's bounce-back.
Fantasy Touches & Targets: Jamaal Williams thrives
Fantasy Touches & Targets: Alvin Kamara dominates
The name of the game in fantasy football is volume. We recap all of the running back touches and wide receiver targets from Week 12 here, including Alvin Kamara's huge game against the Rams.
Fantasy Touches & Targets: Keenan Allen erupts
The name of the game in fantasy football is volume. We recap all of the running back touches and wide receiver targets from Week 11 here, including Keenan Allen's huge game against the Bills.
Fantasy Touches & Targets: Adam Thielen goes off
We recap all of the Week 10 fantasy football action including Minnesota Vikings receiver Adam Theilen's huge day and New Orleans Saints running back Mark Ingram's dominance of the Bills.
Fantasy Touches & Targets: Alvin Kamara breaks out
We recap all of the target and touch leaders from Week 9 fantasy football action including New Orleans Saints back Alvin Kamara's huge day and Alshon Jeffery's unlikely performance against the Denver Broncos.
Fantasy Touches & Targets: DeAndre Hopkins erupts
Volume is the name of the game in fantasy football. We're here to recap what went down in Week 8 as we go over all of the running back touches and wide receiver targets, including DeAndre Hopkins' career day.
Fantasy Touches & Targets: LeSean McCoy rules
Fantasy football is all about volume. We recap all of the targets and touches opportunities from Week 7 action including LeSean McCoy's big day and Jarvis Landry's solid outing.
Fantasy Touches & Targets: Larry Fitzgerald dominates
Volume is the name of the game in fantasy football. We recap all the wide receiver targets and running back touches from Week 6 including Larry Fitzgerald's huge day in Arizona.

redpajama_set_name: RedPajamaCommonCrawl


text:
Clubs need to negotiate with Levy first for Pochettino appointment (Telegraph)
03 Dec / autty
Arsenal and Manchester United face the daunting prospect of having to negotiate with Tottenham Hotspur chairman Daniel Levy if they want to appoint Mauricio Pochettino this season.
Pochettino has been replaced by Jose Mourinho at Spurs, but Telegraph Sport can reveal that, under the terms of his exit, the club would still be entitled to compensation from any club, at home or abroad, that attempts to appoint the Argentine before the summer.
Although Pochettino has left Tottenham, he is not a free agent and any club wishing to appoint him would have to agree a compensation package with Levy. As well as Premier League clubs, that also applies to overseas clubs such as Bayern Munich and Real Madrid.
Contrary to a report over the weekend, Pochettino would not have to pay back £12.5million in compensation, if he took another Premier League job before the end of the season.
Instead, it is understood Pochettino would have to give up the money he was still due if he accepted a job at another club and that club would have to pay compensation to Tottenham if they managed to strike an agreement with Levy.
Whether or not Levy would even be prepared to negotiate with Arsenal or United during the season is not certain and Tottenham refuse to discuss the contracts of any of their past or present employees.
It is unclear how long past the end of the season Spurs could still be entitled to compensation, but it was striking that in the statement confirming Pochettino's departure, the club said he and his coaching staff had been 'relieved of their duties'.
Related: Tottenham HotspurPochettino


There has been unrest in recent months due the Sandanista governments lack of response to wild fires in remote regions of the country. There was clamor about the suppression of information as well. Those protestors made their way to Nicaragua to raise awareness at the end of March. It did not escalate at the time though.
This week's unrest started on Wednesday. It actually has been an issue that has been boiling for a couple years now. The Nicaragua Social Security Institute (INSS) has been accused in La Prensa (as far back as 2009) of loaning out funds to political allies of the government for a wide variety of projects, buildings, housing developments, business building and then other more nefarious accusations. As a result of the abuse of funds INSS is on the brink of financial collapse. Over the last two years the government has raised INSS payroll tax from 16% to the current 19%. That only affected employers.
Then on Wednesday the Sandanista Government announced wide ranging INSS tax hikes that affect everyone. If I understand it correctly it will raise payroll tax to 22.5%, raise tax on employees by .75%, eliminate max salary limits on income tax, and worst of all reduces the pensions of the retired elderly by 5%. People are upset at all levels, mostly because they feel they aren't getting the benefits of the tax, while at the same time there is corrupt use of the funds.
Protests and marches began on Wednesday. Then, government employees were sent to the street to protest in favor of the hikes. Then the police and riot police tried to bring order. Violence erupted when government supported agitators joined into the protest and escalated things. Yesterday three people died in the violence related to the protests. The President and First Lady have called for calm, but at the same time shut down major news outlets that were covering the story. We are praying for calm but it also seems like a lot of people are fed up with corruption and they want changes. It is not a good situation.
We are praying for everything to be resolved peacefully. We are asking God to bring justice and truth to the situation and our nation as a whole.
I hope this helped inform things a little bit and thank you for praying with us!
Update Saturday Night April 21: There has been more protesting today and sadly there are more reported deaths including a reporter on the East Coast. Daniel Ortega did speak today and call for calm and dialogue, but people are frustrated by the violent suppression of peaceful protests by his regime. It doesn't seem it will stop and there are still protests going on tonight. Some of the huge metal symbolic trees the Ortegas put up all around the capital are being torn down.
I appreciated this video from neighboring Costa Rica…people gathered in their national stadium praying for Nicaragua. Amen.
Update Sunday Morning April 22: international coverage NewYork Times, CNN, Al Jazeera, BBC, The Gaurdian (the Pope has called for end of violence) And still the best site I have found for current videos and often a livestream on FB is local news here in Nicaragua.
This morning during church we all prayed together for our nation as we sung the worship song "Sana Nuestra Tierra" Please do keep praying for Nicaragua.
Update Sunday Afternoon April 22: Today has been a nervous day. There was lots of looting and rushes on grocery stores and gas stations. The looting is the scary part as many are saying that Sandanista Youth agitators are intentionally causing unrest by looting stores. Many if not most Nicaraguans are going to their corner stores and local supermarkets and defending them when the agitators arrive. Our friends Michael and Jessica Folsom live near the looting area and they shared this live video this morning. Our neighborhood has gathered at our highway entrance to stop looters who are traveling in vehicles around Managua. Below is a video of a looter being confronted with a TV he stole, caution on the language in Spanish. It is a bit violent but shows how the people aren't allowing it.
Update Monday Morning April 23: Yesterday President Daniel Ortega held a press conference with Free Trade Zone businesses to announce he would not implement the Social Security Reforms he proposed that started the unrest. That was a good step and the evening was calmer. But then last night the police attacked University students protesting at a state University and several students were shot and at least one reportedly died. Today there is supposed to be a major march. We are praying the government has decided to stop using deadly force to stop protests and marches. Today will be telling as to whether or not things will calm down.
We usually fast on Mondays until 5pm here with Open Hearts and would ask you to join us today and keep Nicaragua in your prayers throughout the day. Thank you.
Update Monday Night April 23: The march this afternoon in Managua was pretty incredible, there were tens of thousands if not hundreds of thousands of people who took to the streets. And Praise Jesus, there was no violence against the marchers. This was a great sign and hopefully tomorrow will bring some more steps in a peaceful direction. There are still some really hard feelings among the people after all that has happened, so we will keep praying for peace, truth and justice.
Update Tuesday Morning April 24: The night passed without any violence which is also very positive. Today we really don't know what is going to happen, but the protests and call for regime change are still there. The march was huge and it could have been bigger if public transport hadn't been closed down. We are praying today for more peace and always for truth and justice.
This news report is very informative…thank you for your continued prayers!
Update Wednesday Morning April 25: Yesterday was peaceful from morning to evening. There were protests at the National Assembly building and also the Ortega Regime released some of the protesters from prison. The release was criticized because they gave each kid 20 cordobas and dropped them on a highway way out of town without advising anyone. But there was no more violence, praise Jesus! One concerning development is the government decided to cut the free wifi they previously installed at all the public parks. School is supposedly starting back up today, but it will not be a normal day considering the Ministry of Education informed everyone yesterday at 5 p.m.
Krista and I went down to Club Esperanza to help serve the food and spend time with our staff. It was a nice time and everyone seemed to be pretty up beat considering the chaos of the last week. We are hesitantly encouraged and praying for things to continue to calm down! The students at the University plan to hold a march today and we are praying that it will not be attacked. The clamoring for change is still in the air. Thank you for your continued prayers for Nicaragua. Peace, truth and justice, in Jesus name. Amen.
News Stories: Havana Times, The Hill (white house reaction), BBC. Also below is Jorge Ramos' interview with Carlos Chamorro who is the son of Violeta Chamorro the first Nicaraguan elected president after the Sandanista revolution. He is a well known journalist here and if you speak Spanish is it worth the update.
There have continued to be marches and memorials services for the dead over the last two days. Again, I say praise Jesus that it has been peaceful and there has been no violence. One of the hardest things to comprehend about this is that in just a short time 63 people were killed and 15 are still missing. The initial spark of the protests was about tax hikes against the people's social security system. But the reaction by the government to crackdown with lethal violence against protests to such an extreme degree is something that is not going to be forgotten by the people. How could anyone just let that pass? Things have been calm but people are very upset and still clamoring for change.
Tonight is a candle light march planned from one of the main universities to one of the major rotundas in Managua. Please keep praying for the peace to continue tonight and into the weekend. On Sunday there is supposed to be a "Dialogue" between the Ortega government and all those calling for his ouster. We are praying for Truth and Justice as those things move forward.
Again, considering how rapidly everything descended into chaos last week its hard to know what will happen moving forward. But we are praying God's hand be upon it. If a change were to come it can happen peacefully, history tells us it is possible. Mr. Buzbee was reflecting yesterday how the Berlin Wall fell, not a single shot fired. One person started hitting with a sledge hammer, the soldiers in their turrets didn't fire, and all of a sudden it was coming down, peace came to Europe. We are praying for the best case scenario if change comes to Nicaragua. How can it not come? 63 people killed in a weekend of protests in such a small nation…Lord we pray for peace, justice and truth.
The major national print Newspaper La Prensa ran a story today to feature the story of 34 of the victims who died during the protests and deadly government reaction last weekend. There are reports of more than 60 deaths nation wide. Click here for the story.
This afternoon there is supposed to be a march to the national Catholic cathedral for a prayer gathering to remember those who were killed. The Catholic Church has been very vocal in support of the students. A popular Bishop Silvio Baez was brought to tears in an interview when talking about the students who were killed. Baez has been out in front supporting the university students and calling for peace since the beginning. Tomorrow there is supposed to be a national dialogue to discuss how to move forward peacefully. The talks are supposed to include President Daniel Ortega. The nation is pretty tense waiting for this "dialogue". Nicaraguans from the more remote parts of the country and the agricultural areas, referred to as "campensinos" by the broader population, are also coming to Managua in caravans to join the weekend marches.
Update Monday April 30th: Saturday was an incredible prayer march for Peace and Justice organized by the Catholic church and joined by tens of thousands if not over 100,000 people of all backgrounds carrying the Nicaraguan flag. It was powerful and this video I saw this evening captures it so well…We are pleading with God for Peace, Justice and Truth in Nicaragua…please keep praying!
Unfortunately that link is no longer available….but this is another moving video of the marches remembering those killed overlaid with a speech given by Pedro Juaquin Chamorro in 70's. Chamorro was a hero of all political parties, and his death was mourned by the entire nation.
Reading updates from you, talking with Luis, praying and fasting. I love you all, Farringtons and Buzbees!


Exclusive Large Breed Adult Chicken and Brown Rice Dog Food is rated 5.0 out of 5 by 1.
Chicken, chicken meal (source of glucosamine and chondroitin), whole grain brown rice, cracked pearled barley, brewers rice, oatmeal, turkey meal, dried plain beet pulp, chicken fat (preserved with mixed tocopherols), natural flavor, dried egg product, flaxseed, fish meal, salt, potassium chloride, vitamins (vitamin E supplement, d-calcium pantothenate, vitamin A supplement, niacin supplement, riboflavin supplement [vitamin B2], thiamine mononitrate [vitamin B1], pyridoxine hydrochloride [vitamin B6], vitamin D3 supplement, menadione sodium bisulfite complex [source of vitamin K activity], folic acid, biotin, vitamin B12 supplement), minerals (calcium carbonate, zinc oxide, zinc proteinate, ferrous sulfate, manganous oxide, copper sulfate, manganese proteinate, calcium iodate, copper proteinate, sodium selenite, cobalt carbonate), brewers dried yeast, salmon oil (source of EPA & DHA), choline chloride, blueberries, cranberries, alfalfa meal, dried chicory root, turmeric, L-ascorbyl-2-polyphosphate (source of ascorbic acid), citric acid (preservative), mixed tocopherols (preservative), L-carnitine, yucca schidigera extract, beta carotene, rosemary extract, yeast culture, dried Enterococcus faecium fermentation product, dried Lactobacillus acidophilus fermentation product, dried Aspergillus niger fermentation extract, dried Trichoderma longibrachiatum fermentation extract, dried Bacillus subtilis fermentation extract.
Nutrition Statement: Exclusive® Large Breed Adult Dog Chicken & Brown Rice Formula is formulated to meet the nutritional levels established by the AAFCO Dog Food Nutrient Profiles for maintenance of adult dogs. Calorie Content (Calculated): Metabolizable Energy (ME) 3,597 kcal/kg, 1,632 kcal/lb., 375 kcal/cup (Based on a standard 8-ounce dry measuring cup).
Over 1505-1/4 + 1/2 cup for each additional 10 lbs. over 150 lbs.
Rated 5 out of 5 by test from Great features! Curabitur blandit tempus porttitor. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Maecenas sed diam eget risus varius blandit sit amet non magna. Vestibulum id ligula porta felis euismod semper.