From 3e38b6e6962cde487afe1c47e1f8dc5570311c2c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 26 Jun 2019 09:59:49 +0200 Subject: [PATCH 01/10] Fix deleting jobs Discussed this with @slugger90 and @erasche on gitter. The traceback was: ``` galaxy.jobs.handler ERROR 2019-06-26 13:09:12,754 [p:32491,w:0,m:2] [JobHandlerStopQueue.monitor_thread] Exception in monitor_step Traceback (most recent call last): File "lib/galaxy/jobs/handler.py", line 866, in monitor self.monitor_step() File "lib/galaxy/jobs/handler.py", line 886, in monitor_step jobs_to_check.append((job, job.stderr)) File "lib/galaxy/model/__init__.py", line 337, in get_stderr stderr += "\n" + self.job_stderr TypeError: unsupported operand type(s) for +=: 'NoneType' and 'unicode' ``` --- lib/galaxy/jobs/handler.py | 5 ++++- lib/galaxy/model/__init__.py | 11 +++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/jobs/handler.py b/lib/galaxy/jobs/handler.py index 33a705caccc..3c30f26d97d 100644 --- a/lib/galaxy/jobs/handler.py +++ b/lib/galaxy/jobs/handler.py @@ -883,7 +883,10 @@ class JobHandlerStopQueue(Monitors): .filter((model.Job.state == model.Job.states.DELETED_NEW) & (model.Job.handler == self.app.config.server_name)).all() for job in newly_deleted_jobs: - jobs_to_check.append((job, job.stderr)) + # job.stderr is always a string (job.job_stderr + job.tool_stderr, possibly `''`), + # while any `not None` message returned in self.queue.get_nowait() is interpreted + # as an error, so here we use None if job.stderr is false-y + jobs_to_check.append((job, job.stderr or None)) # Also pull from the queue (in the case of Administrative stopped jobs) try: while 1: diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 95efaa76ecc..ae71455da81 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -331,18 +331,17 @@ class JobLike(object): def set_stdout(self, stdout): raise NotImplementedError("Attempt to set stdout, must set tool_stdout or job_stdout") - def get_stderr(self): - stderr = self.tool_stderr + @property + def stderr(self): + stderr = self.tool_stderr or '' if self.job_stderr: stderr += "\n" + self.job_stderr return stderr - def set_stderr(self, stderr): + @stderr.setter + def stderr(self, stderr): raise NotImplementedError("Attempt to set stdout, must set tool_stderr or job_stderr") - stdout = property(get_stdout, set_stdout) - stderr = property(get_stderr, set_stderr) - class User(Dictifiable, RepresentById): use_pbkdf2 = True From 96c001c565d713676c9002818d62f01960c1793d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 26 Jun 2019 11:34:05 +0200 Subject: [PATCH 02/10] Add stdout property removed in 3e38b6e6962cde487afe1c47e1f8dc5570311c2c --- lib/galaxy/model/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index ae71455da81..8cdea1c826c 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -322,13 +322,15 @@ class JobLike(object): return "%s[%s,tool_id=%s]" % (self.__class__.__name__, extra, self.tool_id) - def get_stdout(self): - stdout = self.tool_stdout + @property + def stdout(self): + stdout = self.tool_stdout or '' if self.job_stdout: stdout += "\n" + self.job_stdout return stdout - def set_stdout(self, stdout): + @stdout.setter + def stdout(self, stdout): raise NotImplementedError("Attempt to set stdout, must set tool_stdout or job_stdout") @property From 5016dedac73e65b4bc07f0341e216d071ba1c740 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 26 Jun 2019 11:23:11 +0200 Subject: [PATCH 03/10] Add test that ensures cancelling job in admin interface works --- .../test_local_job_cancellation.py | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/test/integration/test_local_job_cancellation.py b/test/integration/test_local_job_cancellation.py index abcaa0a1576..06166c242e3 100644 --- a/test/integration/test_local_job_cancellation.py +++ b/test/integration/test_local_job_cancellation.py @@ -18,29 +18,51 @@ class LocalJobCancellationTestCase(integration_util.IntegrationTestCase): super(LocalJobCancellationTestCase, self).setUp() self.dataset_populator = DatasetPopulator(self.galaxy_interactor) + def setup_cat_data_and_sleep(self, history_id): + hda1 = self.dataset_populator.new_dataset(history_id, content="1 2 3") + running_inputs = { + "input1": {"src": "hda", "id": hda1["id"]}, + "sleep_time": 240, + } + running_response = self.dataset_populator.run_tool( + "cat_data_and_sleep", + running_inputs, + history_id, + assert_ok=False, + ).json() + job_dict = running_response["jobs"][0] + return job_dict + + def test_cancel_job_with_admin_message(self): + with self.dataset_populator.test_history() as history_id: + job_dict = self.setup_cat_data_and_sleep(history_id) + self.galaxy_interactor.wait_for(lambda: self._get("jobs/%s" % job_dict['id']).json()['state'] != 'running', + maxseconds=60) + app = self._app + sa_session = app.model.context.current + Job = app.model.Job + job = sa_session.query(Job).filter_by(tool_id="cat_data_and_sleep").order_by(Job.create_time.desc()).first() + # This is how the admin controller code cancels a job + job.job_stderr = 'admin cancelled job' + job.set_state(app.model.Job.states.DELETED_NEW) + sa_session.add(job) + sa_session.flush() + self.galaxy_interactor.wait_for(lambda: self._get("jobs/%s" % job_dict['id']).json()['state'] != 'error', + maxseconds=60) + def test_kill_process(self): """ """ with self.dataset_populator.test_history() as history_id: - hda1 = self.dataset_populator.new_dataset(history_id, content="1 2 3") - running_inputs = { - "input1": {"src": "hda", "id": hda1["id"]}, - "sleep_time": 240, - } - running_response = self.dataset_populator.run_tool( - "cat_data_and_sleep", - running_inputs, - history_id, - assert_ok=False, - ).json() - job_dict = running_response["jobs"][0] + job_dict = self.setup_cat_data_and_sleep(history_id) app = self._app sa_session = app.model.context.current external_id = None state = False + Job = app.model.Job - job = sa_session.query(app.model.Job).filter_by(tool_id="cat_data_and_sleep").one() + job = sa_session.query(Job).filter_by(tool_id="cat_data_and_sleep").order_by(Job.create_time.desc()).first() # Not checking the state here allows the change from queued to running to overwrite # the change from queued to deleted_new in the API thread - this is a problem because # the job will still run. See issue https://github.com/galaxyproject/galaxy/issues/4960. From 54a60a6aaa992319cdb0f5bc1fb68be83c28ffd2 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 26 Jun 2019 15:53:25 +0200 Subject: [PATCH 04/10] Looks like we never actually cancel the job? --- lib/galaxy/tools/verify/interactor.py | 4 ++-- test/integration/test_local_job_cancellation.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/tools/verify/interactor.py b/lib/galaxy/tools/verify/interactor.py index 2330e8853c5..a83f338a0f1 100644 --- a/lib/galaxy/tools/verify/interactor.py +++ b/lib/galaxy/tools/verify/interactor.py @@ -244,7 +244,7 @@ class GalaxyInteractorApi(object): def wait_for_job(self, job_id, history_id, maxseconds): self.wait_for(lambda: not self.__job_ready(job_id, history_id), maxseconds=maxseconds) - def wait_for(self, func, **kwd): + def wait_for(self, func, what='Tool test run', **kwd): sleep_amount = 0.2 slept = 0 walltime_exceeded = int(kwd.get("maxseconds", DEFAULT_TOOL_TEST_WAIT)) @@ -258,7 +258,7 @@ class GalaxyInteractorApi(object): else: return - message = 'Tool test run exceeded walltime [total %s, max %s], terminating.' % (slept, walltime_exceeded) + message = '%s exceeded walltime [total %s, max %s], terminating.' % (what, slept, walltime_exceeded) log.info(message) raise AssertionError(message) diff --git a/test/integration/test_local_job_cancellation.py b/test/integration/test_local_job_cancellation.py index 06166c242e3..d01cea8bd2e 100644 --- a/test/integration/test_local_job_cancellation.py +++ b/test/integration/test_local_job_cancellation.py @@ -37,6 +37,7 @@ class LocalJobCancellationTestCase(integration_util.IntegrationTestCase): with self.dataset_populator.test_history() as history_id: job_dict = self.setup_cat_data_and_sleep(history_id) self.galaxy_interactor.wait_for(lambda: self._get("jobs/%s" % job_dict['id']).json()['state'] != 'running', + what="Wait for job to start running", maxseconds=60) app = self._app sa_session = app.model.context.current @@ -48,6 +49,7 @@ class LocalJobCancellationTestCase(integration_util.IntegrationTestCase): sa_session.add(job) sa_session.flush() self.galaxy_interactor.wait_for(lambda: self._get("jobs/%s" % job_dict['id']).json()['state'] != 'error', + what="Wait for job to end in error", maxseconds=60) def test_kill_process(self): From 85643c50f53a2d7a42e0d7a9b483421c86dfb4e2 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 28 Jun 2019 18:17:58 +0200 Subject: [PATCH 05/10] Quick hack to allow delayed invocations followed by step that consumes an input parameter Fixes https://github.com/galaxyproject/galaxy/issues/8248, tested with normal input parameters and expression tool input. Proper fix would of course be tracking the OutputSteps that are produced, but would probably require a database migration. --- lib/galaxy/workflow/run.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/workflow/run.py b/lib/galaxy/workflow/run.py index e9120f2529b..3336139eab3 100644 --- a/lib/galaxy/workflow/run.py +++ b/lib/galaxy/workflow/run.py @@ -352,10 +352,16 @@ class WorkflowProgress(object): try: replacement = step_outputs[output_name] except KeyError: - # Must resolve. - template = "Workflow evaluation problem - failed to find output_name %s in step_outputs %s" - message = template % (output_name, step_outputs) - raise Exception(message) + replacement = self.inputs_by_step_id.get(output_step_id) + if connection.output_step.type == 'parameter_input' and output_step_id is not None: + # FIXME: parameter_input step outputs should be properly recorded as step outputs, but for now we can + # short-circuit and just pick the input value + pass + else: + # Must resolve. + template = "Workflow evaluation problem - failed to find output_name %s in step_outputs %s" + message = template % (output_name, step_outputs) + raise Exception(message) if isinstance(replacement, model.HistoryDatasetCollectionAssociation): if not replacement.collection.populated: if not replacement.collection.waiting_for_elements: From d4c802a1752bc9c094618535a25e40873c99133d Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 28 Jun 2019 15:26:49 +0200 Subject: [PATCH 06/10] Fix migration if WorkflowStepConnection index not present Fixes: ``` self = , cursor = statement = '\nDROP INDEX ix_workflow_step_connection_input_subworkflow_step_id', parameters = () context = def do_execute(self, cursor, statement, parameters, context=None): > cursor.execute(statement, parameters) E sqlite3.OperationalError: no such index: ix_workflow_step_connection_input_subworkflow_step_id .venv3/lib/python3.6/site-packages/sqlalchemy/engine/default.py:550: OperationalError The above exception was the direct cause of the following exception: cls = @classmethod def setUpClass(cls): # realpath for docker deployed in a VM on Mac, also done in driver_util. cls.jobs_directory = os.path.realpath(tempfile.mkdtemp()) > super(KubernetesDependencyResolutionIntegrationTestCase, cls).setUpClass() test/integration/test_kubernetes_staging.py:147: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ test/base/integration_util.py:69: in setUpClass cls._test_driver.setup(config_object=cls) test/base/driver_util.py:898: in setup self._register_and_run_servers(config_object) test/base/driver_util.py:953: in _register_and_run_servers self.app = build_galaxy_app(galaxy_config) test/base/driver_util.py:563: in build_galaxy_app app = GalaxyUniverseApplication(**simple_kwargs) lib/galaxy/app.py:88: in __init__ self._configure_models(check_migrate_databases=self.config.check_migrate_databases, check_migrate_tools=check_migrate_tools, config_file=config_file) lib/galaxy/config.py:1149: in _configure_models create_or_verify_database(db_url, config_file, self.config.database_engine_options, app=self) lib/galaxy/model/migrate/check.py:64: in create_or_verify_database migrate() lib/galaxy/model/migrate/check.py:60: in migrate migrate_to_current_version(engine, db_schema) lib/galaxy/model/migrate/check.py:149: in migrate_to_current_version schema.runchange(ver, change, changeset.step) .venv3/lib/python3.6/site-packages/migrate/versioning/schema.py:93: in runchange change.run(self.engine, step) .venv3/lib/python3.6/site-packages/migrate/versioning/script/py.py:148: in run script_func(engine) lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py:48: in upgrade index.drop() .venv3/lib/python3.6/site-packages/sqlalchemy/sql/schema.py:3713: in drop bind._run_visitor(ddl.SchemaDropper, self) .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:2033: in _run_visitor conn._run_visitor(visitorcallable, element, **kwargs) .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1607: in _run_visitor visitorcallable(self.dialect, self, **kwargs).traverse_single(element) .venv3/lib/python3.6/site-packages/sqlalchemy/sql/visitors.py:131: in traverse_single return meth(obj, **kw) .venv3/lib/python3.6/site-packages/sqlalchemy/sql/ddl.py:988: in visit_index self.connection.execute(DropIndex(index)) .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:988: in execute return meth(self, multiparams, params) .venv3/lib/python3.6/site-packages/sqlalchemy/sql/ddl.py:72: in _execute_on_connection return connection._execute_ddl(self, multiparams, params) .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1050: in _execute_ddl compiled, .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1248: in _execute_context e, statement, parameters, cursor, context .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1466: in _handle_dbapi_exception util.raise_from_cause(sqlalchemy_exception, exc_info) .venv3/lib/python3.6/site-packages/sqlalchemy/util/compat.py:383: in raise_from_cause reraise(type(exception), exception, tb=exc_tb, cause=cause) .venv3/lib/python3.6/site-packages/sqlalchemy/util/compat.py:128: in reraise raise value.with_traceback(tb) .venv3/lib/python3.6/site-packages/sqlalchemy/engine/base.py:1244: in _execute_context cursor, statement, parameters, context _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = , cursor = statement = '\nDROP INDEX ix_workflow_step_connection_input_subworkflow_step_id', parameters = () context = def do_execute(self, cursor, statement, parameters, context=None): > cursor.execute(statement, parameters) E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such index: ix_workflow_step_connection_input_subworkflow_step_id E [SQL: E DROP INDEX ix_workflow_step_connection_input_subworkflow_step_id] E (Background on this error at: http://sqlalche.me/e/e3q8) .venv3/lib/python3.6/site-packages/sqlalchemy/engine/default.py:550: OperationalError ``` when running the test in https://github.com/galaxyproject/galaxy/pull/8195 --- .../model/migrate/versions/0145_add_workflow_step_input.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py b/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py index 4944fc3ec9c..a5e799104d5 100644 --- a/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py +++ b/lib/galaxy/model/migrate/versions/0145_add_workflow_step_input.py @@ -45,7 +45,10 @@ def upgrade(migrate_engine): OldWorkflowStepConnection_table = Table("workflow_step_connection", metadata, autoload=True) for index in OldWorkflowStepConnection_table.indexes: - index.drop() + try: + index.drop() + except Exception: + log.exception("Dropping index '%s' from table '%s' failed", index, OldWorkflowStepConnection_table) OldWorkflowStepConnection_table.rename("workflow_step_connection_preupgrade145") # Try to deregister that table to work around some caching problems it seems. OldWorkflowStepConnection_table.deregister() From 9b815ce7a6b7c774340fb27b91919126048bd8fb Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 29 Jun 2019 10:31:44 +0200 Subject: [PATCH 07/10] Refresh job to see update state? --- lib/galaxy/webapps/galaxy/api/jobs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index d5fc9568bbf..de77d68bb11 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -324,6 +324,7 @@ class JobController(BaseAPIController, UsesLibraryMixinItems): for data_assoc in job.output_datasets: if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user): raise exceptions.ItemAccessibilityException("You are not allowed to rerun this job.") + trans.sa_session.refresh(job) return job @expose_api From 02f283e004dfc66035819fac4e52b43c9182d117 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 30 Jun 2019 19:38:29 +0200 Subject: [PATCH 08/10] Include workdir path when reading env var from file `env_setup_commands` runs before `cd $working_directory` in the job file template, so just using the relative path fails with ``` Traceback (most recent call last): File "_evaluate_expression_.py", line 1, in from galaxy_ext.expressions.handle_job import run; run() File "/opt/galaxy/server/lib/galaxy_ext/expressions/handle_job.py", line 31, in run with open(environment_path, "r") as f: IOError: [Errno 2] No such file or directory: '' cat: tmp6CL_1e: No such file or directory ``` --- .../jobs/runners/util/job_script/DEFAULT_JOB_FILE_TEMPLATE.sh | 1 + lib/galaxy/tools/evaluation.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/jobs/runners/util/job_script/DEFAULT_JOB_FILE_TEMPLATE.sh b/lib/galaxy/jobs/runners/util/job_script/DEFAULT_JOB_FILE_TEMPLATE.sh index 81107c568b5..a588fb12b5a 100644 --- a/lib/galaxy/jobs/runners/util/job_script/DEFAULT_JOB_FILE_TEMPLATE.sh +++ b/lib/galaxy/jobs/runners/util/job_script/DEFAULT_JOB_FILE_TEMPLATE.sh @@ -4,6 +4,7 @@ $headers _galaxy_setup_environment() { local _use_framework_galaxy="$1" + _GALAXY_JOB_DIR="$working_directory" _GALAXY_JOB_HOME_DIR="$working_directory/home" _GALAXY_JOB_TMP_DIR=$tmp_dir_creation_statement $env_setup_commands diff --git a/lib/galaxy/tools/evaluation.py b/lib/galaxy/tools/evaluation.py index 162f0c906d1..d71602805ff 100644 --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -521,7 +521,8 @@ class ToolEvaluator(object): os.close(fd) self.__write_workdir_file(config_filename, environment_variable_template, param_dict) config_file_basename = os.path.basename(config_filename) - environment_variable["value"] = "`cat %s`" % config_file_basename + # environment setup in job file template happens before `cd $working_directory` + environment_variable["value"] = '`cat "$_GALAXY_JOB_DIR/%s"`' % config_file_basename environment_variable["raw"] = True environment_variables.append(environment_variable) From 6cff1686bda47e0bbaf33810af43663d4405509f Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Mon, 1 Jul 2019 02:10:05 +0200 Subject: [PATCH 09/10] Fix regex rule count --- client/galaxy/scripts/mvc/rules/rule-definitions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/galaxy/scripts/mvc/rules/rule-definitions.js b/client/galaxy/scripts/mvc/rules/rule-definitions.js index e6fc8154036..4561660c0fa 100644 --- a/client/galaxy/scripts/mvc/rules/rule-definitions.js +++ b/client/galaxy/scripts/mvc/rules/rule-definitions.js @@ -253,7 +253,7 @@ const RULES = { component.addColumnRegexTarget = rule.target_column; component.addColumnRegexExpression = rule.expression; component.addColumnRegexReplacement = rule.replacement; - component.addColumnRegexGroupCount = rule.group_count; + component.addColumnRegexGroupCount = parseInt(rule.group_count); } let addColumnRegexType = "global"; if (component.addColumnRegexGroupCount) { From fc2a7d197b472d9e559b91261c5e8be78d8281e9 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Tue, 2 Jul 2019 16:25:45 +0200 Subject: [PATCH 10/10] Specify radix in parseInt to avoid surprises Thanks @dannon! --- .../scripts/mvc/rules/rule-definitions.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/galaxy/scripts/mvc/rules/rule-definitions.js b/client/galaxy/scripts/mvc/rules/rule-definitions.js index 4561660c0fa..a6f7f65e4d7 100644 --- a/client/galaxy/scripts/mvc/rules/rule-definitions.js +++ b/client/galaxy/scripts/mvc/rules/rule-definitions.js @@ -42,7 +42,7 @@ const applyRegex = function(regex, target, data, replacement, groupCount) { return null; } if (!replacement) { - groupCount = groupCount && parseInt(groupCount); + groupCount = groupCount && parseInt(groupCount, 10); if (groupCount) { if (match.length != groupCount + 1) { failedCount++; @@ -108,7 +108,7 @@ const RULES = { } }, save: (component, rule) => { - rule.start = parseInt(component.addColumnRownumStart); + rule.start = parseInt(component.addColumnRownumStart, 10); }, apply: (rule, data, sources, columns) => { let rownum = rule.start; @@ -169,7 +169,7 @@ const RULES = { const ruleValue = rule.value; let newRow; if (ruleValue.indexOf("identifier") == 0) { - const identifierIndex = parseInt(ruleValue.substring("identifier".length)); + const identifierIndex = parseInt(ruleValue.substring("identifier".length), 10); newRow = (row, index) => { const newRow = row.slice(); newRow.push(sources[index]["identifiers"][identifierIndex]); @@ -347,7 +347,7 @@ const RULES = { }, save: (component, rule) => { rule.target_column = component.addColumnSubstrTarget; - rule.length = parseInt(component.addColumnSubstrLength); + rule.length = parseInt(component.addColumnSubstrLength, 10); rule.substr_type = component.addColumnSubstrType; }, apply: (rule, data, sources, columns) => { @@ -403,7 +403,7 @@ const RULES = { function newRow(row) { const newRow = []; for (const index in row) { - if (targets.indexOf(parseInt(index)) == -1) { + if (targets.indexOf(parseInt(index, 10)) == -1) { newRow.push(row[index]); } } @@ -448,7 +448,7 @@ const RULES = { const target = rule.target_column; const invert = rule.invert; const filterFunction = function(el, index) { - const row = data[parseInt(index)]; + const row = data[parseInt(index, 10)]; return regExp.exec(row[target]) ? !invert : invert; }; sources = sources.filter(filterFunction); @@ -477,13 +477,13 @@ const RULES = { component.addFilterCountWhich = "first"; component.addFilterCountInvert = false; } else { - component.addFilterCountN = parseInt(rule.count); + component.addFilterCountN = parseInt(rule.count, 10); component.addFilterCountWhich = rule.which; component.addFilterCountInvert = rule.inverse; } }, save: (component, rule) => { - rule.count = parseInt(component.addFilterCountN); + rule.count = parseInt(component.addFilterCountN, 10); rule.which = component.addFilterCountWhich; rule.invert = component.addFilterCountInvert; }, @@ -528,7 +528,7 @@ const RULES = { const target = rule.target_column; const invert = rule.invert; const filterFunction = function(el, index) { - const row = data[parseInt(index)]; + const row = data[parseInt(index, 10)]; return row[target].length ? !invert : invert; }; sources = sources.filter(filterFunction); @@ -562,7 +562,7 @@ const RULES = { const invert = rule.invert; const value = rule.value; const filterFunction = function(el, index) { - const row = data[parseInt(index)]; + const row = data[parseInt(index, 10)]; return row[target] == value ? !invert : invert; }; sources = sources.filter(filterFunction); @@ -598,7 +598,7 @@ const RULES = { const compare_type = rule.compare_type; const value = rule.value; const filterFunction = function(el, index) { - const row = data[parseInt(index)]; + const row = data[parseInt(index, 10)]; const targetValue = parseFloat(row[target]); let matches; if (compare_type == "less_than") { @@ -730,7 +730,7 @@ const RULES = { const newRow0 = [], newRow1 = []; for (let index in row) { - index = parseInt(index); + index = parseInt(index, 10); if (targets0.indexOf(index) > -1) { newRow0.push(row[index]); } else if (targets1.indexOf(index) > -1) {