<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import createReactClass from 'create-react-class';
import ReactDOM from 'react-dom';
import { LoadingAnimation, SpyMode, ArbitraryHtmlAndMath, GetPositionMixin } from 'shared';
import { observer } from 'mobx-react';
import classnames from 'classnames';
import { BookContentMixin } from './book-content-mixin';
import { ReferenceBookExerciseShell } from './book-page/exercise';
import RelatedContent from './related-content';

import NotesWidget from './notes';
import { ReferenceBookExerciseActions, ReferenceBookExerciseStore } from '../flux/reference-book-exercise';
import map from 'lodash/map';
import forEach from 'lodash/forEach';

const BookPage = createReactClass({
  displayName: 'BookPage',

  propTypes: {
    ux: PropTypes.object.isRequired,
  },

  mixins: [BookContentMixin, GetPositionMixin],

  getCnxId() {
    return this.props.ux.page.cnx_id;
  },

  UNSAFE_componentWillMount() {
    return this.props.ux.page.ensureLoaded();
  },

  UNSAFE_componentWillReceiveProps() {
    return this.props.ux.page.ensureLoaded();
  },

  getSplashTitle() {
    return this.props.ux.page.title;
  },

  componentDidMount() {
    return this.props.ux.checkForTeacherContent();
  },

  componentDidUpdate() {
    return this.props.ux.checkForTeacherContent();
  },

  // used by BookContentMixin
  shouldOpenNewTab() { return true; },

  waitToScrollToSelector(hash) {
    const images = ReactDOM.findDOMNode(this).querySelectorAll('img');
    let imagesToLoad = images.length;
    const onImageLoad = () => {
      imagesToLoad -= 1;
      if (imagesToLoad === 0) {
        // final scroll to
        this.scrollToSelector(hash);
      }
    };
    for (let image of images) {
      image.addEventListener('load', onImageLoad);
    }

    return images.length > 0;
  },

  renderExercises(exerciseLinks) {
    ReferenceBookExerciseStore.setMaxListeners(exerciseLinks.length);
    const links = map(exerciseLinks, 'href');
    if (!ReferenceBookExerciseStore.isLoaded(links)) { ReferenceBookExerciseActions.loadMultiple(links); }

    return forEach(exerciseLinks, this.renderExercise);
  },

  renderExercise(link) {
    const exerciseAPIUrl = link.href;
    const exerciseNode = link.parentNode.parentNode;
    if (exerciseNode != null) {
      return ReactDOM.render(<ReferenceBookExerciseShell exerciseAPIUrl={exerciseAPIUrl} />, exerciseNode);
    }
    return null;
  },

  render() {
    let isLoading;
    let { ux, ux: { page } } = this.props;

    if (!page || page.api.isPending) {
      if (ux.lastSection) {
        isLoading = true;
        page = ux.pages.byChapterSection.get(ux.lastSection);
      } else {
        return <LoadingAnimation />;
      }
    }


    const related = {
      chapter_section: page.chapter_section.asArray,
      title: this.getSplashTitle(),
    };

    return (
      <div
        className={classnames('book-page', this.props.className, {
          'page-loading loadable is-loading': isLoading,
          'book-is-collated': page.bookIsCollated,
        })}
        {...ux.courseDataProps}>
        {this.props.children}
        <div className="page center-panel">
          <RelatedContent contentId={page.cnx_id} {...related} />
          <ArbitraryHtmlAndMath className="book-content" block={true} html={page.contents} />
        </div>
        <SpyMode.Content className="ecosystem-info">
          PageId: {page.cnx_id}, Ecosystem: {JSON.stringify(page && page.spy)}
        </SpyMode.Content>
        {ux.allowsAnnotating && (
          <NotesWidget
            courseId={ux.course.id}
            chapter={page.chapter_section.chapter}
            section={page.chapter_section.section}
            title={related.title}
            documentId={page.cnx_id} />
        )}
      </div>
    );
  },
});

