<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { Helmet } from 'react-helmet';
import { push, replace } from 'react-router-redux';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { filteredLocation, urlWithFilters } from '../util/location';
import withAsyncFetch from '../common/hocs/AsyncContainer';
import AppButton from '../common/AppButton';
import { selectTopic, filterBySnapshot, filterByTimespan, filterByFocus, fetchTopicSummary, filterByQuery,
  topicStartSpider } from '../../actions/topicActions';
import { addNotice } from '../../actions/appActions';
import { snapshotIsUsable, TOPIC_SNAPSHOT_STATE_COMPLETED, TOPIC_SNAPSHOT_STATE_QUEUED, TOPIC_SNAPSHOT_STATE_RUNNING,
  TOPIC_SNAPSHOT_STATE_ERROR, TOPIC_SNAPSHOT_STATE_CREATED_NOT_QUEUED } from '../../reducers/topics/selected/snapshots';
import { LEVEL_INFO, LEVEL_WARNING, LEVEL_ERROR } from '../common/Notice';
import ModifyTopicDialog from './controlbar/ModifyTopicDialog';
import TopicUnderConstruction from './TopicUnderConstruction';
import TopicHeaderContainer from './TopicHeaderContainer';
import Permissioned from '../common/Permissioned';
import { PERMISSION_TOPIC_WRITE } from '../../lib/auth';
import messages from '../../resources/messages';

const localMessages = {
  needsSnapshotWarning: { id: 'needSnapshot.warning', defaultMessage: 'You\'ve made changes to your Topic that require a new snapshot to be generated!' },
  snapshotBuilderLink: { id: 'needSnapshot.snapshotBuilderLink', defaultMessage: 'Visit the Snapshot Builder for details.' },
  hasAnError: { id: 'topic.hasError', defaultMessage: 'Sorry, this topic has an error!' },
  spiderQueued: { id: 'topic.spiderQueued', defaultMessage: 'This topic is in the queue for spidering stories.  Please reload after a bit to see if it has started spidering.' },
  queueAge: { id: 'topic.spiderQueuedAge', defaultMessage: 'In the {queueName} queue since {lastUpdated}' },
  snapshotQueued: { id: 'snapshotGenerating.warning.queued', defaultMessage: 'We will start creating the new snapshot soon. Please reload this page in a few hours to check if your data is ready.' },
  snapshotRunning: { id: 'snapshotGenerating.warning.running', defaultMessage: 'We are creating a new snapshot right now. Please reload this page in a few hours to check if your data is ready.' },
  snapshotImporting: { id: 'snapshotGenerating.warning.importing', defaultMessage: 'We are importing the new snapshot now. Please reload this page in a few hours to check if your data is ready.' },
  snapshotFailed: { id: 'snapshotFailed.warning', defaultMessage: 'We tried to generate a new snapshot, but it failed.' },
  topicRunning: { id: 'topic.topicRunning', defaultMessage: 'We are scraping the web for all the stories in include in your topic.' },
  notUsingLatestSnapshot: { id: 'topic.notUsingLatestSnapshot', defaultMessage: 'You are not using the latest snapshot!  If you are not doing this on purpose, switch to the latest snapshot to get the best data.' },
  otherError: { id: 'topic.state.otherError', defaultMessage: 'Sorry, this topic has an error.  It says it is "{state}".' },
  trySpidering: { id: 'topic.state.trySpidering', defaultMessage: 'Manually run this topic' },
};

class TopicContainer extends React.Component {
  componentWillMount() {
    const { needsNewSnapshot, addAppNotice } = this.props;
    const { formatMessage } = this.props.intl;
    // warn user if they made changes that require a new snapshot
    if (needsNewSnapshot) {
      addAppNotice({ level: LEVEL_WARNING, message: formatMessage(localMessages.needsSnapshotWarning) });
    }
  }

  componentWillReceiveProps(nextProps) {
    const { topicId, topicInfo, asyncFetch, needsNewSnapshot, addAppNotice } = this.props;
    const { formatMessage } = this.props.intl;
    // if they edited the topic, or the topic changed then reload (unless it is just a isFav change)
    let topicInfoHasChanged = false;
    Object.keys(topicInfo).forEach((key) => {
      if ((key !== 'isFavorite') && (topicInfo[key] !== nextProps.topicInfo[key])) {
        topicInfoHasChanged = true;
      }
    });
    if (topicInfoHasChanged || (nextProps.topicId !== topicId)) {
      asyncFetch();
      // warn user if they made changes that require a new snapshot
      if (needsNewSnapshot) {
        addAppNotice({ level: LEVEL_WARNING, message: formatMessage(localMessages.needsSnapshotWarning) });
      }
    }
  }

  filtersAreSet() {
    const { filters, topicId } = this.props;
    return ((topicId !== null) && (filters.snapshotId !== null) && (filters.timespanId !== null));
  }

  render() {
    const { children, goToUrl, topicInfo, topicId, snapshotCount, handleSpiderRequest, filters, needsNewSnapshot } = this.props;
    const { formatMessage } = this.props.intl;
    // show a big error if there is one to show
    let contentToShow = children;
    if ((topicInfo.state === TOPIC_SNAPSHOT_STATE_RUNNING) && (snapshotCount === 0)) {
      // if the topic is running the initial spider and then show under construction message
      contentToShow = (

          {children}
          <TopicUnderConstruction />

      );
    } else if ((topicInfo.state === TOPIC_SNAPSHOT_STATE_CREATED_NOT_QUEUED) || (topicInfo.state === TOPIC_SNAPSHOT_STATE_ERROR)) {
      contentToShow = (
        <Grid>
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.hasAnError} />

                <AppButton
                  label={formatMessage(localMessages.trySpidering)}
                  onTouchTap={() => handleSpiderRequest(topicInfo.topics_id)}
                  type="submit"
                  color="primary"
                />

            </Col>
          </Row>
        </Grid>
      );
    } else if (topicInfo.state === TOPIC_SNAPSHOT_STATE_QUEUED) {
      contentToShow = (

              <Permissioned onlyTopic={PERMISSION_TOPIC_WRITE}>
                <ModifyTopicDialog
                  topicId={topicId}
                  onUrlChange={goToUrl}
                  needsNewSnapshot={needsNewSnapshot}
                  onSpiderRequest={handleSpiderRequest}
                />
              </Permissioned>

          <TopicUnderConstruction />

      );
    }
    return (

          <Helmet><title>{`${topicInfo.name} | ${formatMessage(messages.topicsToolName)} | ${formatMessage(messages.suiteName)}`}</title></Helmet>
          <TopicHeaderContainer topicId={topicId} topicInfo={topicInfo} filters={filters} />
          {contentToShow}

    );
  }
}

