<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button, FormGroup, Input, FormFeedback } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlusSquare, faTimes } from '@fortawesome/free-solid-svg-icons';

import { thEvents } from '../../helpers/constants';
import { formatModelError } from '../../helpers/errorMessage';
import {
  getJobBtnClass,
  getHoverText,
  findJobInstance,
} from '../../helpers/job';
import { isSHAorCommit } from '../../helpers/revision';
import { getBugUrl } from '../../helpers/url';
import BugJobMapModel from '../../models/bugJobMap';
import JobClassificationModel from '../../models/classification';
import JobModel from '../../models/job';
import { withPinnedJobs } from '../context/PinnedJobs';
import { withPushes } from '../context/Pushes';
import { notify } from '../redux/stores/notifications';
import { setSelectedJob } from '../redux/stores/selectedJob';

class PinBoard extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      enteringBugNumber: false,
      newBugNumber: null,
    };
  }

  componentDidMount() {
    window.addEventListener(thEvents.saveClassification, this.save);
  }

  componentWillUnmount() {
    window.removeEventListener(thEvents.saveClassification, this.save);
  }

  unPinAll = () => {
    this.props.unPinAll();
    this.setState({
      enteringBugNumber: false,
      newBugNumber: null,
    });
  };

  save = () => {
    const {
      isLoggedIn,
      pinnedJobs,
      recalculateUnclassifiedCounts,
      notify,
    } = this.props;

    let errorFree = true;
    if (this.state.enteringBugNumber) {
      // we should save this for the user, as they likely
      // just forgot to hit enter. Returns false if invalid
      errorFree = this.saveEnteredBugNumber();
      if (!errorFree) {
        notify('Please enter a valid bug number', 'danger');
      }
    }
    if (!this.canSaveClassifications() && isLoggedIn) {
      notify('Please classify this failure before saving', 'danger');
      errorFree = false;
    }
    if (!isLoggedIn) {
      notify('Must be logged in to save job classifications', 'danger');
      errorFree = false;
    }
    if (errorFree) {
      const jobs = Object.values(pinnedJobs);
      const classifyPromises = jobs.map(job => this.saveClassification(job));
      const bugPromises = jobs.map(job => this.saveBugs(job));
      Promise.all([...classifyPromises, ...bugPromises]).then(() => {
        window.dispatchEvent(new CustomEvent(thEvents.classificationChanged));
        recalculateUnclassifiedCounts();
        this.unPinAll();
        this.setState({
          enteringBugNumber: false,
          newBugNumber: null,
        });
      });

      // HACK: it looks like Firefox on Linux and Windows doesn't
      // want to accept keyboard input after this change for some
      // reason which I don't understand. Chrome (any platform)
      // or Firefox on Mac works fine though.
      document.getElementById('keyboard-shortcuts').focus();
    }
  };

  createNewClassification = () => {
    const { email } = this.props;
    const {
      failureClassificationId,
      failureClassificationComment,
    } = this.props;

    return new JobClassificationModel({
      text: failureClassificationComment,
      who: email,
      failure_classification_id: failureClassificationId,
    });
  };

  saveClassification = job => {
    const { recalculateUnclassifiedCounts, notify } = this.props;
    const classification = this.createNewClassification();

    // classification can be left unset making this a no-op
    if (classification.failure_classification_id > 0) {
      job.failure_classification_id = classification.failure_classification_id;
      // update the unclassified failure count for the page
      recalculateUnclassifiedCounts();

      classification.job_id = job.id;
      return classification
        .create()
        .then(() => {
          notify(
            `Classification saved for ${job.platform} ${job.job_type_name}`,
            'success',
          );
          // update the job to show that it's now classified
          findJobInstance(job.id).refilter();
        })
        .catch(response => {
          const message = `Error saving classification for ${job.platform} ${
            job.job_type_name
          }`;
          notify(formatModelError(response, message), 'danger');
        });
    }<commit_msg>Bug 1554136 - Fix error when classifying many jobs on the same platform (#5085)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button, FormGroup, Input, FormFeedback } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlusSquare, faTimes } from '@fortawesome/free-solid-svg-icons';

import { thEvents } from '../../helpers/constants';
import { formatModelError } from '../../helpers/errorMessage';
import {
  getJobBtnClass,
  getHoverText,
  findJobInstance,
} from '../../helpers/job';
import { isSHAorCommit } from '../../helpers/revision';
import { getBugUrl } from '../../helpers/url';
import BugJobMapModel from '../../models/bugJobMap';
import JobClassificationModel from '../../models/classification';
import JobModel from '../../models/job';
import { withPinnedJobs } from '../context/PinnedJobs';
import { withPushes } from '../context/Pushes';
import { notify } from '../redux/stores/notifications';
import { setSelectedJob } from '../redux/stores/selectedJob';

class PinBoard extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      enteringBugNumber: false,
      newBugNumber: null,
    };
  }

  componentDidMount() {
    window.addEventListener(thEvents.saveClassification, this.save);
  }

  componentWillUnmount() {
    window.removeEventListener(thEvents.saveClassification, this.save);
  }

  unPinAll = () => {
    this.props.unPinAll();
    this.setState({
      enteringBugNumber: false,
      newBugNumber: null,
    });
  };

  save = () => {
    const {
      isLoggedIn,
      pinnedJobs,
      recalculateUnclassifiedCounts,
      notify,
    } = this.props;

    let errorFree = true;
    if (this.state.enteringBugNumber) {
      // we should save this for the user, as they likely
      // just forgot to hit enter. Returns false if invalid
      errorFree = this.saveEnteredBugNumber();
      if (!errorFree) {
        notify('Please enter a valid bug number', 'danger');
      }
    }
    if (!this.canSaveClassifications() && isLoggedIn) {
      notify('Please classify this failure before saving', 'danger');
      errorFree = false;
    }
    if (!isLoggedIn) {
      notify('Must be logged in to save job classifications', 'danger');
      errorFree = false;
    }
    if (errorFree) {
      const jobs = Object.values(pinnedJobs);
      const classifyPromises = jobs.map(job => this.saveClassification(job));
      const bugPromises = jobs.map(job => this.saveBugs(job));
      Promise.all([...classifyPromises, ...bugPromises]).then(() => {
        window.dispatchEvent(new CustomEvent(thEvents.classificationChanged));
        recalculateUnclassifiedCounts();
        this.unPinAll();
        this.setState({
          enteringBugNumber: false,
          newBugNumber: null,
        });
      });

      // HACK: it looks like Firefox on Linux and Windows doesn't
      // want to accept keyboard input after this change for some
      // reason which I don't understand. Chrome (any platform)
      // or Firefox on Mac works fine though.
      document.getElementById('keyboard-shortcuts').focus();
    }
  };

  createNewClassification = () => {
    const { email } = this.props;
    const {
      failureClassificationId,
      failureClassificationComment,
    } = this.props;

    return new JobClassificationModel({
      text: failureClassificationComment,
      who: email,
      failure_classification_id: failureClassificationId,
    });
  };

  saveClassification = job => {
    const { recalculateUnclassifiedCounts, notify } = this.props;
    const classification = this.createNewClassification();

    // classification can be left unset making this a no-op
    if (classification.failure_classification_id > 0) {
      job.failure_classification_id = classification.failure_classification_id;
      // update the unclassified failure count for the page
      recalculateUnclassifiedCounts();

      classification.job_id = job.id;
      return classification
        .create()
        .then(() => {
          notify(
            `Classification saved for ${job.platform} ${job.job_type_name}`,
            'success',
          );
          // update the job to show that it's now classified
          const jobInstance = findJobInstance(job.id);

          if (jobInstance) {
            jobInstance.refilter();
          }
        })
        .catch(response => {
          const message = `Error saving classification for ${job.platform} ${
            job.job_type_name
          }`;
          notify(formatModelError(response, message), 'danger');
        });
    }
{'fraction_non_alphanumeric': 0.12197504069451981, 'fraction_numerical': 0.0014107433532284319, 'mean_word_length': 2.798845836768343, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 23, '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': '6697790', 'n_tokens_mistral': 2606, 'n_tokens_neox': 2540, 'n_words': 643}