<commit_before>import React, { PureComponent } from "react";
import iframePhone from "iframe-phone";
import { /* handleGetAttachmentUrl, */ IReportItemInitInteractive, IStudentHTML } from "@concord-consortium/interactive-api-host";

interface Props {
  question: any;
  view: "singleAnswer" | "multipleAnswer";
}

interface State {
  requestedHeight: number|null;
  src?: string;
}

export default class ReportItemIframe extends PureComponent<Props, State> {
  private iframePhone: any;

  constructor (props: Props) {
    super(props);
    this.state = {
      // Height requested by interactive itself, using iframe-phone.
      requestedHeight: null,
      src: props.question.get("reportItemUrl")
    };
  }

  componentDidMount() {
    this.connect();
  }

  componentWillUnmount() {
    this.disconnect();
  }

  connect() {
    const phoneAnswered = () => {
      const initMessage: IReportItemInitInteractive = {
        version: 1,
        mode: "reportItem",
        hostFeatures: {}, // TODO
        authoredState: {}, // TODO
        interactiveItemId: this.props.question.get("id"),
        view: this.props.view,
        students: {} // TODO: Record<string, {hasAnswer: boolean}>,
      };
      this.iframePhone.post("initInteractive", initMessage);
    };
    // eslint-disable-next-line react/no-string-refs
    this.iframePhone = new iframePhone.ParentEndpoint(this.refs.iframe, phoneAnswered);

    this.iframePhone.addListener("studentHTML", this.handleStudentHTML);
    this.iframePhone.addListener("height", this.handleHeight);
    // this.iframePhone.addListener("getAttachmentUrl", this.handleGetAttachmentUrl);
  }

  handleStudentHTML = (response: IStudentHTML) => {
    // TODO
  }

  /*

  TODO LATER

  handleGetAttachmentUrl = (request) => {
    const answerMeta = this.props.answer.toJS();
    return handleGetAttachmentUrl({ request, answerMeta })
      .then(response => {
        this.iframePhone.post("attachmentUrl", response);
        return response;
      })
      .catch(console.error);
  }
  */

  handleHeight = (height: number) => {
    this.setState({ requestedHeight: height });
  }

  disconnect() {
    if (this.iframePhone) {
      this.iframePhone.disconnect();
      this.iframePhone = null;
    }
  }

  render() {
    const { requestedHeight, src } = this.state;

    if (!src) {
      return null;
    }

    return (
      // eslint-disable-next-line react/no-string-refs
      <iframe ref="iframe"
        src={src}
        height={requestedHeight || "300px"}
        style={{border: "none", marginTop: "0.5em"}}
      />
    );
  }
}
<commit_msg>Refactored construction of student map<commit_after>import React, { PureComponent } from "react";
import iframePhone from "iframe-phone";
import { /* handleGetAttachmentUrl, */ IReportItemInitInteractive, IStudentHTML } from "@concord-consortium/interactive-api-host";
import { getSortedStudents } from "../../selectors/report";
import { RootState } from "../../reducers";
import { connect } from "react-redux";
import { getAnswersByQuestion } from "../../selectors/report-tree";

interface IProps {
  question: any;
  view: "singleAnswer" | "multipleAnswer";
  students: any;
  answersByQuestion: any;
}

interface IState {
  requestedHeight: number|null;
  src?: string;
}

class ReportItemIframe extends PureComponent<IProps, IState> {
  private iframePhone: any;

  constructor (props: IProps) {
    super(props);
    this.state = {
      // Height requested by interactive itself, using iframe-phone.
      requestedHeight: null,
      src: props.question.get("reportItemUrl")
    };
  }

  componentDidMount() {
    this.connect();
  }

  componentWillUnmount() {
    this.disconnect();
  }

  connect() {
    const phoneAnswered = () => {
      const questionId = this.props.question.get("id");
      const authoredState = this.props.question.get("authored_state") || {};
      const students = this.getStudentsForInitMessage();

      const initMessage: IReportItemInitInteractive = {
        version: 1,
        mode: "reportItem",
        hostFeatures: {}, // no host features supported in report-items
        authoredState,
        interactiveItemId: questionId,
        view: this.props.view,
        students,
      };
      this.iframePhone.post("initInteractive", initMessage);
    };
    // eslint-disable-next-line react/no-string-refs
    this.iframePhone = new iframePhone.ParentEndpoint(this.refs.iframe, phoneAnswered);

    this.iframePhone.addListener("studentHTML", this.handleStudentHTML);
    this.iframePhone.addListener("height", this.handleHeight);
    // this.iframePhone.addListener("getAttachmentUrl", this.handleGetAttachmentUrl);
  }

  getStudentsForInitMessage() {
    const questionId = this.props.question.get("id");
    const answers = this.props.answersByQuestion.get(questionId);

    // construct a map of student id to answer present
    const studentHasAnswers = answers?.reduce((acc: Record<string, boolean>, answer: any) => {
      acc[answer.get("platformUserId")] = true;
      return acc;
    }, {});

    // construct a map of all students to answer present
    const students = this.props.students.reduce((acc: Record<string, {hasAnswer: boolean}>, student: any) => {
      const id = student.get("id");
      acc[id] = {hasAnswer: !!studentHasAnswers?.[id]};
      return acc;
    }, {});

    return students;
  }

  handleStudentHTML = (response: IStudentHTML) => {
    // TODO
  }

  /*

  TODO LATER

  handleGetAttachmentUrl = (request) => {
    const answerMeta = this.props.answer.toJS();
    return handleGetAttachmentUrl({ request, answerMeta })
      .then(response => {
        this.iframePhone.post("attachmentUrl", response);
        return response;
      })
      .catch(console.error);
  }
  */

  handleHeight = (height: number) => {
    this.setState({ requestedHeight: height });
  }

  disconnect() {
    if (this.iframePhone) {
      this.iframePhone.disconnect();
      this.iframePhone = null;
    }
  }

  render() {
    const { requestedHeight, src } = this.state;

    if (!src) {
      return null;
    }

    // eslint-disable-next-line no-console
    console.log("QUESTION", this.props.question.toJS());

    return (
      // eslint-disable-next-line react/no-string-refs
      <iframe ref="iframe"
        src={src}
        height={requestedHeight || "300px"}
        style={{border: "none", marginTop: "0.5em"}}
      />
    );
  }
}

function mapStateToProps(state: RootState): Partial<IProps> {
  const data = state.get("data");
  const error = data.get("error");
  const dataDownloaded = !error && !data.get("isFetching");

  return {
    students: dataDownloaded && getSortedStudents(state),
    answersByQuestion: dataDownloaded && getAnswersByQuestion(state)
  };
}

const mapDispatchToProps = (dispatch: any, ownProps: any): Partial<IProps> => {
  return {
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(ReportItemIframe);

{'fraction_non_alphanumeric': 0.13690733535091512, 'fraction_numerical': 0.0017293558149589277, 'mean_word_length': 3.1681681681681684, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6738845', 'n_tokens_mistral': 2148, 'n_tokens_neox': 2070, 'n_words': 420}