function buildAutoFillAction()

in client/src/components/pipelines/browser/metadata-controls/auto-fill-entities.js [234:448]


function buildAutoFillAction (options) {
  const {
    sourceItems,
    targetItems,
    insertedItems,
    removedItems,
    sourceColumns,
    targetColumns,
    removedColumns,
    insertedColumns
  } = options;
  if (removedItems.length > 0 || removedColumns.length > 0) {
    return undefined;
  }
  const getNumberShift = value => {
    if (!value) {
      return 0;
    }
    const exec = /^[\\-]?(0+[\d]+)$/.exec(`${value}`);
    if (exec && exec.length === 2) {
      return exec[1].length;
    }
    return 0;
  };
  const getShiftedNumber = (number, shift = 0) => {
    const string = `${Math.abs(number)}`;
    const sign = number < 0 ? '-' : '';
    const appendZeroCount = Math.max(0, shift - string.length);
    if (appendZeroCount > 0) {
      return `${sign}${(new Array(appendZeroCount)).fill('0').join('')}${string}`;
    }
    return `${sign}${string}`;
  };
  const splitValue = value => {
    if (!value) {
      return {
        value: undefined
      };
    }
    if (!Number.isNaN(Number(value))) {
      return {
        value,
        number: +value,
        string: '',
        shift: getNumberShift(value)
      };
    }
    const exec = /^(.*[^\d])([\d]+)$/.exec(`${value}`);
    if (exec && exec.length === 3) {
      return {
        value: exec[0],
        string: exec[1],
        number: +exec[2],
        shift: getNumberShift(exec[2])
      };
    }
    return {
      value
    };
  };
  const getValuesDiff = values => {
    if (values.length === 0) {
      return undefined;
    }
    if (values.some(value => value.number === undefined)) {
      return undefined;
    }
    if ((new Set(values.map(value => value.string))).size > 1) {
      return undefined;
    }
    if (values.length === 1) {
      return Math.sign(values[0].number);
    }
    const diff = values[1].number - values[0].number;
    for (let i = 2; i < values.length; i++) {
      if (values[i].number - values[i - 1].number !== diff) {
        return undefined;
      }
    }
    return diff;
  };
  const getValuesShift = values => {
    if (values.length === 0) {
      return 0;
    }
    if (values.some(value => value.number === undefined)) {
      return 0;
    }
    if ((new Set(values.map(value => value.string))).size > 1) {
      return 0;
    }
    return Math.max(...values.map(value => value.shift || 0));
  };
  const valuesAreAutoIncrementable = values => !!getValuesDiff(values);
  const values = [];
  for (let sRow = 0; sRow < sourceItems.length; sRow++) {
    const row = [];
    for (let sColumn = 0; sColumn < sourceColumns.length; sColumn++) {
      const source = sourceItems[sRow].item;
      const column = sourceColumns[sColumn].key;
      const value = source.hasOwnProperty(column)
        ? source[column].value
        : undefined;
      row.push(splitValue(value));
    }
    values.push(row);
  }
  const rotatedValues = [];
  for (let sColumn = 0; sColumn < sourceColumns.length; sColumn++) {
    const row = [];
    for (let sRow = 0; sRow < sourceItems.length; sRow++) {
      const source = sourceItems[sRow].item;
      const column = sourceColumns[sColumn].key;
      const value = source.hasOwnProperty(column)
        ? source[column].value
        : undefined;
      row.push(splitValue(value));
    }
    rotatedValues.push(row);
  }
  const getIncrementedValue = table => {
    const diffs = table.map(getValuesDiff);
    const shifts = table.map(getValuesShift);
    return (source, incrementRatio) => {
      if (source < 0 || source >= table.length) {
        return undefined;
      }
      const row = table[source];
      if (row.length === 0) {
        return undefined;
      }
      const last = row[row.length - 1];
      const diff = diffs[source];
      const shift = shifts[source] || 0;
      if (diff === undefined || last.number === undefined) {
        return undefined;
      }
      const number = last.number + incrementRatio * diff;
      const shiftedNumber = getShiftedNumber(
        !last.string
          ? number
          : Math.abs(number),
        shift
      );
      return `${last.string || ''}${shiftedNumber}`;
    };
  };
  if (insertedItems.length > 0 && rotatedValues.some(valuesAreAutoIncrementable)) {
    const fn = getIncrementedValue(rotatedValues);
    const processItem = (item, index) => {
      const source = sourceItems[index % sourceItems.length].item;
      const payload = {...item.item};
      for (let c = 0; c < targetColumns.length; c++) {
        const column = targetColumns[c].key;
        const value = fn(c, index + 1);
        const type = source.hasOwnProperty(column)
          ? source[column].type
          : undefined;
        if (value === undefined) {
          delete payload[column];
        } else {
          payload[column] = {
            value,
            type: type || 'string'
          };
        }
      }
      return mapItemSaveOperation(payload, options);
    };
    return {
      title: 'Fill cells',
      loadingMessage: 'Filling...',
      action: () => new Promise((resolve, reject) => {
        Promise
          .all(insertedItems.map(processItem))
          .then(resolve)
          .catch(reject);
      })
    };
  }
  if (insertedColumns.length > 0 && values.some(valuesAreAutoIncrementable)) {
    const fn = getIncrementedValue(values);
    const processItem = (item, index) => {
      const payload = {...item.item};
      for (let c = 0; c < insertedColumns.length; c++) {
        const sourceColumn = sourceColumns[c % sourceColumns.length].key;
        const targetColumn = insertedColumns[c].key;
        const value = fn(index, c + 1);
        const type = payload.hasOwnProperty(sourceColumn)
          ? payload[sourceColumn].type
          : undefined;
        if (value === undefined) {
          delete payload[targetColumn];
        } else {
          payload[targetColumn] = {
            value,
            type: type || 'string'
          };
        }
      }
      return mapItemSaveOperation(payload, options);
    };
    return {
      title: 'Fill cells',
      loadingMessage: 'Filling...',
      action: () => new Promise((resolve, reject) => {
        Promise
          .all(targetItems.map(processItem))
          .then(resolve)
          .catch(reject);
      })
    };
  }
  return undefined;
}