<commit_before>import React, { PropTypes } from 'react';
import Immutable from 'immutable';
import SidebarHeader from './SidebarHeader';
import SidebarControls from './SidebarControls';
import { columnTitle } from '../../../domain/dataset';

function deriveTransformationDescription(transformation) {
  const newColumnTitle = transformation.getIn(['args', 'newColumnTitle']);
  const code = transformation.getIn(['args', 'code']);
  return (
    <div>
      Derived {newColumnTitle} using
      <pre style={{ fontFamily: 'monospace' }}>{code}</pre>
    </div>
  );
}

// Search the transformation list to find the column title and fall back to the columnTitle
// function if this column hasn't been transformed before.
function findTitle(columnName, transformations, index, columns) {
  const historyTransformation = transformations.take(index).reverse().find(transformation =>
      transformation.getIn(['changedColumns', columnName]));
  if (historyTransformation != null) {
    return historyTransformation.getIn(['changedColumns', columnName, 'after', 'title']);
  }

  const futureTransformation = transformations.skip(index).find(transformation =>
    transformation.getIn(['changedColumns', columnName]));
  if (futureTransformation != null) {
    return futureTransformation.getIn(['changedColumns', columnName, 'before', 'title']);
  }

  return columnTitle(columnName, columns);
}

function combineTransformationDescription(transformations, index, columns) {
  const transformation = transformations.get(index);
  const firstColumnName = transformation.getIn(['args', 'columnNames', 0]);
  const secondColumnName = transformation.getIn(['args', 'columnNames', 1]);
  const firstTitle = findTitle(firstColumnName, transformations, index, columns);
  const secondTitle = findTitle(secondColumnName, transformations, index, columns);
  const newColumnTitle = transformation.getIn(['args', 'newColumnTitle']);
  return `Combined columns ${firstTitle} and ${secondTitle} into "${newColumnTitle}"`;
}

function transformationDescription(transformations, index, columns) {
  const transformation = transformations.get(index);
  const op = transformation.get('op');
  const columnName = transformation.getIn(['args', 'columnName']);
  const title = transformation.getIn(['changedColumns', columnName, 'before', 'title']);
  switch (op) {
    case 'core/to-lowercase':
      return `${title} to lowercase`;
    case 'core/to-uppercase':
      return `${title} to uppercase`;
    case 'core/to-titlecase':
      return `${title} to titlecase`;
    case 'core/trim':
      return `${title} trimmed whitespace`;
    case 'core/trim-doublespace':
      return `${title} trimmed double spaces`;
    case 'core/change-datatype':
      return `${title} datatype to ${transformation.getIn(['args', 'newType'])}`;
    case 'core/sort-column':
      return `${title} sorted ${transformation.getIn(['args', 'sortDirection'])}`;
    case 'core/rename-column':
      return `Renamed ${title} to ${transformation.getIn(['args', 'newColumnTitle'])}`;
    case 'core/delete-column':
      return `Deleted ${title}`;
    case 'core/combine':
      return combineTransformationDescription(transformations, index, columns);
    case 'core/derive':
      return deriveTransformationDescription(transformation);
    default:
      return op;
  }
}

function TransformationListItem({ transformations, index, columns }) {
  const transformation = transformations.get(index);
  const isUndoingTransformation = transformation.get('undo');
  const isPendingTransformation = transformation.get('pending') && !isUndoingTransformation;

  return (
    <div style={{ opacity: isUndoingTransformation ? 0.5 : 1 }}>
      <div>
        {transformationDescription(transformations, index, columns)}
      </div>
      {isPendingTransformation && <div style={{ fontSize: '0.6em', marginTop: 4 }}>APPLYING TRANSFORMATION</div>}
      {isUndoingTransformation && <div style={{ fontSize: '0.6em', marginTop: 4 }}>UNDOING TRANSFORMATION</div>}
    </div>);
}

TransformationListItem.propTypes = {
  transformations: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired,
  columns: PropTypes.object.isRequired,
};

function TransformationList({ transformations, columns }) {
  return (
    <div
      className="TransformationList"
    >
      <ol>
        {transformations.map((transformation, index) => (
          <li key={index}>
            <TransformationListItem
              transformations={transformations}
              columns={columns}
              index={index}
            />
          </li>
        ))}
      </ol>
    </div>
  );
}

TransformationList.propTypes = {
  transformations: PropTypes.object.isRequired,
  columns: PropTypes.object.isRequired,
};