TopicContainer.propTypes = {
  // from context
  intl: PropTypes.object.isRequired,
  children: PropTypes.node,
  location: PropTypes.object.isRequired,
  topicId: PropTypes.number.isRequired,
  // from dispatch
  asyncFetch: PropTypes.func.isRequired,
  addAppNotice: PropTypes.func.isRequired,
  handleSpiderRequest: PropTypes.func.isRequired,
  // from state
  filters: PropTypes.object.isRequired,
  fetchStatus: PropTypes.string.isRequired,
  topicInfo: PropTypes.object,
  needsNewSnapshot: PropTypes.bool.isRequired,
  snapshotCount: PropTypes.number.isRequired,
  goToUrl: PropTypes.func.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  filters: state.topics.selected.filters,
  fetchStatus: state.topics.selected.info.fetchStatus,
  topicInfo: state.topics.selected.info,
  topicId: parseInt(ownProps.params.topicId, 10),
  needsNewSnapshot: state.topics.selected.needsNewSnapshot,
  snapshotCount: state.topics.selected.snapshots.list.length,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  addAppNotice: (info) => {
    dispatch(addNotice(info));
  },
  goToUrl: (url) => {
    dispatch(push(url));
  },
  asyncFetch: () => {
    dispatch(selectTopic(ownProps.params.topicId));
    // select any filters that are serialized on the url
    const { query } = ownProps.location;
    const { snapshotId } = query;
    if (snapshotId) {
      dispatch(filterBySnapshot(query.snapshotId));
    }
    if (ownProps.location.query.focusId) {
      dispatch(filterByFocus(query.focusId));
    }
    if (ownProps.location.query.timespanId) {
      dispatch(filterByTimespan(query.timespanId));
    }
    if (ownProps.location.query.q) {
      dispatch(filterByQuery(query.q));
    }
    // now that filters are set, fetch the topic summary info
    dispatch(fetchTopicSummary(ownProps.params.topicId))
      .then((response) => {
        // show the subheader info
        // show any warnings based on the topic state
        switch (response.state) {
          case TOPIC_SNAPSHOT_STATE_QUEUED:
            dispatch(addNotice({
              level: LEVEL_INFO,
              message: ownProps.intl.formatMessage(localMessages.spiderQueued),
              details: ownProps.intl.formatMessage(localMessages.queueAge, {
                queueName: response.job_queue,
                lastUpdated: response.spiderJobs[0].last_updated,
              }),
            }));
            break;
          case TOPIC_SNAPSHOT_STATE_RUNNING:
            dispatch(addNotice({
              level: LEVEL_INFO,
              message: ownProps.intl.formatMessage(localMessages.topicRunning),
              details: response.message,
            }));
            break;
          case TOPIC_SNAPSHOT_STATE_ERROR:
            dispatch(addNotice({
              level: LEVEL_ERROR,
              message: ownProps.intl.formatMessage(localMessages.hasAnError),
              details: response.message,
            }));
            break;
          case TOPIC_SNAPSHOT_STATE_COMPLETED:
            // everything is ok
            break;
          default:
            // got some unknown bad state
            dispatch(addNotice({
              level: LEVEL_ERROR,
              message: ownProps.intl.formatMessage(localMessages.otherError, { state: response.state }),
            }));
            break;
        }
        // show any warnings based on the snapshot state
        const snapshots = response.snapshots.list;
        const snapshotJobStatus = response.snapshots.jobStatus;
        const firstReadySnapshot = snapshots.find(s => snapshotIsUsable(s));
        // if no snapshot specified, pick the first usable snapshot
        if ((snapshotId === null) || (snapshotId === undefined)) {
          // default to the latest ready snapshot if none is specified on url
          if (firstReadySnapshot) {
            const newSnapshotId = firstReadySnapshot.snapshots_id;
            const newLocation = filteredLocation(ownProps.location, {
              snapshotId: newSnapshotId,
              timespanId: null,
              focusId: null,
              q: null,
            });
            dispatch(replace(newLocation)); // do a replace, not a push here so the non-snapshot url isn't in the history
            dispatch(filterBySnapshot(newSnapshotId));
          } else if (snapshots.length > 0) {
            // first snapshot doesn't show up as a job, so we gotta check for status here and alert if it is importing :-(
            const firstSnapshot = snapshots[0];
            if (!snapshotIsUsable(firstSnapshot)) {
              dispatch(addNotice({
                level: LEVEL_INFO,
                message: ownProps.intl.formatMessage(localMessages.snapshotImporting),
              }));
            }
          }
        } else if (firstReadySnapshot.snapshots_id !== parseInt(snapshotId, 10)) {
          // if snaphot is specific in URL, but it is not the latest then show a warning
          dispatch(addNotice({
            level: LEVEL_WARNING,
            htmlMessage: ownProps.intl.formatHTMLMessage(localMessages.notUsingLatestSnapshot, {
              url: urlWithFilters(ownProps.location.pathname, {
                snapshotId: firstReadySnapshot.snapshots_id,
              }),
            }),
          }));
        }
        // if a snapshot is in progress then show the user a note about its state
        if (snapshotJobStatus && snapshotJobStatus.length > 0) {
          const latestSnapshotJobStatus = response.snapshots.jobStatus[0];
          switch (latestSnapshotJobStatus.state) {
            case TOPIC_SNAPSHOT_STATE_QUEUED:
              dispatch(addNotice({
                level: LEVEL_INFO,
                message: ownProps.intl.formatMessage(localMessages.snapshotQueued),
                details: latestSnapshotJobStatus.message,
              }));
              break;
            case TOPIC_SNAPSHOT_STATE_RUNNING:
              dispatch(addNotice({
                level: LEVEL_INFO,
                message: ownProps.intl.formatMessage(localMessages.snapshotRunning),
                details: latestSnapshotJobStatus.message,
              }));
              break;
            case TOPIC_SNAPSHOT_STATE_ERROR:
              dispatch(addNotice({
                level: LEVEL_ERROR,
                message: ownProps.intl.formatMessage(localMessages.snapshotFailed),
                details: latestSnapshotJobStatus.message,
              }));
              break;
            case TOPIC_SNAPSHOT_STATE_COMPLETED:
              const latestSnapshot = snapshots[0];
              if (!snapshotIsUsable(latestSnapshot)) {
                dispatch(addNotice({
                  level: LEVEL_INFO,
                  message: ownProps.intl.formatMessage(localMessages.snapshotImporting),
                }));
              }
              break;
            default:
              // don't alert user about anything
          }
        } else if (snapshots.length > 1) {
          // for some reason the second snapshot isn't showing up in the jobs list
          const latestSnapshot = snapshots[0];
          if (!snapshotIsUsable(latestSnapshot)) {
            dispatch(addNotice({
              level: LEVEL_INFO,
              message: ownProps.intl.formatMessage(localMessages.snapshotImporting),
            }));
          }
        }
      });
  },
  handleSpiderRequest: (topicId) => {
    dispatch(topicStartSpider(topicId))
      .then(() => window.location.reload());
  },
});

export default
injectIntl(
  connect(mapStateToProps, mapDispatchToProps)(
    withAsyncFetch(
      TopicContainer
    )
  )
);
<commit_msg>#1371<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { Helmet } from 'react-helmet';
import { push, replace } from 'react-router-redux';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import TextField from '@material-ui/core/TextField';
import { filteredLocation, urlWithFilters } from '../util/location';
import withAsyncFetch from '../common/hocs/AsyncContainer';
import AppButton from '../common/AppButton';
import { updateTopic, selectTopic, filterBySnapshot, filterByTimespan, filterByFocus, fetchTopicSummary, filterByQuery,
  topicStartSpider } from '../../actions/topicActions';
import { addNotice } from '../../actions/appActions';
import { snapshotIsUsable, TOPIC_SNAPSHOT_STATE_COMPLETED, TOPIC_SNAPSHOT_STATE_QUEUED, TOPIC_SNAPSHOT_STATE_RUNNING,
  TOPIC_SNAPSHOT_STATE_ERROR, TOPIC_SNAPSHOT_STATE_CREATED_NOT_QUEUED } from '../../reducers/topics/selected/snapshots';
import { LEVEL_INFO, LEVEL_WARNING, LEVEL_ERROR } from '../common/Notice';
import ModifyTopicDialog from './controlbar/ModifyTopicDialog';
import TopicUnderConstruction from './TopicUnderConstruction';
import TopicHeaderContainer from './TopicHeaderContainer';
import Permissioned from '../common/Permissioned';
import { PERMISSION_TOPIC_WRITE } from '../../lib/auth';
import messages from '../../resources/messages';

const localMessages = {
  needsSnapshotWarning: { id: 'needSnapshot.warning', defaultMessage: 'You\'ve made changes to your Topic that require a new snapshot to be generated!' },
  snapshotBuilderLink: { id: 'needSnapshot.snapshotBuilderLink', defaultMessage: 'Visit the Snapshot Builder for details.' },
  hasAnError: { id: 'topic.hasError', defaultMessage: 'Sorry, this topic has an error!' },
  spiderQueued: { id: 'topic.spiderQueued', defaultMessage: 'This topic is in the queue for spidering stories.  Please reload after a bit to see if it has started spidering.' },
  queueAge: { id: 'topic.spiderQueuedAge', defaultMessage: 'In the {queueName} queue since {lastUpdated}' },
  snapshotQueued: { id: 'snapshotGenerating.warning.queued', defaultMessage: 'We will start creating the new snapshot soon. Please reload this page in a few hours to check if your data is ready.' },
  snapshotRunning: { id: 'snapshotGenerating.warning.running', defaultMessage: 'We are creating a new snapshot right now. Please reload this page in a few hours to check if your data is ready.' },
  snapshotImporting: { id: 'snapshotGenerating.warning.importing', defaultMessage: 'We are importing the new snapshot now. Please reload this page in a few hours to check if your data is ready.' },
  snapshotFailed: { id: 'snapshotFailed.warning', defaultMessage: 'We tried to generate a new snapshot, but it failed.' },
  topicRunning: { id: 'topic.topicRunning', defaultMessage: 'We are scraping the web for all the stories in include in your topic.' },
  notUsingLatestSnapshot: { id: 'topic.notUsingLatestSnapshot', defaultMessage: 'You are not using the latest snapshot!  If you are not doing this on purpose, switch to the latest snapshot to get the best data.' },
  otherError: { id: 'topic.state.otherError', defaultMessage: 'Sorry, this topic has an error.  It says it is "{state}".' },
  trySpidering: { id: 'topic.state.trySpidering', defaultMessage: 'Manually run this topic' },
  updateMaxStories: { id: 'topic.state.updateMaxStories', defaultMessage: 'Increase Max Stories and Respider' },
  maxStories: { id: 'topic.state.maxStories', defaultMessage: 'Max Stories' },
};

class TopicContainer extends React.Component {
  componentWillMount() {
    const { needsNewSnapshot, addAppNotice } = this.props;
    const { formatMessage } = this.props.intl;
    // warn user if they made changes that require a new snapshot
    if (needsNewSnapshot) {
      addAppNotice({ level: LEVEL_WARNING, message: formatMessage(localMessages.needsSnapshotWarning) });
    }
  }

  componentWillReceiveProps(nextProps) {
    const { topicId, topicInfo, asyncFetch, needsNewSnapshot, addAppNotice } = this.props;
    const { formatMessage } = this.props.intl;
    // if they edited the topic, or the topic changed then reload (unless it is just a isFav change)
    let topicInfoHasChanged = false;
    Object.keys(topicInfo).forEach((key) => {
      if ((key !== 'isFavorite') && (topicInfo[key] !== nextProps.topicInfo[key])) {
        topicInfoHasChanged = true;
      }
    });
    if (topicInfoHasChanged || (nextProps.topicId !== topicId)) {
      asyncFetch();
      // warn user if they made changes that require a new snapshot
      if (needsNewSnapshot) {
        addAppNotice({ level: LEVEL_WARNING, message: formatMessage(localMessages.needsSnapshotWarning) });
      }
    }
  }

  filtersAreSet() {
    const { filters, topicId } = this.props;
    return ((topicId !== null) && (filters.snapshotId !== null) && (filters.timespanId !== null));
  }

