<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import statsClient from 'panoptes-client/lib/stats-client';
import moment from 'moment';
import { Progress, Graph } from './charts.jsx';

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

    this.getStats = this.getStats.bind(this);
    this.getRange = this.getRange.bind(this);
    this.workflowSelect = this.workflowSelect.bind(this);
    this.handleWorkflowSelect = this.handleWorkflowSelect.bind(this);
    this.handleGraphChange = this.handleGraphChange.bind(this);
    this.handleRangeChange = this.handleRangeChange.bind(this);

    this.state = {
      workflowsLoaded: false,
      statData: null,
    };
  }

  componentDidMount() {
    this.getStats(this.props.workflowId, this.props.by);
  }

  componentWillReceiveProps(nextProps) {
    // update the stats when dropdown options change
    if ((this.props.workflowId !== nextProps.workflowId) || (this.props.by !== nextProps.by)) {
      this.getStats(nextProps.workflowId, nextProps.by);
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    const newBy = (this.props.by !== nextProps.by);
    const newWorkflowId = (this.props.workflowId !== nextProps.workflowId);
    const newWorkflows = (this.props.workflows !== nextProps.workflows);
    const newState = (this.state !== nextState);
    return newBy || newWorkflowId || newWorkflows || newState;
  }

  getStats(workflowId, binBy) {
    statsClient
      .query({
        projectID: this.props.projectId,
        workflowID: workflowId,
        period: binBy,
        type: this.props.type,
      })
      .then((data) => (
        data.map((statObject) => ({
          label: statObject.key_as_string,
          value: statObject.doc_count,
        }))
      ))
      .then((statData) => {
        this.setState({ statData });
      })
      .catch(() => {
        if (console) {
          console.warn('Failed to fetch stats');
        }
      });
  }

  getRange() {
    let range = [];
    if (this.props.range) {
      for (const r of this.props.range.split(',')) {
        if (r) {
          range.push(parseInt(r, 10));
        } else {
          range.push(undefined);
        }
      }
    } else {
      range = [undefined, undefined];
    }
    return range;
  }

  workflowSelect() {
    let workflowSelect;
    if (this.props.workflows) {
      const options = [
        <option value={`project_id=${this.props.projectId}`} key={"workflowSelectAll"}>All</option>,
      ];
      let key = 0;
      for (const workflow of this.props.workflows) {
        options.push(
          <option value={`workflow_id=${workflow.id}`} key={`workflowSelect${key}`}>
            {workflow.display_name}
          </option>
        );
        key++;
      }
      if (options.length > 1) {
        let value;
        if (this.props.workflowId) {
          value = `workflow_id=${this.props.workflowId}`;
        } else {
          value = `project_id=${this.props.projectId}`;
        }
        workflowSelect = (
          <span>
            {' '}for{' '}
            <select onChange={this.handleWorkflowSelect} value={value}>
              {options}
            </select>
          </span>
        );
      }
    }
    return workflowSelect;
  }

  handleWorkflowSelect(event) {
    this.props.handleWorkflowChange(this.props.type, event);
  }

  handleGraphChange(event) {
    this.props.handleGraphChange(this.props.type, event);
  }

  handleRangeChange(range) {
    this.props.handleRangeChange(this.props.type, range);
  }

  render() {
    let output = (
      <div>
        There is no stats data available at this time.
      </div>
    );
    if (this.state.statData) {
      const range = this.getRange();
      const workflowSelect = this.workflowSelect();
      output = (
        <div>
          {this.props.type[0].toUpperCase() + this.props.type.substring(1)}s per{' '}
          <select value={this.props.by} onChange={this.handleGraphChange}>
            <option value="hour">hour</option>
            <option value="day">day</option>
            <option value="week">week</option>
            <option value="month">month</option>
          </select>
          {workflowSelect}
          <br />
          <Graph
            data={this.state.statData}
            by={this.props.by}
            range={range}
            num={24}
            handleRangeChange={this.handleRangeChange}
          />
        </div>
      );
    }
    return output;
  }
}