function markUndo(transformations, idx) {
  if (idx < 0) {
    return transformations;
  }
  if (transformations.getIn([idx, 'undo'])) {
    return markUndo(transformations, idx - 1);
  }
  return transformations.setIn([idx, 'undo'], true);
}

function transformationLog(persistedTransformations, pendingTransformations) {
  let allTransformations = persistedTransformations;
  pendingTransformations.forEach((pendingTransformation) => {
    if (pendingTransformation.get('op') === 'undo') {
      allTransformations = markUndo(allTransformations, allTransformations.size - 1);
    } else {
      allTransformations = allTransformations.push(pendingTransformation.set('pending', true));
    }
  });
  return allTransformations;
}

export default function TransformationLog({
  onClose,
  onUndo,
  transformations = Immutable.List(),
  columns,
  pendingTransformations,
}) {
  const allTransformations = transformationLog(transformations, pendingTransformations);

  return (
    <div
      className="DataTableSidebar"
      style={{
        width: '300px',
        height: 'calc(100vh - 8rem)',
      }}
    >
      <SidebarHeader onClose={onClose}>
        Transformation Log
      </SidebarHeader>
      <TransformationList
        transformations={allTransformations}
        columns={columns}
      />
      <SidebarControls
        positiveButtonText="Undo"
        onApply={
          allTransformations.every(transformation => transformation.get('undo')) ? null : onUndo
        }
        onClose={onClose}
      />
    </div>
  );
}