  render() {
    const { children, goToUrl, topicInfo, topicId, snapshotCount, handleSpiderRequest, handleUpdateMaxStoriesAndSpiderRequest, filters, needsNewSnapshot } = this.props;
    const { formatMessage } = this.props.intl;
    // show a big error if there is one to show
    let contentToShow = children;
    if ((topicInfo.state === TOPIC_SNAPSHOT_STATE_RUNNING) && (snapshotCount === 0)) {
      // if the topic is running the initial spider and then show under construction message
      contentToShow = (

          {children}
          <TopicUnderConstruction />

      );
    } else if (topicInfo.state === TOPIC_SNAPSHOT_STATE_ERROR) {
      contentToShow = (
        <Grid>
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.hasAnError} />

                <TextField
                  id="maxStories"
                  label={formatMessage(localMessages.maxStories)}
                  fullWidth
                  rows={1}
                />
                <AppButton
                  label={formatMessage(localMessages.updateMaxStories)}
                  onTouchTap={() => handleUpdateMaxStoriesAndSpiderRequest(topicInfo)}
                  type="submit"
                  color="primary"
                />

            </Col>
          </Row>
        </Grid>
      );
    } else if (topicInfo.state === TOPIC_SNAPSHOT_STATE_CREATED_NOT_QUEUED) {
      contentToShow = (
        <Grid>
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.hasAnError} />

                <AppButton
                  label={formatMessage(localMessages.trySpidering)}
                  onTouchTap={() => handleSpiderRequest(topicInfo.topics_id)}
                  type="submit"
                  color="primary"
                />

            </Col>
          </Row>
        </Grid>
      );
    } else if (topicInfo.state === TOPIC_SNAPSHOT_STATE_QUEUED) {
      contentToShow = (

              <Permissioned onlyTopic={PERMISSION_TOPIC_WRITE}>
                <ModifyTopicDialog
                  topicId={topicId}
                  onUrlChange={goToUrl}
                  needsNewSnapshot={needsNewSnapshot}
                  onSpiderRequest={handleSpiderRequest}
                />
              </Permissioned>

          <TopicUnderConstruction />

      );
    }
    return (

          <Helmet><title>{`${topicInfo.name} | ${formatMessage(messages.topicsToolName)} | ${formatMessage(messages.suiteName)}`}</title></Helmet>
          <TopicHeaderContainer topicId={topicId} topicInfo={topicInfo} filters={filters} />
          {contentToShow}

    );
  }
}

TopicContainer.propTypes = {
  // from context
  intl: PropTypes.object.isRequired,
  children: PropTypes.node,
  location: PropTypes.object.isRequired,
  topicId: PropTypes.number.isRequired,
  // from dispatch
  asyncFetch: PropTypes.func.isRequired,
  addAppNotice: PropTypes.func.isRequired,
  handleSpiderRequest: PropTypes.func.isRequired,
  handleUpdateMaxStoriesAndSpiderRequest: PropTypes.func.isRequired,
  // from state
  filters: PropTypes.object.isRequired,
  fetchStatus: PropTypes.string.isRequired,
  topicInfo: PropTypes.object,
  needsNewSnapshot: PropTypes.bool.isRequired,
  snapshotCount: PropTypes.number.isRequired,
  goToUrl: PropTypes.func.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  filters: state.topics.selected.filters,
  fetchStatus: state.topics.selected.info.fetchStatus,
  topicInfo: state.topics.selected.info,
  topicId: parseInt(ownProps.params.topicId, 10),
  needsNewSnapshot: state.topics.selected.needsNewSnapshot,
  snapshotCount: state.topics.selected.snapshots.list.length,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  addAppNotice: (info) => {
    dispatch(addNotice(info));
  },
  goToUrl: (url) => {
    dispatch(push(url));
  },
  asyncFetch: () => {
    dispatch(selectTopic(ownProps.params.topicId));
    // select any filters that are serialized on the url
    const { query } = ownProps.location;
    const { snapshotId } = query;
    if (snapshotId) {
      dispatch(filterBySnapshot(query.snapshotId));
    }
    if (ownProps.location.query.focusId) {
      dispatch(filterByFocus(query.focusId));
    }
    if (ownProps.location.query.timespanId) {
      dispatch(filterByTimespan(query.timespanId));
    }
    if (ownProps.location.query.q) {
      dispatch(filterByQuery(query.q));
    }
    // now that filters are set, fetch the topic summary info
    dispatch(fetchTopicSummary(ownProps.params.topicId))
      .then((response) => {
        // show the subheader info
        // show any warnings based on the topic state
        switch (response.state) {
          case TOPIC_SNAPSHOT_STATE_QUEUED:
            dispatch(addNotice({
              level: LEVEL_INFO,
              message: ownProps.intl.formatMessage(localMessages.spiderQueued),
              details: ownProps.intl.formatMessage(localMessages.queueAge, {
                queueName: response.job_queue,
                lastUpdated: response.spiderJobs[0].last_updated,
              }),
            }));
            break;
          case TOPIC_SNAPSHOT_STATE_RUNNING:
            dispatch(addNotice({
              level: LEVEL_INFO,
              message: ownProps.intl.formatMessage(localMessages.topicRunning),
              details: response.message,
            }));
            break;
          case TOPIC_SNAPSHOT_STATE_ERROR:
            dispatch(addNotice({
              level: LEVEL_ERROR,
              message: ownProps.intl.formatMessage(localMessages.hasAnError),
              details: response.message,
            }));
            break;
          case TOPIC_SNAPSHOT_STATE_COMPLETED:
            // everything is ok
            break;
          default:
            // got some unknown bad state
            dispatch(addNotice({
              level: LEVEL_ERROR,
              message: ownProps.intl.formatMessage(localMessages.otherError, { state: response.state }),
            }));
            break;
        }
        // show any warnings based on the snapshot state
        const snapshots = response.snapshots.list;
        const snapshotJobStatus = response.snapshots.jobStatus;
        const firstReadySnapshot = snapshots.find(s => snapshotIsUsable(s));
        // if no snapshot specified, pick the first usable snapshot
        if ((snapshotId === null) || (snapshotId === undefined)) {
          // default to the latest ready snapshot if none is specified on url
          if (firstReadySnapshot) {
            const newSnapshotId = firstReadySnapshot.snapshots_id;
            const newLocation = filteredLocation(ownProps.location, {
              snapshotId: newSnapshotId,
              timespanId: null,
              focusId: null,
              q: null,
            });
            dispatch(replace(newLocation)); // do a replace, not a push here so the non-snapshot url isn't in the history
            dispatch(filterBySnapshot(newSnapshotId));
          } else if (snapshots.length > 0) {
            // first snapshot doesn't show up as a job, so we gotta check for status here and alert if it is importing :-(
            const firstSnapshot = snapshots[0];
            if (!snapshotIsUsable(firstSnapshot)) {
              dispatch(addNotice({
                level: LEVEL_INFO,
                message: ownProps.intl.formatMessage(localMessages.snapshotImporting),
              }));
            }
          }
        } else if (firstReadySnapshot.snapshots_id !== parseInt(snapshotId, 10)) {
          // if snaphot is specific in URL, but it is not the latest then show a warning
          dispatch(addNotice({
            level: LEVEL_WARNING,
            htmlMessage: ownProps.intl.formatHTMLMessage(localMessages.notUsingLatestSnapshot, {
              url: urlWithFilters(ownProps.location.pathname, {
                snapshotId: firstReadySnapshot.snapshots_id,
              }),
            }),
          }));
        }
        // if a snapshot is in progress then show the user a note about its state
        if (snapshotJobStatus && snapshotJobStatus.length > 0) {
          const latestSnapshotJobStatus = response.snapshots.jobStatus[0];
          switch (latestSnapshotJobStatus.state) {
            case TOPIC_SNAPSHOT_STATE_QUEUED:
              dispatch(addNotice({
                level: LEVEL_INFO,
                message: ownProps.intl.formatMessage(localMessages.snapshotQueued),
                details: latestSnapshotJobStatus.message,
              }));
              break;
            case TOPIC_SNAPSHOT_STATE_RUNNING:
              dispatch(addNotice({
                level: LEVEL_INFO,
                message: ownProps.intl.formatMessage(localMessages.snapshotRunning),
                details: latestSnapshotJobStatus.message,
              }));
              break;
            case TOPIC_SNAPSHOT_STATE_ERROR:
              dispatch(addNotice({
                level: LEVEL_ERROR,
                message: ownProps.intl.formatMessage(localMessages.snapshotFailed),
                details: latestSnapshotJobStatus.message,
              }));
              break;
            case TOPIC_SNAPSHOT_STATE_COMPLETED:
              const latestSnapshot = snapshots[0];
              if (!snapshotIsUsable(latestSnapshot)) {
                dispatch(addNotice({
                  level: LEVEL_INFO,
                  message: ownProps.intl.formatMessage(localMessages.snapshotImporting),
                }));
              }
              break;
            default:
              // don't alert user about anything
          }
        } else if (snapshots.length > 1) {
          // for some reason the second snapshot isn't showing up in the jobs list
          const latestSnapshot = snapshots[0];
          if (!snapshotIsUsable(latestSnapshot)) {
            dispatch(addNotice({
              level: LEVEL_INFO,
              message: ownProps.intl.formatMessage(localMessages.snapshotImporting),
            }));
          }
        }
      });
  },
  handleUpdateMaxStoriesAndSpiderRequest: (topicInfo) => {
    const maxStories = parseInt(document.getElementById('maxStories').value, 10);
    const newTopicInfo = {
      ...topicInfo,
      max_stories: maxStories,
    };
    if ('media_tags' in topicInfo) {
      newTopicInfo['sources[]'] = topicInfo.media_tags.filter(s => s.media_id).map(s => s.media_id);
      newTopicInfo['collections[]'] = topicInfo.media_tags.filter(s => s.tags_id).map(s => s.tags_id);
    } else {
      newTopicInfo['sources[]'] = '';
      newTopicInfo['collections[]'] = '';
    }
    dispatch(updateTopic(newTopicInfo.topics_id, { ...newTopicInfo }))
      .then(dispatch(topicStartSpider(newTopicInfo.topics_id)))
      .then(() => window.location.reload());
  },
  handleSpiderRequest: (topicId) => {
    dispatch(topicStartSpider(topicId))
      .then(() => window.location.reload());
  },
});

