Skip to content

Commit

Permalink
Merge pull request #741 from tadast/tt/query-complexity
Browse files Browse the repository at this point in the history
Console log primitive meta stats for the query
  • Loading branch information
yannickwurm committed May 9, 2024
2 parents db803cd + b3023ff commit 9faa2c4
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 47 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Expand Up @@ -44,6 +44,7 @@ module.exports = {
'circos$': '<rootDir>/public/js/tests/mocks/circos.js',
'grapher': '<rootDir>/public/js/grapher.js',
'histogram': '<rootDir>/public/js/null_plugins/grapher/histogram.js',
'query_stats': '<rootDir>/public/js/null_plugins/query_stats.js'
},
watchPlugins: [
'jest-watch-typeahead/filename',
Expand Down
2 changes: 1 addition & 1 deletion public/css/app.min.css

Large diffs are not rendered by default.

73 changes: 49 additions & 24 deletions public/js/databases.js
Expand Up @@ -4,30 +4,43 @@ import _ from 'underscore';
export class Databases extends Component {
constructor(props) {
super(props);
this.state = { type: '' };
this.state = {
type: '',
currentlySelectedDatabases: [],
};

this.preSelectedDbs = this.props.preSelectedDbs;
this.databases = this.databases.bind(this);
this.nselected = this.nselected.bind(this);
this.categories = this.categories.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.renderDatabases = this.renderDatabases.bind(this);
this.renderDatabase = this.renderDatabase.bind(this);
}
componentDidUpdate() {

componentDidUpdate(_prevProps, prevState) {
// If there's only one database, select it.
if (this.databases() && this.databases().length === 1) {
$('.databases').find('input').prop('checked', true);
this.handleClick(this.databases()[0]);
this.setState({currentlySelectedDatabases: this.databases()});
}

if (this.preSelectedDbs) {
var selectors = this.preSelectedDbs.map((db) => `input[value=${db.id}]`);
$(selectors.join(',')).prop('checked', true);
this.handleClick(this.preSelectedDbs[0]);
if (this.preSelectedDbs && this.preSelectedDbs.length !== 0) {
this.setState({currentlySelectedDatabases: this.preSelectedDbs});
this.preSelectedDbs = null;
}
this.props.onDatabaseTypeChanged(this.state.type);
const type = this.state.currentlySelectedDatabases[0] ? this.state.currentlySelectedDatabases[0].type : '';
if (type != this.state.type) {
this.setState({ type: type });
this.props.onDatabaseTypeChanged(type);
}

if (prevState.currentlySelectedDatabases !== this.state.currentlySelectedDatabases) {
// Call the prop function with the new state
this.props.onDatabaseSelectionChanged(this.state.currentlySelectedDatabases);
}

}

databases(category) {
var databases = this.props.databases;
if (category) {
Expand All @@ -38,29 +51,24 @@ export class Databases extends Component {
}

nselected() {
return $('input[name="databases[]"]:checked').length;
return this.state.currentlySelectedDatabases.length;
}

categories() {
return _.uniq(_.map(this.props.databases, _.iteratee('type'))).sort();
}

handleClick(database) {
var type = this.nselected() ? database.type : '';
if (type != this.state.type) this.setState({ type: type });
}

handleToggle(toggleState, type) {
switch (toggleState) {
case '[Select all]':
$(`.${type} .database input:not(:checked)`).click();
this.setState({ currentlySelectedDatabases: this.databases(type) });
break;
case '[Deselect all]':
$(`.${type} .database input:checked`).click();
this.setState({ currentlySelectedDatabases: [] });
break;
}
this.forceUpdate();
}

renderDatabases(category) {
// Panel name and column width.
var panelTitle = category[0].toUpperCase() + category.substring(1).toLowerCase() + ' databases';
Expand Down Expand Up @@ -117,20 +125,37 @@ export class Databases extends Component {
);
}

handleDatabaseSelectionClick(database) {
const isSelected = this.state.currentlySelectedDatabases.some(db => db.id === database.id);

if (isSelected) {
this.setState(prevState => ({
currentlySelectedDatabases: prevState.currentlySelectedDatabases.filter(db => db.id !== database.id)
}));
} else {
this.setState(prevState => ({
currentlySelectedDatabases: [...prevState.currentlySelectedDatabases, database]
}));
}
}

renderDatabase(database) {
var disabled = this.state.type && this.state.type !== database.type;
const isDisabled = this.state.type && this.state.type !== database.type;
const isChecked = this.state.currentlySelectedDatabases.some(db => db.id === database.id);

return (
<label className={(disabled && 'database text-gray-400') || 'database text-seqblue'}>
<label className={(isDisabled && 'database text-gray-400') || 'database text-seqblue'}>
<input
type="checkbox"
name="databases[]"
value={database.id}
data-type={database.type}
disabled={disabled}
disabled={isDisabled}
checked={isChecked}
onChange={_.bind(function () {
this.handleClick(database);
this.handleDatabaseSelectionClick(database);
}, this)}

/>
{' ' + (database.title || database.name)}
</label>
Expand All @@ -144,4 +169,4 @@ export class Databases extends Component {
</div>
);
}
}
}
56 changes: 44 additions & 12 deletions public/js/form.js
Expand Up @@ -5,6 +5,7 @@ import DatabasesTree from './databases_tree';
import { Databases } from './databases';
import _ from 'underscore';
import { Options } from './options';
import QueryStats from 'query_stats';

/**
* Search form.
Expand All @@ -16,15 +17,24 @@ export class Form extends Component {
constructor(props) {
super(props);
this.state = {
databases: [], preDefinedOpts: {}, tree: {}
databases: [],
preSelectedDbs: [],
currentlySelectedDbs: [],
preDefinedOpts: {},
tree: {},
residuesInQuerySequence: 0,
blastMethod: ''
};
this.useTreeWidget = this.useTreeWidget.bind(this);
this.determineBlastMethod = this.determineBlastMethod.bind(this);
this.determineBlastMethods = this.determineBlastMethods.bind(this);
this.handleSequenceTypeChanged = this.handleSequenceTypeChanged.bind(this);
this.handleSequenceChanged = this.handleSequenceChanged.bind(this);
this.handleDatabaseTypeChanged = this.handleDatabaseTypeChanged.bind(this);
this.handleDatabaseSelectionChanged = this.handleDatabaseSelectionChanged.bind(this);
this.handleAlgoChanged = this.handleAlgoChanged.bind(this);
this.handleFormSubmission = this.handleFormSubmission.bind(this);
this.formRef = createRef();
this.setButtonState = this.setButtonState.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -103,7 +113,7 @@ export class Form extends Component {
});
}

determineBlastMethod() {
determineBlastMethods() {
var database_type = this.databaseType;
var sequence_type = this.sequenceType;

Expand Down Expand Up @@ -138,24 +148,34 @@ export class Form extends Component {
return [];
}

handleSequenceChanged(residuesInQuerySequence) {
if(residuesInQuerySequence !== this.state.residuesInQuerySequence)
this.setState({ residuesInQuerySequence: residuesInQuerySequence});
}

handleSequenceTypeChanged(type) {
this.sequenceType = type;
this.refs.button.setState({
hasQuery: !this.refs.query.isEmpty(),
hasDatabases: !!this.databaseType,
methods: this.determineBlastMethod()
});
this.setButtonState();
}

handleDatabaseTypeChanged(type) {
this.databaseType = type;
this.setButtonState();
}

setButtonState() {
this.refs.button.setState({
hasQuery: !this.refs.query.isEmpty(),
hasDatabases: !!this.databaseType,
methods: this.determineBlastMethod()
methods: this.determineBlastMethods()
});
}

handleDatabaseSelectionChanged(selectedDbs) {
if (!_.isEqual(selectedDbs, this.state.currentlySelectedDbs))
this.setState({ currentlySelectedDbs: selectedDbs });
}

handleAlgoChanged(algo) {
if (algo in this.state.preDefinedOpts) {
var preDefinedOpts = this.state.preDefinedOpts[algo];
Expand All @@ -165,12 +185,18 @@ export class Form extends Component {
value: (preDefinedOpts['last search'] ||
preDefinedOpts['default']).join(' ')
});
this.setState({ blastMethod: algo });
}
else {
this.refs.opts.setState({ preOpts: {}, value: '', method: '' });
this.setState({ blastMethod: '' });
}
}

residuesInSelectedDbs() {
return this.state.currentlySelectedDbs.reduce((sum, db) => sum + parseInt(db.ncharacters, 10), 0);
}

render() {
return (
<div>
Expand All @@ -184,17 +210,19 @@ export class Form extends Component {
</div>

<form id="blast" ref={this.formRef} onSubmit={this.handleFormSubmission}>
<SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} />
<SearchQueryWidget ref="query" onSequenceTypeChanged={this.handleSequenceTypeChanged} onSequenceChanged={this.handleSequenceChanged} />

{this.useTreeWidget() ?
<DatabasesTree ref="databases"
databases={this.state.databases} tree={this.state.tree}
preSelectedDbs={this.state.preSelectedDbs}
onDatabaseTypeChanged={this.handleDatabaseTypeChanged} />
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
:
<Databases ref="databases" databases={this.state.databases}
preSelectedDbs={this.state.preSelectedDbs}
onDatabaseTypeChanged={this.handleDatabaseTypeChanged} />
onDatabaseTypeChanged={this.handleDatabaseTypeChanged}
onDatabaseSelectionChanged={this.handleDatabaseSelectionChanged} />
}

<div className="md:flex flex-row md:space-x-4 items-center my-6">
Expand All @@ -204,6 +232,10 @@ export class Form extends Component {
</label>
<SearchButton ref="button" onAlgoChanged={this.handleAlgoChanged} />
</div>
<QueryStats
residuesInQuerySequence={this.state.residuesInQuerySequence} numberOfDatabasesSelected={this.state.currentlySelectedDbs.length} residuesInSelectedDbs={this.residuesInSelectedDbs()}
currentBlastMethod={this.state.blastMethod}
/>
</form>
</div>
);
Expand Down
11 changes: 11 additions & 0 deletions public/js/null_plugins/query_stats.js
@@ -0,0 +1,11 @@
import React from 'react';

class QueryStats extends React.Component{
render () {}

componentDidUpdate() {
console.log("Query stats:", this.props)
}
}

export default QueryStats;
14 changes: 14 additions & 0 deletions public/js/query.js
Expand Up @@ -124,6 +124,7 @@ export class SearchQueryWidget extends Component {
componentDidUpdate() {
this.hideShowButton();
this.preProcessSequence();
this.props.onSequenceChanged(this.residuesCount());

var type = this.type();
if (!type || type !== this._type) {
Expand Down Expand Up @@ -156,6 +157,19 @@ export class SearchQueryWidget extends Component {
}
}

residuesCount() {
const sequence = this.value();
const lines = sequence.split('\n');
const residuesCount = lines.reduce((count, line) => {
if (!line.startsWith('>')) {
return count + line.length;
}
return count;
}, 0);

return residuesCount;
}

/**
* Clears textarea. Returns `this`.
*
Expand Down
10 changes: 5 additions & 5 deletions public/js/tests/database.spec.js
@@ -1,6 +1,6 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
import { render, screen, fireEvent, waitFor, findByText, getByText } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Databases } from '../databases';
import data from './mock_data/databases.json';

Expand All @@ -21,13 +21,13 @@ describe('DATABASES COMPONENT', () => {
});

test('clicking select all on a database should select all its children', () => {
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={() => { }} />);
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={() => { }} onDatabaseSelectionChanged={() => { }} />);

// select all nucleotide databases
const nucleotideSelectAllBtn = screen.getByRole('heading', { name: /nucleotide databases/i }).parentElement.querySelector('button');
fireEvent.click(nucleotideSelectAllBtn);
const nucleotideCheckboxes = container.querySelector('.databases.nucleotide').querySelectorAll('input[type=checkbox]');

// all nucleotide databases should be checked
nucleotideCheckboxes.forEach((checkbox) => {
expect(checkbox).toBeChecked();
Expand All @@ -45,7 +45,7 @@ describe('DATABASES COMPONENT', () => {

test('checking any item of a database type should disable other database type', () => {
const mockFunction = jest.fn(() => { });
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={mockFunction} />);
const { container } = render(<Databases databases={data.database} onDatabaseTypeChanged={mockFunction} onDatabaseSelectionChanged={mockFunction}/>);

//select a proteinn database
fireEvent.click(screen.getByRole('checkbox', { name: /2020-11 Swiss-Prot insecta/i }));
Expand Down

0 comments on commit 9faa2c4

Please sign in to comment.