<commit_before>import React from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import ErrorTryAgain from '../../common/ErrorTryAgain';
import LoadingSpinner from '../../common/LoadingSpinner';
import InfluentialStories from './InfluentialStories';
import { fetchTopicInfluentialStories, sortTopicInfluentialStories } from '../../../actions/topicActions';
import * as fetchConstants from '../../../lib/fetchConstants.js';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import FlatButton from 'material-ui/FlatButton';
import messages from '../../../resources/messages';
import DownloadButton from '../../common/DownloadButton';

const localMessages = {
  title: { id: 'topic.influentialStories.title', defaultMessage: 'Influential Stories' },
};

class InfluentialStoriesContainer extends React.Component {
  componentDidMount() {
    const { fetchStatus } = this.props;
    if (fetchStatus !== fetchConstants.FETCH_FAILED) {
      this.refetchData();
    }
  }
  componentWillReceiveProps(nextProps) {
    if ((nextProps.filters !== this.props.filters) ||
        (nextProps.sort !== this.props.sort)) {
      const { fetchData } = this.props;
      const { topicId } = this.props.params;
      if ((nextProps.filters.snapshotId !== null) && (nextProps.filters.timespanId !== null)) {
        fetchData(topicId, nextProps.filters.snapshotId, nextProps.filters.timespanId, nextProps.sort);
      }
    }
  }
  onChangeSort = (newSort) => {
    const { sortData } = this.props;
    sortData(newSort);
  }
  getStyles() {
    const styles = {
      contentWrapper: {
      },
      iconStyles: {
        marginRight: 24,
      },
      actionButtons: {
        float: 'right',
      },
    };
    return styles;
  }
  refetchData = () => {
    const { fetchData, filters, sort } = this.props;
    const { topicId } = this.props.params;
    fetchData(topicId, filters.snapshotId, filters.timespanId, sort);
  }
  nextPage = () => {
    const { fetchData, filters, sort, continuationId } = this.props;
    const { topicId } = this.props.params;
    fetchData(topicId, filters.snapshotId, filters.timespanId, sort, continuationId);
  }
  downloadCsv = () => {
    const { filters, sort } = this.props;
    const { topicId } = this.props.params;
    const url = `/api/topics/${topicId}/stories.csv?snapshot=${filters.snapshotId}&timespan=${filters.timespanId}&sort=${sort}`;
    window.location = url;
  }
  render() {
    const { fetchStatus, fetchData, stories, sort } = this.props;
    const { topicId } = this.props.params;
    const { formatMessage } = this.props.intl;
    let content = fetchStatus;
    const styles = this.getStyles();
    let headerContent = null;
    switch (fetchStatus) {
      case fetchConstants.FETCH_SUCCEEDED:
        headerContent = <DownloadButton tooltip={formatMessage(messages.download)} onClick={this.downloadCsv} />;
        content = (
          <div>
            <InfluentialStories topicId={topicId} stories={stories} onChangeSort={this.onChangeSort} sortedBy={sort} />
            <FlatButton label={formatMessage(messages.nextPage)} primary onClick={this.nextPage} />
          </div>
        );
        break;
      case fetchConstants.FETCH_FAILED:
        content = <ErrorTryAgain onTryAgain={fetchData} />;
        break;
      default:
        content = <LoadingSpinner />;
    }
    return (
      <div style={styles.root}>
        <Grid>
          <Row>
            <Col lg={12} md={12} sm={12}>
              <div style={styles.actionButtons}>
                {headerContent}
              </div>
              <h2><FormattedMessage {...localMessages.title} /></h2>
              {content}
            </Col>
          </Row>
        </Grid>
      </div>
    );
  }
}

InfluentialStoriesContainer.ROWS_PER_PAGE = 100;

InfluentialStoriesContainer.propTypes = {
  // from context
  params: React.PropTypes.object.isRequired,       // params from router
  intl: React.PropTypes.object.isRequired,
  // from parent
  // from dispatch
  fetchData: React.PropTypes.func.isRequired,
  sortData: React.PropTypes.func.isRequired,
  // from state
  fetchStatus: React.PropTypes.string.isRequired,
  sort: React.PropTypes.string.isRequired,
  stories: React.PropTypes.array.isRequired,
  topicInfo: React.PropTypes.object.isRequired,
  filters: React.PropTypes.object.isRequired,
  continuationId: React.PropTypes.number,
};

const mapStateToProps = (state) => ({
  fetchStatus: state.topics.selected.stories.fetchStatus,
  sort: state.topics.selected.stories.sort,
  stories: state.topics.selected.stories.stories,
  continuationId: state.topics.selected.stories.continuation_id,
  filters: state.topics.selected.filters,
  topicInfo: state.topics.selected.info,
});

const mapDispatchToProps = (dispatch) => ({
  fetchData: (topicId, snapshotId, timespanId, sort, continuationId) => {
    if ((snapshotId !== null) && (timespanId !== null)) {
      dispatch(fetchTopicInfluentialStories(topicId, snapshotId, timespanId, sort,
        InfluentialStoriesContainer.ROWS_PER_PAGE, continuationId));
    }
  },
  sortData: (sort) => {
    dispatch(sortTopicInfluentialStories(sort));
  },
});