export default
injectIntl(
  connect(mapStateToProps, mapDispatchToProps)(
    withAsyncFetch(
      TopicContainer
    )
  )
);
<|endoftext|>


<commit_before>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');

var attributes = require('./attributes');
var contants = require('./constants');

var name = contants.name;
var buttonAttrs = attributes.buttons;

module.exports = function updateMenusDefaults(layoutIn, layoutOut) {
    var contIn = Array.isArray(layoutIn[name]) ? layoutIn[name] : [],
        contOut = layoutOut[name] = [];

    for(var i = 0; i < contIn.length; i++) {
        var menuIn = contIn[i] || {},
            menuOut = {};

        menuDefaults(menuIn, menuOut, layoutOut);
        menuOut._input = menuIn;
        contOut.push(menuOut);
    }
};

function menuDefaults(menuIn, menuOut, layoutOut) {

    function coerce(attr, dflt) {
        return Lib.coerce(menuIn, menuOut, attributes, attr, dflt);
    }

    var buttons = buttonsDefaults(menuIn, menuOut);

    var visible = coerce('visible', buttons.length > 0);
    if(!visible) return;

    coerce('active');

    coerce('x');
    coerce('y');
    Lib.noneOrAll(menuIn, menuOut, ['x', 'y']);

    coerce('xanchor');
    coerce('yanchor');

    Lib.coerceFont(coerce, 'font', layoutOut.font);

    coerce('bgcolor', layoutOut.paper_bgcolor);
    coerce('bordercolor');
    coerce('borderwidth');
}

function buttonsDefaults(menuIn, menuOut) {
    var buttonsIn = menuIn.buttons || [],
        buttonsOut = menuOut.buttons = [];

    var buttonIn, buttonOut;

    function coerce(attr, dflt) {
        return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
    }

    for(var i = 0; i < buttonsIn.length; i++) {
        buttonIn = buttonsIn[i];
        buttonOut = {};

        // Should we do some validation for 'args' depending on `method`
        // or just let Plotly[method] error out?

        coerce('method');
        coerce('args');
        coerce('label');

        buttonsOut.push(buttonOut);
    }

    return buttonsOut;
}
<commit_msg>skip over non-object buttons and button w/o 'args'<commit_after>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');

var attributes = require('./attributes');
var contants = require('./constants');

var name = contants.name;
var buttonAttrs = attributes.buttons;

module.exports = function updateMenusDefaults(layoutIn, layoutOut) {
    var contIn = Array.isArray(layoutIn[name]) ? layoutIn[name] : [],
        contOut = layoutOut[name] = [];

    for(var i = 0; i < contIn.length; i++) {
        var menuIn = contIn[i] || {},
            menuOut = {};

        menuDefaults(menuIn, menuOut, layoutOut);
        menuOut._input = menuIn;
        contOut.push(menuOut);
    }
};

function menuDefaults(menuIn, menuOut, layoutOut) {

    function coerce(attr, dflt) {
        return Lib.coerce(menuIn, menuOut, attributes, attr, dflt);
    }

    var buttons = buttonsDefaults(menuIn, menuOut);

    var visible = coerce('visible', buttons.length > 0);
    if(!visible) return;

    coerce('active');

    coerce('x');
    coerce('y');
    Lib.noneOrAll(menuIn, menuOut, ['x', 'y']);

    coerce('xanchor');
    coerce('yanchor');

    Lib.coerceFont(coerce, 'font', layoutOut.font);

    coerce('bgcolor', layoutOut.paper_bgcolor);
    coerce('bordercolor');
    coerce('borderwidth');
}

function buttonsDefaults(menuIn, menuOut) {
    var buttonsIn = menuIn.buttons || [],
        buttonsOut = menuOut.buttons = [];

    var buttonIn, buttonOut;

    function coerce(attr, dflt) {
        return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
    }

    for(var i = 0; i < buttonsIn.length; i++) {
        buttonIn = buttonsIn[i];
        buttonOut = {};

        if(!Lib.isPlainObject(buttonIn) || !Array.isArray(buttonIn.args)) {
            continue;
        }

        // Should we do some validation for 'args' depending on `method`
        // or just let Plotly[method] error out?

        coerce('method');
        coerce('args');
        coerce('label');

        buttonsOut.push(buttonOut);
    }

    return buttonsOut;
}
<|endoftext|>


<commit_before>/*
 Copyright 2017 New Vector Ltd.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk';
import UserSettingsStore from '../../../UserSettingsStore';
import FlairStore from '../../../stores/FlairStore';
import dis from '../../../dispatcher';

class FlairAvatar extends React.Component {
    constructor() {
        super();
        this.onClick = this.onClick.bind(this);
    }

    onClick(ev) {
        ev.preventDefault();
        // Don't trigger onClick of parent element
        ev.stopPropagation();
        dis.dispatch({
            action: 'view_group',
            group_id: this.props.groupProfile.groupId,
        });
    }

    render() {
        const httpUrl = this.context.matrixClient.mxcUrlToHttp(
            this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
        const tooltip = this.props.groupProfile.name ?
            `${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
            this.props.groupProfile.groupId;
        return ;
    }
}

FlairAvatar.propTypes = {
    groupProfile: PropTypes.shape({
        groupId: PropTypes.string.isRequired,
        name: PropTypes.string,
        avatarUrl: PropTypes.string.isRequired,
    }),
};

FlairAvatar.contextTypes = {
    matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};

export default class Flair extends React.Component {
    constructor() {
        super();
        this.state = {
            profiles: [],
        };
        this.onRoomStateEvents = this.onRoomStateEvents.bind(this);
    }

    componentWillUnmount() {
        this._unmounted = true;
        this.context.matrixClient.removeListener('RoomState.events', this.onRoomStateEvents);
    }

    componentWillMount() {
        this._unmounted = false;
        this._generateAvatars();
        this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
    }

    onRoomStateEvents(event) {
        if (event.getType() === 'm.room.related_groups' && FlairStore.groupSupport()) {
            this._generateAvatars();
        }
    }

    async _getGroupProfiles(groups) {
        const profiles = [];
        for (const groupId of groups) {
            let groupProfile = null;
            try {
                groupProfile = await FlairStore.getGroupProfileCached(this.context.matrixClient, groupId);
            } catch (err) {
                console.error('Could not get profile for group', groupId, err);
            }
            profiles.push(groupProfile);
        }
        return profiles.filter((p) => p !== null);
    }

    async _generateAvatars() {
        let groups = await FlairStore.getPublicisedGroupsCached(this.context.matrixClient, this.props.userId);
        if (this.props.roomId && this.props.showRelated) {
            const relatedGroupsEvent = this.context.matrixClient
                .getRoom(this.props.roomId)
                .currentState
                .getStateEvents('m.room.related_groups', '');
            const relatedGroups = relatedGroupsEvent ?
                relatedGroupsEvent.getContent().groups || [] : [];
            if (relatedGroups && relatedGroups.length > 0) {
                groups = groups.filter((groupId) => {
                    return relatedGroups.includes(groupId);
                });
            } else {
                groups = [];
            }
        }
        if (!groups || groups.length === 0) {
            return;
        }
        const profiles = await this._getGroupProfiles(groups);
        if (!this.unmounted) {
            this.setState({profiles: profiles.filter((profile) => {return profile.avatarUrl;})});
        }
    }

    render() {
        if (this.state.profiles.length === 0) {
            return
;
        }
        const avatars = this.state.profiles.map((profile, index) => {
            return <FlairAvatar key={index} groupProfile={profile} />;
        });
        return (

                { avatars }

        );
    }
}

Flair.propTypes = {
    userId: PropTypes.string,

    // Whether to show only the flair associated with related groups of the given room,
    // or all flair associated with a user.
    showRelated: PropTypes.bool,
    // The room that this flair will be displayed in. Optional. Only applies when showRelated = true.
    roomId: PropTypes.string,
};

// TODO: We've decided that all components should follow this pattern, which means removing withMatrixClient and using
// this.context.matrixClient everywhere instead of this.props.matrixClient.
// See https://github.com/vector-im/riot-web/issues/4951.
Flair.contextTypes = {
    matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};
<commit_msg>Remove unused UserSettingsStore import<commit_after>/*
 Copyright 2017 New Vector Ltd.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk';
import FlairStore from '../../../stores/FlairStore';
import dis from '../../../dispatcher';

class FlairAvatar extends React.Component {
    constructor() {
        super();
        this.onClick = this.onClick.bind(this);
    }

    onClick(ev) {
        ev.preventDefault();
        // Don't trigger onClick of parent element
        ev.stopPropagation();
        dis.dispatch({
            action: 'view_group',
            group_id: this.props.groupProfile.groupId,
        });
    }

    render() {
        const httpUrl = this.context.matrixClient.mxcUrlToHttp(
            this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
        const tooltip = this.props.groupProfile.name ?
            `${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
            this.props.groupProfile.groupId;
        return ;
    }
}

FlairAvatar.propTypes = {
    groupProfile: PropTypes.shape({
        groupId: PropTypes.string.isRequired,
        name: PropTypes.string,
        avatarUrl: PropTypes.string.isRequired,
    }),
};

FlairAvatar.contextTypes = {
    matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};

export default class Flair extends React.Component {
    constructor() {
        super();
        this.state = {
            profiles: [],
        };
        this.onRoomStateEvents = this.onRoomStateEvents.bind(this);
    }

    componentWillUnmount() {
        this._unmounted = true;
        this.context.matrixClient.removeListener('RoomState.events', this.onRoomStateEvents);
    }

    componentWillMount() {
        this._unmounted = false;
        this._generateAvatars();
        this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
    }

    onRoomStateEvents(event) {
        if (event.getType() === 'm.room.related_groups' && FlairStore.groupSupport()) {
            this._generateAvatars();
        }
    }

    async _getGroupProfiles(groups) {
        const profiles = [];
        for (const groupId of groups) {
            let groupProfile = null;
            try {
                groupProfile = await FlairStore.getGroupProfileCached(this.context.matrixClient, groupId);
            } catch (err) {
                console.error('Could not get profile for group', groupId, err);
            }
            profiles.push(groupProfile);
        }
        return profiles.filter((p) => p !== null);
    }

    async _generateAvatars() {
        let groups = await FlairStore.getPublicisedGroupsCached(this.context.matrixClient, this.props.userId);
        if (this.props.roomId && this.props.showRelated) {
            const relatedGroupsEvent = this.context.matrixClient
                .getRoom(this.props.roomId)
                .currentState
                .getStateEvents('m.room.related_groups', '');
            const relatedGroups = relatedGroupsEvent ?
                relatedGroupsEvent.getContent().groups || [] : [];
            if (relatedGroups && relatedGroups.length > 0) {
                groups = groups.filter((groupId) => {
                    return relatedGroups.includes(groupId);
                });
            } else {
                groups = [];
            }
        }
        if (!groups || groups.length === 0) {
            return;
        }
        const profiles = await this._getGroupProfiles(groups);
        if (!this.unmounted) {
            this.setState({profiles: profiles.filter((profile) => {return profile.avatarUrl;})});
        }
    }

    render() {
        if (this.state.profiles.length === 0) {
            return
;
        }
        const avatars = this.state.profiles.map((profile, index) => {
            return <FlairAvatar key={index} groupProfile={profile} />;
        });
        return (

                { avatars }

        );
    }
}

Flair.propTypes = {
    userId: PropTypes.string,

    // Whether to show only the flair associated with related groups of the given room,
    // or all flair associated with a user.
    showRelated: PropTypes.bool,
    // The room that this flair will be displayed in. Optional. Only applies when showRelated = true.
    roomId: PropTypes.string,
};

// TODO: We've decided that all components should follow this pattern, which means removing withMatrixClient and using
// this.context.matrixClient everywhere instead of this.props.matrixClient.
// See https://github.com/vector-im/riot-web/issues/4951.
Flair.contextTypes = {
    matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};
<|endoftext|>


<commit_before>
/* =========================================================================
 * Svelto - Core - jQuery - Helpers (Is attached)
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @require ../init.js
 * ========================================================================= */

