<commit_before>import _ from 'lodash';
import React from 'react';
import {Link} from 'react-router-dom';
import Cookies from 'js-cookie';

import Count from '/client/modules/common/containers/search_count';
import SearchFiltersBuckets from '/client/modules/common/containers/search_filters_buckets';
import SearchSummariesView from '/client/modules/magic/components/search_summaries_view';
import SearchRowsView from '/client/modules/magic/containers/search_rows_view';
import SearchMapView from '/client/modules/magic/components/search_map_view';
import SearchImagesView from '/client/modules/magic/components/search_images_view';
import SearchDownload from '/client/modules/magic/components/search_download';
import SearchJSONLD from '/client/modules/magic/containers/search_jsonld';
import {portals} from '/lib/configs/portals.js';
import {versions, models} from '/lib/configs/magic/data_models.js';
import {levels, index} from '/lib/configs/magic/search_levels.js';

const model = models[_.last(versions)];
const sortedTables = _.sortBy(_.keys(models[_.last(versions)].tables), (table) => {
  return models[_.last(versions)].tables[table].position;
});

let filters = [
  {type: 'string'   , name: 'summary.contribution._reference.authors._name', title: 'Author'                  , term: 'summary.contribution._reference.authors._name.raw', aggs: {buckets: {terms: {field: 'summary.contribution._reference.authors._name.raw', size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.external_database_ids'           , title: 'External Database'       , term: 'summary._all.external_database_ids.key.raw'       , aggs: {buckets: {terms: {field: 'summary._all.external_database_ids.key.raw'       , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary.contribution._contributor'            , title: 'Contributor'             , term: 'summary.contribution._contributor.raw'            , aggs: {buckets: {terms: {field: 'summary.contribution._contributor.raw'            , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.location_type'                   , title: 'Location Type'           , term: 'summary._all.location_type.raw'                   , aggs: {buckets: {terms: {field: 'summary._all.location_type.raw'                   , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.geologic_type'                   , title: 'Geologic Type'           , term: 'summary._all.geologic_types.raw'                  , aggs: {buckets: {terms: {field: 'summary._all.geologic_types.raw'                  , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.geologic_class'                  , title: 'Geologic Class'          , term: 'summary._all.geologic_classes.raw'                , aggs: {buckets: {terms: {field: 'summary._all.geologic_classes.raw'                , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.lithology'                       , title: 'Lithology'               , term: 'summary._all.lithologies.raw'                     , aggs: {buckets: {terms: {field: 'summary._all.lithologies.raw'                     , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.method_codes'                    , title: 'Method Code'             , term: 'summary._all.method_codes.raw'                    , aggs: {buckets: {terms: {field: 'summary._all.method_codes.raw'                    , size: 1001}}}, maxBuckets: 1000},
  //{type: 'string'   , name: 'summary._all.scientists'                      , title: 'Research Scientist Name' , term: 'summary._all.scientists.raw'                      , aggs: {buckets: {terms: {field: 'summary._all.scientists.raw'                      , size: 1001}}}, maxBuckets: 1000},
  //{type: 'string'   , name: 'summary._all.analysts'                        , title: 'Analyst Name'            , term: 'summary._all.analysts.raw'                        , aggs: {buckets: {terms: {field: 'summary._all.analysts.raw'                        , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.software_packages'               , title: 'Software Package'        , term: 'summary._all.software_packages.raw'               , aggs: {buckets: {terms: {field: 'summary._all.software_packages.raw'               , size: 1001}}}, maxBuckets: 1000},
];
let filterNames = {};
//sortedTables.forEach((table) => {
//  let sortedColumns = _.sortBy(_.keys(model.tables[table].columns), (column) => {
//    return model.tables[table].columns[column].position;
//  });
//  sortedColumns.forEach((column) => {
//    let label = model.tables[table].columns[column].label;
//    let type = model.tables[table].columns[column].type;
//    if (filterNames['magic.filters.' + column] === undefined) {
//      if (type === 'String') {
//        filters.push({ type: 'string', name: 'magic.filters.' + column, term: 'summary._all.' + column + '.raw', title: label });
//      }
//      filterNames['magic.filters.' + column] = true;
//    }
//  });
//});
const searchTerms = {
  "id":          'summary.contribution._history.id',
  "private_key": 'summary.contribution._private_key',
  "doi":         'summary.contribution._reference.doi.raw',
  "author":      'summary.contribution._reference.authors.family.raw',
  "orcid":       'summary.contribution._reference.authors._orcid.raw',
};
const searchSortOption = { name: 'Most Relevant First', sort: [{'_score': 'desc'}] };
const sortOptions = [
  { name: 'Recently Contributed First'  , sort: [{'summary.contribution.timestamp': 'desc'}] },
  { name: 'Recently Contributed Last'   , sort: [{'summary.contribution.timestamp': 'asc'}] },
  { name: 'Recently Published First'    , sort: [{'summary.contribution._reference.year': 'desc'}] },
  { name: 'Recently Published Last'     , sort: [{'summary.contribution._reference.year': 'asc'}]  },
  { name: 'Most Cited Publication First', sort: [{'summary.contribution._reference.n_citations': 'desc'}] },
  { name: 'Citations A-z'               , sort: [{'summary.contribution._reference.citation.raw': 'asc'}] },
  { name: 'Citations z-A'               , sort: [{'summary.contribution._reference.citation.raw': 'desc'}] },
//  { name: 'Youngest Age First'          , sort: [{'summary._all._age_range_ybp': 'asc'}] },
//  { name: 'Oldest Age First'            , sort: [{'summary._all._age_range_ybp': 'desc'}] },
];
const intUnits = [
  {name: 'nT', from: (x) => x/1e9, min: 0, max: Infinity},
  {name: 'µT', from: (x) => x/1e6, min: 0, max: Infinity},
  {name: 'mT', from: (x) => x/1e3, min: 0, max: Infinity},
  {name: 'T' , from: (x) => x    , min: 0, max: Infinity},
];
const intUnitsDefault = 'µT';
const ageUnits = [
  {name: 'AD', from: (x) => -x+1950, min: 1, max: new Date().getFullYear()},
  {name: 'BC', from: (x) =>  x+1949, min: 1, max: Infinity},
  {name: 'ka', from: (x) =>  x*1e3 , min: 0, max: Infinity},
  {name: 'Ma', from: (x) =>  x*1e6 , min: 0, max: Infinity},
  {name: 'Ga', from: (x) =>  x*1e9 , min: 0, max: Infinity},
];
const ageUnitsDefault = 'Ma';

class Search extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      search: this.props.search || '',
      searchInput: this.props.search || '',
      levelNumber: 0,
      view: '',
      sort: 'Recently Contributed First',
      sortDefault: true,
      activeFilters: {},
      height: undefined,
      width: undefined,
      lat_min: undefined,
      lat_max: undefined,
      lon_min: undefined,
      lon_max: undefined,
      age_min: undefined,
      age_min_unit: undefined,
      age_max: undefined,
      age_max_unit: undefined,
      int_min: undefined,
      int_max: undefined,
      int_unit: undefined
    };
    this.styles = {
      a: {cursor: 'pointer', color: '#792f91'},
      table: {width: '100%'},
      input: {borderColor: '#888888', borderLeft: 'none', borderRight: 'none', flex: '1'},
      td: {verticalAlign: 'top', overflow: 'hidden', transition: 'all 0.5s ease', position: 'relative'},
      segment: {padding: '0'},
      searchButton: {marginLeft: '-1px'},
      activeTab: {backgroundColor: '#F0F0F0'},
      countLabel: {color: '#0C0C0C', margin: '-1em -1em -1em 0.5em', minWidth: '4em'},
      searchInput: {padding: '1em', paddingBottom: 0, flex: 1},
      filters: {whiteSpace: 'nowrap', overflowY: 'scroll', border: 'none', flex: 1 },
      filter: {margin: '1em 0 .5em'},
      filterHeader: {margin: '0'},
      filterBuckets: {paddingLeft: '0.5em', position: 'relative'}
    };
    this.updateSearchInput = _.debounce((value) => {
      this.setState({searchInput: value});
    }, 500);

    this.handleNumericInput = _.debounce((input, value, min, max) => {
      console.log(input, value, min, max);
      let parsedValue = parseFloat(value);
      console.log(input, value, min, max, parsedValue);
      if (value === '')
        this.setState({[input]: undefined});
      else if (!isNaN(parsedValue) && parsedValue >= min && parsedValue <= max)
        this.setState({[input]: parsedValue});
      else
        this.setState({[input]: null});
    }, 500);
  }

  componentDidMount() {
    let self = this;
    $(this.refs['sort']).dropdown({ onChange: function (value, text) { self.setState({sort: text, sortDefault: false}); } });
    $(this.refs['age_min_unit']).dropdown({ onChange: function (value, text) { self.setState({age_min_unit: text}); } });
    $(this.refs['age_max_unit']).dropdown({ onChange: function (value, text) { self.setState({age_max_unit: text}); } });
    $(this.refs['int_unit']).dropdown({ onChange: function (value, text) { self.setState({int_unit: text}); } });
    window.addEventListener("resize", this.onWindowResize.bind(this));
    this.onWindowResize();
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.onWindowResize.bind(this));
  }

  componentWillUpdate(nextProps, nextState) {
    if (nextState.view !== this.state.view) {
      _.delay(() => $(window).trigger('resize'), 500);
    }
  }

  componentDidUpdate() {
    this.onWindowResize();
  }

  onWindowResize() {
    try {
      const windowHeight = $(window).height() - 60; // to allow for the footer
      if (windowHeight !== this.windowHeight) {
        this.windowHeight = windowHeight;
        const height = windowHeight - $(this.refs['filters']).offset().top + 20;
        this.setState({height: height});
      }
      const windowWidth = $(window).width();
      if (windowWidth !== this.windowWidth) {
        this.windowWidth = windowWidth;
        const width = windowWidth - $(this.refs['filters']).outerWidth() - 2 * $(this.refs['filters']).offset().left - 10;
        this.setState({width: width});
      }
    } catch(e) {}
  }

  getSearchQueries() {
    let queries = [];
    let search = this.state.search.replace(/(\w+):\"(.+?)\"\s*/g, (match, term, value) => {
      queries.push(
        searchTerms[term] ? {
          term: {
            [searchTerms[term]]: value
          }
        } : {
          wildcard: {
            ['summary._all.' + term + '.raw']: value
          }
        }
      );
      return '';
    });
    if (_.trim(search) !== '') {
      queries.push({
        simple_query_string: {
          query: search,
          default_operator: 'and'
        }
      });
    }
    console.log('queries', queries);<commit_msg>Make doi: search case insensitive<commit_after>import _ from 'lodash';
import React from 'react';
import {Link} from 'react-router-dom';
import Cookies from 'js-cookie';

import Count from '/client/modules/common/containers/search_count';
import SearchFiltersBuckets from '/client/modules/common/containers/search_filters_buckets';
import SearchSummariesView from '/client/modules/magic/components/search_summaries_view';
import SearchRowsView from '/client/modules/magic/containers/search_rows_view';
import SearchMapView from '/client/modules/magic/components/search_map_view';
import SearchImagesView from '/client/modules/magic/components/search_images_view';
import SearchDownload from '/client/modules/magic/components/search_download';
import SearchJSONLD from '/client/modules/magic/containers/search_jsonld';
import {portals} from '/lib/configs/portals.js';
import {versions, models} from '/lib/configs/magic/data_models.js';
import {levels, index} from '/lib/configs/magic/search_levels.js';

const model = models[_.last(versions)];
const sortedTables = _.sortBy(_.keys(models[_.last(versions)].tables), (table) => {
  return models[_.last(versions)].tables[table].position;
});

let filters = [
  {type: 'string'   , name: 'summary.contribution._reference.authors._name', title: 'Author'                  , term: 'summary.contribution._reference.authors._name.raw', aggs: {buckets: {terms: {field: 'summary.contribution._reference.authors._name.raw', size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.external_database_ids'           , title: 'External Database'       , term: 'summary._all.external_database_ids.key.raw'       , aggs: {buckets: {terms: {field: 'summary._all.external_database_ids.key.raw'       , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary.contribution._contributor'            , title: 'Contributor'             , term: 'summary.contribution._contributor.raw'            , aggs: {buckets: {terms: {field: 'summary.contribution._contributor.raw'            , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.location_type'                   , title: 'Location Type'           , term: 'summary._all.location_type.raw'                   , aggs: {buckets: {terms: {field: 'summary._all.location_type.raw'                   , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.geologic_type'                   , title: 'Geologic Type'           , term: 'summary._all.geologic_types.raw'                  , aggs: {buckets: {terms: {field: 'summary._all.geologic_types.raw'                  , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.geologic_class'                  , title: 'Geologic Class'          , term: 'summary._all.geologic_classes.raw'                , aggs: {buckets: {terms: {field: 'summary._all.geologic_classes.raw'                , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.lithology'                       , title: 'Lithology'               , term: 'summary._all.lithologies.raw'                     , aggs: {buckets: {terms: {field: 'summary._all.lithologies.raw'                     , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.method_codes'                    , title: 'Method Code'             , term: 'summary._all.method_codes.raw'                    , aggs: {buckets: {terms: {field: 'summary._all.method_codes.raw'                    , size: 1001}}}, maxBuckets: 1000},
  //{type: 'string'   , name: 'summary._all.scientists'                      , title: 'Research Scientist Name' , term: 'summary._all.scientists.raw'                      , aggs: {buckets: {terms: {field: 'summary._all.scientists.raw'                      , size: 1001}}}, maxBuckets: 1000},
  //{type: 'string'   , name: 'summary._all.analysts'                        , title: 'Analyst Name'            , term: 'summary._all.analysts.raw'                        , aggs: {buckets: {terms: {field: 'summary._all.analysts.raw'                        , size: 1001}}}, maxBuckets: 1000},
  {type: 'string'   , name: 'summary._all.software_packages'               , title: 'Software Package'        , term: 'summary._all.software_packages.raw'               , aggs: {buckets: {terms: {field: 'summary._all.software_packages.raw'               , size: 1001}}}, maxBuckets: 1000},
];
let filterNames = {};
//sortedTables.forEach((table) => {
//  let sortedColumns = _.sortBy(_.keys(model.tables[table].columns), (column) => {
//    return model.tables[table].columns[column].position;
//  });
//  sortedColumns.forEach((column) => {
//    let label = model.tables[table].columns[column].label;
//    let type = model.tables[table].columns[column].type;
//    if (filterNames['magic.filters.' + column] === undefined) {
//      if (type === 'String') {
//        filters.push({ type: 'string', name: 'magic.filters.' + column, term: 'summary._all.' + column + '.raw', title: label });
//      }
//      filterNames['magic.filters.' + column] = true;
//    }
//  });
//});

const searchTerms = {
  "id":          { field: 'summary.contribution._history.id', processor: x => x },
  "private_key": { field: 'summary.contribution._private_key', processor: x => x },
  "doi":         { field: 'summary.contribution._reference.doi.raw', processor: x => _.toUpper(_.trim(x)) },
  "orcid":       { field: 'summary.contribution._reference.authors._orcid.raw', processor: x => x },
};
/*const searchMatches = {
  "author":      'summary.contribution._reference.authors.family',
};*/
const searchSortOption = { name: 'Most Relevant First', sort: [{'_score': 'desc'}] };
const sortOptions = [
  { name: 'Recently Contributed First'  , sort: [{'summary.contribution.timestamp': 'desc'}] },
  { name: 'Recently Contributed Last'   , sort: [{'summary.contribution.timestamp': 'asc'}] },
  { name: 'Recently Published First'    , sort: [{'summary.contribution._reference.year': 'desc'}] },
  { name: 'Recently Published Last'     , sort: [{'summary.contribution._reference.year': 'asc'}]  },
  { name: 'Most Cited Publication First', sort: [{'summary.contribution._reference.n_citations': 'desc'}] },
  { name: 'Citations A-z'               , sort: [{'summary.contribution._reference.citation.raw': 'asc'}] },
  { name: 'Citations z-A'               , sort: [{'summary.contribution._reference.citation.raw': 'desc'}] },
//  { name: 'Youngest Age First'          , sort: [{'summary._all._age_range_ybp': 'asc'}] },
//  { name: 'Oldest Age First'            , sort: [{'summary._all._age_range_ybp': 'desc'}] },
];
const intUnits = [
  {name: 'nT', from: (x) => x/1e9, min: 0, max: Infinity},
  {name: 'µT', from: (x) => x/1e6, min: 0, max: Infinity},
  {name: 'mT', from: (x) => x/1e3, min: 0, max: Infinity},
  {name: 'T' , from: (x) => x    , min: 0, max: Infinity},
];
const intUnitsDefault = 'µT';
const ageUnits = [
  {name: 'AD', from: (x) => -x+1950, min: 1, max: new Date().getFullYear()},
  {name: 'BC', from: (x) =>  x+1949, min: 1, max: Infinity},
  {name: 'ka', from: (x) =>  x*1e3 , min: 0, max: Infinity},
  {name: 'Ma', from: (x) =>  x*1e6 , min: 0, max: Infinity},
  {name: 'Ga', from: (x) =>  x*1e9 , min: 0, max: Infinity},
];
const ageUnitsDefault = 'Ma';

class Search extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      search: this.props.search || '',
      searchInput: this.props.search || '',
      levelNumber: 0,
      view: '',
      sort: 'Recently Contributed First',
      sortDefault: true,
      activeFilters: {},
      height: undefined,
      width: undefined,
      lat_min: undefined,
      lat_max: undefined,
      lon_min: undefined,
      lon_max: undefined,
      age_min: undefined,
      age_min_unit: undefined,
      age_max: undefined,
      age_max_unit: undefined,
      int_min: undefined,
      int_max: undefined,
      int_unit: undefined
    };
    this.styles = {
      a: {cursor: 'pointer', color: '#792f91'},
      table: {width: '100%'},
      input: {borderColor: '#888888', borderLeft: 'none', borderRight: 'none', flex: '1'},
      td: {verticalAlign: 'top', overflow: 'hidden', transition: 'all 0.5s ease', position: 'relative'},
      segment: {padding: '0'},
      searchButton: {marginLeft: '-1px'},
      activeTab: {backgroundColor: '#F0F0F0'},
      countLabel: {color: '#0C0C0C', margin: '-1em -1em -1em 0.5em', minWidth: '4em'},
      searchInput: {padding: '1em', paddingBottom: 0, flex: 1},
      filters: {whiteSpace: 'nowrap', overflowY: 'scroll', border: 'none', flex: 1 },
      filter: {margin: '1em 0 .5em'},
      filterHeader: {margin: '0'},
      filterBuckets: {paddingLeft: '0.5em', position: 'relative'}
    };
    this.updateSearchInput = _.debounce((value) => {
      this.setState({searchInput: value});
    }, 500);

    this.handleNumericInput = _.debounce((input, value, min, max) => {
      console.log(input, value, min, max);
      let parsedValue = parseFloat(value);
      console.log(input, value, min, max, parsedValue);
      if (value === '')
        this.setState({[input]: undefined});
      else if (!isNaN(parsedValue) && parsedValue >= min && parsedValue <= max)
        this.setState({[input]: parsedValue});
      else
        this.setState({[input]: null});
    }, 500);
  }

  componentDidMount() {
    let self = this;
    $(this.refs['sort']).dropdown({ onChange: function (value, text) { self.setState({sort: text, sortDefault: false}); } });
    $(this.refs['age_min_unit']).dropdown({ onChange: function (value, text) { self.setState({age_min_unit: text}); } });
    $(this.refs['age_max_unit']).dropdown({ onChange: function (value, text) { self.setState({age_max_unit: text}); } });
    $(this.refs['int_unit']).dropdown({ onChange: function (value, text) { self.setState({int_unit: text}); } });
    window.addEventListener("resize", this.onWindowResize.bind(this));
    this.onWindowResize();
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.onWindowResize.bind(this));
  }

  componentWillUpdate(nextProps, nextState) {
    if (nextState.view !== this.state.view) {
      _.delay(() => $(window).trigger('resize'), 500);
    }
  }

  componentDidUpdate() {
    this.onWindowResize();
  }

  onWindowResize() {
    try {
      const windowHeight = $(window).height() - 60; // to allow for the footer
      if (windowHeight !== this.windowHeight) {
        this.windowHeight = windowHeight;
        const height = windowHeight - $(this.refs['filters']).offset().top + 20;
        this.setState({height: height});
      }
      const windowWidth = $(window).width();
      if (windowWidth !== this.windowWidth) {
        this.windowWidth = windowWidth;
        const width = windowWidth - $(this.refs['filters']).outerWidth() - 2 * $(this.refs['filters']).offset().left - 10;
        this.setState({width: width});
      }
    } catch(e) {}
  }

  getSearchQueries() {
    let queries = [];
    let search = this.state.search.replace(/(\w+):\"(.+?)\"\s*/g, (match, term, value) => {
      queries.push(
        searchTerms[term] ? {
          term: {
            [searchTerms[term].field]: searchTerms[term].processor(value)
          }
        } : {
          wildcard: {
            ['summary._all.' + term + '.raw']: value
          }
        }
      );
      return '';
    });
    if (_.trim(search) !== '') {
      queries.push({
        simple_query_string: {
          query: search,
          default_operator: 'and'
        }
      });
    }
    console.log('queries', queries);
{'fraction_non_alphanumeric': 0.17463363572923934, 'fraction_numerical': 0.015265177948360084, 'mean_word_length': 2.9342827728208647, 'pattern_counts': {'":': 10, '<': 5, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6634991', 'n_tokens_mistral': 7668, 'n_tokens_neox': 7156, 'n_words': 1555}