<commit_before>import { map } from 'lodash';
import {
    BaseModel, model, runInAction, array, hydrateModel, modelize, observable, computed, action, getParentOf,
} from 'shared/model';
import Exercise from './exercises/exercise';
import urlFor from '../api'
import pluralize from 'pluralize';
import { toSentence } from 'shared/helpers/string'

class Clause extends BaseModel {

    @observable filter = 'uid';
    @observable value = '';

    constructor() {
        super();
        modelize(this);
    }

    get search() { return getParentOf<Search>(this) }

    @computed get description() {
        return `Search by ${this.filter}`;
    }

    @computed get asString() {
        return `${this.filter}="${this.value}"`;
    }

    @action.bound setFilter(filter: string) {
        this.filter = filter;
        this.search.currentPage = 1;
    }

    @action.bound onKey(e: any){
        if(e.keyCode == 13 && e.shiftKey == false) {
            this.search.perform();
        }
    }

    @action.bound onSelect(evKey: string) {
        this.filter = evKey;
    }

    @action.bound setValue({ target: { value } }: any) {
        this.value = value;
    }

    get asQuery() {
        return `${this.filter}:"${this.value}"`;
    }
}

export default
class Search extends BaseModel {

    @model(Clause) clauses = array<Clause>()
    @model(Exercise) exercises = array<Exercise>()
    @observable total_count = 0
    @observable perPageSize = 25
    @observable currentPage = 1
    @observable bookUuid = ''
    @observable sectionUuid = ''

    constructor() {
        super();
        modelize(this);
        if (!this.clauses.length) { this.clauses.push(hydrateModel(Clause, {}, this)) }
    }

    @action.bound execute() {
        this.perform();
    }

    @action.bound setPerPageSize(size: number) {
        const firstVisibleExercise = (this.perPageSize * (this.currentPage - 1)) + 1;
        this.perPageSize = Number(size);
        // keep cursor showing roughly the same exercise
        this.currentPage = Math.floor(firstVisibleExercise / this.perPageSize) + 1;
        this.perform();
    }

    @computed get title() {
        if (!this.exercises.length) {
            return 'Exercise Search';
        }
        return `${pluralize('exercise', this.exercises.length, true)} found for ${toSentence(this.clauses.map(c => c.asString))}`
    }

    @action.bound onPageChange(pg: number) {
        this.currentPage = pg;
        this.perform();
    }

    @computed get pagination() {
        if (!this.total_count) {
            return null;
        }

        return {
            currentPage: this.currentPage ,
            totalPages: Math.floor(this.total_count / this.perPageSize) + 1,
            onChange: this.onPageChange,
        };
    }


    //called by api
    async perform() {
        let query = map(this.clauses, 'asQuery').join(' ')
        if (this.sectionUuid) query += ` tag:"context-cnxmod:${this.sectionUuid}"`

        const { total_count, items } = await this.api.request<{ total_count: number, items: Exercise[] }>(
            urlFor('search', {}, {
                q: query,
                per_page: this.perPageSize,
                page: this.currentPage,
            })
        )


        runInAction(() => {
            this.total_count = total_count;<commit_msg>Ignore blank clauses (return all results if the field is left blank)<commit_after>import { map } from 'lodash';
import {
    BaseModel, model, runInAction, array, hydrateModel, modelize, observable, computed, action, getParentOf,
} from 'shared/model';
import Exercise from './exercises/exercise';
import urlFor from '../api'
import pluralize from 'pluralize';
import { toSentence } from 'shared/helpers/string'

class Clause extends BaseModel {

    @observable filter = 'uid';
    @observable value = '';

    constructor() {
        super();
        modelize(this);
    }

    get search() { return getParentOf<Search>(this) }

    @computed get description() {
        return `Search by ${this.filter}`;
    }

    @computed get asString() {
        return `${this.filter}="${this.value}"`;
    }

    @action.bound setFilter(filter: string) {
        this.filter = filter;
        this.search.currentPage = 1;
    }

    @action.bound onKey(e: any){
        if(e.keyCode == 13 && e.shiftKey == false) {
            this.search.perform();
        }
    }

    @action.bound onSelect(evKey: string) {
        this.filter = evKey;
    }

    @action.bound setValue({ target: { value } }: any) {
        this.value = value;
    }

    get asQuery() {
        return `${this.filter}:"${this.value}"`;
    }
}

export default
class Search extends BaseModel {

    @model(Clause) clauses = array<Clause>()
    @model(Exercise) exercises = array<Exercise>()
    @observable total_count = 0
    @observable perPageSize = 25
    @observable currentPage = 1
    @observable bookUuid = ''
    @observable sectionUuid = ''

    constructor() {
        super();
        modelize(this);
        if (!this.clauses.length) { this.clauses.push(hydrateModel(Clause, {}, this)) }
    }

    @action.bound execute() {
        this.perform();
    }

    @action.bound setPerPageSize(size: number) {
        const firstVisibleExercise = (this.perPageSize * (this.currentPage - 1)) + 1;
        this.perPageSize = Number(size);
        // keep cursor showing roughly the same exercise
        this.currentPage = Math.floor(firstVisibleExercise / this.perPageSize) + 1;
        this.perform();
    }

    @computed get title() {
        if (!this.exercises.length) {
            return 'Exercise Search';
        }
        return `${pluralize('exercise', this.exercises.length, true)} found for ${toSentence(this.clauses.map(c => c.asString))}`
    }

    @action.bound onPageChange(pg: number) {
        this.currentPage = pg;
        this.perform();
    }

    @computed get pagination() {
        if (!this.total_count) {
            return null;
        }

        return {
            currentPage: this.currentPage ,
            totalPages: Math.floor(this.total_count / this.perPageSize) + 1,
            onChange: this.onPageChange,
        };
    }


    //called by api
    async perform() {
        const clauses = this.clauses.filter(c => c.value)
        if (this.sectionUuid) {
            clauses.push(hydrateModel(
                Clause, {filter: 'tag', value: `context-cnxmod:${this.sectionUuid}`}, this
            ))
        }

        const { total_count, items } = await this.api.request<{ total_count: number, items: Exercise[] }>(
            urlFor('search', {}, {
                q: map(clauses, 'asQuery').join(' '),
                per_page: this.perPageSize,
                page: this.currentPage,
            })
        )


        runInAction(() => {
            this.total_count = total_count;
{'fraction_non_alphanumeric': 0.13555099648300117, 'fraction_numerical': 0.003223915592028136, 'mean_word_length': 2.260869565217391, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 16, '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': '3603066', 'n_tokens_mistral': 2112, 'n_tokens_neox': 2032, 'n_words': 456}