(function ( $ ) {

  'use strict';

  /* IS ATTACHED */

  $.fn.isAttached = function () {

    return $.contains ( document.documentElement, this[0] );

  };

}( jQuery ));
<commit_msg>Fixed `$('html').isAttached ()` that was previously returning `false`<commit_after>
/* =========================================================================
 * Svelto - Core - jQuery - Helpers (Is attached)
 * =========================================================================
 * Copyright (c) 2015-2016 Fabio Spampinato
 * Licensed under MIT (https://github.com/svelto/svelto/blob/master/LICENSE)
 * =========================================================================
 * @require ../init.js
 * ========================================================================= */

(function ( $ ) {

  'use strict';

  /* IS ATTACHED */

  $.fn.isAttached = function () {

    let html = document.documentElement;

    return ( this[0] === html ) || $.contains ( html, this[0] );

  };

}( jQuery ));
<|endoftext|>


<commit_before>import { module } from 'substance-test'
import { DefaultDOMElement } from 'substance'
import { REQUIRED_ELEMENT_CITATION_ELEMENTS } from '../../src/constants'

// TODO: export all trafos via index.es.js, and import {..} from '../../index.es.js'
import ConvertElementCitation from '../../src/converter/r2t/ConvertElementCitation'
const test = module('Element Citation Converter')
import readFixture from '../fixture/readFixture'
let fixture = readFixture('element-citation.xml')

test("r2t: Import content-loc", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)

  let r1 = dom.find('#r1')
  let contentLoc = r1.find('content-loc')
  // fpage,lpage,page-range should be wrapped in content-loc
  t.equal(contentLoc.attr('type'), 'print', 'Should have a print content-loc')
  t.ok(contentLoc.find('fpage'), 'contentLoc should have an fpage')
  t.ok(contentLoc.find('lpage'), 'contentLoc should have an lpage')
  t.ok(contentLoc.find('page-range'), 'empty page-range element should have been created')
  t.end()
})

test("r2t: Create empty elements on import if not present", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)
  let r1 = dom.find('#r1')
  console.log(r1.getNativeElement())
  // should have all possible elements expanded
  t.ok(_hasElements(r1, REQUIRED_ELEMENT_CITATION_ELEMENTS), 'should have all required elements expanded')
  t.end()
})

test("r2t: Fill in missing pub-ids for journal publication", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)

  let r1 = dom.find('#r1')
  let doi = r1.find('pub-id[pub-id-type=doi]')
  let pmid = r1.find('pub-id[pub-id-type=pmid]')

  t.ok(doi, 'doi should be present')
  t.ok(pmid, 'pmid should be present')
  t.end()
})

test("r2t: Strip empty elements on export", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)
  converter.export(dom)
  let r1 = dom.find('#r1')
  let issue = r1.find('issue')
  t.notOk(issue, 'Should not have an empty issue element after export')
  t.end()
})

function _hasElements(el, elements) {
  let result = true
  elements.forEach((element) => {
    let [tagName, attrib, attribVal] = element
    let matcher = tagName
    if (attrib) {
      matcher = `${tagName}[${attrib}=${attribVal}]`
    }
    if (!el.find(matcher)) {
      result = false
    }
  })
  return result
}
<commit_msg>Silence.<commit_after>import { module } from 'substance-test'
import { DefaultDOMElement } from 'substance'
import { REQUIRED_ELEMENT_CITATION_ELEMENTS } from '../../src/constants'

// TODO: export all trafos via index.es.js, and import {..} from '../../index.es.js'
import ConvertElementCitation from '../../src/converter/r2t/ConvertElementCitation'
const test = module('Element Citation Converter')
import readFixture from '../fixture/readFixture'
let fixture = readFixture('element-citation.xml')

test("r2t: Import content-loc", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)

  let r1 = dom.find('#r1')
  let contentLoc = r1.find('content-loc')
  // fpage,lpage,page-range should be wrapped in content-loc
  t.equal(contentLoc.attr('type'), 'print', 'Should have a print content-loc')
  t.ok(contentLoc.find('fpage'), 'contentLoc should have an fpage')
  t.ok(contentLoc.find('lpage'), 'contentLoc should have an lpage')
  t.ok(contentLoc.find('page-range'), 'empty page-range element should have been created')
  t.end()
})

test("r2t: Create empty elements on import if not present", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)
  let r1 = dom.find('#r1')
  // should have all possible elements expanded
  t.ok(_hasElements(r1, REQUIRED_ELEMENT_CITATION_ELEMENTS), 'should have all required elements expanded')
  t.end()
})

test("r2t: Fill in missing pub-ids for journal publication", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)

  let r1 = dom.find('#r1')
  let doi = r1.find('pub-id[pub-id-type=doi]')
  let pmid = r1.find('pub-id[pub-id-type=pmid]')

  t.ok(doi, 'doi should be present')
  t.ok(pmid, 'pmid should be present')
  t.end()
})

test("r2t: Strip empty elements on export", function(t) {
  let dom = DefaultDOMElement.parseXML(fixture)
  let converter = new ConvertElementCitation()
  converter.import(dom)
  converter.export(dom)
  let r1 = dom.find('#r1')
  let issue = r1.find('issue')
  t.notOk(issue, 'Should not have an empty issue element after export')
  t.end()
})

function _hasElements(el, elements) {
  let result = true
  elements.forEach((element) => {
    let [tagName, attrib, attribVal] = element
    let matcher = tagName
    if (attrib) {
      matcher = `${tagName}[${attrib}=${attribVal}]`
    }
    if (!el.find(matcher)) {
      result = false
    }
  })
  return result
}
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule shouldUpdateReactComponent
 * @typechecks static-only
 */

'use strict';

var warning = require('warning');

/**
 * Given a `prevElement` and `nextElement`, determines if the existing
 * instance should be updated as opposed to being destroyed or replaced by a new
 * instance. Both arguments are elements. This ensures that this logic can
 * operate on stateless trees without any backing instance.
 *
 * @param {?object} prevElement
 * @param {?object} nextElement
 * @return {boolean} True if the existing instance should be updated.
 * @protected
 */
function shouldUpdateReactComponent(prevElement, nextElement) {
  if (prevElement != null && nextElement != null) {
    var prevType = typeof prevElement;
    var nextType = typeof nextElement;
    if (prevType === 'string' || prevType === 'number') {
      return (nextType === 'string' || nextType === 'number');
    } else {
      if (nextType === 'object' &&
          prevElement.type === nextElement.type &&
          prevElement.key === nextElement.key) {
        var ownersMatch = prevElement._owner === nextElement._owner;
        var prevName = null;
        var nextName = null;
        var nextDisplayName = null;
        if (__DEV__) {
          if (!ownersMatch) {
            if (prevElement._owner != null &&
                prevElement._owner.getPublicInstance() != null &&
                prevElement._owner.getPublicInstance().constructor != null) {
              prevName =
                prevElement._owner.getPublicInstance().constructor.displayName;
            }
            if (nextElement._owner != null &&
                nextElement._owner.getPublicInstance() != null &&
                nextElement._owner.getPublicInstance().constructor != null) {
              nextName =
                nextElement._owner.getPublicInstance().constructor.displayName;
            }
            if (nextElement.type != null &&
                nextElement.type.displayName != null) {
              nextDisplayName = nextElement.type.displayName;
            }
            warning(
              false,
              '<%s /> is being rendered by both %s and %s using the same key ' +
              '(%s) in the same place. Currently, this means that they ' +
              'don\'t preserve state. This behavior should be very rare ' +
              'so we\'re considering deprecating it. Please contact the ' +
              'React team and explain your use case so that we can take that ' +
              'into consideration.',
              nextDisplayName || 'Unknown Component',
              prevName || '[Unknown]',
              nextName || '[Unknown]',
              prevElement.key
            );
          }
        }
        return ownersMatch;
      }
    }
  }
  return false;
}