export default injectIntl(connect(
  mapStateToProps,
  mapDispatchToProps
)(InfluentialStoriesContainer));<commit_msg>refactor to use composeAsyncWidget<commit_after>import React from 'react';
import { FormattedMessage, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import InfluentialStories from './InfluentialStories';
import { fetchTopicInfluentialStories, sortTopicInfluentialStories } from '../../../actions/topicActions';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import FlatButton from 'material-ui/FlatButton';
import messages from '../../../resources/messages';
import DownloadButton from '../../common/DownloadButton';
import DataCard from '../../common/DataCard';
import composeAsyncWidget from '../../util/composeAsyncWidget';

const localMessages = {
  title: { id: 'topic.influentialStories.title', defaultMessage: 'Influential Stories' },
};

class InfluentialStoriesContainer extends React.Component {
  componentWillReceiveProps(nextProps) {
    if ((nextProps.filters !== this.props.filters) ||
        (nextProps.sort !== this.props.sort)) {
      const { fetchData } = this.props;
      if ((nextProps.filters.snapshotId !== null) && (nextProps.filters.timespanId !== null)) {
        fetchData(nextProps);
      }
    }
  }
  onChangeSort = (newSort) => {
    const { sortData } = this.props;
    sortData(newSort);
  }
  refetchData = () => {
    const { fetchData } = this.props;
    fetchData(this.props);
  }
  nextPage = () => {
    const { fetchData } = this.props;
    fetchData(this.props);
  }
  downloadCsv = () => {
    const { filters, sort } = this.props;
    const { topicId } = this.props.params;
    const url = `/api/topics/${topicId}/stories.csv?snapshot=${filters.snapshotId}&timespan=${filters.timespanId}&sort=${sort}`;
    window.location = url;
  }
  render() {
    const { stories, sort } = this.props;
    const { topicId } = this.props.params;
    const { formatMessage } = this.props.intl;
    return (
      <div>
        <Grid>
          <Row>
            <Col lg={12} md={12} sm={12}>
              <DataCard>
                <div className="actions">
                  <DownloadButton tooltip={formatMessage(messages.download)} onClick={this.downloadCsv} />
                </div>
                <h2><FormattedMessage {...localMessages.title} /></h2>
                <InfluentialStories topicId={topicId} stories={stories} onChangeSort={this.onChangeSort} sortedBy={sort} />
                <FlatButton label={formatMessage(messages.nextPage)} primary onClick={this.nextPage} />
              </DataCard>
            </Col>
          </Row>
        </Grid>
      </div>
    );
  }
}

InfluentialStoriesContainer.ROWS_PER_PAGE = 100;

InfluentialStoriesContainer.propTypes = {
  // from context
  params: React.PropTypes.object.isRequired,       // params from router
  intl: React.PropTypes.object.isRequired,
  // from parent
  // from dispatch
  fetchData: React.PropTypes.func.isRequired,
  sortData: React.PropTypes.func.isRequired,
  // from state
  fetchStatus: React.PropTypes.string.isRequired,
  sort: React.PropTypes.string.isRequired,
  stories: React.PropTypes.array.isRequired,
  topicInfo: React.PropTypes.object.isRequired,
  filters: React.PropTypes.object.isRequired,
  continuationId: React.PropTypes.number,
  topicId: React.PropTypes.number.isRequired,
};

const mapStateToProps = (state) => ({
  fetchStatus: state.topics.selected.stories.fetchStatus,
  sort: state.topics.selected.stories.sort,
  stories: state.topics.selected.stories.stories,
  continuationId: state.topics.selected.stories.continuation_id,
  filters: state.topics.selected.filters,
  topicInfo: state.topics.selected.info,
  topicId: state.topics.selected.id,
});

const mapDispatchToProps = (dispatch) => ({
  fetchData: (props) => {
    dispatch(fetchTopicInfluentialStories(props.topicId, props.filters.snapshotId,
      props.filters.timespanId, props.sort, InfluentialStoriesContainer.ROWS_PER_PAGE,
      props.continuationId));
  },
  sortData: (sort) => {
    dispatch(sortTopicInfluentialStories(sort));
  },
});

function mergeProps(stateProps, dispatchProps, ownProps) {
  return Object.assign({}, stateProps, dispatchProps, ownProps, {
    asyncFetch: () => {
      dispatchProps.fetchData(stateProps);
    },
  });
}

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps, mergeProps)(
      composeAsyncWidget(
        InfluentialStoriesContainer
      )
    )
  );
{'fraction_non_alphanumeric': 0.13741482552137105, 'fraction_numerical': 0.0024778030146603344, 'mean_word_length': 3.284387439186201, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5775641', 'n_tokens_mistral': 2953, 'n_tokens_neox': 2947, 'n_words': 539}