export default observer(BookPage);
<commit_msg>add ecosystem and cnx uuid to spy info<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { get, map, forEach } from 'lodash';
import createReactClass from 'create-react-class';
import ReactDOM from 'react-dom';
import { LoadingAnimation, SpyMode, ArbitraryHtmlAndMath, GetPositionMixin } from 'shared';
import { observer } from 'mobx-react';
import classnames from 'classnames';
import { BookContentMixin } from './book-content-mixin';
import { ReferenceBookExerciseShell } from './book-page/exercise';
import RelatedContent from './related-content';

import NotesWidget from './notes';
import { ReferenceBookExerciseActions, ReferenceBookExerciseStore } from '../flux/reference-book-exercise';

const BookPage = createReactClass({
  displayName: 'BookPage',

  propTypes: {
    ux: PropTypes.object.isRequired,
  },

  mixins: [BookContentMixin, GetPositionMixin],

  getCnxId() {
    return this.props.ux.page.cnx_id;
  },

  UNSAFE_componentWillMount() {
    return this.props.ux.page.ensureLoaded();
  },

  UNSAFE_componentWillReceiveProps() {
    return this.props.ux.page.ensureLoaded();
  },

  getSplashTitle() {
    return this.props.ux.page.title;
  },

  componentDidMount() {
    return this.props.ux.checkForTeacherContent();
  },

  componentDidUpdate() {
    return this.props.ux.checkForTeacherContent();
  },

  // used by BookContentMixin
  shouldOpenNewTab() { return true; },

  waitToScrollToSelector(hash) {
    const images = ReactDOM.findDOMNode(this).querySelectorAll('img');
    let imagesToLoad = images.length;
    const onImageLoad = () => {
      imagesToLoad -= 1;
      if (imagesToLoad === 0) {
        // final scroll to
        this.scrollToSelector(hash);
      }
    };
    for (let image of images) {
      image.addEventListener('load', onImageLoad);
    }

    return images.length > 0;
  },

  renderExercises(exerciseLinks) {
    ReferenceBookExerciseStore.setMaxListeners(exerciseLinks.length);
    const links = map(exerciseLinks, 'href');
    if (!ReferenceBookExerciseStore.isLoaded(links)) { ReferenceBookExerciseActions.loadMultiple(links); }

    return forEach(exerciseLinks, this.renderExercise);
  },

  renderExercise(link) {
    const exerciseAPIUrl = link.href;
    const exerciseNode = link.parentNode.parentNode;
    if (exerciseNode != null) {
      return ReactDOM.render(<ReferenceBookExerciseShell exerciseAPIUrl={exerciseAPIUrl} />, exerciseNode);
    }
    return null;
  },

  render() {
    let isLoading;
    let { ux, ux: { page } } = this.props;

    if (!page || page.api.isPending) {
      if (ux.lastSection) {
        isLoading = true;
        page = ux.pages.byChapterSection.get(ux.lastSection);
      } else {
        return <LoadingAnimation />;
      }
    }


    const related = {
      chapter_section: page.chapter_section.asArray,
      title: this.getSplashTitle(),
    };

    return (
      <div
        className={classnames('book-page', this.props.className, {
          'page-loading loadable is-loading': isLoading,
          'book-is-collated': page.bookIsCollated,
        })}
        {...ux.courseDataProps}>
        {this.props.children}
        <div className="page center-panel">
          <RelatedContent contentId={page.cnx_id} {...related} />
          <ArbitraryHtmlAndMath className="book-content" block={true} html={page.contents} />
        </div>
        <SpyMode.Content className="ecosystem-info">
          Page: {page.cnx_id}, Book: {get(page,'chapter.book.cnx_id')} Ecosystem: {get(page,'chapter.book.uuid')}
        </SpyMode.Content>
        {ux.allowsAnnotating && (
          <NotesWidget
            courseId={ux.course.id}
            chapter={page.chapter_section.chapter}
            section={page.chapter_section.section}
            title={related.title}
            documentId={page.cnx_id} />
        )}
      </div>
    );
  },
});

export default observer(BookPage);

{'fraction_non_alphanumeric': 0.1295774647887324, 'fraction_numerical': 0.0007682458386683739, 'mean_word_length': 3.1197257383966246, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4492642', 'n_tokens_mistral': 2420, 'n_tokens_neox': 2331, 'n_words': 444}