module.exports = shouldUpdateReactComponent;
<commit_msg>Only monitor components that are likely stateful (inputs and composites)<commit_after>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule shouldUpdateReactComponent
 * @typechecks static-only
 */

'use strict';

var warning = require('warning');

/**
 * Given a `prevElement` and `nextElement`, determines if the existing
 * instance should be updated as opposed to being destroyed or replaced by a new
 * instance. Both arguments are elements. This ensures that this logic can
 * operate on stateless trees without any backing instance.
 *
 * @param {?object} prevElement
 * @param {?object} nextElement
 * @return {boolean} True if the existing instance should be updated.
 * @protected
 */
function shouldUpdateReactComponent(prevElement, nextElement) {
  if (prevElement != null && nextElement != null) {
    var prevType = typeof prevElement;
    var nextType = typeof nextElement;
    if (prevType === 'string' || prevType === 'number') {
      return (nextType === 'string' || nextType === 'number');
    } else {
      if (nextType === 'object' &&
          prevElement.type === nextElement.type &&
          prevElement.key === nextElement.key) {
        var ownersMatch = prevElement._owner === nextElement._owner;
        var prevName = null;
        var nextName = null;
        var nextDisplayName = null;
        if (__DEV__) {
          if (!ownersMatch) {
            if (prevElement._owner != null &&
                prevElement._owner.getPublicInstance() != null &&
                prevElement._owner.getPublicInstance().constructor != null) {
              prevName =
                prevElement._owner.getPublicInstance().constructor.displayName;
            }
            if (nextElement._owner != null &&
                nextElement._owner.getPublicInstance() != null &&
                nextElement._owner.getPublicInstance().constructor != null) {
              nextName =
                nextElement._owner.getPublicInstance().constructor.displayName;
            }
            if (nextElement.type != null &&
                nextElement.type.displayName != null) {
              nextDisplayName = nextElement.type.displayName;
            }
            if (nextElement.type != null && typeof nextElement.type === 'string') {
              nextDisplayName = nextElement.type;
            }
            if (typeof nextElement.type !== 'string' ||
                nextElement.type === 'input' ||
                nextElement.type === 'textarea') {
              warning(
                false,
                '<%s /> is being rendered by both %s and %s using the same key ' +
                '(%s) in the same place. Currently, this means that they ' +
                'don\'t preserve state. This behavior should be very rare ' +
                'so we\'re considering deprecating it. Please contact the ' +
                'React team and explain your use case so that we can take that ' +
                'into consideration.',
                nextDisplayName || 'Unknown Component',
                prevName || '[Unknown]',
                nextName || '[Unknown]',
                prevElement.key
              );
            }
          }
        }
        return ownersMatch;
      }
    }
  }
  return false;
}

module.exports = shouldUpdateReactComponent;
<|endoftext|>


<commit_before>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

/**
 * @fileoverview V8LogImporter imports v8.log files into the provided model.
 */
tvcm.require('tracing.importer.importer');
tvcm.require('tracing.trace_model');
tvcm.require('tracing.trace_model.slice');
tvcm.require('tracing.color_scheme');
tvcm.require('tracing.importer.v8.log_reader');
tvcm.require('tracing.importer.v8.codemap');

tvcm.exportTo('tracing.importer', function() {

  var Importer = tracing.importer.Importer;

  function V8LogImporter(model, eventData) {

    this.importPriority = 3;
    this.model_ = model;

    this.logData_ = eventData;

    this.code_map_ = new tracing.importer.v8.CodeMap();
    this.v8_timer_thread_ = undefined;
    this.v8_thread_ = undefined;

    this.root_stack_frame_ = new tracing.trace_model.StackFrame(
        undefined, 'v8-root-stack-frame',
        'v8-root-stack-frame', 'v8-root-stack-frame', 0);

    // We reconstruct the stack timeline from ticks.
    this.v8_stack_timeline_ = new Array();
  }

  var kV8BinarySuffixes = ['/d8', '/libv8.so'];

  var TimerEventDefaultArgs = {
    'V8.Execute': { pause: false, no_execution: false},
    'V8.External': { pause: false, no_execution: true},
    'V8.CompileFullCode': { pause: true, no_execution: true},
    'V8.RecompileSynchronous': { pause: true, no_execution: true},
    'V8.RecompileParallel': { pause: false, no_execution: false},
    'V8.CompileEval': { pause: true, no_execution: true},
    'V8.Parse': { pause: true, no_execution: true},
    'V8.PreParse': { pause: true, no_execution: true},
    'V8.ParseLazy': { pause: true, no_execution: true},
    'V8.GCScavenger': { pause: true, no_execution: true},
    'V8.GCCompactor': { pause: true, no_execution: true},
    'V8.GCContext': { pause: true, no_execution: true}
  };

  /**
   * @return {boolean} Whether obj is a V8 log string.
   */
  V8LogImporter.canImport = function(eventData) {
    if (typeof(eventData) !== 'string' && !(eventData instanceof String))
      return false;

    return eventData.substring(0, 12) == 'timer-event,' ||
           eventData.substring(0, 5) == 'tick,' ||
           eventData.substring(0, 15) == 'shared-library,' ||
           eventData.substring(0, 9) == 'profiler,' ||
           eventData.substring(0, 14) == 'code-creation,';
  };

  V8LogImporter.prototype = {

    __proto__: Importer.prototype,

    processTimerEvent_: function(name, start, length) {
      var args = TimerEventDefaultArgs[name];
      if (args === undefined) return;
      start /= 1000;  // Convert to milliseconds.
      length /= 1000;
      var colorId = tvcm.ui.getStringColorId(name);
      var slice = new tracing.trace_model.Slice('v8', name, colorId, start,
          args, length);
      this.v8_timer_thread_.sliceGroup.pushSlice(slice);
    },

    processTimerEventStart_: function(name, start) {
      var args = TimerEventDefaultArgs[name];
      if (args === undefined) return;
      start /= 1000;  // Convert to milliseconds.
      this.v8_timer_thread_.sliceGroup.beginSlice('v8', name, start, args);
    },

    processTimerEventEnd_: function(name, end) {
      end /= 1000;  // Convert to milliseconds.
      this.v8_timer_thread_.sliceGroup.endSlice(end);
    },

    processCodeCreateEvent_: function(type, kind, address, size, name) {
      var code_entry = new tracing.importer.v8.CodeMap.CodeEntry(size, name);
      code_entry.kind = kind;
      this.code_map_.addCode(address, code_entry);
    },

    processCodeMoveEvent_: function(from, to) {
      this.code_map_.moveCode(from, to);
    },

    processCodeDeleteEvent_: function(address) {
      this.code_map_.deleteCode(address);
    },

    processSharedLibrary_: function(name, start, end) {
      var code_entry = new tracing.importer.v8.CodeMap.CodeEntry(
          end - start, name);
      code_entry.kind = -3;  // External code kind.
      for (var i = 0; i < kV8BinarySuffixes.length; i++) {
        var suffix = kV8BinarySuffixes[i];
        if (name.indexOf(suffix, name.length - suffix.length) >= 0) {
          code_entry.kind = -1;  // V8 runtime code kind.
          break;
        }
      }
      this.code_map_.addLibrary(start, code_entry);
    },

    findCodeKind_: function(kind) {
      for (name in CodeKinds) {
        if (CodeKinds[name].kinds.indexOf(kind) >= 0) {
          return CodeKinds[name];
        }
      }
    },

    processTickEvent_: function(pc, start, unused_x, unused_y, vmstate, stack) {
      start /= 1000;

      function findChildWithEntryID(stackFrame, entryID) {
        for (var i = 0; i < stackFrame.children.length; i++) {
          if (stackFrame.children[i].entryID == entryID)
            return stackFrame.children[i];
        }
        return undefined;
      }

      var lastStackFrame;
      if (stack && stack.length) {

        lastStackFrame = this.root_stack_frame_;
        // v8 log stacks are inverted, leaf first and the root at the end.
        stack = stack.reverse();
        for (var i = 0; i < stack.length; i++) {
          if (!stack[i])
            break;
          var entry = this.code_map_.findEntry(stack[i]);

          var entryID = entry ? entry.id : 'Unknown';
          var childFrame = findChildWithEntryID(lastStackFrame, entryID);
          if (childFrame === undefined) {
            var entryName = entry ? entry.name : 'Unknown';
            lastStackFrame = new tracing.trace_model.StackFrame(
                lastStackFrame, 'v8sf-' + tvcm.GUID.allocate(),
                'v8', entryName,
                tvcm.ui.getStringColorId(entryName));
            lastStackFrame.entryID = entryID;
            this.model_.addStackFrame(lastStackFrame);
          } else {
            lastStackFrame = childFrame;
          }
        }
      } else {
        var pcEntry = this.code_map_.findEntry(pc);
        var pcEntryID = 'v8pc-' + (pcEntry ? pcEntry.id : 'Unknown');
        if (this.model_.stackFrames[pcEntryID] === undefined) {
          var pcEntryName = pcEntry ? pcEntry.name : 'Unknown';
          lastStackFrame = new tracing.trace_model.StackFrame(
              undefined, pcEntryID,
              'v8', pcEntryName,
              tvcm.ui.getStringColorId(entryName));
          this.model_.addStackFrame(lastStackFrame);
        }
        lastStackFrame = this.model_.stackFrames[pcEntryID];
      }

      var sample = new tracing.trace_model.Sample(
          undefined, this.v8_thread_, 'V8 PC',
          start, lastStackFrame, 1);
      this.model_.samples.push(sample);
    },

    processDistortion_: function(distortion_in_picoseconds) {
      distortion_per_entry = distortion_in_picoseconds / 1000000;
    },

    processPlotRange_: function(start, end) {
      xrange_start_override = start;
      xrange_end_override = end;
    },

    /**
     * Walks through the events_ list and outputs the structures discovered to
     * model_.
     */
    importEvents: function() {
      var logreader = new tracing.importer.v8.LogReader(
          { 'timer-event' : {
            parsers: [null, parseInt, parseInt],
            processor: this.processTimerEvent_.bind(this)
          },
          'shared-library': {
            parsers: [null, parseInt, parseInt],
            processor: this.processSharedLibrary_.bind(this)
          },
          'timer-event-start' : {
            parsers: [null, parseInt],
            processor: this.processTimerEventStart_.bind(this)
          },
          'timer-event-end' : {
            parsers: [null, parseInt],
            processor: this.processTimerEventEnd_.bind(this)
          },
          'code-creation': {
            parsers: [null, parseInt, parseInt, parseInt, null],
            processor: this.processCodeCreateEvent_.bind(this)
          },
          'code-move': {
            parsers: [parseInt, parseInt],
            processor: this.processCodeMoveEvent_.bind(this)
          },
          'code-delete': {
            parsers: [parseInt],
            processor: this.processCodeDeleteEvent_.bind(this)
          },
          'tick': {
            parsers: [parseInt, parseInt, null, null, parseInt, 'var-args'],
            processor: this.processTickEvent_.bind(this)
          },
          'distortion': {
            parsers: [parseInt],
            processor: this.processDistortion_.bind(this)
          },
          'plot-range': {
            parsers: [parseInt, parseInt],
            processor: this.processPlotRange_.bind(this)
          }
          });

      this.v8_timer_thread_ =
          this.model_.getOrCreateProcess(-32).getOrCreateThread(1);
      this.v8_timer_thread_.name = 'V8 Timers';
      this.v8_thread_ =
          this.model_.getOrCreateProcess(-32).getOrCreateThread(2);
      this.v8_thread_.name = 'V8';

      var lines = this.logData_.split('\n');
      for (var i = 0; i < lines.length; i++) {
        logreader.processLogLine(lines[i]);
      }

      // The processing of samples adds all the root stack frame to
      // this.root_stack_frame_. But, we don't want that stack frame in the real
      // model. So get rid of it.
      this.root_stack_frame_.removeAllChildren();

      function addSlices(slices, thread) {
        for (var i = 0; i < slices.length; i++) {
          var duration = slices[i].end - slices[i].start;
          var slice = new tracing.trace_model.Slice('v8', slices[i].name,
              tvcm.ui.getStringColorId(slices[i].name),
              slices[i].start, {}, duration);
          thread.sliceGroup.pushSlice(slice);
          addSlices(slices[i].children, thread);
        }
      }
      addSlices(this.v8_stack_timeline_, this.v8_thread_);
    }
  };

  tracing.TraceModel.registerImporter(V8LogImporter);

  return {
    V8LogImporter: V8LogImporter
  };
});
<commit_msg>Fixed the fix for stack frame coloring. (108230045).<commit_after>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