GraphSelect.defaultProps = {
  by: 'day',
};

GraphSelect.propTypes = {
  by: PropTypes.string,
  failedWorkflows: PropTypes.array,
  projectId: PropTypes.string,
  workflowId: PropTypes.string,
  range: PropTypes.string,
  type: PropTypes.string,
  workflows: PropTypes.array,
  handleGraphChange: PropTypes.func,
  handleRangeChange: PropTypes.func,
  handleWorkflowChange: PropTypes.func,
};

export const Eta = (props) => {
  let output = <div />;
  if (props.numDays !== undefined) {
    output = (
      <div>
        <span className="progress-stats-label">ETC*</span> {`${props.numDays} days`}
      </div>
    );
  }
  return output;
};

Eta.propTypes = {
  numDays: PropTypes.number,
};

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

    this.componentDidMount = this.componentDidMount.bind(this);

    this.state = {
      statData: null,
    };
  }

  componentDidMount() {
    statsClient
      .query({
        workflowID: this.props.workflow.id,
        period: 'day',
        type: 'classification',
      })
      .then((data) => (
        data.map((statObject) => (
          statObject.doc_count
        ))
      ))
      .then((statData) => {
        this.setState({ statData });
      })
      .catch(() => {
        if (console) {
          console.warn('Failed to fetch stats');
        }
      });
  }

  calcDaysToCompletion(totalCount) {
    let numDays = undefined;
    const dataLength = this.state.statData.length;
    const compeletness = this.props.workflow.completeness == 1;
    if (compeletness) {
      numDays = 0;
    }
    else if (dataLength > 1) {
      let value;
      let days;
      if (dataLength > 15) {
        // don't count the last bin since the current day is not over yet
        value = this.state.statData.slice(dataLength - 15, dataLength - 1);
        days = 14;
      } else {
        value = this.state.statData.slice(0, dataLength - 1);
        days = dataLength - 1;
      }
      const rate = value.reduce((a, b) => (a + b));
      numDays = Math.max(0, Math.ceil(days * (totalCount - this.props.workflow.classifications_count) / rate));
    }
    return numDays;
  }

  render() {
    let retirement;
    let eta;
    let retiredDiv;
    let classificationDiv;
    let completeness = this.props.workflow.completeness;
    const totalCount = this.props.workflow.subjects_count * this.props.workflow.retirement.options.count;
    if (this.props.workflow.retirement.criteria !== 'never_retire') {
      retiredDiv = (
        <div>
          <span className="progress-stats-label">Images retired:</span>
          {' '}{this.props.workflow.retired_set_member_subjects_count.toLocaleString()}
          {' / '}{this.props.workflow.subjects_count.toLocaleString()}
        </div>
      );
    }
    let classificationsString = ` ${this.props.workflow.classifications_count.toLocaleString()}`;
    if (this.props.workflow.retirement.criteria === 'classification_count') {
      retirement = (
        <div>
          <span className="progress-stats-label">Retirement limit:</span>
          {' '}{this.props.workflow.retirement.options.count.toLocaleString()}
        </div>
      );
      if (this.state.statData) {
        eta = <Eta numDays={this.calcDaysToCompletion(totalCount)}/>;
      }
      if (this.props.workflow.configuration) {
        if (this.props.workflow.configuration.stats_completeness_type === 'classification') {
          completeness = this.props.workflow.classifications_count / totalCount;
          classificationsString += ` / ${totalCount.toLocaleString()}`;
          classificationDiv = (
            <div>
              <span className="progress-stats-label">Classifications:</span>
              {classificationsString}
            </div>
          );
        }<commit_msg>Edit workflow stats card if complete<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import statsClient from 'panoptes-client/lib/stats-client';
import moment from 'moment';
import { Progress, Graph } from './charts.jsx';

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

    this.getStats = this.getStats.bind(this);
    this.getRange = this.getRange.bind(this);
    this.workflowSelect = this.workflowSelect.bind(this);
    this.handleWorkflowSelect = this.handleWorkflowSelect.bind(this);
    this.handleGraphChange = this.handleGraphChange.bind(this);
    this.handleRangeChange = this.handleRangeChange.bind(this);

    this.state = {
      workflowsLoaded: false,
      statData: null,
    };
  }

  componentDidMount() {
    this.getStats(this.props.workflowId, this.props.by);
  }

  componentWillReceiveProps(nextProps) {
    // update the stats when dropdown options change
    if ((this.props.workflowId !== nextProps.workflowId) || (this.props.by !== nextProps.by)) {
      this.getStats(nextProps.workflowId, nextProps.by);
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    const newBy = (this.props.by !== nextProps.by);
    const newWorkflowId = (this.props.workflowId !== nextProps.workflowId);
    const newWorkflows = (this.props.workflows !== nextProps.workflows);
    const newState = (this.state !== nextState);
    return newBy || newWorkflowId || newWorkflows || newState;
  }

  getStats(workflowId, binBy) {
    statsClient
      .query({
        projectID: this.props.projectId,
        workflowID: workflowId,
        period: binBy,
        type: this.props.type,
      })
      .then((data) => (
        data.map((statObject) => ({
          label: statObject.key_as_string,
          value: statObject.doc_count,
        }))
      ))
      .then((statData) => {
        this.setState({ statData });
      })
      .catch(() => {
        if (console) {
          console.warn('Failed to fetch stats');
        }
      });
  }

  getRange() {
    let range = [];
    if (this.props.range) {
      for (const r of this.props.range.split(',')) {
        if (r) {
          range.push(parseInt(r, 10));
        } else {
          range.push(undefined);
        }
      }
    } else {
      range = [undefined, undefined];
    }
    return range;
  }

  workflowSelect() {
    let workflowSelect;
    if (this.props.workflows) {
      const options = [
        <option value={`project_id=${this.props.projectId}`} key={"workflowSelectAll"}>All</option>,
      ];
      let key = 0;
      for (const workflow of this.props.workflows) {
        options.push(
          <option value={`workflow_id=${workflow.id}`} key={`workflowSelect${key}`}>
            {workflow.display_name}
          </option>
        );
        key++;
      }
      if (options.length > 1) {
        let value;
        if (this.props.workflowId) {
          value = `workflow_id=${this.props.workflowId}`;
        } else {
          value = `project_id=${this.props.projectId}`;
        }
        workflowSelect = (
          <span>
            {' '}for{' '}
            <select onChange={this.handleWorkflowSelect} value={value}>
              {options}
            </select>
          </span>
        );
      }
    }
    return workflowSelect;
  }

  handleWorkflowSelect(event) {
    this.props.handleWorkflowChange(this.props.type, event);
  }

  handleGraphChange(event) {
    this.props.handleGraphChange(this.props.type, event);
  }

  handleRangeChange(range) {
    this.props.handleRangeChange(this.props.type, range);
  }

  render() {
    let output = (
      <div>
        There is no stats data available at this time.
      </div>
    );
    if (this.state.statData) {
      const range = this.getRange();
      const workflowSelect = this.workflowSelect();
      output = (
        <div>
          {this.props.type[0].toUpperCase() + this.props.type.substring(1)}s per{' '}
          <select value={this.props.by} onChange={this.handleGraphChange}>
            <option value="hour">hour</option>
            <option value="day">day</option>
            <option value="week">week</option>
            <option value="month">month</option>
          </select>
          {workflowSelect}
          <br />
          <Graph
            data={this.state.statData}
            by={this.props.by}
            range={range}
            num={24}
            handleRangeChange={this.handleRangeChange}
          />
        </div>
      );
    }
    return output;
  }
}

GraphSelect.defaultProps = {
  by: 'day',
};

GraphSelect.propTypes = {
  by: PropTypes.string,
  failedWorkflows: PropTypes.array,
  projectId: PropTypes.string,
  workflowId: PropTypes.string,
  range: PropTypes.string,
  type: PropTypes.string,
  workflows: PropTypes.array,
  handleGraphChange: PropTypes.func,
  handleRangeChange: PropTypes.func,
  handleWorkflowChange: PropTypes.func,
};

export const Eta = (props) => {
  let output = <div />;
  if (props.numDays !== undefined) {
    output = (
      <div>
        <span className="progress-stats-label">ETC*</span> {`${props.numDays} days`}
      </div>
    );
  }
  return output;
};

Eta.propTypes = {
  numDays: PropTypes.number,
};

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

    this.componentDidMount = this.componentDidMount.bind(this);

    this.state = {
      statData: null,
    };
  }

  componentDidMount() {
    statsClient
      .query({
        workflowID: this.props.workflow.id,
        period: 'day',
        type: 'classification',
      })
      .then((data) => (
        data.map((statObject) => (
          statObject.doc_count
        ))
      ))
      .then((statData) => {
        this.setState({ statData });
      })
      .catch(() => {
        if (console) {
          console.warn('Failed to fetch stats');
        }
      });
  }

  calcDaysToCompletion(totalCount) {
    let numDays = undefined;
    const dataLength = this.state.statData.length;
    const compeletness = this.props.workflow.completeness == 1;
    if (compeletness) {
      numDays = 0;
    }
    else if (dataLength > 1) {
      let value;
      let days;
      if (dataLength > 15) {
        // don't count the last bin since the current day is not over yet
        value = this.state.statData.slice(dataLength - 15, dataLength - 1);
        days = 14;
      } else {
        value = this.state.statData.slice(0, dataLength - 1);
        days = dataLength - 1;
      }
      const rate = value.reduce((a, b) => (a + b));
      numDays = Math.max(0, Math.ceil(days * (totalCount - this.props.workflow.classifications_count) / rate));
    }
    return numDays;
  }

  render() {
    let retirement;
    let eta;
    let retiredDiv;
    let classificationDiv;
    let completeness = this.props.workflow.completeness;
    const totalCount = this.props.workflow.subjects_count * this.props.workflow.retirement.options.count;
    if (this.props.workflow.retirement.criteria !== 'never_retire') {
      retiredDiv = (
        <div>
          <span className="progress-stats-label">Images retired:</span>
          {' '}{this.props.workflow.retired_set_member_subjects_count.toLocaleString()}
          {' / '}{this.props.workflow.subjects_count.toLocaleString()}
        </div>
      );
    }
    let classificationsString = ` ${this.props.workflow.classifications_count.toLocaleString()}`;
    if (this.props.workflow.retirement.criteria === 'classification_count') {
      retirement = (
        <div>
          <span className="progress-stats-label">Retirement limit:</span>
          {' '}{this.props.workflow.retirement.options.count.toLocaleString()}
        </div>
      );
      if (this.state.statData) {
        eta = <Eta numDays={this.calcDaysToCompletion(totalCount)}/>;
      }
      if (this.props.workflow.configuration) {
        if (this.props.workflow.configuration.stats_completeness_type === 'classification' && completeness !== 1) {
          completeness = this.props.workflow.classifications_count / totalCount;
          classificationsString += ` / ${totalCount.toLocaleString()}`;
          classificationDiv = (
            <div>
              <span className="progress-stats-label">Classifications:</span>
              {classificationsString}
            </div>
          );
        }
{'fraction_non_alphanumeric': 0.1377969245497517, 'fraction_numerical': 0.0026925148088314485, 'mean_word_length': 2.3308090872857714, 'pattern_counts': {'":': 0, '<': 87, '<?xml version=': 0, '>': 113, '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': '1535694', 'n_tokens_mistral': 5182, 'n_tokens_neox': 5111, 'n_words': 911}