TransformationLog.propTypes = {
  onClose: PropTypes.func.isRequired,
  onUndo: PropTypes.func.isRequired,
  transformations: PropTypes.object,
  columns: PropTypes.object.isRequired,
  pendingTransformations: PropTypes.object.isRequired,
};
<commit_msg>[#545] Fix text transform transformation log descriptions<commit_after>import React, { PropTypes } from 'react';
import Immutable from 'immutable';
import SidebarHeader from './SidebarHeader';
import SidebarControls from './SidebarControls';
import { columnTitle } from '../../../domain/dataset';

function deriveTransformationDescription(transformation) {
  const newColumnTitle = transformation.getIn(['args', 'newColumnTitle']);
  const code = transformation.getIn(['args', 'code']);
  return (
    <div>
      Derived {newColumnTitle} using
      <pre style={{ fontFamily: 'monospace' }}>{code}</pre>
    </div>
  );
}

// Search the transformation list to find the column title and fall back to the columnTitle
// function if this column hasn't been transformed before.
function findTitle(columnName, transformations, index, columns) {
  const historyTransformation = transformations.take(index).reverse().find(transformation =>
      transformation.getIn(['changedColumns', columnName]));
  if (historyTransformation != null) {
    return historyTransformation.getIn(['changedColumns', columnName, 'after', 'title']);
  }

  const futureTransformation = transformations.skip(index).find(transformation =>
    transformation.getIn(['changedColumns', columnName]));
  if (futureTransformation != null) {
    return futureTransformation.getIn(['changedColumns', columnName, 'before', 'title']);
  }

  return columnTitle(columnName, columns);
}

function combineTransformationDescription(transformations, index, columns) {
  const transformation = transformations.get(index);
  const firstColumnName = transformation.getIn(['args', 'columnNames', 0]);
  const secondColumnName = transformation.getIn(['args', 'columnNames', 1]);
  const firstTitle = findTitle(firstColumnName, transformations, index, columns);
  const secondTitle = findTitle(secondColumnName, transformations, index, columns);
  const newColumnTitle = transformation.getIn(['args', 'newColumnTitle']);
  return `Combined columns ${firstTitle} and ${secondTitle} into "${newColumnTitle}"`;
}

function textTransformationDescription(transformations, index, columns) {
  const transformation = transformations.get(index);
  const columnName = transformation.getIn(['args', 'columnName']);
  const title = findTitle(columnName, transformations, index, columns);
  const opDescription = ({
    'core/to-lowercase': 'to lower case',
    'core/to-uppercase': 'to upper case',
    'core/to-titlecase': 'to title case',
    'core/trim': 'trimmed whitespace',
    'core/trim-doublespace': 'removed double spaces',
  })[transformation.get('op')];
  return `${title}: ${opDescription}`;
}

function transformationDescription(transformations, index, columns) {
  const transformation = transformations.get(index);
  const op = transformation.get('op');
  const columnName = transformation.getIn(['args', 'columnName']);
  const title = transformation.getIn(['changedColumns', columnName, 'before', 'title']);
  switch (op) {
    case 'core/to-lowercase':
    case 'core/to-uppercase':
    case 'core/to-titlecase':
    case 'core/trim':
    case 'core/trim-doublespace':
      return textTransformationDescription(transformations, index, columns);
    case 'core/change-datatype':
      return `${title} datatype to ${transformation.getIn(['args', 'newType'])}`;
    case 'core/sort-column':
      return `${title} sorted ${transformation.getIn(['args', 'sortDirection'])}`;
    case 'core/rename-column':
      return `Renamed ${title} to ${transformation.getIn(['args', 'newColumnTitle'])}`;
    case 'core/delete-column':
      return `Deleted ${title}`;
    case 'core/combine':
      return combineTransformationDescription(transformations, index, columns);
    case 'core/derive':
      return deriveTransformationDescription(transformation);
    default:
      return op;
  }
}

function TransformationListItem({ transformations, index, columns }) {
  const transformation = transformations.get(index);
  const isUndoingTransformation = transformation.get('undo');
  const isPendingTransformation = transformation.get('pending') && !isUndoingTransformation;

  return (
    <div style={{ opacity: isUndoingTransformation ? 0.5 : 1 }}>
      <div>
        {transformationDescription(transformations, index, columns)}
      </div>
      {isPendingTransformation && <div style={{ fontSize: '0.6em', marginTop: 4 }}>APPLYING TRANSFORMATION</div>}
      {isUndoingTransformation && <div style={{ fontSize: '0.6em', marginTop: 4 }}>UNDOING TRANSFORMATION</div>}
    </div>);
}

TransformationListItem.propTypes = {
  transformations: PropTypes.object.isRequired,
  index: PropTypes.number.isRequired,
  columns: PropTypes.object.isRequired,
};

function TransformationList({ transformations, columns }) {
  return (
    <div
      className="TransformationList"
    >
      <ol>
        {transformations.map((transformation, index) => (
          <li key={index}>
            <TransformationListItem
              transformations={transformations}
              columns={columns}
              index={index}
            />
          </li>
        ))}
      </ol>
    </div>
  );
}

TransformationList.propTypes = {
  transformations: PropTypes.object.isRequired,
  columns: PropTypes.object.isRequired,
};

function markUndo(transformations, idx) {
  if (idx < 0) {
    return transformations;
  }
  if (transformations.getIn([idx, 'undo'])) {
    return markUndo(transformations, idx - 1);
  }
  return transformations.setIn([idx, 'undo'], true);
}

function transformationLog(persistedTransformations, pendingTransformations) {
  let allTransformations = persistedTransformations;
  pendingTransformations.forEach((pendingTransformation) => {
    if (pendingTransformation.get('op') === 'undo') {
      allTransformations = markUndo(allTransformations, allTransformations.size - 1);
    } else {
      allTransformations = allTransformations.push(pendingTransformation.set('pending', true));
    }
  });
  return allTransformations;
}

export default function TransformationLog({
  onClose,
  onUndo,
  transformations = Immutable.List(),
  columns,
  pendingTransformations,
}) {
  const allTransformations = transformationLog(transformations, pendingTransformations);

  return (
    <div
      className="DataTableSidebar"
      style={{
        width: '300px',
        height: 'calc(100vh - 8rem)',
      }}
    >
      <SidebarHeader onClose={onClose}>
        Transformation Log
      </SidebarHeader>
      <TransformationList
        transformations={allTransformations}
        columns={columns}
      />
      <SidebarControls
        positiveButtonText="Undo"
        onApply={
          allTransformations.every(transformation => transformation.get('undo')) ? null : onUndo
        }
        onClose={onClose}
      />
    </div>
  );
}

TransformationLog.propTypes = {
  onClose: PropTypes.func.isRequired,
  onUndo: PropTypes.func.isRequired,
  transformations: PropTypes.object,
  columns: PropTypes.object.isRequired,
  pendingTransformations: PropTypes.object.isRequired,
};

{'fraction_non_alphanumeric': 0.13084386945016171, 'fraction_numerical': 0.00330785063216701, 'mean_word_length': 4.24479568234387, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3225684', 'n_tokens_mistral': 3752, 'n_tokens_neox': 3723, 'n_words': 833}