/**
 * @fileoverview V8LogImporter imports v8.log files into the provided model.
 */
tvcm.require('tracing.importer.importer');
tvcm.require('tracing.trace_model');
tvcm.require('tracing.trace_model.slice');
tvcm.require('tracing.color_scheme');
tvcm.require('tracing.importer.v8.log_reader');
tvcm.require('tracing.importer.v8.codemap');

tvcm.exportTo('tracing.importer', function() {

  var Importer = tracing.importer.Importer;

  function V8LogImporter(model, eventData) {

    this.importPriority = 3;
    this.model_ = model;

    this.logData_ = eventData;

    this.code_map_ = new tracing.importer.v8.CodeMap();
    this.v8_timer_thread_ = undefined;
    this.v8_thread_ = undefined;

    this.root_stack_frame_ = new tracing.trace_model.StackFrame(
        undefined, 'v8-root-stack-frame',
        'v8-root-stack-frame', 'v8-root-stack-frame', 0);

    // We reconstruct the stack timeline from ticks.
    this.v8_stack_timeline_ = new Array();
  }

  var kV8BinarySuffixes = ['/d8', '/libv8.so'];

  var TimerEventDefaultArgs = {
    'V8.Execute': { pause: false, no_execution: false},
    'V8.External': { pause: false, no_execution: true},
    'V8.CompileFullCode': { pause: true, no_execution: true},
    'V8.RecompileSynchronous': { pause: true, no_execution: true},
    'V8.RecompileParallel': { pause: false, no_execution: false},
    'V8.CompileEval': { pause: true, no_execution: true},
    'V8.Parse': { pause: true, no_execution: true},
    'V8.PreParse': { pause: true, no_execution: true},
    'V8.ParseLazy': { pause: true, no_execution: true},
    'V8.GCScavenger': { pause: true, no_execution: true},
    'V8.GCCompactor': { pause: true, no_execution: true},
    'V8.GCContext': { pause: true, no_execution: true}
  };

  /**
   * @return {boolean} Whether obj is a V8 log string.
   */
  V8LogImporter.canImport = function(eventData) {
    if (typeof(eventData) !== 'string' && !(eventData instanceof String))
      return false;

    return eventData.substring(0, 12) == 'timer-event,' ||
           eventData.substring(0, 5) == 'tick,' ||
           eventData.substring(0, 15) == 'shared-library,' ||
           eventData.substring(0, 9) == 'profiler,' ||
           eventData.substring(0, 14) == 'code-creation,';
  };

  V8LogImporter.prototype = {

    __proto__: Importer.prototype,

    processTimerEvent_: function(name, start, length) {
      var args = TimerEventDefaultArgs[name];
      if (args === undefined) return;
      start /= 1000;  // Convert to milliseconds.
      length /= 1000;
      var colorId = tvcm.ui.getStringColorId(name);
      var slice = new tracing.trace_model.Slice('v8', name, colorId, start,
          args, length);
      this.v8_timer_thread_.sliceGroup.pushSlice(slice);
    },

    processTimerEventStart_: function(name, start) {
      var args = TimerEventDefaultArgs[name];
      if (args === undefined) return;
      start /= 1000;  // Convert to milliseconds.
      this.v8_timer_thread_.sliceGroup.beginSlice('v8', name, start, args);
    },

    processTimerEventEnd_: function(name, end) {
      end /= 1000;  // Convert to milliseconds.
      this.v8_timer_thread_.sliceGroup.endSlice(end);
    },

    processCodeCreateEvent_: function(type, kind, address, size, name) {
      var code_entry = new tracing.importer.v8.CodeMap.CodeEntry(size, name);
      code_entry.kind = kind;
      this.code_map_.addCode(address, code_entry);
    },

    processCodeMoveEvent_: function(from, to) {
      this.code_map_.moveCode(from, to);
    },

    processCodeDeleteEvent_: function(address) {
      this.code_map_.deleteCode(address);
    },

    processSharedLibrary_: function(name, start, end) {
      var code_entry = new tracing.importer.v8.CodeMap.CodeEntry(
          end - start, name);
      code_entry.kind = -3;  // External code kind.
      for (var i = 0; i < kV8BinarySuffixes.length; i++) {
        var suffix = kV8BinarySuffixes[i];
        if (name.indexOf(suffix, name.length - suffix.length) >= 0) {
          code_entry.kind = -1;  // V8 runtime code kind.
          break;
        }
      }
      this.code_map_.addLibrary(start, code_entry);
    },

    findCodeKind_: function(kind) {
      for (name in CodeKinds) {
        if (CodeKinds[name].kinds.indexOf(kind) >= 0) {
          return CodeKinds[name];
        }
      }
    },

    processTickEvent_: function(pc, start, unused_x, unused_y, vmstate, stack) {
      start /= 1000;

      function findChildWithEntryID(stackFrame, entryID) {
        for (var i = 0; i < stackFrame.children.length; i++) {
          if (stackFrame.children[i].entryID == entryID)
            return stackFrame.children[i];
        }
        return undefined;
      }

      var lastStackFrame;
      if (stack && stack.length) {

        lastStackFrame = this.root_stack_frame_;
        // v8 log stacks are inverted, leaf first and the root at the end.
        stack = stack.reverse();
        for (var i = 0; i < stack.length; i++) {
          if (!stack[i])
            break;
          var entry = this.code_map_.findEntry(stack[i]);

          var entryID = entry ? entry.id : 'Unknown';
          var childFrame = findChildWithEntryID(lastStackFrame, entryID);
          if (childFrame === undefined) {
            var entryName = entry ? entry.name : 'Unknown';
            lastStackFrame = new tracing.trace_model.StackFrame(
                lastStackFrame, 'v8sf-' + tvcm.GUID.allocate(),
                'v8', entryName,
                tvcm.ui.getStringColorId(entryName));
            lastStackFrame.entryID = entryID;
            this.model_.addStackFrame(lastStackFrame);
          } else {
            lastStackFrame = childFrame;
          }
        }
      } else {
        var pcEntry = this.code_map_.findEntry(pc);
        var pcEntryID = 'v8pc-' + (pcEntry ? pcEntry.id : 'Unknown');
        if (this.model_.stackFrames[pcEntryID] === undefined) {
          var pcEntryName = pcEntry ? pcEntry.name : 'Unknown';
          lastStackFrame = new tracing.trace_model.StackFrame(
              undefined, pcEntryID,
              'v8', pcEntryName,
              tvcm.ui.getStringColorId(pcEntryName));
          this.model_.addStackFrame(lastStackFrame);
        }
        lastStackFrame = this.model_.stackFrames[pcEntryID];
      }

      var sample = new tracing.trace_model.Sample(
          undefined, this.v8_thread_, 'V8 PC',
          start, lastStackFrame, 1);
      this.model_.samples.push(sample);
    },

    processDistortion_: function(distortion_in_picoseconds) {
      distortion_per_entry = distortion_in_picoseconds / 1000000;
    },

    processPlotRange_: function(start, end) {
      xrange_start_override = start;
      xrange_end_override = end;
    },

    /**
     * Walks through the events_ list and outputs the structures discovered to
     * model_.
     */
    importEvents: function() {
      var logreader = new tracing.importer.v8.LogReader(
          { 'timer-event' : {
            parsers: [null, parseInt, parseInt],
            processor: this.processTimerEvent_.bind(this)
          },
          'shared-library': {
            parsers: [null, parseInt, parseInt],
            processor: this.processSharedLibrary_.bind(this)
          },
          'timer-event-start' : {
            parsers: [null, parseInt],
            processor: this.processTimerEventStart_.bind(this)
          },
          'timer-event-end' : {
            parsers: [null, parseInt],
            processor: this.processTimerEventEnd_.bind(this)
          },
          'code-creation': {
            parsers: [null, parseInt, parseInt, parseInt, null],
            processor: this.processCodeCreateEvent_.bind(this)
          },
          'code-move': {
            parsers: [parseInt, parseInt],
            processor: this.processCodeMoveEvent_.bind(this)
          },
          'code-delete': {
            parsers: [parseInt],
            processor: this.processCodeDeleteEvent_.bind(this)
          },
          'tick': {
            parsers: [parseInt, parseInt, null, null, parseInt, 'var-args'],
            processor: this.processTickEvent_.bind(this)
          },
          'distortion': {
            parsers: [parseInt],
            processor: this.processDistortion_.bind(this)
          },
          'plot-range': {
            parsers: [parseInt, parseInt],
            processor: this.processPlotRange_.bind(this)
          }
          });

      this.v8_timer_thread_ =
          this.model_.getOrCreateProcess(-32).getOrCreateThread(1);
      this.v8_timer_thread_.name = 'V8 Timers';
      this.v8_thread_ =
          this.model_.getOrCreateProcess(-32).getOrCreateThread(2);
      this.v8_thread_.name = 'V8';

      var lines = this.logData_.split('\n');
      for (var i = 0; i < lines.length; i++) {
        logreader.processLogLine(lines[i]);
      }

      // The processing of samples adds all the root stack frame to
      // this.root_stack_frame_. But, we don't want that stack frame in the real
      // model. So get rid of it.
      this.root_stack_frame_.removeAllChildren();

      function addSlices(slices, thread) {
        for (var i = 0; i < slices.length; i++) {
          var duration = slices[i].end - slices[i].start;
          var slice = new tracing.trace_model.Slice('v8', slices[i].name,
              tvcm.ui.getStringColorId(slices[i].name),
              slices[i].start, {}, duration);
          thread.sliceGroup.pushSlice(slice);
          addSlices(slices[i].children, thread);
        }
      }
      addSlices(this.v8_stack_timeline_, this.v8_thread_);
    }
  };

  tracing.TraceModel.registerImporter(V8LogImporter);

  return {
    V8LogImporter: V8LogImporter
  };
});
<|endoftext|>


<commit_before>import React from 'react';

import { mount } from 'enzyme';

const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require("sinon-chai");

chai.use(sinonChai);
const expect = chai.expect;

import * as commutable from 'commutable';
import { dummyStore } from '../../../utils';

import Toolbar from '../../../../src/notebook/components/cell/toolbar';
import { setNotificationSystem } from '../../../../src/notebook/actions';

describe('Toolbar', () => {
  it('should be able to render a toolbar', () => {
    const toolbar = mount(
      <Toolbar />
    );
    expect(toolbar).to.not.be.null;
    expect(toolbar.find('div.cell-toolbar').length).to.be.greaterThan(0);
  });
  it('clearOutputs does not throw error', () => {
    const toolbar = mount(
      <Toolbar />, { context: { store: dummyStore() }}
    );
    expect(() => {toolbar.instance().clearOutputs()}).to.not.throw(Error);
  });
});

describe('Toolbar.executeCell', () => {
  it('dispatches an executeCell action', () => {
    const cell = commutable.emptyCodeCell.set('source', 'print("sup")')
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store: store } }
    );

    const button = toolbar
      .find('.executeButton');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'EXECUTE_CELL',
      source: 'print("sup")',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.removeCell', () => {
  it('dispatches a REMOVE_CELL action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.deleteButton');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'REMOVE_CELL',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.toggleStickyCell', () => {
  it('dispatches TOGGLE_STICKY_CELL action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.stickyButton');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'TOGGLE_STICKY_CELL',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.clearOutputs', () => {
  it('dispatches CLEAR_OUTPUTS action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'}/>,
      { context: { store } }
    );

    const button = toolbar
      .find('.clearOutput');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CLEAR_OUTPUTS',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.changeInputVisibility', () => {
  it('dispatches CHANGE_INPUT_VISIBILITY action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.inputVisibility');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CHANGE_INPUT_VISIBILITY',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.changeOutputVisibility', () => {
  it('dispatches CHANGE_OUTPUT_VISIBILITY action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'}/>,
      { context: { store } }
    );

    const button = toolbar
      .find('.outputVisibility');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CHANGE_OUTPUT_VISIBILITY',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.changeCellType', () => {
  it('dispatches CHANGE_CELL_TYPE action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.changeType');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CHANGE_CELL_TYPE',
      id: '0-1-2-3',
      to: 'markdown',
    });
  });
});

describe('Toolbar.toggleOutputExpansion', () => {
  it('dispatches a TOGGLE_OUTPUT_EXPANSION action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.outputExpanded');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'TOGGLE_OUTPUT_EXPANSION',
      id: '0-1-2-3',
    });
  });
});
<commit_msg>Add tests for toolbar rendering<commit_after>import React from 'react';

import { mount } from 'enzyme';

const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require("sinon-chai");

chai.use(sinonChai);
const expect = chai.expect;

import * as commutable from 'commutable';
import { dummyStore } from '../../../utils';

import Toolbar from '../../../../src/notebook/components/cell/toolbar';
import { setNotificationSystem } from '../../../../src/notebook/actions';

describe('Toolbar', () => {
  it('should be able to render a toolbar', () => {
    const toolbar = mount(
      <Toolbar />
    );
    expect(toolbar).to.not.be.null;
    expect(toolbar.find('div.cell-toolbar').length).to.be.greaterThan(0);
  });
  it('clearOutputs does not throw error', () => {
    const toolbar = mount(
      <Toolbar />, { context: { store: dummyStore() }}
    );
    expect(() => {toolbar.instance().clearOutputs()}).to.not.throw(Error);
  });
  it('shows "convert to code cell" menu entry for markdown type', () => {
    const toolbar = mount(
      <Toolbar type={'markdown'} />
    );
    expect(toolbar.text()).to.contain('Convert to Code Cell');
  });
  it('shows "convert to markdown cell" menu entry for code type', () => {
    const toolbar = mount(
      <Toolbar type={'code'} />
    );
    expect(toolbar.text()).to.contain('Convert to Markdown Cell');
  });
  it('changes "Convert to ..." menu entry on type change', () => {
    const toolbar = mount(
      <Toolbar type={'code'} />
    );
    expect(toolbar.text()).to.contain('Convert to Markdown Cell');
    toolbar.setProps({ type: 'markdown' });
    expect(toolbar.text()).to.contain('Convert to Code Cell');
  });
});

describe('Toolbar.executeCell', () => {
  it('dispatches an executeCell action', () => {
    const cell = commutable.emptyCodeCell.set('source', 'print("sup")')
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store: store } }
    );

    const button = toolbar
      .find('.executeButton');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'EXECUTE_CELL',
      source: 'print("sup")',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.removeCell', () => {
  it('dispatches a REMOVE_CELL action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.deleteButton');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'REMOVE_CELL',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.toggleStickyCell', () => {
  it('dispatches TOGGLE_STICKY_CELL action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.stickyButton');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'TOGGLE_STICKY_CELL',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.clearOutputs', () => {
  it('dispatches CLEAR_OUTPUTS action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'}/>,
      { context: { store } }
    );

    const button = toolbar
      .find('.clearOutput');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CLEAR_OUTPUTS',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.changeInputVisibility', () => {
  it('dispatches CHANGE_INPUT_VISIBILITY action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.inputVisibility');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CHANGE_INPUT_VISIBILITY',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.changeOutputVisibility', () => {
  it('dispatches CHANGE_OUTPUT_VISIBILITY action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'}/>,
      { context: { store } }
    );

    const button = toolbar
      .find('.outputVisibility');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CHANGE_OUTPUT_VISIBILITY',
      id: '0-1-2-3',
    });
  });
});

describe('Toolbar.changeCellType', () => {
  it('dispatches CHANGE_CELL_TYPE action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.changeType');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'CHANGE_CELL_TYPE',
      id: '0-1-2-3',
      to: 'markdown',
    });
  });
});

describe('Toolbar.toggleOutputExpansion', () => {
  it('dispatches a TOGGLE_OUTPUT_EXPANSION action', () => {
    const cell = commutable.emptyCodeCell;
    const store = dummyStore();
    store.dispatch = sinon.spy();

    const toolbar = mount(
      <Toolbar id={'0-1-2-3'} cell={cell} type={'code'} />,
      { context: { store } }
    );

    const button = toolbar
      .find('.outputExpanded');

    button.simulate('click');

    expect(store.dispatch.firstCall).to.be.calledWith({
      type: 'TOGGLE_OUTPUT_EXPANSION',
      id: '0-1-2-3',
    });
  });
});
<|endoftext|>