From e21b2c93bd0bff39a318476f01ab7f258fd70de9 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Mon, 19 Jul 2021 15:18:35 +0200 Subject: [PATCH 01/88] [DOC] describe how to write queue.job.function in case of function defined in abstract model --- queue_job/readme/USAGE.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/queue_job/readme/USAGE.rst b/queue_job/readme/USAGE.rst index 0b0f6e133e..9612501d75 100644 --- a/queue_job/readme/USAGE.rst +++ b/queue_job/readme/USAGE.rst @@ -43,6 +43,13 @@ they have different xmlids. On uninstall, the merged record is deleted when all the modules using it are uninstalled. +**Job function: model** + +If the function is defined in an abstract model, you can not write +```` +but you have to define a function for each model that inherits from the abstract model. + + **Job function: channel** The channel where the job will be delayed. The default channel is ``root``. From dc51586393d8a4653355c500123dbc3412033978 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 1 Feb 2021 11:28:10 +0100 Subject: [PATCH 02/88] Change technical fields to read-only These fields should not be changed by users. --- queue_job/models/queue_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 157e4d8cbc..a80eb2f9af 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -107,8 +107,8 @@ class QueueJob(models.Model): compute="_compute_channel", inverse="_inverse_channel", store=True, index=True ) - identity_key = fields.Char() - worker_pid = fields.Integer() + identity_key = fields.Char(readonly=True) + worker_pid = fields.Integer(readonly=True) def init(self): self._cr.execute( From 369bdd72b13290f19cd6b906a7e4d69f93894d39 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 2 Feb 2021 07:57:50 +0100 Subject: [PATCH 03/88] Optimize queue.job creation Several fields on queue.job are initialized using computed fields, then never changed again. On creation of a queue.job record, we'll have an initial INSERT + at least one following UPDATE for the computed fields. Replace all the stored computed fields by a raw initialization of the values in `Job.store()` when the job is created, so we have only a single INSERT. Highlights: * as channel is no longer a compute/inverse field, override_channel is useless, I dropped it (actually the same value was stored in both channel and override_channel as the channel field was stored) * one functional diff is that now, when changing a channel on a job.function, the channel is no longer synchronized on existing jobs, it will be applied only on new jobs: actually this is an improvement, because it was impossible to change the channel of a job function in a large queue_job table as it meant writing on all the done/started jobs * searching the queue.job.function is now cached, as each job using the same will run a query on queue_job_function --- queue_job/controllers/main.py | 2 +- queue_job/job.py | 47 ++++++---- queue_job/models/queue_job.py | 101 ++++++--------------- queue_job/models/queue_job_function.py | 5 +- queue_job/tests/test_model_job_function.py | 3 +- 5 files changed, 65 insertions(+), 93 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index d877254bfe..f42af7beaa 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -108,7 +108,7 @@ def retry_postpone(job, message, seconds=None): @http.route("/queue_job/create_test_job", type="http", auth="user") def create_test_job( - self, priority=None, max_retries=None, channel="root", description="Test job" + self, priority=None, max_retries=None, channel=None, description="Test job" ): if not http.request.env.user.has_group("base.group_erp_manager"): raise Forbidden(_("Access Denied")) diff --git a/queue_job/job.py b/queue_job/job.py index 36d2b13158..8b646a5781 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -439,13 +439,7 @@ def __init__( self.job_model_name = "queue.job" self.job_config = ( - self.env["queue.job.function"] - .sudo() - .job_config( - self.env["queue.job.function"].job_function_name( - self.model_name, self.method_name - ) - ) + self.env["queue.job.function"].sudo().job_config(self.job_function_name) ) self.state = PENDING @@ -558,27 +552,35 @@ def store(self): if db_record: db_record.with_context(_job_edit_sentinel=edit_sentinel).write(vals) else: - date_created = self.date_created - # The following values must never be modified after the - # creation of the job vals.update( { + "user_id": self.env.uid, + "channel": self.channel, + # The following values must never be modified after the + # creation of the job "uuid": self.uuid, "name": self.description, - "date_created": date_created, + "func_string": self.func_string, + "date_created": self.date_created, + "model_name": self.recordset._name, "method_name": self.method_name, + "job_function_id": self.job_config.job_function_id, + "channel_method_name": self.job_function_name, "records": self.recordset, "args": self.args, "kwargs": self.kwargs, } ) - # it the channel is not specified, lets the job_model compute - # the right one to use - if self.channel: - vals.update({"channel": self.channel}) - job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create(vals) + @property + def func_string(self): + model = repr(self.recordset) + args = [repr(arg) for arg in self.args] + kwargs = ["{}={!r}".format(key, val) for key, val in self.kwargs.items()] + all_args = ", ".join(args + kwargs) + return "{}.{}({})".format(model, self.method_name, all_args) + def db_record(self): return self.db_record_from_uuid(self.env, self.uuid) @@ -587,6 +589,11 @@ def func(self): recordset = self.recordset.with_context(job_uuid=self.uuid) return getattr(recordset, self.method_name) + @property + def job_function_name(self): + func_model = self.env["queue.job.function"].sudo() + return func_model.job_function_name(self.recordset._name, self.method_name) + @property def identity_key(self): if self._identity_key is None: @@ -644,6 +651,14 @@ def eta(self, value): else: self._eta = value + @property + def channel(self): + return self._channel or self.job_config.channel + + @channel.setter + def channel(self, value): + self._channel = value + def set_pending(self, result=None, reset_retry=True): self.state = PENDING self.date_enqueued = None diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index a80eb2f9af..b360bdc502 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -37,27 +37,22 @@ class QueueJob(models.Model): "date_created", "model_name", "method_name", + "func_string", + "channel_method_name", + "job_function_id", "records", "args", "kwargs", ) uuid = fields.Char(string="UUID", readonly=True, index=True, required=True) - user_id = fields.Many2one( - comodel_name="res.users", - string="User ID", - compute="_compute_user_id", - inverse="_inverse_user_id", - store=True, - ) + user_id = fields.Many2one(comodel_name="res.users", string="User ID") company_id = fields.Many2one( comodel_name="res.company", string="Company", index=True ) name = fields.Char(string="Description", readonly=True) - model_name = fields.Char( - string="Model", compute="_compute_model_name", store=True, readonly=True - ) + model_name = fields.Char(string="Model", readonly=True) method_name = fields.Char(readonly=True) # record_ids field is only for backward compatibility (e.g. used in related # actions), can be removed (replaced by "records") in 14.0 @@ -69,9 +64,7 @@ class QueueJob(models.Model): ) args = JobSerialized(readonly=True, base_type=tuple) kwargs = JobSerialized(readonly=True, base_type=dict) - func_string = fields.Char( - string="Task", compute="_compute_func_string", readonly=True, store=True - ) + func_string = fields.Char(string="Task", readonly=True) state = fields.Selection(STATES, readonly=True, required=True, index=True) priority = fields.Integer() @@ -91,21 +84,13 @@ class QueueJob(models.Model): "max. retries.\n" "Retries are infinite when empty.", ) - channel_method_name = fields.Char( - readonly=True, compute="_compute_job_function", store=True - ) + # FIXME the name of this field is very confusing + channel_method_name = fields.Char(readonly=True) job_function_id = fields.Many2one( - comodel_name="queue.job.function", - compute="_compute_job_function", - string="Job Function", - readonly=True, - store=True, + comodel_name="queue.job.function", string="Job Function", readonly=True, ) - override_channel = fields.Char() - channel = fields.Char( - compute="_compute_channel", inverse="_inverse_channel", store=True, index=True - ) + channel = fields.Char(index=True) identity_key = fields.Char(readonly=True) worker_pid = fields.Integer(readonly=True) @@ -122,65 +107,18 @@ def init(self): "'enqueued') AND identity_key IS NOT NULL;" ) - @api.depends("records") - def _compute_user_id(self): - for record in self: - record.user_id = record.records.env.uid - - def _inverse_user_id(self): - for record in self.with_context(_job_edit_sentinel=self.EDIT_SENTINEL): - record.records = record.records.with_user(record.user_id.id) - - @api.depends("records") - def _compute_model_name(self): - for record in self: - record.model_name = record.records._name - @api.depends("records") def _compute_record_ids(self): for record in self: record.record_ids = record.records.ids - def _inverse_channel(self): - for record in self: - record.override_channel = record.channel - - @api.depends("job_function_id.channel_id") - def _compute_channel(self): - for record in self: - channel = ( - record.override_channel or record.job_function_id.channel or "root" - ) - if record.channel != channel: - record.channel = channel - - @api.depends("model_name", "method_name", "job_function_id.channel_id") - def _compute_job_function(self): - for record in self: - func_model = self.env["queue.job.function"] - channel_method_name = func_model.job_function_name( - record.model_name, record.method_name - ) - function = func_model.search([("name", "=", channel_method_name)], limit=1) - record.channel_method_name = channel_method_name - record.job_function_id = function - - @api.depends("model_name", "method_name", "records", "args", "kwargs") - def _compute_func_string(self): - for record in self: - model = repr(record.records) - args = [repr(arg) for arg in record.args] - kwargs = ["{}={!r}".format(key, val) for key, val in record.kwargs.items()] - all_args = ", ".join(args + kwargs) - record.func_string = "{}.{}({})".format(model, record.method_name, all_args) - @api.model_create_multi def create(self, vals_list): if self.env.context.get("_job_edit_sentinel") is not self.EDIT_SENTINEL: # Prevent to create a queue.job record "raw" from RPC. # ``with_delay()`` must be used. raise exceptions.AccessError( - _("Queue jobs must created by calling 'with_delay()'.") + _("Queue jobs must be created by calling 'with_delay()'.") ) return super().create(vals_list) @@ -196,10 +134,25 @@ def write(self, vals): ) ) + different_user_jobs = self.browse() + if vals.get("user_id"): + different_user_jobs = self.filtered( + lambda records: records.env.user.id != vals["user_id"] + ) + if vals.get("state") == "failed": self._message_post_on_failure() - return super().write(vals) + result = super().write(vals) + + for record in different_user_jobs: + # the user is stored in the env of the record, but we still want to + # have a stored user_id field to be able to search/groupby, so + # synchronize the env of records with user_id + super(QueueJob, record).write( + {"records": record.records.with_user(vals["user_id"])} + ) + return result def open_related_action(self): """Open the related action associated to the job""" diff --git a/queue_job/models/queue_job_function.py b/queue_job/models/queue_job_function.py index db9eea3c94..4f351659bd 100644 --- a/queue_job/models/queue_job_function.py +++ b/queue_job/models/queue_job_function.py @@ -27,7 +27,8 @@ class QueueJobFunction(models.Model): "retry_pattern " "related_action_enable " "related_action_func_name " - "related_action_kwargs ", + "related_action_kwargs " + "job_function_id ", ) def _default_channel(self): @@ -147,6 +148,7 @@ def job_default_config(self): related_action_enable=True, related_action_func_name=None, related_action_kwargs={}, + job_function_id=None, ) def _parse_retry_pattern(self): @@ -179,6 +181,7 @@ def job_config(self, name): related_action_enable=config.related_action.get("enable", True), related_action_func_name=config.related_action.get("func_name"), related_action_kwargs=config.related_action.get("kwargs", {}), + job_function_id=config.id, ) def _retry_pattern_format_error_message(self): diff --git a/queue_job/tests/test_model_job_function.py b/queue_job/tests/test_model_job_function.py index 965e26d8f2..84676fdb65 100644 --- a/queue_job/tests/test_model_job_function.py +++ b/queue_job/tests/test_model_job_function.py @@ -31,7 +31,7 @@ def test_function_job_config(self): channel = self.env["queue.job.channel"].create( {"name": "foo", "parent_id": self.env.ref("queue_job.channel_root").id} ) - self.env["queue.job.function"].create( + job_function = self.env["queue.job.function"].create( { "model_id": self.env.ref("base.model_res_users").id, "method": "read", @@ -52,5 +52,6 @@ def test_function_job_config(self): related_action_enable=True, related_action_func_name="related_action_foo", related_action_kwargs={"b": 1}, + job_function_id=job_function.id, ), ) From e1f8262b05dd922567be5dafaab2a3fd53584235 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 1 Feb 2021 10:32:56 +0100 Subject: [PATCH 04/88] Remove initial create notification and follower Everytime a job is created, a mail.message "Queue Job created" is created. This is useless, as we already have the creation date and user, and nobody will ever want to receive a notification for a created job anyway. Removing the on creation auto-subscription of the user that created the job makes sense as well since we automatically subscribe the queue job managers for failures. Add the owner of the jobs to the followers on failures only as well. It allows to remove a lot of insertions of records (and of deletions when autovacuuming jobs). --- queue_job/models/queue_job.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index b360bdc502..6b729c68e8 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -120,7 +120,10 @@ def create(self, vals_list): raise exceptions.AccessError( _("Queue jobs must be created by calling 'with_delay()'.") ) - return super().create(vals_list) + return super( + QueueJob, + self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True), + ).create(vals_list) def write(self, vals): if self.env.context.get("_job_edit_sentinel") is not self.EDIT_SENTINEL: @@ -192,9 +195,10 @@ def _message_post_on_failure(self): # subscribe the users now to avoid to subscribe them # at every job creation domain = self._subscribe_users_domain() - users = self.env["res.users"].search(domain) - self.message_subscribe(partner_ids=users.mapped("partner_id").ids) + base_users = self.env["res.users"].search(domain) for record in self: + users = base_users | record.user_id + record.message_subscribe(partner_ids=users.mapped("partner_id").ids) msg = record._message_failed_job() if msg: record.message_post(body=msg, subtype_xmlid="queue_job.mt_job_failed") From e026133c8a3031a4912fe05821fdd68f1a5fa42f Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 10 Feb 2021 09:42:06 +0100 Subject: [PATCH 05/88] Add model in search view / group by --- queue_job/views/queue_job_views.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index a68c03d34f..2bb4d58771 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -126,6 +126,7 @@ + + From 271d83e727fdcf29d44ab3a7f72e78ecc4545e96 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Feb 2021 16:43:43 +0100 Subject: [PATCH 06/88] queue_job: add exec time to view some stats --- queue_job/job.py | 9 +++++ .../migrations/13.0.3.7.0/pre-migration.py | 35 +++++++++++++++++++ queue_job/models/queue_job.py | 5 +++ queue_job/views/queue_job_views.xml | 28 ++++++++++++++- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 queue_job/migrations/13.0.3.7.0/pre-migration.py diff --git a/queue_job/job.py b/queue_job/job.py index 8b646a5781..fed7f690dd 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -527,6 +527,7 @@ def store(self): "date_enqueued": False, "date_started": False, "date_done": False, + "exec_time": False, "eta": False, "identity_key": False, "worker_pid": self.worker_pid, @@ -538,6 +539,8 @@ def store(self): vals["date_started"] = self.date_started if self.date_done: vals["date_done"] = self.date_done + if self.exec_time: + vals["exec_time"] = self.exec_time if self.eta: vals["eta"] = self.eta if self.identity_key: @@ -659,6 +662,12 @@ def channel(self): def channel(self, value): self._channel = value + @property + def exec_time(self): + if self.date_done and self.date_started: + return (self.date_done - self.date_started).total_seconds() + return None + def set_pending(self, result=None, reset_retry=True): self.state = PENDING self.date_enqueued = None diff --git a/queue_job/migrations/13.0.3.7.0/pre-migration.py b/queue_job/migrations/13.0.3.7.0/pre-migration.py new file mode 100644 index 0000000000..c14d6800ad --- /dev/null +++ b/queue_job/migrations/13.0.3.7.0/pre-migration.py @@ -0,0 +1,35 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) + +import logging + +from odoo.tools.sql import column_exists + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not column_exists(cr, "queue_job", "exec_time"): + # Disable trigger otherwise the update takes ages. + cr.execute( + """ + ALTER TABLE queue_job DISABLE TRIGGER queue_job_notify; + """ + ) + cr.execute( + """ + ALTER TABLE queue_job ADD COLUMN exec_time double precision DEFAULT 0; + """ + ) + cr.execute( + """ + UPDATE + queue_job + SET + exec_time = EXTRACT(EPOCH FROM (date_done - date_started)); + """ + ) + cr.execute( + """ + ALTER TABLE queue_job ENABLE TRIGGER queue_job_notify; + """ + ) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 6b729c68e8..7a1992972a 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -75,6 +75,11 @@ class QueueJob(models.Model): date_started = fields.Datetime(string="Start Date", readonly=True) date_enqueued = fields.Datetime(string="Enqueue Time", readonly=True) date_done = fields.Datetime(readonly=True) + exec_time = fields.Float( + string="Execution Time (avg)", + group_operator="avg", + help="Time required to execute this job in seconds. Average when grouped.", + ) eta = fields.Datetime(string="Execute only after") retry = fields.Integer(string="Current try") diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 2bb4d58771..59d2fa01df 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -57,6 +57,8 @@ + + @@ -109,6 +111,7 @@ + @@ -116,6 +119,29 @@ + + queue.job.pivot + queue.job + + + + + + + + + + + queue.job.graph + queue.job + + + + + + + + queue.job.search queue.job @@ -182,7 +208,7 @@ Jobs queue.job - tree,form + tree,form,pivot,graph {'search_default_pending': 1, 'search_default_enqueued': 1, 'search_default_started': 1, From fad22b1c1a476c88fdf46b965e8f6fdc9f5541e4 Mon Sep 17 00:00:00 2001 From: Wolfgang Pichler Date: Thu, 19 Nov 2020 08:16:47 +0100 Subject: [PATCH 07/88] Fix missing rollback on retried jobs When RetryableJobError was raised, any change done by the job was not rollbacked. Using `raise` would throw the exception up to the core and rollback, but we would have a stack trace in the logs for each try. Calling rollback manually (rollback also clears the env) hide the tracebacks, however, when the last try fails, the full traceback is still shown in the logs. Fixes #261 --- queue_job/controllers/main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index f42af7beaa..2ef6e4144a 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -74,10 +74,10 @@ def retry_postpone(job, message, seconds=None): if err.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY: raise - retry_postpone( - job, tools.ustr(err.pgerror, errors="replace"), seconds=PG_RETRY - ) _logger.debug("%s OperationalError, postponed", job) + raise RetryableJobError( + tools.ustr(err.pgerror, errors="replace"), seconds=PG_RETRY + ) except NothingToDoJob as err: if str(err): @@ -92,6 +92,10 @@ def retry_postpone(job, message, seconds=None): # delay the job later, requeue retry_postpone(job, str(err), seconds=err.seconds) _logger.debug("%s postponed", job) + # Do not trigger the error up because we don't want an exception + # traceback in the logs we should have the traceback when all + # retries are exhausted + env.cr.rollback() except (FailedJobError, Exception): buff = StringIO() From 5befe80da3187b912cc57f1a30ea790968972f1b Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 10 Feb 2021 12:01:51 +0100 Subject: [PATCH 08/88] Fix date_done set when state changes back to pending When Job.date_done has been set, for instance because the job has been performed, if the job is set back to pending (e.g. a RetryableJobError is raised), the date_done is kept. --- queue_job/job.py | 1 + 1 file changed, 1 insertion(+) diff --git a/queue_job/job.py b/queue_job/job.py index fed7f690dd..3f8020ed75 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -672,6 +672,7 @@ def set_pending(self, result=None, reset_retry=True): self.state = PENDING self.date_enqueued = None self.date_started = None + self.date_done = None self.worker_pid = None if reset_retry: self.retry = 0 From a94ef2426888b986e67f3e2cdcdf114e7057e7d3 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 08:39:28 +0200 Subject: [PATCH 09/88] queue_job: close buffer when done --- queue_job/controllers/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index 2ef6e4144a..d098a894d1 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -106,6 +106,7 @@ def retry_postpone(job, message, seconds=None): job.env = api.Environment(new_cr, SUPERUSER_ID, {}) job.set_failed(exc_info=buff.getvalue()) job.store() + buff.close() raise return "" From f60765a813618665e26065545d7d8f23926efcca Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 09:15:36 +0200 Subject: [PATCH 10/88] queue_job: improve filtering and grouping --- queue_job/views/queue_job_views.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 59d2fa01df..ac62e973fd 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -112,6 +112,8 @@ + + @@ -153,6 +155,10 @@ + + + + + + From 94a0ae1b0478d6ab14ca7abf5d2d3236601ec280 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 10:06:32 +0200 Subject: [PATCH 11/88] queue_job: add hook to customize stored values --- queue_job/job.py | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/queue_job/job.py b/queue_job/job.py index 3f8020ed75..8a7b908481 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -516,6 +516,22 @@ def perform(self): def store(self): """Store the Job""" + job_model = self.env["queue.job"] + # The sentinel is used to prevent edition sensitive fields (such as + # method_name) from RPC methods. + edit_sentinel = job_model.EDIT_SENTINEL + + db_record = self.db_record() + if db_record: + db_record.with_context(_job_edit_sentinel=edit_sentinel).write( + self._store_values() + ) + else: + job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create( + self._store_values(create=True) + ) + + def _store_values(self, create=False): vals = { "state": self.state, "priority": self.priority, @@ -546,15 +562,7 @@ def store(self): if self.identity_key: vals["identity_key"] = self.identity_key - job_model = self.env["queue.job"] - # The sentinel is used to prevent edition sensitive fields (such as - # method_name) from RPC methods. - edit_sentinel = job_model.EDIT_SENTINEL - - db_record = self.db_record() - if db_record: - db_record.with_context(_job_edit_sentinel=edit_sentinel).write(vals) - else: + if create: vals.update( { "user_id": self.env.uid, @@ -574,7 +582,24 @@ def store(self): "kwargs": self.kwargs, } ) - job_model.with_context(_job_edit_sentinel=edit_sentinel).sudo().create(vals) + + vals_from_model = self._store_values_from_model() + # Sanitize values: make sure you cannot screw core values + vals_from_model = {k: v for k, v in vals_from_model.items() if k not in vals} + vals.update(vals_from_model) + return vals + + def _store_values_from_model(self): + vals = {} + value_handlers_candidates = ( + "_job_store_values_for_" + self.method_name, + "_job_store_values", + ) + for candidate in value_handlers_candidates: + handler = getattr(self.recordset, candidate, None) + if handler is not None: + vals = handler(self) + return vals @property def func_string(self): From 8529226e1a7aed9bdc9b6544e032b0ed9717dc5a Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 11:34:30 +0200 Subject: [PATCH 12/88] queue_job: migration step to store exception data --- .../migrations/13.0.3.8.0/post-migration.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 queue_job/migrations/13.0.3.8.0/post-migration.py diff --git a/queue_job/migrations/13.0.3.8.0/post-migration.py b/queue_job/migrations/13.0.3.8.0/post-migration.py new file mode 100644 index 0000000000..f6eff72707 --- /dev/null +++ b/queue_job/migrations/13.0.3.8.0/post-migration.py @@ -0,0 +1,47 @@ +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) + +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + _logger.info("Computing exception name for failed jobs") + _compute_jobs_new_values(env) + + +def _compute_jobs_new_values(env): + for job in env["queue.job"].search( + [("state", "=", "failed"), ("exc_info", "!=", False)] + ): + exception_details = _get_exception_details(job) + if exception_details: + job.update(exception_details) + + +def _get_exception_details(job): + for line in reversed(job.exc_info.splitlines()): + if _find_exception(line): + name, msg = line.split(":", 1) + return { + "exc_name": name.strip(), + "exc_message": msg.strip("()', \""), + } + + +def _find_exception(line): + # Just a list of common errors. + # If you want to target others, add your own migration step for your db. + exceptions = ( + "Error:", # catch all well named exceptions + # other live instance errors found + "requests.exceptions.MissingSchema", + "botocore.errorfactory.NoSuchKey", + ) + for exc in exceptions: + if exc in line: + return exc From 10b492c8062dd1d1c9c32b2cc60f49d50e979262 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 31 May 2021 21:34:19 +0200 Subject: [PATCH 13/88] Fix display on exec_time on tree view as seconds The float_time widget shows hours seconds, we only want seconds. The widget had been removed on the form view, but not on the tree view. --- queue_job/views/queue_job_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index ac62e973fd..5032fdd8bb 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -111,7 +111,7 @@ - + From 2e01ad024a016f8d243c455b757df5ac240221a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=A9ng=20=28Tr=E1=BA=A7n=20=C4=90=C3=ACnh=29?= Date: Tue, 23 Nov 2021 18:24:18 +0700 Subject: [PATCH 14/88] [IMP] queue_job: black, isort, prettier --- queue_job/models/queue_job.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 7a1992972a..46d78bfe14 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -92,7 +92,9 @@ class QueueJob(models.Model): # FIXME the name of this field is very confusing channel_method_name = fields.Char(readonly=True) job_function_id = fields.Many2one( - comodel_name="queue.job.function", string="Job Function", readonly=True, + comodel_name="queue.job.function", + string="Job Function", + readonly=True, ) channel = fields.Char(index=True) From 8a94b68c5749c9b35bea7e43f5926437cc98b174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=A9ng=20=28Tr=E1=BA=A7n=20=C4=90=C3=ACnh=29?= Date: Tue, 23 Nov 2021 19:37:47 +0700 Subject: [PATCH 15/88] Forward migration scripts from #309 #328 --- .../migrations/{13.0.3.8.0 => 15.0.1.1.0}/post-migration.py | 0 .../migrations/{13.0.3.7.0 => 15.0.1.1.0}/pre-migration.py | 4 ---- 2 files changed, 4 deletions(-) rename queue_job/migrations/{13.0.3.8.0 => 15.0.1.1.0}/post-migration.py (100%) rename queue_job/migrations/{13.0.3.7.0 => 15.0.1.1.0}/pre-migration.py (93%) diff --git a/queue_job/migrations/13.0.3.8.0/post-migration.py b/queue_job/migrations/15.0.1.1.0/post-migration.py similarity index 100% rename from queue_job/migrations/13.0.3.8.0/post-migration.py rename to queue_job/migrations/15.0.1.1.0/post-migration.py diff --git a/queue_job/migrations/13.0.3.7.0/pre-migration.py b/queue_job/migrations/15.0.1.1.0/pre-migration.py similarity index 93% rename from queue_job/migrations/13.0.3.7.0/pre-migration.py rename to queue_job/migrations/15.0.1.1.0/pre-migration.py index c14d6800ad..7b15de8e58 100644 --- a/queue_job/migrations/13.0.3.7.0/pre-migration.py +++ b/queue_job/migrations/15.0.1.1.0/pre-migration.py @@ -1,11 +1,7 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) -import logging - from odoo.tools.sql import column_exists -_logger = logging.getLogger(__name__) - def migrate(cr, version): if not column_exists(cr, "queue_job", "exec_time"): From cfdcdeaebdf5f299b4a6d467a8b8e30da54982e0 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 09:12:07 +0200 Subject: [PATCH 16/88] queue_job: store exception name and message --- queue_job/controllers/main.py | 24 +++++++++++++++++++----- queue_job/job.py | 21 ++++++++++++++++++--- queue_job/models/queue_job.py | 2 ++ queue_job/views/queue_job_views.xml | 21 +++++++++++++-------- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index d098a894d1..a35cc052cd 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -77,7 +77,7 @@ def retry_postpone(job, message, seconds=None): _logger.debug("%s OperationalError, postponed", job) raise RetryableJobError( tools.ustr(err.pgerror, errors="replace"), seconds=PG_RETRY - ) + ) from err except NothingToDoJob as err: if str(err): @@ -97,20 +97,34 @@ def retry_postpone(job, message, seconds=None): # retries are exhausted env.cr.rollback() - except (FailedJobError, Exception): + except (FailedJobError, Exception) as orig_exception: buff = StringIO() traceback.print_exc(file=buff) - _logger.error(buff.getvalue()) + traceback_txt = buff.getvalue() + _logger.error(traceback_txt) job.env.clear() with registry(job.env.cr.dbname).cursor() as new_cr: - job.env = api.Environment(new_cr, SUPERUSER_ID, {}) - job.set_failed(exc_info=buff.getvalue()) + job.env = job.env(cr=new_cr) + vals = self._get_failure_values(job, traceback_txt, orig_exception) + job.set_failed(**vals) job.store() buff.close() raise return "" + def _get_failure_values(self, job, traceback_txt, orig_exception): + """Collect relevant data from exception.""" + exception_name = orig_exception.__class__.__name__ + if hasattr(orig_exception, "__module__"): + exception_name = orig_exception.__module__ + "." + exception_name + exc_message = getattr(orig_exception, "name", str(orig_exception)) + return { + "exc_info": traceback_txt, + "exc_name": exception_name, + "exc_message": exc_message, + } + @http.route("/queue_job/create_test_job", type="http", auth="user") def create_test_job( self, priority=None, max_retries=None, channel=None, description="Test job" diff --git a/queue_job/job.py b/queue_job/job.py index 8a7b908481..0f77e066a0 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -214,6 +214,14 @@ class Job(object): A description of the result (for humans). + .. attribute:: exc_name + + Exception error name when the job failed. + + .. attribute:: exc_message + + Exception error message when the job failed. + .. attribute:: exc_info Exception information (traceback) when the job failed. @@ -476,6 +484,8 @@ def __init__( self.date_done = None self.result = None + self.exc_name = None + self.exc_message = None self.exc_info = None if "company_id" in env.context: @@ -537,6 +547,8 @@ def _store_values(self, create=False): "priority": self.priority, "retry": self.retry, "max_retries": self.max_retries, + "exc_name": self.exc_name, + "exc_message": self.exc_message, "exc_info": self.exc_info, "company_id": self.company_id, "result": str(self.result) if self.result else False, @@ -717,15 +729,17 @@ def set_started(self): def set_done(self, result=None): self.state = DONE + self.exc_name = None self.exc_info = None self.date_done = datetime.now() if result is not None: self.result = result - def set_failed(self, exc_info=None): + def set_failed(self, **kw): self.state = FAILED - if exc_info is not None: - self.exc_info = exc_info + for k, v in kw.items(): + if v is not None: + setattr(self, k, v) def __repr__(self): return "" % (self.uuid, self.priority) @@ -756,6 +770,7 @@ def postpone(self, result=None, seconds=None): """ eta_seconds = self._get_retry_seconds(seconds) self.eta = timedelta(seconds=eta_seconds) + self.exc_name = None self.exc_info = None if result is not None: self.result = result diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 46d78bfe14..2d8ef74538 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -68,6 +68,8 @@ class QueueJob(models.Model): state = fields.Selection(STATES, readonly=True, required=True, index=True) priority = fields.Integer() + exc_name = fields.Char(string="Exception", readonly=True) + exc_message = fields.Char(string="Exception Message", readonly=True) exc_info = fields.Text(string="Exception Info", readonly=True) result = fields.Text(readonly=True) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 5032fdd8bb..cb7aeac28b 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -71,6 +71,18 @@ > If the max. retries is 0, the number of retries is infinite. + +
+
+ +
- - -
@@ -108,8 +113,8 @@ - + From 5cc31ab8d833bac0a2fedff4900b7e60f0911214 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Tue, 22 Jun 2021 15:11:13 +0200 Subject: [PATCH 17/88] [FIX] queue_job: Migrations raising errors with OpenUpgrade --- queue_job/migrations/15.0.1.1.0/pre-migration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/queue_job/migrations/15.0.1.1.0/pre-migration.py b/queue_job/migrations/15.0.1.1.0/pre-migration.py index 7b15de8e58..8ae6cb3a5f 100644 --- a/queue_job/migrations/15.0.1.1.0/pre-migration.py +++ b/queue_job/migrations/15.0.1.1.0/pre-migration.py @@ -1,10 +1,12 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) -from odoo.tools.sql import column_exists +from odoo.tools.sql import column_exists, table_exists def migrate(cr, version): - if not column_exists(cr, "queue_job", "exec_time"): + if table_exists(cr, "queue_job") and not column_exists( + cr, "queue_job", "exec_time" + ): # Disable trigger otherwise the update takes ages. cr.execute( """ From 8299ab074b68244426466bed70443872ff890a74 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 14 May 2021 15:37:20 +0200 Subject: [PATCH 18/88] [IMP] queue_job: Add cancelled state to queue.job --- queue_job/__manifest__.py | 1 + queue_job/job.py | 16 +++++++++ queue_job/models/queue_job.py | 12 ++++++- queue_job/security/ir.model.access.csv | 1 + queue_job/views/queue_job_views.xml | 10 +++++- queue_job/wizards/__init__.py | 1 + queue_job/wizards/queue_jobs_to_cancelled.py | 17 ++++++++++ .../wizards/queue_jobs_to_cancelled_views.xml | 34 +++++++++++++++++++ 8 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 queue_job/wizards/queue_jobs_to_cancelled.py create mode 100644 queue_job/wizards/queue_jobs_to_cancelled_views.xml diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 819ef764fc..869918e0ac 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -17,6 +17,7 @@ "views/queue_job_channel_views.xml", "views/queue_job_function_views.xml", "wizards/queue_jobs_to_done_views.xml", + "wizards/queue_jobs_to_cancelled_views.xml", "wizards/queue_requeue_job_views.xml", "views/queue_job_menus.xml", "data/queue_data.xml", diff --git a/queue_job/job.py b/queue_job/job.py index 0f77e066a0..2ac4ded012 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -16,6 +16,7 @@ PENDING = "pending" ENQUEUED = "enqueued" +CANCELLED = "cancelled" DONE = "done" STARTED = "started" FAILED = "failed" @@ -25,6 +26,7 @@ (ENQUEUED, "Enqueued"), (STARTED, "Started"), (DONE, "Done"), + (CANCELLED, "Cancelled"), (FAILED, "Failed"), ] @@ -301,6 +303,9 @@ def _load_from_db_record(cls, job_db_record): if stored.date_done: job_.date_done = stored.date_done + if stored.date_cancelled: + job_.date_cancelled = stored.date_cancelled + job_.state = stored.state job_.result = stored.result if stored.result else None job_.exc_info = stored.exc_info if stored.exc_info else None @@ -482,6 +487,7 @@ def __init__( self.date_enqueued = None self.date_started = None self.date_done = None + self.date_cancelled = None self.result = None self.exc_name = None @@ -556,6 +562,7 @@ def _store_values(self, create=False): "date_started": False, "date_done": False, "exec_time": False, + "date_cancelled": False, "eta": False, "identity_key": False, "worker_pid": self.worker_pid, @@ -569,6 +576,8 @@ def _store_values(self, create=False): vals["date_done"] = self.date_done if self.exec_time: vals["exec_time"] = self.exec_time + if self.date_cancelled: + vals["date_cancelled"] = self.date_cancelled if self.eta: vals["eta"] = self.eta if self.identity_key: @@ -711,6 +720,7 @@ def set_pending(self, result=None, reset_retry=True): self.date_started = None self.date_done = None self.worker_pid = None + self.date_cancelled = None if reset_retry: self.retry = 0 if result is not None: @@ -735,6 +745,12 @@ def set_done(self, result=None): if result is not None: self.result = result + def set_cancelled(self, result=None): + self.state = CANCELLED + self.date_cancelled = datetime.now() + if result is not None: + self.result = result + def set_failed(self, **kw): self.state = FAILED for k, v in kw.items(): diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 2d8ef74538..b6f8b52a6e 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -8,7 +8,7 @@ from odoo.osv import expression from ..fields import JobSerialized -from ..job import DONE, PENDING, STATES, Job +from ..job import CANCELLED, DONE, PENDING, STATES, Job _logger = logging.getLogger(__name__) @@ -82,6 +82,7 @@ class QueueJob(models.Model): group_operator="avg", help="Time required to execute this job in seconds. Average when grouped.", ) + date_cancelled = fields.Datetime(readonly=True) eta = fields.Datetime(string="Execute only after") retry = fields.Integer(string="Current try") @@ -187,6 +188,8 @@ def _change_job_state(self, state, result=None): job_.set_done(result=result) elif state == PENDING: job_.set_pending(result=result) + elif state == CANCELLED: + job_.set_cancelled(result=result) else: raise ValueError("State not supported: %s" % state) job_.store() @@ -196,6 +199,11 @@ def button_done(self): self._change_job_state(DONE, result=result) return True + def button_cancelled(self): + result = _("Cancelled by %s") % self.env.user.name + self._change_job_state(CANCELLED, result=result) + return True + def requeue(self): self._change_job_state(PENDING) return True @@ -255,7 +263,9 @@ def autovacuum(self): while True: jobs = self.search( [ + "|", ("date_done", "<=", deadline), + ("date_cancelled", "<=", deadline), ("channel", "=", channel.complete_name), ], limit=1000, diff --git a/queue_job/security/ir.model.access.csv b/queue_job/security/ir.model.access.csv index 9242305158..634daf8ede 100644 --- a/queue_job/security/ir.model.access.csv +++ b/queue_job/security/ir.model.access.csv @@ -4,3 +4,4 @@ access_queue_job_function_manager,queue job functions manager,queue_job.model_qu access_queue_job_channel_manager,queue job channel manager,queue_job.model_queue_job_channel,queue_job.group_queue_job_manager,1,1,1,1 access_queue_requeue_job,queue requeue job manager,queue_job.model_queue_requeue_job,queue_job.group_queue_job_manager,1,1,1,1 access_queue_jobs_to_done,queue jobs to done manager,queue_job.model_queue_jobs_to_done,queue_job.group_queue_job_manager,1,1,1,1 +access_queue_jobs_to_cancelled,queue jobs to cancelled manager,queue_job.model_queue_jobs_to_cancelled,queue_job.group_queue_job_manager,1,1,1,1 diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index cb7aeac28b..9121e1b188 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -23,12 +23,20 @@ type="object" groups="queue_job.group_queue_job_manager" /> +
From 840a5f2559a7761dbcb575672f63120d7bcdabb5 Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 31 Oct 2022 16:36:35 +0000 Subject: [PATCH 25/88] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: queue-15.0/queue-15.0-queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-15-0/queue-15-0-queue_job/ --- queue_job/i18n/de.po | 99 +++++++++++++++++++++++++++++++++++- queue_job/i18n/es.po | 108 +++++++++++++++++++++++++++++++++++++++- queue_job/i18n/zh_CN.po | 99 +++++++++++++++++++++++++++++++++++- 3 files changed, 302 insertions(+), 4 deletions(-) diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index d6799537b3..87e8bb8c8e 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -80,10 +80,38 @@ msgstr "Basis" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled #: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_done msgid "Cancel" msgstr "Abbrechen" +#. module: queue_job +#: model:ir.model,name:queue_job.model_queue_jobs_to_cancelled +msgid "Cancel all selected jobs" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Cancel job" +msgstr "" + +#. module: queue_job +#: model:ir.actions.act_window,name:queue_job.action_set_jobs_cancelled +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled +msgid "Cancel jobs" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled +msgid "Cancelled" +msgstr "" + +#. module: queue_job +#: code:addons/queue_job/models/queue_job.py:0 +#, python-format +msgid "Cancelled by %s" +msgstr "" + #. module: queue_job #: code:addons/queue_job/models/queue_job_channel.py:0 #, python-format @@ -140,6 +168,7 @@ msgstr "Erstellt am" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_uid +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__create_uid #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__create_uid #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__create_uid msgid "Created by" @@ -147,6 +176,7 @@ msgstr "Erstellt von" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__create_date #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__create_date #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__create_date msgid "Created on" @@ -162,6 +192,11 @@ msgstr "Aktueller Versuch" msgid "Current try / max. retries" msgstr "Aktueller Versuch / max. Anzahl der Wiederholung" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__date_cancelled +msgid "Date Cancelled" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__date_done msgid "Date Done" @@ -176,6 +211,7 @@ msgstr "Beschreibung" #: model:ir.model.fields,field_description:queue_job.field_queue_job__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__display_name +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__display_name msgid "Display Name" @@ -198,6 +234,12 @@ msgstr "Zeit der Einreihung" msgid "Enqueued" msgstr "Eingereiht" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_name +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_info msgid "Exception Info" @@ -208,11 +250,31 @@ msgstr "Exception-Info" msgid "Exception Information" msgstr "Exception-Information" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_message +msgid "Exception Message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Exception:" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__eta msgid "Execute only after" msgstr "Erst ausführen nach" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exec_time +msgid "Execution Time (avg)" +msgstr "" + #. module: queue_job #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__failed #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search @@ -259,6 +321,7 @@ msgstr "" #: model:ir.model.fields,field_description:queue_job.field_queue_job__id #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__id #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__id +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__id #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__id #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__id msgid "ID" @@ -360,10 +423,13 @@ msgstr "Job unterbrochen und als Erledigt markiert: Es ist nicht zu tun." #. module: queue_job #: model:ir.actions.act_window,name:queue_job.action_queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__job_ids #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__job_ids #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__job_ids #: model:ir.ui.menu,name:queue_job.menu_queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_graph +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_pivot #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Jobs" msgstr "Jobs" @@ -384,6 +450,7 @@ msgstr "Kwargs" #: model:ir.model.fields,field_description:queue_job.field_queue_job____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_job_function____last_update +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job____last_update msgid "Last Modified on" @@ -391,6 +458,7 @@ msgstr "Zuletzt geändert am" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__write_uid #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__write_uid #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__write_uid msgid "Last Updated by" @@ -398,6 +466,7 @@ msgstr "Zuletzt aktualisiert von" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_date +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__write_date #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__write_date #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__write_date msgid "Last Updated on" @@ -442,6 +511,7 @@ msgstr "Methodenname" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__model_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__model_id +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Model" msgstr "Modell" @@ -514,6 +584,10 @@ msgstr "Das ist die Anzahl von Nachrichten mit Übermittlungsfehler" msgid "Override Channel" msgstr "Kanal überschreiben" +#: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter +msgid "Number of unread messages" +msgstr "Das ist die Anzahl von ungelesenen Nachrichten" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__parent_id msgid "Parent Channel" @@ -561,7 +635,7 @@ msgstr "Job einreihen" #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, python-format -msgid "Queue jobs must created by calling 'with_delay()'." +msgid "Queue jobs must be created by calling 'with_delay()'." msgstr "" #. module: queue_job @@ -603,6 +677,11 @@ msgstr "Zugehöriger Datensatz" msgid "Related Records" msgstr "Zugehörige Datensätze" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_tree +msgid "Remaining days to execute" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__removal_interval msgid "Removal Interval" @@ -740,6 +819,11 @@ msgstr "" "Wenn Letzteres nicht gesetzt ist, werden unendlich viele Versuche " "unternommen." +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled +msgid "The selected jobs will be cancelled." +msgstr "" + #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job msgid "The selected jobs will be requeued." @@ -750,6 +834,16 @@ msgstr "Die ausgewählten Jobs werden erneut eingereiht." msgid "The selected jobs will be set to done." msgstr "Die ausgewählten Jobs werden als Erledigt markiert." +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Time (s)" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,help:queue_job.field_queue_job__exec_time +msgid "Time required to execute this job in seconds. Average when grouped." +msgstr "" + #. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__activity_exception_decoration msgid "Type of the exception activity on record." @@ -806,6 +900,9 @@ msgstr "" #~ msgid "Unread Messages Counter" #~ msgstr "Zähler für ungelesene Nachrichten" +#~ msgid "Override Channel" +#~ msgstr "Kanal überschreiben" + #~ msgid "Website Messages" #~ msgstr "Website Nachrichten" diff --git a/queue_job/i18n/es.po b/queue_job/i18n/es.po index 0031b3dcc8..e568132980 100644 --- a/queue_job/i18n/es.po +++ b/queue_job/i18n/es.po @@ -1,3 +1,4 @@ +msgstr "" # Translation of Odoo Server. # This file contains the translation of the following modules: # * queue_job @@ -80,10 +81,38 @@ msgstr "Base" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled #: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_done msgid "Cancel" msgstr "Cancelar" +#. module: queue_job +#: model:ir.model,name:queue_job.model_queue_jobs_to_cancelled +msgid "Cancel all selected jobs" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Cancel job" +msgstr "" + +#. module: queue_job +#: model:ir.actions.act_window,name:queue_job.action_set_jobs_cancelled +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled +msgid "Cancel jobs" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled +msgid "Cancelled" +msgstr "" + +#. module: queue_job +#: code:addons/queue_job/models/queue_job.py:0 +#, python-format +msgid "Cancelled by %s" +msgstr "" + #. module: queue_job #: code:addons/queue_job/models/queue_job_channel.py:0 #, python-format @@ -140,6 +169,7 @@ msgstr "Fecha de creación" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_uid +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__create_uid #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__create_uid #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__create_uid msgid "Created by" @@ -147,6 +177,7 @@ msgstr "Creado por" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__create_date #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__create_date #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__create_date msgid "Created on" @@ -162,6 +193,11 @@ msgstr "Intento actual" msgid "Current try / max. retries" msgstr "Intento actual / reintentos máx." +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__date_cancelled +msgid "Date Cancelled" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__date_done msgid "Date Done" @@ -176,6 +212,7 @@ msgstr "Descripción" #: model:ir.model.fields,field_description:queue_job.field_queue_job__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__display_name +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__display_name msgid "Display Name" @@ -198,6 +235,12 @@ msgstr "Hora en que se puso en cola" msgid "Enqueued" msgstr "En la cola" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_name +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_info msgid "Exception Info" @@ -208,11 +251,31 @@ msgstr "Información de la excepción" msgid "Exception Information" msgstr "Información de la excepción" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_message +msgid "Exception Message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Exception:" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__eta msgid "Execute only after" msgstr "Ejecutar solo después de" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exec_time +msgid "Execution Time (avg)" +msgstr "" + #. module: queue_job #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__failed #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search @@ -259,6 +322,7 @@ msgstr "Tiene un mensaje" #: model:ir.model.fields,field_description:queue_job.field_queue_job__id #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__id #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__id +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__id #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__id #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__id msgid "ID" @@ -360,10 +424,13 @@ msgstr "Trabajo interrumpido y marcado como hecho: nada que hacer" #. module: queue_job #: model:ir.actions.act_window,name:queue_job.action_queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__job_ids #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__job_ids #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__job_ids #: model:ir.ui.menu,name:queue_job.menu_queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_graph +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_pivot #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Jobs" msgstr "Trabajos" @@ -384,6 +451,7 @@ msgstr "Kwargs" #: model:ir.model.fields,field_description:queue_job.field_queue_job____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_job_function____last_update +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job____last_update msgid "Last Modified on" @@ -391,6 +459,7 @@ msgstr "Última modificación el" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__write_uid #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__write_uid #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__write_uid msgid "Last Updated by" @@ -398,6 +467,7 @@ msgstr "Última actualización por" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_date +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__write_date #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__write_date #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__write_date msgid "Last Updated on" @@ -442,6 +512,7 @@ msgstr "Nombre del método" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__model_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__model_id +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Model" msgstr "Modelo" @@ -514,6 +585,10 @@ msgstr "Número de mensajes con error de envío" msgid "Override Channel" msgstr "Sobreescribir canal" +#: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter +msgid "Number of unread messages" +msgstr "Número de mensajes no leidos" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__parent_id msgid "Parent Channel" @@ -568,8 +643,8 @@ msgstr "Cola de trabajos" #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, python-format -msgid "Queue jobs must created by calling 'with_delay()'." -msgstr "Los trabajos de la cola se deben crear llamando a `with_delay()`" +msgid "Queue jobs must be created by calling 'with_delay()'." +msgstr "" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__record_ids @@ -608,6 +683,11 @@ msgstr "Registro relacionado" msgid "Related Records" msgstr "Registros relacionados" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_tree +msgid "Remaining days to execute" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__removal_interval msgid "Removal Interval" @@ -748,6 +828,11 @@ msgstr "" "El trabajo fallará si alcanza el máx. de re intentos.\n" "Los reintentos son infinitos si se deja vacío." +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled +msgid "The selected jobs will be cancelled." +msgstr "" + #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job msgid "The selected jobs will be requeued." @@ -758,6 +843,16 @@ msgstr "Los trabajos seleccionados volverán a ponerse en la cola." msgid "The selected jobs will be set to done." msgstr "Los trabajos seleccionados se marcarán como hechos." +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Time (s)" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,help:queue_job.field_queue_job__exec_time +msgid "Time required to execute this job in seconds. Average when grouped." +msgstr "" + #. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__activity_exception_decoration msgid "Type of the exception activity on record." @@ -809,6 +904,7 @@ msgstr "Asistente para volver a poner en cola una selección de trabajos" msgid "Worker Pid" msgstr "Pid del trabajador" + #~ msgid "Number of unread messages" #~ msgstr "Número de mensajes no leidos" @@ -817,3 +913,11 @@ msgstr "Pid del trabajador" #~ msgid "Unread Messages Counter" #~ msgstr "Nº de mensajes sin leer" + +#~ msgid "Override Channel" +#~ msgstr "Sobreescribir canal" + +#, python-format +#~ msgid "Queue jobs must created by calling 'with_delay()'." +#~ msgstr "Los trabajos de la cola se deben crear llamando a `with_delay()`" + diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po index 4f2ad13124..d164f2fa6f 100644 --- a/queue_job/i18n/zh_CN.po +++ b/queue_job/i18n/zh_CN.po @@ -80,10 +80,38 @@ msgstr "基础" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled #: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_done msgid "Cancel" msgstr "取消" +#. module: queue_job +#: model:ir.model,name:queue_job.model_queue_jobs_to_cancelled +msgid "Cancel all selected jobs" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Cancel job" +msgstr "" + +#. module: queue_job +#: model:ir.actions.act_window,name:queue_job.action_set_jobs_cancelled +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled +msgid "Cancel jobs" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__cancelled +msgid "Cancelled" +msgstr "" + +#. module: queue_job +#: code:addons/queue_job/models/queue_job.py:0 +#, python-format +msgid "Cancelled by %s" +msgstr "" + #. module: queue_job #: code:addons/queue_job/models/queue_job_channel.py:0 #, python-format @@ -140,6 +168,7 @@ msgstr "创建日期" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_uid +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__create_uid #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__create_uid #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__create_uid msgid "Created by" @@ -147,6 +176,7 @@ msgstr "创建者" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__create_date +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__create_date #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__create_date #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__create_date msgid "Created on" @@ -162,6 +192,11 @@ msgstr "当前尝试" msgid "Current try / max. retries" msgstr "当前尝试/最大重试次数" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__date_cancelled +msgid "Date Cancelled" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__date_done msgid "Date Done" @@ -176,6 +211,7 @@ msgstr "说明" #: model:ir.model.fields,field_description:queue_job.field_queue_job__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__display_name +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__display_name #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__display_name msgid "Display Name" @@ -198,6 +234,12 @@ msgstr "排队时间" msgid "Enqueued" msgstr "排队" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_name +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_info msgid "Exception Info" @@ -208,11 +250,31 @@ msgstr "异常信息" msgid "Exception Information" msgstr "异常信息" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exc_message +msgid "Exception Message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Exception message" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Exception:" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__eta msgid "Execute only after" msgstr "仅在此之后执行" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__exec_time +msgid "Execution Time (avg)" +msgstr "" + #. module: queue_job #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__failed #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search @@ -259,6 +321,7 @@ msgstr "" #: model:ir.model.fields,field_description:queue_job.field_queue_job__id #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__id #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__id +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__id #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__id #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__id msgid "ID" @@ -358,10 +421,13 @@ msgstr "作业中断并设置为已完成:无需执行任何操作。" #. module: queue_job #: model:ir.actions.act_window,name:queue_job.action_queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__job_ids #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__job_ids #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__job_ids #: model:ir.ui.menu,name:queue_job.menu_queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_graph +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_pivot #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Jobs" msgstr "作业" @@ -382,6 +448,7 @@ msgstr "关键字参数" #: model:ir.model.fields,field_description:queue_job.field_queue_job____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_job_function____last_update +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done____last_update #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job____last_update msgid "Last Modified on" @@ -389,6 +456,7 @@ msgstr "最后修改日" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_uid +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__write_uid #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__write_uid #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__write_uid msgid "Last Updated by" @@ -396,6 +464,7 @@ msgstr "最后更新者" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__write_date +#: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_cancelled__write_date #: model:ir.model.fields,field_description:queue_job.field_queue_jobs_to_done__write_date #: model:ir.model.fields,field_description:queue_job.field_queue_requeue_job__write_date msgid "Last Updated on" @@ -441,6 +510,7 @@ msgstr "方法名称" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__model_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__model_id +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Model" msgstr "模型" @@ -513,6 +583,10 @@ msgstr "递送错误消息数量" msgid "Override Channel" msgstr "覆盖频道" +#: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter +msgid "Number of unread messages" +msgstr "未读消息数量" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__parent_id msgid "Parent Channel" @@ -560,7 +634,7 @@ msgstr "队列作业" #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, python-format -msgid "Queue jobs must created by calling 'with_delay()'." +msgid "Queue jobs must be created by calling 'with_delay()'." msgstr "" #. module: queue_job @@ -602,6 +676,11 @@ msgstr "相关记录" msgid "Related Records" msgstr "相关记录" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_tree +msgid "Remaining days to execute" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__removal_interval msgid "Removal Interval" @@ -736,6 +815,11 @@ msgstr "" "如果尝试次数达到最大重试次数,作业将失败。\n" "空的时候重试是无限的。" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled +msgid "The selected jobs will be cancelled." +msgstr "" + #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job msgid "The selected jobs will be requeued." @@ -746,6 +830,16 @@ msgstr "所选作业将重新排队。" msgid "The selected jobs will be set to done." msgstr "所选作业将设置为完成。" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Time (s)" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,help:queue_job.field_queue_job__exec_time +msgid "Time required to execute this job in seconds. Average when grouped." +msgstr "" + #. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__activity_exception_decoration msgid "Type of the exception activity on record." @@ -802,6 +896,9 @@ msgstr "" #~ msgid "Unread Messages Counter" #~ msgstr "未读消息计数器" +#~ msgid "Override Channel" +#~ msgstr "覆盖频道" + #~ msgid "Website Messages" #~ msgstr "网站消息" From 42b464502198e3e8559c436974a7fc565951293f Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 2 Jul 2019 21:41:24 +0200 Subject: [PATCH 26/88] Store dependencies --- queue_job/job.py | 76 +++++++++++++++++++++++++++++++++++ queue_job/models/queue_job.py | 4 +- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/queue_job/job.py b/queue_job/job.py index 2ac4ded012..e56f960cf8 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -7,8 +7,11 @@ import os import sys import uuid +import weakref + from datetime import datetime, timedelta from random import randint +from functools import total_ordering import odoo @@ -147,6 +150,7 @@ def identity_example(job_): return hasher.hexdigest() +@total_ordering class Job(object): """A Job is a task to execute. It is the in-memory representation of a job. @@ -315,6 +319,13 @@ def _load_from_db_record(cls, job_db_record): job_.company_id = stored.company_id.id job_.identity_key = stored.identity_key job_.worker_pid = stored.worker_pid + + job_.__depends_on_uuids.update( + stored.dependencies.get('depends_on', []) + ) + job_.__reverse_depends_on_uuids.update( + stored.dependencies.get('reverse_depends_on', []) + ) return job_ def job_record_with_same_identity_key(self): @@ -468,6 +479,11 @@ def __init__( self.args = args self.kwargs = kwargs + self.__depends_on_uuids = set() + self.__reverse_depends_on_uuids = set() + self._depends_on = set() + self._reverse_depends_on = weakref.WeakSet() + self.priority = priority if self.priority is None: self.priority = DEFAULT_PRIORITY @@ -504,6 +520,20 @@ def __init__( self.channel = channel self.worker_pid = None + def add_depends(self, jobs): + self.__depends_on_uuids |= {j.uuid for j in jobs} + self._depends_on.update(jobs) + for parent in jobs: + parent.__reverse_depends_on_uuids.add(self.uuid) + parent._reverse_depends_on.add(self) + + def add_reverse_depends(self, jobs): + self.__reverse_depends_on_uuids |= {j.uuid for j in jobs} + self._reverse_depends_on.update(jobs) + for child in jobs: + child.__depends_on_uuids.add(self.uuid) + child._depends_on.add(self) + def perform(self): """Execute the job. @@ -583,6 +613,16 @@ def _store_values(self, create=False): if self.identity_key: vals["identity_key"] = self.identity_key + dependencies = { + 'depends_on': [ + parent.uuid for parent in self.depends_on + ], + 'reverse_depends_on': [ + children.uuid for children in self.reverse_depends_on + ], + } + vals['dependencies'] = dependencies + if create: vals.update( { @@ -630,6 +670,22 @@ def func_string(self): all_args = ", ".join(args + kwargs) return "{}.{}({})".format(model, self.method_name, all_args) + def __eq__(self, other): + return self.uuid == other.uuid + + def __hash__(self): + return self.uuid.__hash__() + + def sorting_key(self): + return self.eta, self.priority, self.date_created, self.seq + + def __lt__(self, other): + if self.eta and not other.eta: + return True + elif not self.eta and other.eta: + return False + return self.sorting_key() < other.sorting_key() + def db_record(self): return self.db_record_from_uuid(self.env, self.uuid) @@ -661,6 +717,26 @@ def identity_key(self, value): self._identity_key = None self._identity_key_func = value + @property + def depends_on(self): + if not self._depends_on: + # TODO batch load instead of loop + self._depends_on = { + Job.load(self.env, parent_uuid) for parent_uuid + in self.__depends_on_uuids + } + return self._depends_on + + @property + def reverse_depends_on(self): + if not self._reverse_depends_on: + # TODO batch load instead of loop + self._reverse_depends_on = { + Job.load(self.env, child_uuid) for child_uuid + in self.__reverse_depends_on_uuids + } + return set(self._reverse_depends_on) + @property def description(self): if self._description: diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index b6f8b52a6e..693aea0a78 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -4,7 +4,8 @@ import logging from datetime import datetime, timedelta -from odoo import _, api, exceptions, fields, models +from odoo import _, api, exceptions, fields, models, tools +from odoo.addons.base_sparse_field.models.fields import Serialized from odoo.osv import expression from ..fields import JobSerialized @@ -62,6 +63,7 @@ class QueueJob(models.Model): readonly=True, base_type=models.BaseModel, ) + dependencies = Serialized(readonly=True) args = JobSerialized(readonly=True, base_type=tuple) kwargs = JobSerialized(readonly=True, base_type=dict) func_string = fields.Char(string="Task", readonly=True) From 4b0543804f6eed333912b3c84ee9c9a5c2e0f75e Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 2 Jul 2019 22:39:26 +0200 Subject: [PATCH 27/88] Add wait dependencies state --- queue_job/job.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/queue_job/job.py b/queue_job/job.py index e56f960cf8..3786466950 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -17,6 +17,7 @@ from .exception import FailedJobError, NoSuchJobError, RetryableJobError +WAIT_DEPENDENCIES = 'wait_dependencies' PENDING = "pending" ENQUEUED = "enqueued" CANCELLED = "cancelled" @@ -25,6 +26,7 @@ FAILED = "failed" STATES = [ + (WAIT_DEPENDENCIES, 'Wait Dependencies'), (PENDING, "Pending"), (ENQUEUED, "Enqueued"), (STARTED, "Started"), @@ -526,6 +528,8 @@ def add_depends(self, jobs): for parent in jobs: parent.__reverse_depends_on_uuids.add(self.uuid) parent._reverse_depends_on.add(self) + if any(j.state != DONE for j in jobs): + self.state = WAIT_DEPENDENCIES def add_reverse_depends(self, jobs): self.__reverse_depends_on_uuids |= {j.uuid for j in jobs} @@ -558,8 +562,20 @@ def perform(self): ) raise new_exc from err raise + return self.result + # TODO call in an isolated transaction with retries (in RunJobController) + def enqueue_waiting(self): + children = self.reverse_depends_on + for child in children: + if child.state != WAIT_DEPENDENCIES: + continue + parents = child.depends_on + if all(parent.state == 'done' for parent in parents): + child.state = PENDING + child.store() + def store(self): """Store the Job""" job_model = self.env["queue.job"] From b027782c617970f73d7e90d3a14671647fe03749 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 3 Jul 2019 21:30:29 +0200 Subject: [PATCH 28/88] Enqueue waiting jobs when parent jobs are done --- queue_job/controllers/main.py | 5 +++++ queue_job/job.py | 6 ++++-- queue_job/models/queue_job.py | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index a35cc052cd..09cccf299a 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -35,6 +35,9 @@ def _try_perform_job(self, env, job): env.cr.commit() _logger.debug("%s done", job) + def _enqueue_dependent_jobs(self, env, job): + job.enqueue_waiting() + @http.route("/queue_job/runjob", type="http", auth="none", save_session=False) def runjob(self, db, job_uuid, **kw): http.request.session.db = db @@ -111,6 +114,8 @@ def retry_postpone(job, message, seconds=None): buff.close() raise + self._enqueue_dependent_jobs(env, job) + return "" def _get_failure_values(self, job, traceback_txt, orig_exception): diff --git a/queue_job/job.py b/queue_job/job.py index 3786466950..2e414dddb5 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -565,7 +565,6 @@ def perform(self): return self.result - # TODO call in an isolated transaction with retries (in RunJobController) def enqueue_waiting(self): children = self.reverse_depends_on for child in children: @@ -807,7 +806,10 @@ def exec_time(self): return None def set_pending(self, result=None, reset_retry=True): - self.state = PENDING + if any(j.state != DONE for j in self.depends_on): + self.state = WAIT_DEPENDENCIES + else: + self.state = PENDING self.date_enqueued = None self.date_started = None self.date_done = None diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 693aea0a78..2937de85a6 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -188,13 +188,16 @@ def _change_job_state(self, state, result=None): job_ = Job.load(record.env, record.uuid) if state == DONE: job_.set_done(result=result) + job_.store() + job_.enqueue_waiting() elif state == PENDING: job_.set_pending(result=result) + job_.store() elif state == CANCELLED: job_.set_cancelled(result=result) + job_.store() else: raise ValueError("State not supported: %s" % state) - job_.store() def button_done(self): result = _("Manually set to done by %s") % self.env.user.name From 64b23719eb7702cf4a382dc38a98f1a080e56c63 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 4 Jul 2019 08:37:07 +0200 Subject: [PATCH 29/88] Optimize and make enqueue of waiting jobs more robust --- queue_job/controllers/main.py | 39 ++++++++++++++++++++++++++++++-- queue_job/job.py | 40 ++++++++++++++++++++++++++------- queue_job/jobrunner/channels.py | 10 ++++++--- queue_job/models/queue_job.py | 5 +++++ 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index 09cccf299a..193c854115 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -2,11 +2,13 @@ # Copyright 2013-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +import random import logging +import time import traceback from io import StringIO -from psycopg2 import OperationalError +from psycopg2 import OperationalError, errorcodes from werkzeug.exceptions import Forbidden from odoo import SUPERUSER_ID, _, api, http, registry, tools @@ -19,6 +21,8 @@ PG_RETRY = 5 # seconds +DEPENDS_MAX_TRIES_ON_CONCURRENCY_FAILURE = 5 + class RunJobController(http.Controller): def _try_perform_job(self, env, job): @@ -36,7 +40,36 @@ def _try_perform_job(self, env, job): _logger.debug("%s done", job) def _enqueue_dependent_jobs(self, env, job): - job.enqueue_waiting() + tries = 0 + # FIXME: timing condition, when 2 "parent" jobs are done + # at the same time neither will see the other as done (I think) + while True: + try: + job.enqueue_waiting() + except OperationalError as err: + # Automatically retry the typical transaction serialization + # errors + if err.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY: + raise + if tries >= DEPENDS_MAX_TRIES_ON_CONCURRENCY_FAILURE: + _logger.info( + "%s, maximum number of tries reached to" + " update dependencies", + errorcodes.lookup(err.pgcode) + ) + raise + wait_time = random.uniform(0.0, 2 ** tries) + tries += 1 + _logger.info( + "%s, retry %d/%d in %.04f sec...", + errorcodes.lookup(err.pgcode), + tries, + DEPENDS_MAX_TRIES_ON_CONCURRENCY_FAILURE, + wait_time + ) + time.sleep(wait_time) + else: + break @http.route("/queue_job/runjob", type="http", auth="none", save_session=False) def runjob(self, db, job_uuid, **kw): @@ -114,7 +147,9 @@ def retry_postpone(job, message, seconds=None): buff.close() raise + _logger.debug('%s enqueue depends started', job) self._enqueue_dependent_jobs(env, job) + _logger.debug('%s enqueue depends done', job) return "" diff --git a/queue_job/job.py b/queue_job/job.py index 2e414dddb5..351d896a37 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -523,6 +523,8 @@ def __init__( self.worker_pid = None def add_depends(self, jobs): + if self in jobs: + raise ValueError('job cannot depend on itself') self.__depends_on_uuids |= {j.uuid for j in jobs} self._depends_on.update(jobs) for parent in jobs: @@ -532,6 +534,8 @@ def add_depends(self, jobs): self.state = WAIT_DEPENDENCIES def add_reverse_depends(self, jobs): + if self in jobs: + raise ValueError('job cannot depend on itself') self.__reverse_depends_on_uuids |= {j.uuid for j in jobs} self._reverse_depends_on.update(jobs) for child in jobs: @@ -566,14 +570,34 @@ def perform(self): return self.result def enqueue_waiting(self): - children = self.reverse_depends_on - for child in children: - if child.state != WAIT_DEPENDENCIES: - continue - parents = child.depends_on - if all(parent.state == 'done' for parent in parents): - child.state = PENDING - child.store() + # TODO replace states by constants + sql = """ + UPDATE queue_job + SET state = 'pending' + FROM ( + SELECT child.id, array_agg(parent.state) as parent_states + FROM queue_job job + JOIN LATERAL + json_array_elements_text( + job.dependencies::json->'reverse_depends_on' + ) child_deps ON true + JOIN queue_job child + ON child.uuid = child_deps + JOIN LATERAL + json_array_elements_text( + child.dependencies::json->'depends_on' + ) parent_deps ON true + JOIN queue_job parent + ON parent.uuid = parent_deps + WHERE job.uuid = %s + GROUP BY child.id + ) jobs + WHERE + queue_job.id = jobs.id + AND 'done' = ALL(jobs.parent_states) + AND state = 'wait_dependencies'; + """ + self.env.cr.execute(sql, (self.uuid,)) def store(self): """Store the Job""" diff --git a/queue_job/jobrunner/channels.py b/queue_job/jobrunner/channels.py index 2d7e0a8be0..9108ffdad6 100644 --- a/queue_job/jobrunner/channels.py +++ b/queue_job/jobrunner/channels.py @@ -7,9 +7,10 @@ from weakref import WeakValueDictionary from ..exception import ChannelNotFound -from ..job import DONE, ENQUEUED, FAILED, PENDING, STARTED - -NOT_DONE = (PENDING, ENQUEUED, STARTED, FAILED) +from ..job import ( + PENDING, ENQUEUED, STARTED, FAILED, DONE, WAIT_DEPENDENCIES +) +NOT_DONE = (WAIT_DEPENDENCIES, PENDING, ENQUEUED, STARTED, FAILED) _logger = logging.getLogger(__name__) @@ -1054,6 +1055,9 @@ def notify( job.channel.set_running(job) elif state == FAILED: job.channel.set_failed(job) + elif state == WAIT_DEPENDENCIES: + # wait until all parent jobs are done + pass else: _logger.error("unexpected state %s for job %s", state, job) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 2937de85a6..ce41e5d8a4 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -210,6 +210,11 @@ def button_cancelled(self): return True def requeue(self): + # FIXME if leaves are requeued before their done parents + # they will be pending instead of wait_dependencies + # (in a scenario where we select all the jobs and requeue them) + # Requeue them in reverse order of the graph? Or recheck the state + # after they are all updated. self._change_job_state(PENDING) return True From 802c47b8ccc07806092692934e400e2e3df82836 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 5 Jul 2019 23:47:57 +0200 Subject: [PATCH 30/88] Adapt views for state wait_dependencies --- queue_job/views/queue_job_views.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index cf049fdfeb..c51b1c34fc 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -17,7 +17,7 @@ />
+ + + Date: Thu, 11 Jul 2019 22:03:15 +0200 Subject: [PATCH 34/88] Show the dependency widget in a tab When displayed in a tab, the widget show the nodes offset, the d3 network is probably confused by the size or visibility of its canvas. Install a listener on tabs to trigger a fit() on the network. --- queue_job/static/src/js/queue_job_fields.js | 39 ++++++++++++++ queue_job/views/queue_job_views.xml | 56 ++++++++++++--------- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/queue_job/static/src/js/queue_job_fields.js b/queue_job/static/src/js/queue_job_fields.js index 692ed48ddc..50dbb8ac42 100644 --- a/queue_job/static/src/js/queue_job_fields.js +++ b/queue_job/static/src/js/queue_job_fields.js @@ -21,6 +21,44 @@ var JobDirectedGraph = AbstractField.extend({ jsLibs: [ '/queue_job/static/lib/vis/vis-network.min.js' ], + init: function () { + this._super.apply(this, arguments); + this.network = null; + this.tabListenerInstalled = false; + }, + start: function () { + var def = this._super(); + + core.bus.on('DOM_updated', this, function () { + this._installTabListener(); + }.bind(this)); + + return def; + }, + _fitNetwork: function () { + if (this.network) { + this.network.fit(this.network.body.nodeIndices); + } + }, + /* + * Add a listener on tabs if any: when the widget is render inside a tab, + * it does not view the view. Install a listener that will fit the network + * graph to show all the nodes when we switch tab. + */ + _installTabListener: function () { + if (this.tabListenerInstalled) { + return; + } + this.tabListenerInstalled = true; + + var tab = this.$el.closest('div.tab-pane'); + if (!tab.length) { + return; + } + $('a[href="#' + tab[0].id + '"]').on('shown.bs.tab', function (e) { + this._fitNetwork(); + }.bind(this)); + }, _render: function () { var self = this; this.$el.empty(); @@ -69,6 +107,7 @@ var JobDirectedGraph = AbstractField.extend({ network.selectNodes([self.res_id]); } }); + this.network = network; }, openDependencyJob: function (res_id) { var self = this; diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index a29ea53504..d8028e63d1 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -79,36 +79,42 @@ > If the max. retries is 0, the number of retries is infinite. - -
-
- -
- + + + + + + +
+
+ +
+
+ -
- - - + +
From 4ff135281e61a2bbfccbf1b5781b84aa02e0f5f4 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 4 Oct 2019 19:25:43 +0200 Subject: [PATCH 35/88] Add documentation on 'base' model public methods --- queue_job/job.py | 2 +- queue_job/models/base.py | 110 +++++++++++++++++++++++++++------------ 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/queue_job/job.py b/queue_job/job.py index acdf9ed28d..3946f4b446 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -75,7 +75,7 @@ def __init__( self.channel = channel self.identity_key = identity_key - # TODO it should use the new DelayableBuilder + # TODO it should use the new Delayable def __getattr__(self, name): if name in self.recordset: raise AttributeError( diff --git a/queue_job/models/base.py b/queue_job/models/base.py index 67c480c7cd..1d2b691280 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -33,23 +33,92 @@ def with_delay( ): """Return a ``DelayableRecordset`` - The returned instance allows to enqueue any method of the recordset's - Model. - - Usage:: + It is a shortcut for the longer form as shown below:: - self.env['res.users'].with_delay().write({'name': 'test'}) + self.with_delay(priority=20).action_done() + # is equivalent to: + self.delayable().set(priority=20).action_done().delay() ``with_delay()`` accepts job properties which specify how the job will be executed. Usage with job properties:: - delayable = env['a.model'].with_delay(priority=30, eta=60*60*5) + env['a.model'].with_delay(priority=30, eta=60*60*5).action_done() delayable.export_one_thing(the_thing_to_export) # => the job will be executed with a low priority and not before a # delay of 5 hours from now + When using :meth:``with_delay``, the final ``delay()`` is implicit. + See the documentation of :meth:``delayable`` for more details. + + :return: instance of a DelayableRecordset + :rtype: :class:`odoo.addons.queue_job.job.DelayableRecordset` + """ + if os.getenv('TEST_QUEUE_JOB_NO_DELAY'): + _logger.warn( + '`TEST_QUEUE_JOB_NO_DELAY` env var found. NO JOB scheduled.' + ) + return self + if self.env.context.get('test_queue_job_no_delay'): + _logger.warn( + '`test_queue_job_no_delay` ctx key found. NO JOB scheduled.' + ) + return self + return DelayableRecordset(self, priority=priority, + eta=eta, + max_retries=max_retries, + description=description, + channel=channel, + identity_key=identity_key) + + @api.multi + def delayable(self, priority=None, eta=None, + max_retries=None, description=None, + channel=None, identity_key=None): + """Return a ``Delayable`` + + The returned instance allows to enqueue any method of the recordset's + Model. + + Usage:: + + delayable = self.env['res.users'].browse(10).delayable(priority=20) + delayable.do_work({'name': 'test'}).delay() + + In the line above, ``do_work`` is allowed to be delayed because the + method definition of the fictive method ``do_work`` is decorated by + ``@job``. The ``do_work`` method will to be executed directly. It will + be executed in an asynchronous job. + + Method calls on a Delayable generally return themselves, so calls can + be chained together:: + + delayable.set(priority=15).do_work({'name': 'test'}).delay() + + The order of the calls that build the job is not relevant, beside + the call to ``delay()`` that must happen at the very end. This is + equivalent to the one before:: + + delayable.do_work({'name': 'test'}).set(priority=15).delay() + + Very importantly, ``delay()`` must be called on the top-most parent + of a chain of jobs, so if you have this:: + + job1 = record1.delayable().do_work() + job2 = record2.delayable().do_work() + job1.done(job2) + + The ``delay()`` call must be made on ``job1``, otherwise ``job2`` will + be delayed, but ``job1`` will never be. When done on ``job1``, the + ``delay()`` call will traverse the graph of jobs and delay all of + them:: + + job1.delay() + + For more details on the graph dependencies, read the documentation of + :module:`~odoo.addons.queue_job.delay`. + :param priority: Priority of the job, 0 being the higher priority. Default is 10. :param eta: Estimated Time of Arrival of the job. It will not be @@ -67,8 +136,9 @@ def with_delay( the new job will not be added. It is either a string, either a function that takes the job as argument (see :py:func:`..job.identity_exact`). - :return: instance of a DelayableRecordset - :rtype: :class:`odoo.addons.queue_job.job.DelayableRecordset` + the new job will not be added. + :return: instance of a Delayable + :rtype: :class:`odoo.addons.queue_job.job.Delayable` Note for developers: if you want to run tests or simply disable jobs queueing for debugging purposes, you can: @@ -80,30 +150,6 @@ def with_delay( @mute_logger('odoo.addons.queue_job.models.base') """ - if os.getenv("TEST_QUEUE_JOB_NO_DELAY"): - _logger.warning( - "`TEST_QUEUE_JOB_NO_DELAY` env var found. NO JOB scheduled." - ) - return self - if self.env.context.get("test_queue_job_no_delay"): - _logger.warning( - "`test_queue_job_no_delay` ctx key found. NO JOB scheduled." - ) - return self - return DelayableRecordset( - self, - priority=priority, - eta=eta, - max_retries=max_retries, - description=description, - channel=channel, - identity_key=identity_key, - ) - - @api.multi - def delayable(self, priority=None, eta=None, - max_retries=None, description=None, - channel=None, identity_key=None): return Delayable(self, priority=priority, eta=eta, max_retries=max_retries, From 0798d1f73bd5117f0aa65bac2ab44e450c53cce6 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 4 Oct 2019 19:27:28 +0200 Subject: [PATCH 36/88] Improve loading of dependencies using batch read --- queue_job/job.py | 51 ++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/queue_job/job.py b/queue_job/job.py index 3946f4b446..41eef95432 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -263,14 +263,26 @@ class Job(object): @classmethod def load(cls, env, job_uuid): - """Read a job from the Database""" - stored = cls.db_record_from_uuid(env, job_uuid) + """Read a single job from the Database + + Raise an error if the job is not found. + """ + stored = cls.db_records_from_uuids(env, [job_uuid]) if not stored: raise NoSuchJobError( "Job %s does no longer exist in the storage." % job_uuid ) return cls._load_from_db_record(stored) + @classmethod + def load_many(cls, env, job_uuids): + """Read jobs in batch from the Database + + Jobs not found are ignored. + """ + recordset = cls.db_records_from_uuids(env, job_uuids) + return {cls._load_from_db_record(record) for record in recordset} + @classmethod def _load_from_db_record(cls, job_db_record): stored = job_db_record @@ -405,8 +417,14 @@ def _enqueue_job(self): @staticmethod def db_record_from_uuid(env, job_uuid): + # TODO remove in 15.0 or 16.0 + _logger.debug("deprecated, use 'db_records_from_uuids") + return Job.db_records_from_uuids(env, [job_uuid]) + + @staticmethod + def db_records_from_uuids(env, job_uuids): model = env["queue.job"].sudo() - record = model.search([("uuid", "=", job_uuid)], limit=1) + record = model.search([("uuid", "in", tuple(job_uuids))]) return record.with_env(env).sudo() def __init__( @@ -574,10 +592,9 @@ def perform(self): return self.result def enqueue_waiting(self): - # TODO replace states by constants sql = """ UPDATE queue_job - SET state = 'pending' + SET state = %s FROM ( SELECT child.id, array_agg(parent.state) as parent_states FROM queue_job job @@ -598,10 +615,10 @@ def enqueue_waiting(self): ) jobs WHERE queue_job.id = jobs.id - AND 'done' = ALL(jobs.parent_states) - AND state = 'wait_dependencies'; + AND %s = ALL(jobs.parent_states) + AND state = %s; """ - self.env.cr.execute(sql, (self.uuid,)) + self.env.cr.execute(sql, (PENDING, self.uuid, DONE, WAIT_DEPENDENCIES)) def store(self): """Store the Job""" @@ -730,7 +747,7 @@ def __lt__(self, other): return self.sorting_key() < other.sorting_key() def db_record(self): - return self.db_record_from_uuid(self.env, self.uuid) + return self.db_records_from_uuids(self.env, [self.uuid]) @property def func(self): @@ -763,21 +780,17 @@ def identity_key(self, value): @property def depends_on(self): if not self._depends_on: - # TODO batch load instead of loop - self._depends_on = { - Job.load(self.env, parent_uuid) for parent_uuid - in self.__depends_on_uuids - } + self._depends_on = Job.load_many( + self.env, self.__depends_on_uuids + ) return self._depends_on @property def reverse_depends_on(self): if not self._reverse_depends_on: - # TODO batch load instead of loop - self._reverse_depends_on = { - Job.load(self.env, child_uuid) for child_uuid - in self.__reverse_depends_on_uuids - } + self._reverse_depends_on = Job.load_many( + self.env, self.__reverse_depends_on_uuids + ) return set(self._reverse_depends_on) @property From 5ae57de058e1ec6c155732ce23ebf9f8881cf4dc Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 4 Oct 2019 20:39:06 +0200 Subject: [PATCH 37/88] Use Delayable in DelayableRecordset --- queue_job/delay.py | 85 ++++++++++++++++++++++++++++++++++++---- queue_job/job.py | 74 +++++----------------------------- queue_job/models/base.py | 5 +++ 3 files changed, 92 insertions(+), 72 deletions(-) diff --git a/queue_job/delay.py b/queue_job/delay.py index 818784965c..b3cbfbf4ff 100644 --- a/queue_job/delay.py +++ b/queue_job/delay.py @@ -4,6 +4,7 @@ import itertools import logging +import os from collections import deque @@ -130,9 +131,31 @@ def delay(self): vertex._build_job() for vertex, neighbour in graph.edges(): neighbour._generated_job.add_depends({vertex._generated_job}) + + # If all the jobs of the graph have another job with the same identity, + # we do not create them. Maybe we should check that the found jobs are + # part of the same graph, but not sure it's really required... + # Also, maybe we want to check only the root jobs. + existing_mapping = {} + for vertex in graph.vertices(): + if not vertex.identity_key: + continue + existing = vertex._generated_job.job_record_with_same_identity_key() + if not existing: + # at least one does not exist yet, we'll delay the whole graph + existing_mapping.clear() + break + existing_mapping[vertex] = existing + + # We'll replace the generated jobs by the existing ones, so callers + # can retrieve the existing job in "_generated_job". + # existing_mapping contains something only if *all* the job with an + # identity have an existing one. + for vertex, existing in existing_mapping.items(): + vertex._generated_job = existing + return + for vertex in graph.vertices(): - # TODO it ignores identity key now, what should we do if - # we have dependencies...? vertex._generated_job.store() @@ -300,11 +323,57 @@ def __getattr__(self, name): (name, self.recordset) ) recordset_method = getattr(self.recordset, name) - if not getattr(recordset_method, 'delayable', None): - raise AttributeError( - 'method %s on %s is not allowed to be delayed, ' - 'it should be decorated with odoo.addons.queue_job.job.job' % - (name, self.recordset) - ) self._job_method = recordset_method return self._store_args + + +class DelayableRecordset(object): + """Allow to delay a method for a recordset (shortcut way) + + Usage:: + + delayable = DelayableRecordset(recordset, priority=20) + delayable.method(args, kwargs) + + ``method`` must be a method of the recordset's Model, decorated with + :func:`~odoo.addons.queue_job.job.job`. + + The method call will be processed asynchronously in the job queue, with + the passed arguments. + + This class will generally not be used directly, it is used internally + by :meth:`~odoo.addons.queue_job.models.base.Base.with_delay` + """ + + __slots__ = ('delayable',) + + def __init__(self, recordset, priority=None, eta=None, + max_retries=None, description=None, channel=None, + identity_key=None): + self.delayable = Delayable( + recordset, + priority=priority, + eta=eta, + max_retries=max_retries, + description=description, + channel=channel, + identity_key=identity_key, + ) + + @property + def recordset(self): + return self.delayable.recordset + + def __getattr__(self, name): + def _delay_delayable(*args, **kwargs): + getattr(self.delayable, name)(*args, **kwargs).delay() + return self.delayable._generated_job + return _delay_delayable + + def __str__(self): + return "DelayableRecordset(%s%s)" % ( + self.delayable.recordset._name, + getattr(self.delayable.recordset, '_ids', "") + ) + + __repr__ = __str__ diff --git a/queue_job/job.py b/queue_job/job.py index 41eef95432..ae9d799e19 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -42,70 +42,14 @@ _logger = logging.getLogger(__name__) -class DelayableRecordset(object): - """Allow to delay a method for a recordset - - Usage:: - - delayable = DelayableRecordset(recordset, priority=20) - delayable.method(args, kwargs) - - The method call will be processed asynchronously in the job queue, with - the passed arguments. - - This class will generally not be used directly, it is used internally - by :meth:`~odoo.addons.queue_job.models.base.Base.with_delay` - """ - - def __init__( - self, - recordset, - priority=None, - eta=None, - max_retries=None, - description=None, - channel=None, - identity_key=None, - ): - self.recordset = recordset - self.priority = priority - self.eta = eta - self.max_retries = max_retries - self.description = description - self.channel = channel - self.identity_key = identity_key - - # TODO it should use the new Delayable - def __getattr__(self, name): - if name in self.recordset: - raise AttributeError( - "only methods can be delayed ({} called on {})".format( - name, self.recordset - ) - ) - recordset_method = getattr(self.recordset, name) - - def delay(*args, **kwargs): - return Job.enqueue( - recordset_method, - args=args, - kwargs=kwargs, - priority=self.priority, - max_retries=self.max_retries, - eta=self.eta, - description=self.description, - channel=self.channel, - identity_key=self.identity_key, - ) - - return delay - - def __str__(self): - return "DelayableRecordset({}{})".format( - self.recordset._name, getattr(self.recordset, "_ids", "") - ) - - __repr__ = __str__ +# TODO remove in 15.0 or 16.0, used to keep compatibility as the +# class has been moved in 'delay'. +def DelayableRecordset(*args, **kwargs): + # prevent circular import + from .delay import DelayableRecordset as dr + _logger.debug("DelayableRecordset moved from the queue_job.job" + " to the queue_job.delay python module") + return dr(*args, **kwargs) def identity_exact(job_): @@ -358,6 +302,7 @@ def job_record_with_same_identity_key(self): ) return existing + # TODO to deprecate (not called anymore) @classmethod def enqueue( cls, @@ -393,6 +338,7 @@ def enqueue( ) return new_job._enqueue_job() + # TODO to deprecate (not called anymore) def _enqueue_job(self): if self.identity_key: existing = self.job_record_with_same_identity_key() diff --git a/queue_job/models/base.py b/queue_job/models/base.py index 1d2b691280..d68391da82 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -55,6 +55,11 @@ def with_delay( :return: instance of a DelayableRecordset :rtype: :class:`odoo.addons.queue_job.job.DelayableRecordset` """ + # TODO Implement this for ``delayable``. 2 options: + # 1. store the jobs in db, traverse the graph and process + # them in the same transaction + # 2. do not store them, instead, traverse the graph and + # execute the calls directly. if os.getenv('TEST_QUEUE_JOB_NO_DELAY'): _logger.warn( '`TEST_QUEUE_JOB_NO_DELAY` env var found. NO JOB scheduled.' From 78ce4bc364a1d8a4a94ba2ffa79a9df640b296c5 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 24 May 2021 21:35:16 +0200 Subject: [PATCH 38/88] Add documentation --- queue_job/README.rst | 4 +- queue_job/readme/USAGE.rst | 127 +++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/queue_job/README.rst b/queue_job/README.rst index b76f841c62..6633de159a 100644 --- a/queue_job/README.rst +++ b/queue_job/README.rst @@ -23,7 +23,7 @@ Job Queue :target: https://runbot.odoo-community.org/runbot/230/16.0 :alt: Try me on Runbot -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This addon adds an integrated Job Queue to Odoo. @@ -392,7 +392,7 @@ promote its widespread use. Current `maintainer `__: -|maintainer-guewen| +|maintainer-guewen| This module is part of the `OCA/queue `_ project on GitHub. diff --git a/queue_job/readme/USAGE.rst b/queue_job/readme/USAGE.rst index 9612501d75..eaf283f25b 100644 --- a/queue_job/readme/USAGE.rst +++ b/queue_job/readme/USAGE.rst @@ -159,3 +159,130 @@ Tip: you can do this at test case level like this Then all your tests execute the job methods synchronously without delaying any jobs. + +Delaying jobs +~~~~~~~~~~~~~ + +The fast way to enqueue a job for a method is to use ``with_delay()`` on a record +or model: + + +.. code-block:: python + + def button_done(self): + self.with_delay().print_confirmation_document(self.state) + self.write({"state": "done"}) + return True + +Here, the method ``print_confirmation_document`` will be executed asynchronously +as a job. ``with_delay()`` can take several parameters to define more precisely how +the job is executed (priority, ...). + +All the arguments passed to the method being delayed are stored in the job and +passed to the method when it is executed asynchronously, including ``self``, so +the current record is maintained during the job execution (warning: the context +is not kept). + +Dependencies can be expressed between jobs. To start a graph of jobs, use ``delayable()`` +on a record or model. The following is the equivalent of ``with_delay()`` but using the +long form: + +.. code-block:: python + + def button_done(self): + delayable = self.delayable() + delayable.print_confirmation_document(self.state) + delayable.delay() + self.write({"state": "done"}) + return True + +Methods of Delayable objects return itself, so it can be used as a builder pattern, +which in some cases allow to build the jobs dynamically: + +.. code-block:: python + + def button_generate_simple_with_delayable(self): + self.ensure_one() + # Introduction of a delayable object, using a builder pattern + # allowing to chain jobs or set properties. The delay() method + # on the delayable object actually stores the delayable objects + # in the queue_job table + ( + self.delayable() + .generate_thumbnail((50, 50)) + .set(priority=30) + .set(description=_("generate xxx")) + .delay() + ) + +The simplest way to define a dependency is to use ``.done(job)`` on a Delayable: + +.. code-block:: python + + def button_chain_done(self): + self.ensure_one() + job1 = self.browse(1).delayable().generate_thumbnail((50, 50)) + job2 = self.browse(1).delayable().generate_thumbnail((50, 50)) + job3 = self.browse(1).delayable().generate_thumbnail((50, 50)) + # job 3 is executed when job 2 is done which is executed when job 1 is done + job1.done(job2.done(job3)).delay() + +Delayables can be chained to form more complex graphs using the ``chain()`` and +``group()`` primitives. +A chain represents a sequence of jobs to execute in order, a group represents +jobs which can be executed in parallel. Using ``chain()`` has the same effect as +using several nested ``done()`` but is more readable. Both can be combined to +form a graph, for instance we can group [A] of jobs, which blocks another group +[B] of jobs. When and only when all the jobs of the group [A] are executed, the +jobs of the group [B] are executed. The code would look like: + +.. code-block:: python + + from odoo.addons.queue_job import group, chain + + def button_done(self): + group_a = group(self.delayable().method_foo(), self.delayable().method_bar()) + group_b = group(self.delayable().method_baz(1), self.delayable().method_baz(2)) + chain(group_a, group_b).delay() + self.write({"state": "done"}) + return True + +Note: ``delay()`` must be called on the delayable, chain, or group which is at the top +of the graph. In the example above, if it was called on ``group_a``, then ``group_b`` +would never be delayed (but a warning would be shown). + + +Enqueing Job Options +-------------------- + +* priority: default is 10, the closest it is to 0, the faster it will be + executed +* eta: Estimated Time of Arrival of the job. It will not be executed before this + date/time +* max_retries: default is 5, maximum number of retries before giving up and set + the job state to 'failed'. A value of 0 means infinite retries. +* description: human description of the job. If not set, description is computed + from the function doc or method name +* channel: the complete name of the channel to use to process the function. If + specified it overrides the one defined on the function +* identity_key: key uniquely identifying the job, if specified and a job with + the same key has not yet been run, the new job will not be created + + +Caveats +------- + +* TODO + +Tips and tricks +~~~~~~~~~~~~~~~ + +* **Idempotency** (https://www.restapitutorial.com/lessons/idempotency.html): The queue_job should be idempotent so they can be retried several times without impact on the data. +* **The job should test at the very beginning its relevance**: the moment the job will be executed is unknown by design. So the first task of a job should be to check if the related work is still relevant at the moment of the execution. + +Patterns +~~~~~~~~ +Through the time, two main patterns emerged: + +1. For data exposed to users, a model should store the data and the model should be the creator of the job. The job is kept hidden from the users +2. For technical data, that are not exposed to the users, it is generally alright to create directly jobs with data passed as arguments to the job, without intermediary models. From 95ec8e2c547cdf6f75ff654f05e655fe7bcfe9c9 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 26 May 2021 20:33:08 +0200 Subject: [PATCH 39/88] Add a graph UUID A graph of jobs always share the same graph_uuid, which can be used to group jobs, but is also a faster way to find all the jobs of the graph. Then we can use the dependencies field to build the whole graph from the pre-selection of jobs. --- queue_job/delay.py | 10 +++-- queue_job/job.py | 32 ++++++++++++++- queue_job/models/queue_job.py | 63 ++++++++++++++--------------- queue_job/views/queue_job_views.xml | 4 ++ 4 files changed, 70 insertions(+), 39 deletions(-) diff --git a/queue_job/delay.py b/queue_job/delay.py index b3cbfbf4ff..ffdc796b2f 100644 --- a/queue_job/delay.py +++ b/queue_job/delay.py @@ -4,7 +4,6 @@ import itertools import logging -import os from collections import deque @@ -127,8 +126,11 @@ def _connect_graphs(self): def delay(self): graph = self._connect_graphs() - for vertex in graph.vertices(): + vertices = graph.vertices() + + for vertex in vertices: vertex._build_job() + for vertex, neighbour in graph.edges(): neighbour._generated_job.add_depends({vertex._generated_job}) @@ -137,7 +139,7 @@ def delay(self): # part of the same graph, but not sure it's really required... # Also, maybe we want to check only the root jobs. existing_mapping = {} - for vertex in graph.vertices(): + for vertex in vertices: if not vertex.identity_key: continue existing = vertex._generated_job.job_record_with_same_identity_key() @@ -155,7 +157,7 @@ def delay(self): vertex._generated_job = existing return - for vertex in graph.vertices(): + for vertex in vertices: vertex._generated_job.store() diff --git a/queue_job/job.py b/queue_job/job.py index ae9d799e19..11d98c4d36 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -108,6 +108,10 @@ class Job(object): Id (UUID) of the job. + .. attribute:: graph_uuid + + Shared UUID of the job's graph. Empty if the job is a single job. + .. attribute:: state State of the job, can pending, enqueued, started, done or failed. @@ -270,6 +274,7 @@ def _load_from_db_record(cls, job_db_record): job_.date_cancelled = stored.date_cancelled job_.state = stored.state + job_.graph_uuid = stored.graph_uuid if stored.graph_uuid else None job_.result = stored.result if stored.result else None job_.exc_info = stored.exc_info if stored.exc_info else None job_.retry = stored.retry @@ -445,6 +450,7 @@ def __init__( self.max_retries = max_retries self._uuid = job_uuid + self.graph_uuid = None self.args = args self.kwargs = kwargs @@ -498,6 +504,7 @@ def add_depends(self, jobs): for parent in jobs: parent.__reverse_depends_on_uuids.add(self.uuid) parent._reverse_depends_on.add(self) + self._initialize_or_propagate_graph_uuid(jobs) if any(j.state != DONE for j in jobs): self.state = WAIT_DEPENDENCIES @@ -509,6 +516,24 @@ def add_reverse_depends(self, jobs): for child in jobs: child.__depends_on_uuids.add(self.uuid) child._depends_on.add(self) + self._initialize_or_propagate_graph_uuid(jobs) + + def _initialize_or_propagate_graph_uuid(self, other_jobs): + graph_uuids = set( + other.graph_uuid for other in other_jobs if other.graph_uuid + ) + if self.graph_uuid: + graph_uuids.add(self.graph_uuid) + + if len(graph_uuids) > 1: + raise ValueError("Jobs cannot have dependencies on several graphs") + elif len(graph_uuids) == 1: + graph_uuid = graph_uuids.pop() + else: + graph_uuid = str(uuid.uuid4()) + self.graph_uuid = graph_uuid + for other_job in other_jobs: + other_job.graph_uuid = graph_uuid def perform(self): """Execute the job. @@ -549,13 +574,15 @@ def enqueue_waiting(self): job.dependencies::json->'reverse_depends_on' ) child_deps ON true JOIN queue_job child - ON child.uuid = child_deps + ON child.graph_uuid = job.graph_uuid + AND child.uuid = child_deps JOIN LATERAL json_array_elements_text( child.dependencies::json->'depends_on' ) parent_deps ON true JOIN queue_job parent - ON parent.uuid = parent_deps + ON parent.graph_uuid = job.graph_uuid + AND parent.uuid = parent_deps WHERE job.uuid = %s GROUP BY child.id ) jobs @@ -602,6 +629,7 @@ def _store_values(self, create=False): "eta": False, "identity_key": False, "worker_pid": self.worker_pid, + "graph_uuid": self.graph_uuid, } if self.date_enqueued: diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 7d3cb89b6c..9593cf10d7 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -48,6 +48,12 @@ class QueueJob(models.Model): ) uuid = fields.Char(string="UUID", readonly=True, index=True, required=True) + graph_uuid = fields.Char( + string="Graph UUID", + readonly=True, + index=True, + help="Single shared identifier of a Graph. Empty for a single job." + ) user_id = fields.Many2one(comodel_name="res.users", string="User ID") company_id = fields.Many2one( comodel_name="res.company", string="Company", index=True @@ -127,43 +133,34 @@ def _compute_record_ids(self): for record in self: record.record_ids = record.records.ids - @api.multi @api.depends('dependencies') def _compute_dependency_graph(self): for record in self: - # Can we write a clever SQL query - # to get that graph? + if not record.graph_uuid: + record.dependency_graph = {} + continue + + # TODO in 13.0, we could maybe use read_group, apparently, we can + # only call this field on one record at a time here + graph_jobs = self.search([("graph_uuid", "=", record.graph_uuid)]) + + graph_ids = { + graph_job.uuid: graph_job.id for graph_job in graph_jobs + } + graph = Graph() - jobs = [record] - seen = set() - while jobs: - current = jobs.pop() - seen.add(current.id) - graph.add_vertex(current.id) - - dependencies = current.dependencies - depends_on = dependencies.get('depends_on', []) - reverse_depends_on = dependencies.get( - 'reverse_depends_on', [] - ) - parents = self.search([ - ('uuid', 'in', depends_on) - ]) - children = self.search([ - ('uuid', 'in', reverse_depends_on) - ]) - jobs += [ - parent for parent in parents - if parent.id not in seen - ] - jobs += [ - child for child in children - if child.id not in seen - ] - for parent in parents: - graph.add_edge(parent.id, current.id) - for child in children: - graph.add_edge(current.id, child.id) + for graph_job in graph_jobs: + graph.add_vertex(graph_job.id) + for parent_uuid in graph_job.dependencies['depends_on']: + parent_id = graph_ids.get(parent_uuid) + if not parent_id: + continue + graph.add_edge(parent_id, graph_job.id) + for child_uuid in graph_job.dependencies['reverse_depends_on']: + child_id = graph_ids.get(child_uuid) + if not child_id: + continue + graph.add_edge(graph_job.id, child_id) # this is the most portable format for json for the graph, # as we cannot have integer as dictionary keys diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index d8028e63d1..405e8d4158 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -45,6 +45,7 @@ + @@ -107,6 +108,7 @@ + @@ -256,6 +259,7 @@ string="Exception message" context="{'group_by': 'exc_message'}" /> + From c374e0a7c4a192134426bc8331b8ca6a749bc7d4 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 26 May 2021 20:35:30 +0200 Subject: [PATCH 40/88] Hide some technical fields --- queue_job/views/queue_job_views.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 405e8d4158..82551c3e20 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -46,7 +46,7 @@ - + @@ -63,7 +63,7 @@ - + From c1ff4f514405cbdf1340614233f89e51d9a61450 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 26 May 2021 22:34:16 +0200 Subject: [PATCH 41/88] Ignore requeues on dependency jobs waiting on parent jobs Jobs waiting that their dependencies are executed cannot be requeued. They have to keep waiting their turn. --- queue_job/models/queue_job.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 9593cf10d7..cb235a1a03 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -10,7 +10,7 @@ from ..delay import Graph from ..fields import JobSerialized -from ..job import CANCELLED, DONE, PENDING, STATES, Job +from ..job import CANCELLED, DONE, PENDING, STATES, Job, WAIT_DEPENDENCIES _logger = logging.getLogger(__name__) @@ -257,12 +257,10 @@ def button_cancelled(self): return True def requeue(self): - # FIXME if leaves are requeued before their done parents - # they will be pending instead of wait_dependencies - # (in a scenario where we select all the jobs and requeue them) - # Requeue them in reverse order of the graph? Or recheck the state - # after they are all updated. - self._change_job_state(PENDING) + jobs_to_requeue = self.filtered( + lambda job_: job_.state != WAIT_DEPENDENCIES + ) + jobs_to_requeue._change_job_state(PENDING) return True def _message_post_on_failure(self): From 90a35efd3d0c0fefb2a8648cd8d46d6ba09b86a7 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 27 May 2021 07:49:35 +0200 Subject: [PATCH 42/88] Fix lint --- queue_job/delay.py | 3 ++- queue_job/models/queue_job.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/queue_job/delay.py b/queue_job/delay.py index ffdc796b2f..a1916981cc 100644 --- a/queue_job/delay.py +++ b/queue_job/delay.py @@ -142,7 +142,8 @@ def delay(self): for vertex in vertices: if not vertex.identity_key: continue - existing = vertex._generated_job.job_record_with_same_identity_key() + generated_job = vertex._generated_job + existing = generated_job.job_record_with_same_identity_key() if not existing: # at least one does not exist yet, we'll delay the whole graph existing_mapping.clear() diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index cb235a1a03..baad1bd57f 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -104,7 +104,7 @@ class QueueJob(models.Model): "Retries are infinite when empty.", ) # FIXME the name of this field is very confusing - channel_method_name = fields.Char(readonly=True) + channel_method_name = fields.Char(string="Method Name", readonly=True) job_function_id = fields.Many2one( comodel_name="queue.job.function", string="Job Function", From 034820fa6ab715d76ff6a5e54801199afde97141 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 27 May 2021 21:09:18 +0200 Subject: [PATCH 43/88] Update vis-network js --- queue_job/static/lib/vis/vis-network.min.css | 2 +- queue_job/static/lib/vis/vis-network.min.js | 35 +++++++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/queue_job/static/lib/vis/vis-network.min.css b/queue_job/static/lib/vis/vis-network.min.css index ea487a481d..d708f173b6 100644 --- a/queue_job/static/lib/vis/vis-network.min.css +++ b/queue_job/static/lib/vis/vis-network.min.css @@ -1 +1 @@ -.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper::after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%)}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}div.vis-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none;z-index:5}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%)}div.vis-color-picker div.vis-new-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;left:159px;text-align:right;padding-right:2px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-initial-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;left:10px;text-align:left;padding-left:2px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px}div.vis-network div.vis-manipulation{box-sizing:content-box;border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;cursor:pointer;padding:0 8px 0 8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px} \ No newline at end of file +.vis-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:0 0 10px 0 rgba(0,0,0,.5)}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow:after,div.vis-color-picker div.vis-arrow:before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:hsla(0,0%,100%,0) #fff hsla(0,0%,100%,0) hsla(0,0%,100%,0);border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(180deg,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#4c4c4c",endColorstr="#131313",GradientType=0)}div.vis-color-picker div.vis-new-color{left:159px;text-align:right;padding-right:2px}div.vis-color-picker div.vis-initial-color,div.vis-color-picker div.vis-new-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-initial-color{left:10px;text-align:left;padding-left:2px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper:after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:transparent;width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(180deg,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);border:1px solid #999;box-shadow:0 0 3px 0 #aaa;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2,#385380);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(180deg,#3876c2 0,#385380);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#3876c2",endColorstr="#385380",GradientType=0);box-shadow:0 0 1px 0 #111927;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:none}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(180deg,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#9d9d9d",endColorstr="#c8c8c8",GradientType=0)}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(180deg,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);border:1px solid #999;box-shadow:0 0 3px 0 #aaa;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0) rgba(136,183,213,0) rgba(136,183,213,0) rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0) rgba(194,225,245,0) rgba(194,225,245,0) #f2faff;border-width:12px;margin-top:-12px}div.vis-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none;z-index:5}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url("data:image/png;base64,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");bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url("data:image/png;base64,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");bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url("data:image/png;base64,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");bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url("data:image/png;base64,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");bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url("data:image/png;base64,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");bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABV5JREFUeNq0l2tQVVUYht/3W/vACMr16IFRQDiAgChpgiikMqY1WjnN9KsfGOXYTOVgkvbDUsZuXrK0qZmGUSvNspjI8TZOmo6AGBoZYly8YB6Qw80DBwQ6jJ3dj30OZZmiwvtv77XW96y91l7v9y1iMNLBuCI84tZkIXU9gwqxAILdokNBOtzgJQWWuYEDFxfcLAGh3y0k79iaD4mfjOVu4WYhoItngBiR6RkuFJAyEJBA3m/lri3Ih/uewXFFyAG4A8oAWkcm2meEzrFNH53Vkhg4xWnxCXcBQGu/3bfGeTbwjKPUcsZRElnfUxcuFLh1Nwh5vurx7s8GDbZ+L+tI/U0hkGGZX5c9/pXqOZYn2gazK8Vth0fvsRUknbx+bIJQQPCts/Mda+4KthbJFoqeKwSejX6pfO2kjytxH1pfuyqlsGH7dJAgZWvFo23L/9muboF+JxtE0/OEwMqJG46uSHinFvepTPO8lhGaX+fPHSdjCKaPy/b3v7az58h/wHFFyIHCRirgjUlbfsiJWXEFD6iUoOkdQaaQ6z9dP2YVahljF4+yXdvZ/evf4G+hQk2sEAUsti4vWxa35gKGSBMDp3T23OxxVXdXRijKovSFzrerC6ELAMT6IhcCZIyeX7c68YPzGGLlxq89PyM0q5YU2M1RuQAg0EERbiaA7Ohl1RgmPTM2p1qjBk1Mm6GDErsfswAgLiDZPmfMwrbhAqeHzm6P8Z9gV9SQdTx2lpCyAEKkhc62YZiVEjTdRgo0zXeBRnImAaSFzm7xdjjtOBGyvmZVZkNvfZjXDhU14+BToFEDKRAQpAJ0HRTjP6XHpYUKEX7RzS9bV5c+FJTmAICUgNSWQ/ZCgJwhIOJIQVLgFKcXvKHm9cyGvithFDUAFQqECho1CBUIggYapAJ1QEFBExNMYoISDU1/NIR9cvndTG/c2IBkp2fC8ZpQgknBGI/3AsDvvRfDlJhwem5zwYMs7VNlaUtbXE1h3mezj9mlGSsXrBkzkFsGKGoDmedBJLfLjxQQgAYdHRSxtPfbfceNsPYBQPTI+GZbT31YxrGIpYoKpIKigkAgFOggNBrbQBBCBaEM2L+iGGmTgnF+Uc1epqO/3VejAoAOUZSLQkFN17lAb4eVCe+VRvvHN4sH6t1feqAmMUGoPHvvhdLzTjzfKoj0sza/GLOy1Bu3vqc20Pgl5YIGkVOEZFZ0nLLMszzdDADTgjIdX6Uf3zfUx6m6u8riKRhOCcmDAqLCURo53Oe4rrsyUlGD0nlIqubdKNZJXOm9FH6y7Yh5uKBnO8vNTX2N4YoKE2fMLREQOsE8AfFN4/ak4QIfbd2XJFRQkLx85ruN7NTp2AoAZxwlCR9dWJc81NDdtoLkc86KBIJwXQ3aOpCPqwuhR2SPbCBlUc2NyogQX3N7wqgU51BAf2w9EFXUtCtLqADqS76ev6/ilgrk2q6esxHZgf5CySh3FMcG+5jbE0ZNdj4odHdDwWPGcZNNO1MPbrxtzdW4s+tI5HPBwQTTzziKY3v/7HGlhmS23g90T+OO5L1Nu7MMw3Fv/Tx1f97/FnsAYPui8/D4nBB/oZZR230uoq67auQoLaB37Iio3sEAK52nR39p+zS13HFiilHeYtOOabdC71jQzz2R+ALBbcrjWNF+cfaUwLSrk4KmtsT4T+gK9jG7AKKjv93X1lcfUNNVaantropqddnDCcIoa7lk29S92+/5CpOvQ04VJ79KUe/7iI/Hh40U6c3PyuPjhmWKN8G8Fvnw1A/zmX/vV5h/T+CXstRMUp4kOFOjZiUlWBkFQYdALitRZXRzf3RqWumdgF79NQDBOa2V/iYSHAAAAABJRU5ErkJggg==");bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url("data:image/png;base64,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");bottom:50px;right:15px}div.vis-network div.vis-manipulation{box-sizing:content-box;border:0 solid #d6d9d8;border-bottom:1px;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff,#fcfcfc 48%,#fafafa 50%,#fcfcfc);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(180deg,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#ffffff",endColorstr="#fcfcfc",GradientType=0);padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network button.vis-edit-mode,div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network button.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-color:transparent;background-position:20px 3px;background-repeat:no-repeat;background-image:url("data:image/png;base64,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/GMzLdsAADvGaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTQtMDItMTRUMTE6NTU6MzUrMDE6MDA8L3htcDpDcmVhdGVEYXRlPgogICAgICAgICA8eG1wOk1ldGFkYXRhRGF0ZT4yMDE0LTAyLTE0VDEyOjA1OjE3KzAxOjAwPC94bXA6TWV0YWRhdGFEYXRlPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxNC0wMi0xNFQxMjowNToxNyswMTowMDwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXBNTTpJbnN0YW5jZUlEPnhtcC5paWQ6NjU0YmM5YmQtMWI2Yi1jYjRhLTllOWQtNWY2MzgxNDVjZjk0PC94bXBNTTpJbnN0YW5jZUlEPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD54bXAuZGlkOjk4MmM2MGIwLWUzZjMtMDk0MC04MjU0LTFiZTliNWE0ZTE4MzwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjk4MmM2MGIwLWUzZjMtMDk0MC04MjU0LTFiZTliNWE0ZTE4MzwveG1wTU06T3JpZ2luYWxEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06SGlzdG9yeT4KICAgICAgICAgICAgPHJkZjpTZXE+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmNyZWF0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDo5ODJjNjBiMC1lM2YzLTA5NDAtODI1NC0xYmU5YjVhNGUxODM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDItMTRUMTE6NTU6MzUrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjIxODYxNmM2LTM1MWMtNDI0OS04YWFkLWJkZDQ2ZTczNWE0NDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNC0wMi0xNFQxMTo1NTozNSswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6NjU0YmM5YmQtMWI2Yi1jYjRhLTllOWQtNWY2MzgxNDVjZjk0PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE0LTAyLTE0VDEyOjA1OjE3KzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjc8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NzwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+cZUZMwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAA2ElEQVR42gDLADT/AS0tLUQFBQUVFxcXtPHx8fPl5eUNCAgITCkpKesEHx8fGgYGBjH+/v4a+Pj4qgQEBFU6OjodMTExzwQUFBSvEBAQEfX19SD19fVqNDQ0CElJSd/9/f2vAwEBAfrn5+fkBwcHLRYWFgsXFxfz29vbo9LS0uwDDQ0NDfPz81orKysXIyMj+ODg4Avh4eEa/f391gMkJCRYPz8/KUhISOMCAgKh8fHxHRsbGx4UFBQQBDk5OeY7Ozv7CAgItPb29vMEBASaJSUlTQ0NDesDAEwpT0Ko8Ri2AAAAAElFTkSuQmCC");border:none;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network button.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode button.vis-button,div.vis-network div.vis-manipulation button.vis-button{float:left;font-family:verdana;font-size:12px;border:none;box-sizing:content-box;-moz-border-radius:15px;border-radius:15px;background-color:transparent;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;cursor:pointer;padding:0 8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-manipulation button.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation button.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation button.vis-button.vis-back{background-image:url("data:image/png;base64,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/GMzLdsAAEEOaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNC0wMi0wNFQxNTowMTowOSswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTQtMDItMDRUMTU6MDE6MDkrMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8eG1wTU06SW5zdGFuY2VJRD54bXAuaWlkOmI2YjQwMjVkLTAxNjQtMzU0OC1hOTdlLTQ4ZmYxMWM3NTYzMzwveG1wTU06SW5zdGFuY2VJRD4KICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6RUE2MEEyNEUxOTg0RTMxMUFEQUZFRkU2RUMzMzNFMDM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDEtMjNUMTk6MTg6MDcrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDpmOWQ3OGY4ZC1lNzY0LTc1NDgtODZiNy1iNmQ1OGMzZDg2OTc8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDItMDRUMTU6MDE6MDkrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmI2YjQwMjVkLTAxNjQtMzU0OC1hOTdlLTQ4ZmYxMWM3NTYzMzwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNC0wMi0wNFQxNTowMTowOSswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06RGVyaXZlZEZyb20gcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICA8c3RSZWY6aW5zdGFuY2VJRD54bXAuaWlkOmY5ZDc4ZjhkLWU3NjQtNzU0OC04NmI3LWI2ZDU4YzNkODY5Nzwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwvc3RSZWY6ZG9jdW1lbnRJRD4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDA5MC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDkwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4jq1U/AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAVTSURBVHjanFVfTFNnFP+d77ve8qeVFbBrpcVgRrCRFikFByLxwSAaE32oRCHD6JMxxhhn8G2RxxH3MsOTbyYsmCAxPMmMMYtkIUYmK60OO0qAK23BFlNob0uh3x7WS5jLZPpLbm6+k/P9zrm5v9855PF4UFhYCABgjIExBgAgIqRSqRIi6gDQRkQ1RGTB3wgR0e8AHgH4Sa/XR/EBiAiJRAJ04cIF5Ofng4g2n0gkUkxENwF0c843LzHGQEQQQkCLExEA9ALotVgsUQAQQmgNQhJCbF5kjCEUCl0moj4t5na7fTU1NUpVVVXUYrEkASAcDhe8efOmxOfzWScmJqoBdBNR99LS0hWz2dynNSSEAF28eBGFhYVgjCEcDn9HRD1EhIMHD3o9Hs9kWVlZAh9BKBQqGB4edr58+dKZ+6JbJpOpBwBWV1fB6+rqIMsyIpHIFcZYL2MMra2tY5cuXRrfuXNnBtvAYDBk3G63oqpqZm5uzgrgSDKZjBoMhueZTAbc5XIhFouVEtFTxhiOHTs2dv78eS8+Efv374+oqpqZnZ21cs5PJJPJPlmWkyynnBuMMTQ0NHi7uro+mVyDx+Pxulwu71ZOlkqlSonoJhGhvb39s8k1nDx50ss5hyRJN9PpdKlERB2aWjSVaEilUvzBgwcORVEs5eXloXPnzk1sV8BkMiUdDofP7/dXZ7PZDilnIhw4cGBeS1pbW2P37t1zBwKBikQiUUREWFhYsHHO0d7evm0Ru90+/+rVq2rO+XGJiJxEhMrKyhgAjI6OWoeHh5tWVla+4JzDZrO9bW5unhwcHGzz+/32np4e+xaDbfoHAMxmc6ijo2O0oqIiJkkSNjY2HBIRmRljMJvNyWfPnln7+/tPMMZQXl6+0NbW9qK2tjYcj8floaEhqKpq+HCkbD3PzMwYBgYG0NXV9UuusFna2kEgELAQEQ4dOvSis7PzN41Ar9dnrl27NqCNkv/C3bt3zy4tLVmICJxzEBFJRBQmorLFxcWCqqqq0Pj4eO3Y2JhbUZTdra2tL2pra8OJRGLHnTt3zkqS9K+huHU4EhHMZnMoGo0W5OIh7nK5jjLGKq1W69vDhw8rRqMxMjc3t2t5eXnX5ORklc/nM+fl5SWnpqa+0uv1K/n5+Ws6nW5NluXNd15e3ppOp1uz2WyzZ86cGQ0Gg6ZAIFCZzWZ/lYjokRDiuN/vt7W0tMw3NTUpbrd78P79++5gMFgRiUTKHj58WMYYQ3V19etTp05tq6Lp6Wkb5xxCiEfc7XZPM8a6FxcXTfX19a/1en2Gcy5qamreNjY2/qGq6joRZe12+9Tp06e3JY/FYgWPHz8+mhvr3/CWlpbk+vp6PmOseWVlBS6XS9GSJUkSdrs93NDQ8Oe+ffvC/8fJIyMjddFo9Esi6pVleVjT2m0A8Hq9zqGhIefnjoknT544A4GAM/eDbxMReFNTE0pKSpKqqsaI6Pj8/LxVVdWM3W6PfCr5xMTE1zllXS0uLn6aSqXAGxsbodPpoNfrn6uqCs75EUVRrJFIZMfevXsXdTrdxseIE4mEPDIyUu/3++tynd8yGo29RIR0Og26fv06ioqKwBgD5xzv3r27zBjrIyJIkgSHwzFZWVmp7NmzJ1ZaWpoAgGg0WqgoSvHMzIw1GAw6tvjhitFo7NPW5fv370Hd3d0oKCgA53zTQMvLy+VCiKuSJH0rSdLmztZytIWv5RPRD0T0Y3Fx8dzWfby6ugopHo//w4mcc8iyPMc5v5FOp7/PZrOdQohWInIC2C2EgBBigYi8Qoifs9lsv06nWyIiaFxagXg8jr8GAGxuIe7LBeWhAAAAAElFTkSuQmCC")}div.vis-network div.vis-manipulation div.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-none{padding:0;line-height:23px}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation button.vis-button.vis-add{background-image:url("data:image/png;base64,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/GMzLdsAAEEOaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNC0wMi0wNFQxNDo0MDoyOSswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTQtMDItMDRUMTQ6NDA6MjkrMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8eG1wTU06SW5zdGFuY2VJRD54bXAuaWlkOjVkNWIwNmQwLTVmMjAtOGE0NC1hMzIwLWZmMTEzMzQwNDc0YjwveG1wTU06SW5zdGFuY2VJRD4KICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6RUE2MEEyNEUxOTg0RTMxMUFEQUZFRkU2RUMzMzNFMDM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDEtMjNUMTk6MTg6MDcrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDo2OWVmYWE1NS01ZTI5LTIzNGUtYTUzMy0xNDkxYjM1NDNmYmE8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDItMDRUMTQ6NDA6MjkrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjVkNWIwNmQwLTVmMjAtOGE0NC1hMzIwLWZmMTEzMzQwNDc0Yjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNC0wMi0wNFQxNDo0MDoyOSswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06RGVyaXZlZEZyb20gcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICA8c3RSZWY6aW5zdGFuY2VJRD54bXAuaWlkOjY5ZWZhYTU1LTVlMjktMjM0ZS1hNTMzLTE0OTFiMzU0M2ZiYTwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwvc3RSZWY6ZG9jdW1lbnRJRD4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDA5MC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDkwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz5WKqp9AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAYXSURBVHjafFZtUFTXGX7e9z27sveuMCwYV8ElrA7YSFYHtJUPkaaI0aRqG8wP00zUzljDINNSA/2ROtpO24SxnahlxjYd7SSjmUkymcxYlDhQPzHGisEVp8HwYWCVVVgEsrsuLnL74+5uqTF9Z+7cO/d8PO95zvO851BlZSV0XQcAMDOYGQBARDhX3JRmMDYZwLPMWAzGHACYIgwS46oBNBNwtOL8CwE8EkSEUCgE2rJlC2w2G4go8Zwo/bMDgnoG6gxLfAAAYvPDMCCszKTAMIAGAhrWnf15AAAMwwARIRKJgDZv3gy73Q4iAjPjxIr9VVOMRhbAYKB8zvrO0llrfEsdKwLZek6YAPSFvtSu3GtLawu0ZJ6625SHGBQB1T88t6MxvopgMAjaunUrdF0HM+P4yv27DMYeJmB1RqW3Jnf3tQX2p0L4P9EXuqEd7PmDp+XuMU9sRbvXnnt1TxxACgoKYLVacbzsQDUJGkSATe6qi28uPtzusM6Kxie6NHLGUX3lxVUNX9StPHnn4wy3njuUYcu6n2pNi66avcEXnByP/nv8aiaIyrqz2gO5A9+9FI1GIfn5+WhZdTAdjFMkwMvZOy7uWnTAOz3L4Yk71m3t69fdfTDoUGTBeHTUfiHQ6lo7Z2OXJvpDAChKe+aOCdKRKWxZ2+1qb3yyd3GYmRkQ7GQBVs99wfv6on3eR2k4PdTkDEbH7IuS8/svld/561PJS/pDk1/bzwx94pze7xc5v/H+YPY6r5BAkdrJzODTK46lE6PeYEJt7u+8j+OZwCBiEAgAoNgKJoEQf6PvNvdrXgtZoNhSf7q0KZ3B2AQmVMze0Jmt54S/DcDCVig2NcvEUGxJAE4Pl+YOr0iv6BRSIPAmBeBZAmHlE2sH4p1uhrq1s0MnnEQMBsf8wRASAICQQCCITN1X7/sOuc0kgOVp3/fPs2WHv+coG7gQOJUnLGsUCTxEjPzUohEA+NfIWUdtx0+efzA1kSSkIGyBAQNCKgHAEBAJ3u79U7kiAcWoem/gb5Fd33nrH3kp+SMWtuAB+GllMJxMjCx9QRgA3uiqL5kwHiTlpxb3smlfMDGYGPP1hcMAkJvs8ScpfdJspdj+MK6Pf+5+u29vyb4lR4+BGEziVESAkEpw6Av1OhUpHCz4qOXbzFWz4Ncdj/v/o08Lt92ODDgZDCEFJYoUGH4mzugP92puPTf0pD3H7wvfdFZdqSxnMtWjoGAAmG9fOLxjwesdjT2/XzIQ7ks3sycYMSEwGHNtWf5bkX5NkYCJBxUBXiGV0XHvosOt54Zey33j/K+8P33++vjnbiGJbbLE+J9SANAb6nJ2B79wcUwETAwQQ7fMjPzMvfP8ja87HUIKMOiaAqMZhrGmLdAy78eZrwwsTS0eObTs+IdtgVanxBUExqGbb5VzrIISGIoUXsmqbgEhJldCQWqRf27SvPAn/o8XmgLhZsUkR4ll37mhk3n94Z4OlzY/7NLcYZfm7o1z2zT4vsvUNSXqprBCkmiTFbPX90/fh8GIT2sf+zTPdDMf4dVnNg4z+E0ixsGeBs9jd5ViSgLHjCb/peaR+MD3d4/ZJg2llyuG2Vwy7QWAs8PNnn1f7vkGSGxAzE6mk+kxkx/p/4unffSCR0hAoL1EBCYiPNdWNcwkNQTCR7feWX6g+7f/A7I8rcw/U6UEe0Ndrhc/W7mtL9ztmqlSgstSS/zTJ28dalpOpkRryrwbhwBACgsLMWPGDOT4ll3qyeqAkJTdCF7P/CrUY/GkLL1rE+2hTbSH8+0Lb/WEuhzhyaA905blf9Vd/895WnZwLHrPevir/cvOB1oLYpTtLrm6oYGIMDExAaqtrUVKSgqYGSKCk0WHq5ikkWEWtNL0imv5qUW+RclLRjJsrhBAuH1/QL8R7HR4xy5nescuP23E6hOA6mLv+sb4uTw6Ogqqq6uDpmkQkcStorX4XRcM1FjZ+kvFFjCJKU1WpkNJJUqIMtX1RyLeX3JtQ0JRhmGYZ/L27duRnJycuFGISOJ9pqh5lrB6iYgqGOxRrOaa54DcZmKvkJxk8JHC9rKh+KVhOsD4+Dj+MwADIf8n5m4xGwAAAABJRU5ErkJggg==")}div.vis-network div.vis-edit-mode button.vis-button.vis-edit,div.vis-network div.vis-manipulation button.vis-button.vis-edit{background-image:url("data:image/png;base64,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/GMzLdsAAEEOaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNC0wMi0wNVQxNDoxMjoyNSswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTQtMDItMDVUMTQ6MTI6MjUrMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8eG1wTU06SW5zdGFuY2VJRD54bXAuaWlkOjY5OTM3ZGZjLTJjNzQtYTU0YS05OTIzLTQyMmZhNDNkMjljNDwveG1wTU06SW5zdGFuY2VJRD4KICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6RUE2MEEyNEUxOTg0RTMxMUFEQUZFRkU2RUMzMzNFMDM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDEtMjNUMTk6MTg6MDcrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDozOWNhNzE5ZC03YzNlLTUyNGEtYmY1NS03NGVmMmM1MzE0YTc8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDItMDVUMTQ6MTI6MjUrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjY5OTM3ZGZjLTJjNzQtYTU0YS05OTIzLTQyMmZhNDNkMjljNDwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNC0wMi0wNVQxNDoxMjoyNSswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06RGVyaXZlZEZyb20gcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICA8c3RSZWY6aW5zdGFuY2VJRD54bXAuaWlkOjM5Y2E3MTlkLTdjM2UtNTI0YS1iZjU1LTc0ZWYyYzUzMTRhNzwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwvc3RSZWY6ZG9jdW1lbnRJRD4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDA5MC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDkwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4ykninAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAYpSURBVHjafFZtTFvnFX7Oea+NudiY2Hwam4CBlgQwXdKREDKUoYg0jbRJ29RJ2VZ1mjRFUxSpA3VTfkzJfkQbS7spU6rtx5Z2UtppScjaHxvLuiatWi2jLEoMIUDCh23g2gbj7+tPuPvhOurawPl1dc99n+c55z33fV46ceIEZFkGADAziAgAQERoe/9ZK4GPM/AcgbsIXAcABCgMvkfAqAa89eDoJyF8LogIqqqChoaGYDAYHr8kItS8uc8iIH6iAa9IkAo5EAQX8pqmgUVBCBggYFgDhv0/GAsBgKZpICJkMhnQ4OAgZFkGEYGZUXmp+0cS+CKBwWA0DVRPOg5Zl2q6zaHyJlnVAMQXVTkwHrUqH0Xsvn+tdQAAMQDgpPLS2MViFY8rkGUZzIzaS/t/xqCzGggtz9e697zsnKhoLUtim4jOq/LE6x7X0nsh16dEZ5a/O3a2SCAOHjwInU6Hujd6ThJ4mCDQ+b2G232v7v6vwarPbQn8MGlMr+X0kpE3Wr5Zt5hL5HPhqYSdQIfKJ+yhxDPKWC6Xg+jt7UXD5b5KBt1kCHS85Ljd8/On3NupfnhFaZj4rWff1B98B1R/hnUmKd36bdtCNl4g0en4edNE/cXwLq8qMTMIPAQwmo/WuHvObA8+9c58k/dKtD0TyZWXN5YGA7ej7epKxspM//7SoNOdWc/Jyq2wiwhDzPxT8cP0jys3VMM7OmL0/77zn4Ydui3b8uiK0jD7RrA77c9Wd57cefPpF+2T6bWsFPWkaiPTCWvTsZpHFU+XrS+8G3AR08F6X+1FJvBxQQzHQOWk2SmrW4FPX/U2LVwPuDZj+fJKl2khPpeyAqA9rzR/YqwuiWXX8taN/CabGkrVuq9YJlkQQDjOAJ5jAhz9Vt9W4N5/rNp8I+vtMV/aZm4zLnUNNt0urdYnF68HWoJj4Wo1mLGUNRr8LEgDgNqeCh8xQIKOsgC7iAjVe83rT9zQa8uNM28u70kspessu8q8zq/V3NcZpVzb9+0zmVhOvvvrhaMVzrJg0zeq7xMVCCwdpnWSGBqjUyJwLTFgbvxie3w31uoWR1Y74r60rdxZqrR8q85t2W2MGCp12bm/KC3hyaSTiMhxuGrKcahqpbjOaDOoEhOEoFqJQCCJvqA85I6bfTdDjQlf2lbxVNlS6wt19yy7jRHZZlDnrinNj/6sHMhnNw2Ogco7O79e5fm/xQywRBBCEAuwn4gQ96bkYj4Vyuq9N1Z3Bj4Od5bs0MXt/dZZ21ctiqFan174q985P+Lfp+U1g7XDON/1ctP458WlVjLyJhOISZE0wM0S1QfuRC3lTjkJAKKEtNC9eIOhSh9xHLZOJRZTFuXDsEoStLkR/768ummsaJG9Pb9oe+9J+xaeSVokiQDSJphAo5uaBuWjiKP4QTqS1cUWU7ayesN66wu22frD1vmVW6GW6T8u9eVjGyZzs+w78Nqu0a2mbvVu1KEJQAgeZRL0liQYyx+GOmKeQpu0rMYsAJPNEFGD2dLodLIy6c9Ys7G8yeSUl3tf2/X3rcBVJSOv34l3sCBogi7z1LH/rBHjl4IJ93/ncQFAnjeImJD0Z8zuCwu9q3djDXqTlAKID5xv+9t2R8n8VcUFBljQ8Gyfe40BYBM4DwDLt8Kue79ZcFkbzfEdbUbv+oN4c9KTtsfm1MbYQqqh+2zrVZYKs/7Ef+byimt1POYiJhDhPBFBIiIEXhxfs7/dfYoIF+auBfYTE/pebx/V8hqBP2ODvD34yvuh/WCAmU75Bx6sIgaI/v5+6PV6JLqUsYr7dpDAoehs0h73pHTWrvKgThYbRSt9UmSjef3MpaUvBz4O72UmADgTOPJguGiZor+/HyUlJWBmJFz+D8xTtlUiOpbwpmrmrweeSXrT+g11k4SBN3RGKUcAVCVdFhyP1nreDbY//NPyEXUlU/Pp4XYycGT6V0Ux2WwWdO7cOZSWlkII8diX7SPPNgDaKdbxoNAxwATBAEkEEgSWCEQAqPAMwqvMdCEwMO0tVqZpWsGTT58+DaPR+PhGIYQAAAgh0P7B3ioW/B0iGiCGiwXbCuOHFSJys6AbYFye2T+xWhT3WYJEIoH/DQBMw3kes8OJPgAAAABJRU5ErkJggg==")}div.vis-network div.vis-edit-mode button.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation button.vis-button.vis-connect{background-image:url("data:image/png;base64,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/GMzLdsAAEEOaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNC0wMi0wNFQxNDozODo1NyswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTQtMDItMDRUMTQ6Mzg6NTcrMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8eG1wTU06SW5zdGFuY2VJRD54bXAuaWlkOjlmYjUwMDU0LWE3ODEtMWQ0OC05ZTllLTU2ZWQ5YzhlYjdjNjwveG1wTU06SW5zdGFuY2VJRD4KICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6RUE2MEEyNEUxOTg0RTMxMUFEQUZFRkU2RUMzMzNFMDM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDEtMjNUMTk6MTg6MDcrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDo3ZWRhMjI0MC0yYTQxLTNlNDQtYWM2My1iNzNiYTE5OWI3Y2E8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDItMDRUMTQ6Mzg6NTcrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjlmYjUwMDU0LWE3ODEtMWQ0OC05ZTllLTU2ZWQ5YzhlYjdjNjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNC0wMi0wNFQxNDozODo1NyswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06RGVyaXZlZEZyb20gcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICA8c3RSZWY6aW5zdGFuY2VJRD54bXAuaWlkOjdlZGEyMjQwLTJhNDEtM2U0NC1hYzYzLWI3M2JhMTk5YjdjYTwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwvc3RSZWY6ZG9jdW1lbnRJRD4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDA5MC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDkwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4ubxs+AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAUtSURBVHjajJZ/bNT1Gcdfz/P53PV6B4W7VltLqdAaplIOiMOoyxxJCSs/Gv/yB4gzJroAosmmDklwkYWR0bQsdmkykoojTpcsWYLxD/lRZdMQkTHRtkLZRqG0tIVe7662vTu43n32x/VKZ/jh89cn38/zvN7P5/l88zwf2blzJz6fDwARQUSm1n8s31CM0/VAnbNmsUPuAsDpgEO+Bg4C7//iyv5hvmMiQiqVQpqamvB6vVNwEeG1JZtCBrYi/MrkAwDNgjhwAlbzICBLA0rDb0+/839C6XQaaWxspLCw8Dp86cbNmqVFJQddE6KzdjZ9D89g+B6fSyCOcyn1nxil+O9xKg5HqWFSHGXLjrP7W/ICqVQK2bNnDz6fDxFh65KNvxbHDhF4rJj2bXPo+IGfcW5h5xL4f99P+FCEMIAob75x9t0dAMlkElNXV4e1lteXbNqiQoMaeOFOjrdU868SD2luYyEP6dUh+sYmSHeOU6GO5Z8VLx5+NNZxIpPJ5AS2L3upROCoCvz8Lo7vnkf77cAHhpiz/zIL9vWz8L8p/NvupmM0Q7pjnAoLqz8tDrc8MnQqYVUVhVdF4LEg7b+rvDn8wDDlH0WoPpukLJImSBaMwjcJqmwWts2jPZLG/8kwYVFeVdXXZcFf4yVDc2cNKfBFmD9X+0ncCP58F48eG+Feo2CAUkvs4dl0V/uJvdXLiiV+ut++n7YLSfxPfMMG54ChzB3WIesVWB2i82bw1AR6fJR7C4VsfYiv6u/k3A9nEgP4zXke8DiYHyAOMK+QxPIgnZ9GqSHr1itQJ8DK2fTerDQ+S/bHRXQJaHSCwNIZ2Xh+7+S3VAmwNMBA/tuPZtErgKquUmdMWIFlRURvdamRNEXGwIWrlP47pTMzLiunxghGMwTLvcTWlHAp77s4QNSrYMQtss6ZMgWqCm5cHoDHO1nbk6K8zEN8+3zatv2Hn1b59EqJZdxmYUERg9P9KwpIiAOTdWUWBXuLzB/vZG3P1Un4PNp2d1MbmyD45TWCxuCsQm0x56bHGHFYEZwxok7toAA9Sfw3hCcoL/NOwi9QO5wmWO1j4JEgZxTkodmcWRGkf3pcX0r8xoAaBixKu4U5/xwndM+0tpAvS6mP+PZK2nb1UBvPEKwKMLDvPj4ESGc55lGy303sdJKQdZB2rkMdctAB/4gzN+/Q2ENNd4LyUi/xN+bTtquX2thk5nk4wI3gAF+OMNcA1nFQDfK+BY5GqbkwWabTY5QZhXWlnNx1ntrY1Rz87fuvw29m/Sn8J+PUGAFj5T19baA1IspuBZp7cx1x4SwG1cEf+lgRSROs8jGwb+Ht4QB/GSSsAhYano39LWIBxNEIbP14hPDuiyS2VtJuHXQlKKvxM/jiXDq/D/xPlwifGMkJZB2NIoKpr69nxeiZxLHicFSFVWfGqBidIP3LSjrWltD94CyufF/4kQgPuVz2Lz93+dDRa9eu5QQ8Hg8/iXee+Dy4CKMs7xqn4nwKz9IirhQqmVuB42m8ey+x7LMoD6iAON782eChhqmRuXfvXgKBAKqKqtI0/8nNKrQI4BVYXkzHgzPpC88gWuHL/caXrhLoGiN0apSKr0ZZRBZM7q2w5ZnLR1oAnHOMjY0hra2tFBQUYIyZmstvVT1Z6eDlAuEVq7merxmwueNPDXy9PvybjKP5mctHLk4/XTKZRJqbm/H7/VNw1VyEMYbW4FN3WNWnnchKoy5sHeVGBRX6VWi3ymFx7r11Ix8MTX/y5C2RSPC/AQB61erowbpqSwAAAABJRU5ErkJggg==")}div.vis-network div.vis-manipulation button.vis-button.vis-delete{background-image:url("data:image/png;base64,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/GMzLdsAAEEOaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNC0wMi0wNFQxNDo0MTowNCswMTowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMTQtMDItMDRUMTQ6NDE6MDQrMDE6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8eG1wTU06SW5zdGFuY2VJRD54bXAuaWlkOjc3NDkzYmUxLTEyZGItOTg0NC1iNDYyLTg2NGVmNGIzMzM3MTwveG1wTU06SW5zdGFuY2VJRD4KICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE0LTAxLTIyVDE5OjI0OjUxKzAxOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6RUE2MEEyNEUxOTg0RTMxMUFEQUZFRkU2RUMzMzNFMDM8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDEtMjNUMTk6MTg6MDcrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+c2F2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0Omluc3RhbmNlSUQ+eG1wLmlpZDowNmE3NWYwMy04MDdhLWUzNGYtYjk1Zi1jZGU2MjM0Mzg4OGY8L3N0RXZ0Omluc3RhbmNlSUQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDp3aGVuPjIwMTQtMDItMDRUMTQ6NDE6MDQrMDE6MDA8L3N0RXZ0OndoZW4+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpzb2Z0d2FyZUFnZW50PkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jb252ZXJ0ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+ZnJvbSBhcHBsaWNhdGlvbi92bmQuYWRvYmUucGhvdG9zaG9wIHRvIGltYWdlL3BuZzwvc3RFdnQ6cGFyYW1ldGVycz4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPmRlcml2ZWQ8L3N0RXZ0OmFjdGlvbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnBhcmFtZXRlcnM+Y29udmVydGVkIGZyb20gYXBwbGljYXRpb24vdm5kLmFkb2JlLnBob3Rvc2hvcCB0byBpbWFnZS9wbmc8L3N0RXZ0OnBhcmFtZXRlcnM+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjc3NDkzYmUxLTEyZGItOTg0NC1iNDYyLTg2NGVmNGIzMzM3MTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNC0wMi0wNFQxNDo0MTowNCswMTowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8eG1wTU06RGVyaXZlZEZyb20gcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICA8c3RSZWY6aW5zdGFuY2VJRD54bXAuaWlkOjA2YTc1ZjAzLTgwN2EtZTM0Zi1iOTVmLWNkZTYyMzQzODg4Zjwvc3RSZWY6aW5zdGFuY2VJRD4KICAgICAgICAgICAgPHN0UmVmOmRvY3VtZW50SUQ+eG1wLmRpZDpFQTc2MkY5Njc0ODNFMzExOTQ4QkQxM0UyQkU3OTlBMTwvc3RSZWY6ZG9jdW1lbnRJRD4KICAgICAgICAgICAgPHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD54bXAuZGlkOjczQjYyQUFEOTE4M0UzMTE5NDhCRDEzRTJCRTc5OUExPC9zdFJlZjpvcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDwveG1wTU06RGVyaXZlZEZyb20+CiAgICAgICAgIDxwaG90b3Nob3A6Q29sb3JNb2RlPjM8L3Bob3Rvc2hvcDpDb2xvck1vZGU+CiAgICAgICAgIDxwaG90b3Nob3A6SUNDUHJvZmlsZT5zUkdCIElFQzYxOTY2LTIuMTwvcGhvdG9zaG9wOklDQ1Byb2ZpbGU+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDA5MC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDkwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjI0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4aYJzYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAYGSURBVHjalJZ7UJTnFcZ/73m/72PdJY1RbhoQp6lkXRAvmIYxdCUadLVOozPNtGObap1JsKipjiShbdoRbeKEiQHpQK3xj0xa03aamTbaTGyAYV1QGeqFi+JyiZFLAlmESBkWRmS3fyzslGkmnZ5/v/M873Oe75zzvqqoqAibzQaAiKCUAkApRdHIK/NFsx2NR91nOSILADDoJyzNaM4xxbtvPHh0iC+JiYkJ1OHDh4mJiUEpFSXPv/ziPC28TIiXDCOSrAClQDSEpsCwJPIhrEBRQpiSytXlQwDhcBilFPfu3UMVFxdjt9ujFTzfcLBADCoEEAFr1ZbrrNjch2vtEImPBgHob7fTcWE+bVXJNJ/NiFQlEGLvieXHKmYqGB8fRx05cgSbzYaIsPvywV8pKFaA7fGtLTzz61YWpo/xVTHQbufsq5lcez9zWuWhk5mvFwMEg0H0+vXrMU2Tn1wp3CtCiQ5DjGd3A/m/v8IDCZP8r4iNmyRrWx/j/5qktykZpXKzAjVDVxPzGqemptDr1q1jX3NRnIJarcDKK2hgR2ULXRfncv7UYv7xpovhnhiW5Mz+kefeSKO6LJ1A1xzEuk/Ojm4mRibpuZaMZW3OCtRUND60NmiICCIUShisx7a2sLMiQn4s77uEQgIabnqdfHIlgT1/qQeg8vs5dHhdCNB1wYn3RIiC995j26stjAbsNH+YiZJCESnS1Y/XxIXu8r4YIPv/VkVs3CTnTy2ms34xro1+sp9po6sxlTu34ultmsPVvy6is86FCHgO+DDs49zpjufBpCG+seYOC9OHaTidieicb9ouVAhKtouAseI710ma7pLuqwmgYfHqAFt+6WdLoQ/LBl11Lm7VudAa8vb72PCin9TlAWIsGGhLACD+kSAZnusYBii1XQAPYWDllt6ov2lrBkDBR2+6Ofuak2//3M+G/T4wAAPW7fPhKfRTVeqk9qQbFKRmDUTxS3N7QYGYmwzCkqklBGlPDEcTNv+sg9tNCbTXuvBWujE0bHrZj9JE1B/wU1Pm5PwJN6YBS9a2kVvQEcWnrh5GTFD3lxkYkqRMgYQlwVldUvDnen73LHTUuqitdKM0eAr9AFQfd1J/yo2aJn+2sn4Wdn5qEFODJskgBIjx5T0uCrQA08pnIjS9PERDjPnfOKXAMEBECUoGEIHBj+2zkt76UQ6dXheGAev3+cg74Kf6uJPqcicbfuond7cPy4SOiy7+tD9nFvZurx00KOk3CNEC+mE+vjSPBc7IWqgqTaPT60IMcO/xsXGa3HfKjRgRdbl7/KDg0jtubje6aHj7c7J3dgLQ2zoPwwQ91SooOQdAW1VKVMHty0kA5Bb48BycJn/LjWFGbLv4thvvb53kFvjJ+XEdWkPfjQVR/CcNKYgGMc8JWt5Fa2j+MIPPuyI2pa4IoHSkt6vLIuRaQ9q32khzt4GCxtNu6k46GeiIR2lIfDQQsafPzq1LGRGL9Gk9d+vrwewvfHPQOoexQVjxdB/auk/zmaUMdsfz6bVUtIalT7bxveP1ZHh6GPDPYeSzeD69kcpIfxymFWLNrka+ljhBTWkWwz2JiJT84YHnz2iPx0P20PkmRF5i6HYiwZFJsn/YzdezbzE3cQibY5xV266z6RfXohakb+xB9CjanCD9qTbW7Grk4WV38VZm0l6dhQiEw9taHSuDqrS0FIfDwXM3X9mHMsvRAk/sauDpQy38P+GtzOTGB9mEpkD0C2dS8n8zOjqK9ng8WJZFU+JTjasGvaCNXPpvJBPoMlm0OoDNMfWVxONfWNSUPUZ7TUQ56tCZlPwSgMnJSVRpaSmxsbFE1raw82ZxAZZRQUiBYUKGp5UlOX2krBzmoUVjiIKhHge9rfPo+Wcy3ZeXIYASgL1/X5RfMXMvj46OosrLy7HZbGitUUohIuzoem0RofALaOsghgWGjky0MiJTL8b0lOvI8hN1DKXKP0jd3TNTWDgcJhgMoo4ePYrD4Yi+KmaeLlprnrtXFo9h/AAlG1AqE8yFmBrC+jO0bgH9EVpO/1F2Dc5g//OAsbEx/j0Af+USsQynL1UAAAAASUVORK5CYII=")}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px} \ No newline at end of file diff --git a/queue_job/static/lib/vis/vis-network.min.js b/queue_job/static/lib/vis/vis-network.min.js index c7ae08b02b..aa1897181e 100644 --- a/queue_job/static/lib/vis/vis-network.min.js +++ b/queue_job/static/lib/vis/vis-network.min.js @@ -1,30 +1,27 @@ /** - * vis.js - * https://github.com/almende/vis + * vis-network + * https://visjs.github.io/vis-network/ * * A dynamic, browser-based visualization library. * - * @version 4.21.0 - * @date 2017-10-12 + * @version 9.0.4 + * @date 2021-03-16T05:44:27.440Z * - * @license - * Copyright (C) 2011-2017 Almende B.V, http://almende.com + * @copyright (c) 2011-2017 Almende B.V, http://almende.com + * @copyright (c) 2017-2019 visjs contributors, https://github.com/visjs * - * Vis.js is dual licensed under both + * @license + * vis.js is dual licensed under both * - * * The Apache 2.0 License - * http://www.apache.org/licenses/LICENSE-2.0 + * 1. The Apache 2.0 License + * http://www.apache.org/licenses/LICENSE-2.0 * - * and + * and * - * * The MIT License - * http://opensource.org/licenses/MIT + * 2. The MIT License + * http://opensource.org/licenses/MIT * - * Vis.js may be distributed under either license. + * vis.js may be distributed under either license. */ -"use strict";!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.vis=t():e.vis=t()}(this,function(){return function(e){function t(n){if(i[n])return i[n].exports;var o=i[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var i={};return t.m=e,t.c=i,t.d=function(e,i,n){t.o(e,i)||Object.defineProperty(e,i,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var i=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(i,"a",i),i},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=83)}([function(e,t,i){t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,i){t.__esModule=!0;var n=i(127),o=function(e){return e&&e.__esModule?e:{default:e}}(n);t.default=function(){function e(e,t){for(var i=0;i2&&void 0!==arguments[2]&&arguments[2];for(var s in e)void 0!==i[s]&&(null===i[s]||"object"!==(0,c.default)(i[s])?o(e,i,s,n):"object"===(0,c.default)(e[s])&&t.fillIfDefined(e[s],i[s],n))},t.extend=function(e,t){for(var i=1;i3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(n))throw new TypeError("Arrays are not supported by deepExtend");for(var r=0;r3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(n))throw new TypeError("Arrays are not supported by deepExtend");for(var r in n)if(n.hasOwnProperty(r)&&-1===e.indexOf(r))if(n[r]&&n[r].constructor===Object)void 0===i[r]&&(i[r]={}),i[r].constructor===Object?t.deepExtend(i[r],n[r]):o(i,n,r,s);else if(Array.isArray(n[r])){i[r]=[];for(var a=0;a2&&void 0!==arguments[2]&&arguments[2],s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];for(var r in i)if(i.hasOwnProperty(r)||!0===n)if(i[r]&&i[r].constructor===Object)void 0===e[r]&&(e[r]={}),e[r].constructor===Object?t.deepExtend(e[r],i[r],n):o(e,i,r,s);else if(Array.isArray(i[r])){e[r]=[];for(var a=0;a=0&&(t="DOMMouseScroll"),e.addEventListener(t,i,n)):e.attachEvent("on"+t,i)},t.removeEventListener=function(e,t,i,n){e.removeEventListener?(void 0===n&&(n=!1),"mousewheel"===t&&navigator.userAgent.indexOf("Firefox")>=0&&(t="DOMMouseScroll"),e.removeEventListener(t,i,n)):e.detachEvent("on"+t,i)},t.preventDefault=function(e){e||(e=window.event),e.preventDefault?e.preventDefault():e.returnValue=!1},t.getTarget=function(e){e||(e=window.event);var t;return e.target?t=e.target:e.srcElement&&(t=e.srcElement),void 0!=t.nodeType&&3==t.nodeType&&(t=t.parentNode),t},t.hasParent=function(e,t){for(var i=e;i;){if(i===t)return!0;i=i.parentNode}return!1},t.option={},t.option.asBoolean=function(e,t){return"function"==typeof e&&(e=e()),null!=e?0!=e:t||null},t.option.asNumber=function(e,t){return"function"==typeof e&&(e=e()),null!=e?Number(e)||t||null:t||null},t.option.asString=function(e,t){return"function"==typeof e&&(e=e()),null!=e?String(e):t||null},t.option.asSize=function(e,i){return"function"==typeof e&&(e=e()),t.isString(e)?e:t.isNumber(e)?e+"px":i||null},t.option.asElement=function(e,t){return"function"==typeof e&&(e=e()),e||t||null},t.hexToRGB=function(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(t,function(e,t,i,n){return t+t+i+i+n+n});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return i?{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}:null},t.overrideOpacity=function(e,i){var n;return-1!=e.indexOf("rgba")?e:-1!=e.indexOf("rgb")?(n=e.substr(e.indexOf("(")+1).replace(")","").split(","),"rgba("+n[0]+","+n[1]+","+n[2]+","+i+")"):(n=t.hexToRGB(e),null==n?e:"rgba("+n.r+","+n.g+","+n.b+","+i+")")},t.RGBToHex=function(e,t,i){return"#"+((1<<24)+(e<<16)+(t<<8)+i).toString(16).slice(1)},t.parseColor=function(e){var i;if(!0===t.isString(e)){if(!0===t.isValidRGB(e)){var n=e.substr(4).substr(0,e.length-5).split(",").map(function(e){return parseInt(e)});e=t.RGBToHex(n[0],n[1],n[2])}if(!0===t.isValidHex(e)){var o=t.hexToHSV(e),s={h:o.h,s:.8*o.s,v:Math.min(1,1.02*o.v)},r={h:o.h,s:Math.min(1,1.25*o.s),v:.8*o.v},a=t.HSVToHex(r.h,r.s,r.v),d=t.HSVToHex(s.h,s.s,s.v);i={background:e,border:a,highlight:{background:d,border:a},hover:{background:d,border:a}}}else i={background:e,border:e,highlight:{background:e,border:e},hover:{background:e,border:e}}}else i={},i.background=e.background||void 0,i.border=e.border||void 0,t.isString(e.highlight)?i.highlight={border:e.highlight,background:e.highlight}:(i.highlight={},i.highlight.background=e.highlight&&e.highlight.background||void 0,i.highlight.border=e.highlight&&e.highlight.border||void 0),t.isString(e.hover)?i.hover={border:e.hover,background:e.hover}:(i.hover={},i.hover.background=e.hover&&e.hover.background||void 0,i.hover.border=e.hover&&e.hover.border||void 0);return i},t.RGBToHSV=function(e,t,i){e/=255,t/=255,i/=255;var n=Math.min(e,Math.min(t,i)),o=Math.max(e,Math.max(t,i));if(n==o)return{h:0,s:0,v:n};var s=e==n?t-i:i==n?e-t:i-e;return{h:60*((e==n?3:i==n?1:5)-s/(o-n))/360,s:(o-n)/o,v:o}};var g={split:function(e){var t={};return e.split(";").forEach(function(e){if(""!=e.trim()){var i=e.split(":"),n=i[0].trim(),o=i[1].trim();t[n]=o}}),t},join:function(e){return(0,l.default)(e).map(function(t){return t+": "+e[t]}).join("; ")}};t.addCssText=function(e,i){var n=g.split(e.style.cssText),o=g.split(i),s=t.extend(n,o);e.style.cssText=g.join(s)},t.removeCssText=function(e,t){var i=g.split(e.style.cssText),n=g.split(t);for(var o in n)n.hasOwnProperty(o)&&delete i[o];e.style.cssText=g.join(i)},t.HSVToRGB=function(e,t,i){var n,o,s,r=Math.floor(6*e),a=6*e-r,d=i*(1-t),h=i*(1-a*t),l=i*(1-(1-a)*t);switch(r%6){case 0:n=i,o=l,s=d;break;case 1:n=h,o=i,s=d;break;case 2:n=d,o=i,s=l;break;case 3:n=d,o=h,s=i;break;case 4:n=l,o=d,s=i;break;case 5:n=i,o=d,s=h}return{r:Math.floor(255*n),g:Math.floor(255*o),b:Math.floor(255*s)}},t.HSVToHex=function(e,i,n){var o=t.HSVToRGB(e,i,n);return t.RGBToHex(o.r,o.g,o.b)},t.hexToHSV=function(e){var i=t.hexToRGB(e);return t.RGBToHSV(i.r,i.g,i.b)},t.isValidHex=function(e){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(e)},t.isValidRGB=function(e){return e=e.replace(" ",""),/rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(e)},t.isValidRGBA=function(e){return e=e.replace(" ",""),/rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(e)},t.selectiveBridgeObject=function(e,i){if(null!==i&&"object"===(void 0===i?"undefined":(0,c.default)(i))){for(var n=(0,d.default)(i),o=0;o0&&t(n,e[o-1])<0;o--)e[o]=e[o-1];e[o]=n}return e},t.mergeOptions=function(e,t,i){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=function(e){return null!==e&&void 0!==e},s=function(e){return null!==e&&"object"===(void 0===e?"undefined":(0,c.default)(e))};if(!s(e))throw new Error("Parameter mergeTarget must be an object");if(!s(t))throw new Error("Parameter options must be an object");if(!o(i))throw new Error("Parameter option must have a value");if(!s(n))throw new Error("Parameter globalOptions must be an object");var r=t[i],a=s(n)&&!function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0}(n),h=a?n[i]:void 0,l=h?h.enabled:void 0;if(void 0!==r){if("boolean"==typeof r)return s(e[i])||(e[i]={}),void(e[i].enabled=r);if(null===r&&!s(e[i])){if(!o(h))return;e[i]=(0,d.default)(h)}if(s(r)){var u=!0;void 0!==r.enabled?u=r.enabled:void 0!==l&&(u=h.enabled),function(e,t,i){s(e[i])||(e[i]={});var n=t[i],o=e[i];for(var r in n)n.hasOwnProperty(r)&&(o[r]=n[r])}(e,t,i),e[i].enabled=u}}},t.binarySearchCustom=function(e,t,i,n){for(var o=0,s=0,r=e.length-1;s<=r&&o<1e4;){var a=Math.floor((s+r)/2),d=e[a],h=void 0===n?d[i]:d[i][n],l=t(h);if(0==l)return a;-1==l?s=a+1:r=a-1,o++}return-1},t.binarySearchValue=function(e,t,i,n,o){var s,r,a,d,h=0,l=0,u=e.length-1;for(o=void 0!=o?o:function(e,t){return e==t?0:e0)return"before"==n?Math.max(0,d-1):d;if(o(r,t)<0&&o(a,t)>0)return"before"==n?d:Math.min(e.length-1,d+1);o(r,t)<0?l=d+1:u=d-1,h++}return-1},t.easingFunctions={linear:function(e){return e},easeInQuad:function(e){return e*e},easeOutQuad:function(e){return e*(2-e)},easeInOutQuad:function(e){return e<.5?2*e*e:(4-2*e)*e-1},easeInCubic:function(e){return e*e*e},easeOutCubic:function(e){return--e*e*e+1},easeInOutCubic:function(e){return e<.5?4*e*e*e:(e-1)*(2*e-2)*(2*e-2)+1},easeInQuart:function(e){return e*e*e*e},easeOutQuart:function(e){return 1- --e*e*e*e},easeInOutQuart:function(e){return e<.5?8*e*e*e*e:1-8*--e*e*e*e},easeInQuint:function(e){return e*e*e*e*e},easeOutQuint:function(e){return 1+--e*e*e*e*e},easeInOutQuint:function(e){return e<.5?16*e*e*e*e*e:1+16*--e*e*e*e*e}},t.getScrollBarWidth=function(){var e=document.createElement("p");e.style.width="100%",e.style.height="200px";var t=document.createElement("div");t.style.position="absolute",t.style.top="0px",t.style.left="0px",t.style.visibility="hidden",t.style.width="200px",t.style.height="150px",t.style.overflow="hidden",t.appendChild(e),document.body.appendChild(t);var i=e.offsetWidth;t.style.overflow="scroll";var n=e.offsetWidth;return i==n&&(n=t.clientWidth),document.body.removeChild(t),i-n},t.topMost=function(e,t){var i=void 0;Array.isArray(t)||(t=[t]);var n=!0,o=!1,s=void 0;try{for(var a,d=(0,r.default)(e);!(n=(a=d.next()).done);n=!0){var h=a.value;if(h){i=h[t[0]];for(var l=1;l0&&(this.enableBorderDashes(e,t),e.stroke(),this.disableBorderDashes(e,t)),e.restore()}},{key:"performFill",value:function(e,t){this.enableShadow(e,t),e.fill(),this.disableShadow(e,t),this.performStroke(e,t)}},{key:"_addBoundingBoxMargin",value:function(e){this.boundingBox.left-=e,this.boundingBox.top-=e,this.boundingBox.bottom+=e,this.boundingBox.right+=e}},{key:"_updateBoundingBox",value:function(e,t,i,n,o){void 0!==i&&this.resize(i,n,o),this.left=e-this.width/2,this.top=t-this.height/2,this.boundingBox.left=this.left,this.boundingBox.top=this.top,this.boundingBox.bottom=this.top+this.height,this.boundingBox.right=this.left+this.width}},{key:"updateBoundingBox",value:function(e,t,i,n,o){this._updateBoundingBox(e,t,i,n,o)}},{key:"getDimensionsFromLabel",value:function(e,t,i){this.textSize=this.labelModule.getTextSize(e,t,i);var n=this.textSize.width,o=this.textSize.height;return 0===n&&(n=14,o=14),{width:n,height:o}}}]),e}();t.default=l},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(15),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{size:this.options.size};if(this.needsRefresh(t,i)){this.labelModule.getTextSize(e,t,i);var o=2*n.size;this.width=o,this.height=o,this.radius=.5*this.width}}},{key:"_drawShape",value:function(e,t,i,n,o,s,r,a){if(this.resize(e,s,r,a),this.left=n-this.width/2,this.top=o-this.height/2,this.initContextForDraw(e,a),e[t](n,o,a.size),this.performFill(e,a),void 0!==this.options.label){this.labelModule.calculateLabelSize(e,s,r,n,o,"hanging");var d=o+.5*this.height+.5*this.labelModule.size.height;this.labelModule.draw(e,n,d,s,r,"hanging")}this.updateBoundingBox(n,o)}},{key:"updateBoundingBox",value:function(e,t){this.boundingBox.top=t-this.options.size,this.boundingBox.left=e-this.options.size,this.boundingBox.right=e+this.options.size,this.boundingBox.bottom=t+this.options.size,void 0!==this.options.label&&this.labelModule.size.width>0&&(this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelModule.size.height))}}]),t}(v.default);t.default=g},function(e,t,i){var n=i(59),o=i(38);e.exports=function(e){return n(o(e))}},function(e,t,i){var n=i(12),o=i(27);e.exports=i(13)?function(e,t,i){return n.f(e,t,o(1,i))}:function(e,t,i){return e[t]=i,e}},function(e,t,i){var n=i(25);e.exports=function(e){if(!n(e))throw TypeError(e+" is not an object!");return e}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,i){e.exports={default:i(120),__esModule:!0}},function(e,t,i){function n(){var e=function(){};return{on:e,off:e,destroy:e,emit:e,get:function(t){return{set:e}}}}if("undefined"!=typeof window){var o=i(124),s=window.Hammer||i(125);e.exports=o(s,{preventDefault:"mouse"})}else e.exports=function(){return n()}},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var o=i(133),s=n(o),r=i(58),a=n(r);t.default=function(){function e(e,t){var i=[],n=!0,o=!1,s=void 0;try{for(var r,d=(0,a.default)(e);!(n=(r=d.next()).done)&&(i.push(r.value),!t||i.length!==t);n=!0);}catch(e){o=!0,s=e}finally{try{!n&&d.return&&d.return()}finally{if(o)throw s}}return i}return function(t,i){if(Array.isArray(t))return t;if((0,s.default)(Object(t)))return e(t,i);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}()},function(e,t){e.exports={}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,i){var n=i(65),o=i(45);e.exports=Object.keys||function(e){return n(e,o)}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t){var i=0,n=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++i+n).toString(36))}},function(e,t,i){var n=i(38);e.exports=function(e){return Object(n(e))}},function(e,t,i){e.exports={default:i(98),__esModule:!0}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(e&&!Array.isArray(e)&&(t=e,e=null),this._options=t||{},this._data={},this.length=0,this._fieldId=this._options.fieldId||"id",this._type={},this._options.type)for(var i=(0,l.default)(this._options.type),n=0,o=i.length;no?1:nr)&&(s=d,r=h)}return s},o.prototype.min=function(e){var t,i,n=this._data,o=(0,l.default)(n),s=null,r=null;for(t=0,i=o.length;te.left&&this.shape.tope.top}},{key:"isBoundingBoxOverlappingWith",value:function(e){return this.shape.boundingBox.lefte.left&&this.shape.boundingBox.tope.top}}],[{key:"updateGroupOptions",value:function(e,t,i){if(void 0!==i){var n=e.group;if(void 0!==t&&void 0!==t.group&&n!==t.group)throw new Error("updateGroupOptions: group values in options don't match.");if("number"==typeof n||"string"==typeof n&&""!=n){var o=i.get(n);d.selectiveNotDeepExtend(["font"],e,o),e.color=d.parseColor(e.color)}}}},{key:"parseOptions",value:function(t,i){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},s=arguments[4],r=["color","fixed","shadow"];if(d.selectiveNotDeepExtend(r,t,i,n),e.checkMass(i),d.mergeOptions(t,i,"shadow",o),void 0!==i.color&&null!==i.color){var a=d.parseColor(i.color);d.fillIfDefined(t.color,a)}else!0===n&&null===i.color&&(t.color=d.bridgeObject(o.color));void 0!==i.fixed&&null!==i.fixed&&("boolean"==typeof i.fixed?(t.fixed.x=i.fixed,t.fixed.y=i.fixed):(void 0!==i.fixed.x&&"boolean"==typeof i.fixed.x&&(t.fixed.x=i.fixed.x),void 0!==i.fixed.y&&"boolean"==typeof i.fixed.y&&(t.fixed.y=i.fixed.y))),!0===n&&null===i.font&&(t.font=d.bridgeObject(o.font)),e.updateGroupOptions(t,i,s),void 0!==i.scaling&&d.mergeOptions(t.scaling,i.scaling,"label",o.scaling)}},{key:"checkMass",value:function(e,t){if(void 0!==e.mass&&e.mass<=0){var i="";void 0!==t&&(i=" in node id: "+t),console.log("%cNegative or zero mass disallowed"+i+", setting mass to 1.",S),e.mass=1}}}]),e}();t.default=D},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(7),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(5),u=function(){function e(){(0,a.default)(this,e)}return(0,h.default)(e,null,[{key:"choosify",value:function(e,t){var i=["node","edge","label"],n=!0,o=l.topMost(t,"chosen");if("boolean"==typeof o)n=o;else if("object"===(void 0===o?"undefined":(0,s.default)(o))){if(-1===i.indexOf(e))throw new Error("choosify: subOption '"+e+"' should be one of '"+i.join("', '")+"'");var r=l.topMost(t,["chosen",e]);"boolean"!=typeof r&&"function"!=typeof r||(n=r)}return n}},{key:"pointInRect",value:function(e,t,i){if(e.width<=0||e.height<=0)return!1;if(void 0!==i){var n={x:t.x-i.x,y:t.y-i.y};if(0!==i.angle){var o=-i.angle;t={x:Math.cos(o)*n.x-Math.sin(o)*n.y,y:Math.sin(o)*n.x+Math.cos(o)*n.y}}else t=n}var s=e.x+e.width,r=e.y+e.width;return e.leftt.x&&e.topt.y}},{key:"isValidLabel",value:function(e){return"string"==typeof e&&""!==e}}]),e}();t.default=u},function(e,t,i){t.onTouch=function(e,t){t.inputHandler=function(e){e.isFirst&&t(e)},e.on("hammer.input",t.inputHandler)},t.onRelease=function(e,t){return t.inputHandler=function(e){e.isFinal&&t(e)},e.on("hammer.input",t.inputHandler)},t.offTouch=function(e,t){e.off("hammer.input",t.inputHandler)},t.offRelease=t.offTouch,t.disablePreventDefaultVertically=function(e){return e.getTouchAction=function(){return["pan-y"]},e}},function(e,t,i){i(85);for(var n=i(9),o=i(18),s=i(24),r=i(8)("toStringTag"),a="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),d=0;ddocument.F=Object<\/script>"),e.close(),d=e.F;n--;)delete d.prototype[s[n]];return d()};e.exports=Object.create||function(e,t){var i;return null!==e?(a.prototype=n(e),i=new a,a.prototype=null,i[r]=e):i=d(),void 0===t?i:o(i,t)}},function(e,t){var i=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:i)(e)}},function(e,t,i){var n=i(44)("keys"),o=i(28);e.exports=function(e){return n[e]||(n[e]=o(e))}},function(e,t,i){var n=i(9),o=n["__core-js_shared__"]||(n["__core-js_shared__"]={});e.exports=function(e){return o[e]||(o[e]={})}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,i){var n=i(12).f,o=i(14),s=i(8)("toStringTag");e.exports=function(e,t,i){e&&!o(e=i?e:e.prototype,s)&&n(e,s,{configurable:!0,value:t})}},function(e,t,i){var n=i(95)(!0);i(60)(String,"String",function(e){this._t=String(e),this._i=0},function(){var e,t=this._t,i=this._i;return i>=t.length?{value:void 0,done:!0}:(e=n(t,i),this._i+=e.length,{value:e,done:!1})})},function(e,t,i){t.f=i(8)},function(e,t,i){var n=i(9),o=i(6),s=i(39),r=i(48),a=i(12).f;e.exports=function(e){var t=o.Symbol||(o.Symbol=s?{}:n.Symbol||{});"_"==e.charAt(0)||e in t||a(t,e,{value:r.f(e)})}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,i){function n(e,t){this._data=null,this._ids={},this.length=0,this._options=t||{},this._fieldId="id",this._subscribers={};var i=this;this.listener=function(){i._onEvent.apply(i,arguments)},this.setData(e)}var o=i(10),s=function(e){return e&&e.__esModule?e:{default:e}}(o),r=i(5),a=i(32);n.prototype.setData=function(e){var t,i,n,o,s;if(this._data){for(this._data.off&&this._data.off("*",this.listener),t=this._data.getIds({filter:this._options&&this._options.filter}),s=[],n=0,o=t.length;nthis.imageObj.height?i=this.imageObj.width/this.imageObj.height:n=this.imageObj.height/this.imageObj.width),e=2*this.options.size*i,t=2*this.options.size*n}else e=this.imageObj.width,t=this.imageObj.height;this.width=e,this.height=t,this.radius=.5*this.width}},{key:"_drawRawCircle",value:function(e,t,i,n){this.initContextForDraw(e,n),e.circle(t,i,n.size),this.performFill(e,n)}},{key:"_drawImageAtPosition",value:function(e,t){if(0!=this.imageObj.width){e.globalAlpha=1,this.enableShadow(e,t);var i=1;!0===this.options.shapeProperties.interpolation&&(i=this.imageObj.width/this.width/this.body.view.scale),this.imageObj.drawImageAtPosition(e,i,this.left,this.top,this.width,this.height),this.disableShadow(e,t)}}},{key:"_drawImageLabel",value:function(e,t,i,n,o){var s,r=0;if(void 0!==this.height){r=.5*this.height;var a=this.labelModule.getTextSize(e,n,o);a.lineCount>=1&&(r+=a.height/2)}s=i+r,this.options.label&&(this.labelOffset=r),this.labelModule.draw(e,t,s,n,o,"hanging")}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.printStyle=void 0;var o=i(21),s=n(o),r=i(7),a=n(r),d=i(10),h=n(d),l=i(0),u=n(l),c=i(1),f=n(c),p=i(5),v=!1,g=void 0,y="background: #FFeeee; color: #dd0000",m=function(){function e(){(0,u.default)(this,e)}return(0,f.default)(e,null,[{key:"validate",value:function(t,i,n){v=!1,g=i;var o=i;return void 0!==n&&(o=i[n]),e.parse(t,o,[]),v}},{key:"parse",value:function(t,i,n){for(var o in t)t.hasOwnProperty(o)&&e.check(o,t,i,n)}},{key:"check",value:function(t,i,n,o){if(void 0===n[t]&&void 0===n.__any__)return void e.getSuggestion(t,n,o);var s=t,r=!0;void 0===n[t]&&void 0!==n.__any__&&(s="__any__",r="object"===e.getType(i[t]));var a=n[s];r&&void 0!==a.__type__&&(a=a.__type__),e.checkFields(t,i,n,s,a,o)}},{key:"checkFields",value:function(t,i,n,o,s,r){var a=function(i){console.log("%c"+i+e.printLocation(r,t),y)},d=e.getType(i[t]),l=s[d];void 0!==l?"array"===e.getType(l)&&-1===l.indexOf(i[t])?(a('Invalid option detected in "'+t+'". Allowed values are:'+e.print(l)+' not "'+i[t]+'". '),v=!0):"object"===d&&"__any__"!==o&&(r=p.copyAndExtendArray(r,t),e.parse(i[t],n[o],r)):void 0===s.any&&(a('Invalid type received for "'+t+'". Expected: '+e.print((0,h.default)(s))+". Received ["+d+'] "'+i[t]+'"'),v=!0)}},{key:"getType",value:function(e){var t=void 0===e?"undefined":(0,a.default)(e);return"object"===t?null===e?"null":e instanceof Boolean?"boolean":e instanceof Number?"number":e instanceof String?"string":Array.isArray(e)?"array":e instanceof Date?"date":void 0!==e.nodeType?"dom":!0===e._isAMomentObject?"moment":"object":"number"===t?"number":"boolean"===t?"boolean":"string"===t?"string":void 0===t?"undefined":t}},{key:"getSuggestion",value:function(t,i,n){var o=e.findInOptions(t,i,n,!1),s=e.findInOptions(t,g,[],!0),r=void 0;r=void 0!==o.indexMatch?" in "+e.printLocation(o.path,t,"")+'Perhaps it was incomplete? Did you mean: "'+o.indexMatch+'"?\n\n':s.distance<=4&&o.distance>s.distance?" in "+e.printLocation(o.path,t,"")+"Perhaps it was misplaced? Matching option found at: "+e.printLocation(s.path,s.closestMatch,""):o.distance<=8?'. Did you mean "'+o.closestMatch+'"?'+e.printLocation(o.path,t):". Did you mean one of these: "+e.print((0,h.default)(i))+e.printLocation(n,t),console.log('%cUnknown option detected: "'+t+'"'+r,y),v=!0}},{key:"findInOptions",value:function(t,i,n){var o=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s=1e9,r="",a=[],d=t.toLowerCase(),h=void 0;for(var l in i){var u=void 0;if(void 0!==i[l].__type__&&!0===o){var c=e.findInOptions(t,i[l],p.copyAndExtendArray(n,l));s>c.distance&&(r=c.closestMatch,a=c.path,s=c.distance,h=c.indexMatch)}else-1!==l.toLowerCase().indexOf(d)&&(h=l),u=e.levenshteinDistance(t,l),s>u&&(r=l,a=p.copyArray(n),s=u)}return{closestMatch:r,path:a,distance:s,indexMatch:h}}},{key:"printLocation",value:function(e,t){for(var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Problem value found at: \n",n="\n\n"+i+"options = {\n",o=0;oi.shape.height?(r=i.x+.5*i.shape.width,a=i.y-d):(r=i.x+d,a=i.y-.5*i.shape.height),o=this._pointOnCircle(r,a,d,.125),this.labelModule.draw(e,o.x,o.y,this.selected,this.hover)}}}},{key:"getItemsOnPoint",value:function(e){var t=[];if(this.labelModule.visible()){var i=this._getRotation();g.pointInRect(this.labelModule.getSize(),e,i)&&t.push({edgeId:this.id,labelId:0})}var n={left:e.x,top:e.y};return this.isOverlappingWith(n)&&t.push({edgeId:this.id}),t}},{key:"isOverlappingWith",value:function(e){if(this.connected){var t=this.from.x,i=this.from.y,n=this.to.x,o=this.to.y,s=e.left,r=e.top;return this.edgeType.getDistanceToEdge(t,i,n,o,s,r)<10}return!1}},{key:"_getRotation",value:function(e){var t=this.edgeType.getViaNode(),i=this.edgeType.getPoint(.5,t);void 0!==e&&this.labelModule.calculateLabelSize(e,this.selected,this.hover,i.x,i.y);var n={x:i.x,y:this.labelModule.size.yLine,angle:0};if(!this.labelModule.visible())return n;if("horizontal"===this.options.font.align)return n;var o=this.from.y-this.to.y,s=this.from.x-this.to.x,r=Math.atan2(o,s);return(r<-1&&s<0||r>0&&s<0)&&(r+=Math.PI),n.angle=r,n}},{key:"_pointOnCircle",value:function(e,t,i,n){var o=2*n*Math.PI;return{x:e+i*Math.cos(o),y:t-i*Math.sin(o)}}},{key:"select",value:function(){this.selected=!0}},{key:"unselect",value:function(){this.selected=!1}},{key:"cleanup",value:function(){return this.edgeType.cleanup()}},{key:"remove",value:function(){this.cleanup(),this.disconnect(),delete this.body.edges[this.id]}},{key:"endPointsValid",value:function(){return void 0!==this.body.nodes[this.fromId]&&void 0!==this.body.nodes[this.toId]}}],[{key:"parseOptions",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=arguments.length>4&&void 0!==arguments[4]&&arguments[4],r=["arrowStrikethrough","id","from","hidden","hoverWidth","labelHighlightBold","length","line","opacity","physics","scaling","selectionWidth","selfReferenceSize","to","title","value","width","font","chosen","widthConstraint"];if(p.selectiveDeepExtend(r,e,t,i),g.isValidLabel(t.label)?e.label=t.label:e.label=void 0,p.mergeOptions(e,t,"smooth",n),p.mergeOptions(e,t,"shadow",n),void 0!==t.dashes&&null!==t.dashes?e.dashes=t.dashes:!0===i&&null===t.dashes&&(e.dashes=(0,h.default)(n.dashes)),void 0!==t.scaling&&null!==t.scaling?(void 0!==t.scaling.min&&(e.scaling.min=t.scaling.min),void 0!==t.scaling.max&&(e.scaling.max=t.scaling.max),p.mergeOptions(e.scaling,t.scaling,"label",n.scaling)):!0===i&&null===t.scaling&&(e.scaling=(0,h.default)(n.scaling)),void 0!==t.arrows&&null!==t.arrows)if("string"==typeof t.arrows){var d=t.arrows.toLowerCase();e.arrows.to.enabled=-1!=d.indexOf("to"),e.arrows.middle.enabled=-1!=d.indexOf("middle"),e.arrows.from.enabled=-1!=d.indexOf("from")}else{if("object"!==(0,a.default)(t.arrows))throw new Error("The arrow newOptions can only be an object or a string. Refer to the documentation. You used:"+(0,s.default)(t.arrows));p.mergeOptions(e.arrows,t.arrows,"to",n.arrows),p.mergeOptions(e.arrows,t.arrows,"middle",n.arrows),p.mergeOptions(e.arrows,t.arrows,"from",n.arrows)}else!0===i&&null===t.arrows&&(e.arrows=(0,h.default)(n.arrows));if(void 0!==t.color&&null!==t.color){var l=t.color,u=e.color;if(o)p.deepExtend(u,n.color,!1,i);else for(var c in u)u.hasOwnProperty(c)&&delete u[c];if(p.isString(u))u.color=u,u.highlight=u,u.hover=u,u.inherit=!1,void 0===l.opacity&&(u.opacity=1);else{var f=!1;void 0!==l.color&&(u.color=l.color,f=!0),void 0!==l.highlight&&(u.highlight=l.highlight,f=!0),void 0!==l.hover&&(u.hover=l.hover,f=!0),void 0!==l.inherit&&(u.inherit=l.inherit),void 0!==l.opacity&&(u.opacity=Math.min(1,Math.max(0,l.opacity))),!0===f?u.inherit=!1:void 0===u.inherit&&(u.inherit="from")}}else!0===i&&null===t.color&&(e.color=p.bridgeObject(n.color));!0===i&&null===t.font&&(e.font=p.bridgeObject(n.font))}}]),e}();t.default=w},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(78),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"_findBorderPositionBezier",value:function(e,t){var i,n,o,s,r,a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this._getViaCoordinates(),d=0,h=0,l=1,u=this.to,c=!1;for(e.id===this.from.id&&(u=this.from,c=!0);h<=l&&d<10;){var f=.5*(h+l);if(i=this.getPoint(f,a),n=Math.atan2(u.y-i.y,u.x-i.x),o=u.distanceToBorder(t,n),s=Math.sqrt(Math.pow(i.x-u.x,2)+Math.pow(i.y-u.y,2)),r=o-s,Math.abs(r)<.2)break;r<0?!1===c?h=f:l=f:!1===c?l=f:h=f,d++}return i.t=f,i}},{key:"_getDistanceToBezierEdge",value:function(e,t,i,n,o,s,r){var a=1e9,d=void 0,h=void 0,l=void 0,u=void 0,c=void 0,f=e,p=t;for(h=1;h<10;h++)l=.1*h,u=Math.pow(1-l,2)*e+2*l*(1-l)*r.x+Math.pow(l,2)*i,c=Math.pow(1-l,2)*t+2*l*(1-l)*r.y+Math.pow(l,2)*n,h>0&&(d=this._getDistanceToLine(f,p,u,c,o,s),a=d1&&void 0!==arguments[1]?arguments[1]:[],n=1e9,o=-1e9,s=1e9,r=-1e9;if(i.length>0)for(var a=0;at.shape.boundingBox.left&&(s=t.shape.boundingBox.left),rt.shape.boundingBox.top&&(n=t.shape.boundingBox.top),o1&&void 0!==arguments[1]?arguments[1]:[],n=1e9,o=-1e9,s=1e9,r=-1e9;if(i.length>0)for(var a=0;at.x&&(s=t.x),rt.y&&(n=t.y),od;)n(a,i=t[d++])&&(~s(h,i)||h.push(i));return h}},function(e,t,i){var n=i(14),o=i(29),s=i(43)("IE_PROTO"),r=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=o(e),n(e,s)?e[s]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?r:null}},function(e,t,i){var n=i(37),o=i(8)("toStringTag"),s="Arguments"==n(function(){return arguments}()),r=function(e,t){try{return e[t]}catch(e){}};e.exports=function(e){var t,i,a;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=r(t=Object(e),o))?i:s?n(t):"Object"==(a=n(t))&&"function"==typeof t.callee?"Arguments":a}},function(e,t,i){var n=i(11),o=i(6),s=i(20);e.exports=function(e,t){var i=(o.Object||{})[e]||Object[e],r={};r[e]=t(i),n(n.S+n.F*s(function(){i(1)}),"Object",r)}},function(e,t,i){var n=i(65),o=i(45).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return n(e,o)}},function(e,t,i){var n=i(31),o=i(27),s=i(17),r=i(40),a=i(14),d=i(62),h=Object.getOwnPropertyDescriptor;t.f=i(13)?h:function(e,t){if(e=s(e),t=r(t,!0),d)try{return h(e,t)}catch(e){}if(a(e,t))return o(!n.f.call(e,t),e[t])}},function(e,t,i){e.exports="undefined"!=typeof window&&window.moment||i(114)},function(e,t,i){function n(e){this.delay=null,this.max=1/0,this._queue=[],this._timeout=null,this._extended=null,this.setOptions(e)}n.prototype.setOptions=function(e){e&&void 0!==e.delay&&(this.delay=e.delay),e&&void 0!==e.max&&(this.max=e.max),this._flushIfNeeded()},n.extend=function(e,t){var i=new n(t);if(void 0!==e.flush)throw new Error("Target object already has a property flush");e.flush=function(){i.flush()};var o=[{name:"flush",original:void 0}];if(t&&t.replace)for(var s=0;sthis.max&&this.flush(),clearTimeout(this._timeout),this.queue.length>0&&"number"==typeof this.delay){var e=this;this._timeout=setTimeout(function(){e.flush()},this.delay)}},n.prototype.flush=function(){for(;this._queue.length>0;){var e=this._queue.shift();e.fn.apply(e.context||e.fn,e.args||[])}},e.exports=n},function(e,t){function i(e){if(e)return n(e)}function n(e){for(var t in i.prototype)e[t]=i.prototype[t];return e}e.exports=i,i.prototype.on=i.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks[e]=this._callbacks[e]||[]).push(t),this},i.prototype.once=function(e,t){function i(){n.off(e,i),t.apply(this,arguments)}var n=this;return this._callbacks=this._callbacks||{},i.fn=t,this.on(e,i),this},i.prototype.off=i.prototype.removeListener=i.prototype.removeAllListeners=i.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var i=this._callbacks[e];if(!i)return this;if(1==arguments.length)return delete this._callbacks[e],this;for(var n,o=0;o=0;i--){var a=s[i];a.nodes||(a.nodes=[]),-1===a.nodes.indexOf(o)&&a.nodes.push(o)}t.attr&&(o.attr=d(o.attr,t.attr))}function u(e,t){if(e.edges||(e.edges=[]),e.edges.push(t),e.edge){var i=d({},e.edge);t.attr=d(i,t.attr)}}function c(e,t,i,n,o){var s={from:t,to:i,type:n};return e.edge&&(s.attr=d({},e.edge)),s.attr=d(s.attr||{},o),s}function f(){for(A=I.NULL,R="";" "===z||"\t"===z||"\n"===z||"\r"===z;)s();do{var e=!1;if("#"===z){for(var t=B-1;" "===N.charAt(t)||"\t"===N.charAt(t);)t--;if("\n"===N.charAt(t)||""===N.charAt(t)){for(;""!=z&&"\n"!=z;)s();e=!0}}if("/"===z&&"/"===r()){for(;""!=z&&"\n"!=z;)s();e=!0}if("/"===z&&"*"===r()){for(;""!=z;){if("*"===z&&"/"===r()){s(),s();break}s()}e=!0}for(;" "===z||"\t"===z||"\n"===z||"\r"===z;)s()}while(e);if(""===z)return void(A=I.DELIMITER);var i=z+r();if(F[i])return A=I.DELIMITER,R=i,s(),void s();if(F[z])return A=I.DELIMITER,R=z,void s();if(a(z)||"-"===z){for(R+=z,s();a(z);)R+=z,s();return"false"===R?R=!1:"true"===R?R=!0:isNaN(Number(R))||(R=Number(R)),void(A=I.IDENTIFIER)}if('"'===z){for(s();""!=z&&('"'!=z||'"'===z&&'"'===r());)'"'===z?(R+=z,s()):"\\"===z&&"n"===r()?(R+="\n",s()):R+=z,s();if('"'!=z)throw k('End of string " expected');return s(),void(A=I.IDENTIFIER)}for(A=I.UNKNOWN;""!=z;)R+=z,s();throw new SyntaxError('Syntax error in part "'+x(R,30)+'"')}function p(){var e={};if(o(),f(),"strict"===R&&(e.strict=!0,f()),"graph"!==R&&"digraph"!==R||(e.type=R,f()),A===I.IDENTIFIER&&(e.id=R,f()),"{"!=R)throw k("Angle bracket { expected");if(f(),v(e),"}"!=R)throw k("Angle bracket } expected");if(f(),""!==R)throw k("End of file expected");return f(),delete e.node,delete e.edge,delete e.graph,e}function v(e){for(;""!==R&&"}"!=R;)g(e),";"===R&&f()}function g(e){var t=y(e);if(t)return void _(e,t);if(!m(e)){if(A!=I.IDENTIFIER)throw k("Identifier expected");var i=R;if(f(),"="===R){if(f(),A!=I.IDENTIFIER)throw k("Identifier expected");e[i]=R,f()}else b(e,i)}}function y(e){var t=null;if("subgraph"===R&&(t={},t.type="subgraph",f(),A===I.IDENTIFIER&&(t.id=R,f())),"{"===R){if(f(),t||(t={}),t.parent=e,t.node=e.node,t.edge=e.edge,t.graph=e.graph,v(t),"}"!=R)throw k("Angle bracket } expected");f(),delete t.node,delete t.edge,delete t.graph,delete t.parent,e.subgraphs||(e.subgraphs=[]),e.subgraphs.push(t)}return t}function m(e){return"node"===R?(f(),e.node=w(),"node"):"edge"===R?(f(),e.edge=w(),"edge"):"graph"===R?(f(),e.graph=w(),"graph"):null}function b(e,t){var i={id:t},n=w();n&&(i.attr=n),l(e,i),_(e,t)}function _(e,t){for(;"->"===R||"--"===R;){var i,n=R;f();var o=y(e);if(o)i=o;else{if(A!=I.IDENTIFIER)throw k("Identifier or subgraph expected");i=R,l(e,{id:i}),f()}u(e,c(e,t,i,n,w())),t=i}}function w(){for(var e=null,t={dashed:!0,solid:!1,dotted:[1,5]};"["===R;){for(f(),e={};""!==R&&"]"!=R;){if(A!=I.IDENTIFIER)throw k("Attribute name expected");var i=R;if(f(),"="!=R)throw k("Equal sign = expected");if(f(),A!=I.IDENTIFIER)throw k("Attribute value expected");var n=R;"style"===i&&(n=t[n]),h(e,i,n),f(),","==R&&f()}if("]"!=R)throw k("Bracket ] expected");f()}return e}function k(e){return new SyntaxError(e+', got "'+x(R,30)+'" (char '+B+")")}function x(e,t){return e.length<=t?e:e.substr(0,27)+"..."}function O(e,t,i){Array.isArray(e)?e.forEach(function(e){Array.isArray(t)?t.forEach(function(t){i(e,t)}):i(e,t)}):Array.isArray(t)?t.forEach(function(t){i(e,t)}):i(e,t)}function M(e,t,i){for(var n=t.split("."),o=n.pop(),s=e,r=0;r":!0,"--":!0},N="",B=0,z="",R="",A=I.NULL,j=/[a-zA-Z_0-9.:#]/;t.parseDOT=n,t.DOTToGraph=S},function(e,t,i){function n(e,t){var i=[],n=[],o={edges:{inheritColor:!1},nodes:{fixed:!1,parseColor:!1}};void 0!==t&&(void 0!==t.fixed&&(o.nodes.fixed=t.fixed),void 0!==t.parseColor&&(o.nodes.parseColor=t.parseColor),void 0!==t.inheritColor&&(o.edges.inheritColor=t.inheritColor));for(var s=e.edges,r=e.nodes,a=0;a2&&void 0!==arguments[2]&&arguments[2];(0,h.default)(this,e),this.body=t,this.pointToSelf=!1,this.baseSize=void 0,this.fontOptions={},this.setOptions(i),this.size={top:0,left:0,width:0,height:0,yLine:0},this.isEdgeLabel=n}return(0,u.default)(e,[{key:"setOptions",value:function(e){if(this.elementOptions=e,this.initFontOptions(e.font),f.isValidLabel(e.label)?this.labelDirty=!0:e.label="",void 0!==e.font&&null!==e.font)if("string"==typeof e.font)this.baseSize=this.fontOptions.size;else if("object"===(0,a.default)(e.font)){var t=e.font.size;void 0!==t&&(this.baseSize=t)}}},{key:"initFontOptions",value:function(t){var i=this;if(c.forEach(v,function(e){i.fontOptions[e]={}}),e.parseFontString(this.fontOptions,t))return void(this.fontOptions.vadjust=0);c.forEach(t,function(e,t){void 0!==e&&null!==e&&"object"!==(void 0===e?"undefined":(0,a.default)(e))&&(i.fontOptions[t]=e)})}},{key:"constrain",value:function(e){var t={constrainWidth:!1,maxWdt:-1,minWdt:-1,constrainHeight:!1,minHgt:-1,valign:"middle"},i=c.topMost(e,"widthConstraint");if("number"==typeof i)t.maxWdt=Number(i),t.minWdt=Number(i);else if("object"===(void 0===i?"undefined":(0,a.default)(i))){var n=c.topMost(e,["widthConstraint","maximum"]);"number"==typeof n&&(t.maxWdt=Number(n));var o=c.topMost(e,["widthConstraint","minimum"]);"number"==typeof o&&(t.minWdt=Number(o))}var s=c.topMost(e,"heightConstraint");if("number"==typeof s)t.minHgt=Number(s);else if("object"===(void 0===s?"undefined":(0,a.default)(s))){var r=c.topMost(e,["heightConstraint","minimum"]);"number"==typeof r&&(t.minHgt=Number(r));var d=c.topMost(e,["heightConstraint","valign"]);"string"==typeof d&&("top"!==d&&"bottom"!==d||(t.valign=d))}return t}},{key:"update",value:function(e,t){this.setOptions(e,!0),this.propagateFonts(t),c.deepExtend(this.fontOptions,this.constrain(t)),this.fontOptions.chooser=f.choosify("label",t)}},{key:"adjustSizes",value:function(e){var t=e?e.right+e.left:0;this.fontOptions.constrainWidth&&(this.fontOptions.maxWdt-=t,this.fontOptions.minWdt-=t);var i=e?e.top+e.bottom:0;this.fontOptions.constrainHeight&&(this.fontOptions.minHgt-=i)}},{key:"addFontOptionsToPile",value:function(e,t){for(var i=0;i5&&void 0!==arguments[5]?arguments[5]:"middle";if(void 0!==this.elementOptions.label){var r=this.fontOptions.size*this.body.view.scale;this.elementOptions.label&&r=this.elementOptions.scaling.label.maxVisible&&(r=Number(this.elementOptions.scaling.label.maxVisible)/this.body.view.scale),this.calculateLabelSize(e,n,o,t,i,s),this._drawBackground(e),this._drawText(e,t,this.size.yLine,s,r))}}},{key:"_drawBackground",value:function(e){if(void 0!==this.fontOptions.background&&"none"!==this.fontOptions.background){e.fillStyle=this.fontOptions.background;var t=this.getSize();e.fillRect(t.left,t.top,t.width,t.height)}}},{key:"_drawText",value:function(e,t,i){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"middle",o=arguments[4],r=this._setAlignment(e,t,i,n),a=(0,s.default)(r,2);t=a[0],i=a[1],e.textAlign="left",t-=this.size.width/2,this.fontOptions.valign&&this.size.height>this.size.labelHeight&&("top"===this.fontOptions.valign&&(i-=(this.size.height-this.size.labelHeight)/2),"bottom"===this.fontOptions.valign&&(i+=(this.size.height-this.size.labelHeight)/2));for(var d=0;d0&&(e.lineWidth=c.strokeWidth,e.strokeStyle=g,e.lineJoin="round"),e.fillStyle=v,c.strokeWidth>0&&e.strokeText(c.text,t+l,i+c.vadjust),e.fillText(c.text,t+l,i+c.vadjust),l+=c.width}i+=h.height}}}},{key:"_setAlignment",value:function(e,t,i,n){if(this.isEdgeLabel&&"horizontal"!==this.fontOptions.align&&!1===this.pointToSelf){t=0,i=0;"top"===this.fontOptions.align?(e.textBaseline="alphabetic",i-=4):"bottom"===this.fontOptions.align?(e.textBaseline="hanging",i+=4):e.textBaseline="middle"}else e.textBaseline=n;return[t,i]}},{key:"_getColor",value:function(e,t,i){var n=e||"#000000",o=i||"#ffffff";if(t<=this.elementOptions.scaling.label.drawThreshold){var s=Math.max(0,Math.min(1,1-(this.elementOptions.scaling.label.drawThreshold-t)));n=c.overrideOpacity(n,s),o=c.overrideOpacity(o,s)}return[n,o]}},{key:"getTextSize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return this._processLabel(e,t,i),{width:this.size.width,height:this.size.height,lineCount:this.lineCount}}},{key:"getSize",value:function(){var e=this.size.left,t=this.size.top-1;if(this.isEdgeLabel){var i=.5*-this.size.width;switch(this.fontOptions.align){case"middle":e=i,t=.5*-this.size.height;break;case"top":e=i,t=-(this.size.height+2);break;case"bottom":e=i,t=2}}return{left:e,top:t,width:this.size.width,height:this.size.height}}},{key:"calculateLabelSize",value:function(e,t,i){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,s=arguments.length>5&&void 0!==arguments[5]?arguments[5]:"middle";this._processLabel(e,t,i),this.size.left=n-.5*this.size.width,this.size.top=o-.5*this.size.height,this.size.yLine=o+.5*(1-this.lineCount)*this.fontOptions.size,"hanging"===s&&(this.size.top+=.5*this.fontOptions.size,this.size.top+=4,this.size.yLine+=4)}},{key:"getFormattingValues",value:function(e,t,i,n){var o=function(e,t,i){return"normal"===t?"mod"===i?"":e[i]:void 0!==e[t][i]?e[t][i]:e[i]},s={color:o(this.fontOptions,n,"color"),size:o(this.fontOptions,n,"size"),face:o(this.fontOptions,n,"face"),mod:o(this.fontOptions,n,"mod"),vadjust:o(this.fontOptions,n,"vadjust"),strokeWidth:this.fontOptions.strokeWidth,strokeColor:this.fontOptions.strokeColor};(t||i)&&("normal"===n&&!0===this.fontOptions.chooser&&this.elementOptions.labelHighlightBold?s.mod="bold":"function"==typeof this.fontOptions.chooser&&this.fontOptions.chooser(s,this.elementOptions.id,t,i));var r="";return void 0!==s.mod&&""!==s.mod&&(r+=s.mod+" "),r+=s.size+"px "+s.face,e.font=r.replace(/"/g,""),s.font=e.font,s.height=s.size,s}},{key:"differentState",value:function(e,t){return e!==this.selectedState||t!==this.hoverState}},{key:"_processLabelText",value:function(e,t,i,n){return new p(e,this,t,i).process(n)}},{key:"_processLabel",value:function(e,t,i){if(!1!==this.labelDirty||this.differentState(t,i)){var n=this._processLabelText(e,t,i,this.elementOptions.label);this.fontOptions.minWdt>0&&n.width0&&n.heightn.shape.height?(t=n.x+.5*n.shape.width,i=n.y-o):(t=n.x+o,i=n.y-.5*n.shape.height),[t,i,o]}},{key:"_pointOnCircle",value:function(e,t,i,n){var o=2*n*Math.PI;return{x:e+i*Math.cos(o),y:t-i*Math.sin(o)}}},{key:"_findBorderPositionCircle",value:function(e,t,i){for(var n=i.x,o=i.y,s=i.low,r=i.high,a=i.direction,d=0,h=this.options.selfReferenceSize,l=void 0,u=void 0,c=void 0,f=void 0,p=void 0,v=.5*(s+r);s<=r&&d<10&&(v=.5*(s+r),l=this._pointOnCircle(n,o,h,v),u=Math.atan2(e.y-l.y,e.x-l.x),c=e.distanceToBorder(t,u),f=Math.sqrt(Math.pow(l.x-e.x,2)+Math.pow(l.y-e.y,2)),p=c-f,!(Math.abs(p)<.05));)p>0?a>0?s=v:r=v:a>0?r=v:s=v,d++;return l.t=v,l}},{key:"getLineWidth",value:function(e,t){return!0===e?Math.max(this.selectionWidth,.3/this.body.view.scale):!0===t?Math.max(this.hoverWidth,.3/this.body.view.scale):Math.max(this.options.width,.3/this.body.view.scale)}},{key:"getColor",value:function(e,t,i,n){if(!1!==t.inheritsColor){if("both"===t.inheritsColor&&this.from.id!==this.to.id){var o=e.createLinearGradient(this.from.x,this.from.y,this.to.x,this.to.y),s=void 0,r=void 0;return s=this.from.options.color.highlight.border,r=this.to.options.color.highlight.border,!1===this.from.selected&&!1===this.to.selected?(s=l.overrideOpacity(this.from.options.color.border,t.opacity),r=l.overrideOpacity(this.to.options.color.border,t.opacity)):!0===this.from.selected&&!1===this.to.selected?r=this.to.options.color.border:!1===this.from.selected&&!0===this.to.selected&&(s=this.from.options.color.border),o.addColorStop(0,s),o.addColorStop(1,r),o}return"to"===t.inheritsColor?l.overrideOpacity(this.to.options.color.border,t.opacity):l.overrideOpacity(this.from.options.color.border,t.opacity)}return l.overrideOpacity(t.color,t.opacity)}},{key:"_circle",value:function(e,t,i,n,o){this.enableShadow(e,t),e.beginPath(),e.arc(i,n,o,0,2*Math.PI,!1),e.stroke(),this.disableShadow(e,t)}},{key:"getDistanceToEdge",value:function(e,t,i,n,o,r,a,d){var h=0;if(this.from!=this.to)h=this._getDistanceToEdge(e,t,i,n,o,r,a);else{var l=this._getCircleData(void 0),u=(0,s.default)(l,3),c=u[0],f=u[1],p=u[2],v=c-o,g=f-r;h=Math.abs(Math.sqrt(v*v+g*g)-p)}return h}},{key:"_getDistanceToLine",value:function(e,t,i,n,o,s){var r=i-e,a=n-t,d=r*r+a*a,h=((o-e)*r+(s-t)*a)/d;h>1?h=1:h<0&&(h=0);var l=e+h*r,u=t+h*a,c=l-o,f=u-s;return Math.sqrt(c*c+f*f)}},{key:"getArrowData",value:function(e,t,i,n,o,r){var a=void 0,d=void 0,h=void 0,l=void 0,u=void 0,c=void 0,f=void 0,p=r.width;if("from"===t?(h=this.from,l=this.to,u=.1,c=r.fromArrowScale,f=r.fromArrowType):"to"===t?(h=this.to,l=this.from,u=-.1,c=r.toArrowScale,f=r.toArrowType):(h=this.to,l=this.from,c=r.middleArrowScale,f=r.middleArrowType),h!=l)if("middle"!==t)if(!0===this.options.smooth.enabled){d=this.findBorderPosition(h,e,{via:i});var v=this.getPoint(Math.max(0,Math.min(1,d.t+u)),i);a=Math.atan2(d.y-v.y,d.x-v.x)}else a=Math.atan2(h.y-l.y,h.x-l.x),d=this.findBorderPosition(h,e);else a=Math.atan2(h.y-l.y,h.x-l.x),d=this.getPoint(.5,i);else{var g=this._getCircleData(e),y=(0,s.default)(g,3),m=y[0],b=y[1],_=y[2];"from"===t?(d=this.findBorderPosition(this.from,e,{x:m,y:b,low:.25,high:.6,direction:-1}),a=-2*d.t*Math.PI+1.5*Math.PI+.1*Math.PI):"to"===t?(d=this.findBorderPosition(this.from,e,{x:m,y:b,low:.6,high:1,direction:1}),a=-2*d.t*Math.PI+1.5*Math.PI-1.1*Math.PI):(d=this._pointOnCircle(m,b,_,.175),a=3.9269908169872414)}"middle"===t&&c<0&&(p*=-1);var w=15*c+3*p;return{point:d,core:{x:d.x-.9*w*Math.cos(a),y:d.y-.9*w*Math.sin(a)},angle:a,length:w,type:f}}},{key:"drawArrowHead",value:function(e,t,i,n,o){e.strokeStyle=this.getColor(e,t,i,n),e.fillStyle=e.strokeStyle,e.lineWidth=t.width,u.draw(e,o),this.enableShadow(e,t),e.fill(),this.disableShadow(e,t)}},{key:"enableShadow",value:function(e,t){!0===t.shadow&&(e.shadowColor=t.shadowColor,e.shadowBlur=t.shadowSize,e.shadowOffsetX=t.shadowX,e.shadowOffsetY=t.shadowY)}},{key:"disableShadow",value:function(e,t){!0===t.shadow&&(e.shadowColor="rgba(0,0,0,0)",e.shadowBlur=0,e.shadowOffsetX=0,e.shadowOffsetY=0)}}]),e}();t.default=c},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(3),a=n(r),d=i(4),h=n(d),l=i(0),u=n(l),c=i(1),f=n(c),p=function(){function e(){(0,u.default)(this,e)}return(0,f.default)(e,null,[{key:"transform",value:function(e,t){e instanceof Array||(e=[e]);for(var i=t.point.x,n=t.point.y,o=t.angle,s=t.length,r=0;r0){var e=void 0,t=this.body.nodes,i=this.physicsBody.physicsNodeIndices,n=i.length,o=this._formBarnesHutTree(t,i);this.barnesHutTree=o;for(var s=0;s0&&this._getForceContributions(o.root,e)}}},{key:"_getForceContributions",value:function(e,t){this._getForceContribution(e.children.NW,t),this._getForceContribution(e.children.NE,t),this._getForceContribution(e.children.SW,t),this._getForceContribution(e.children.SE,t)}},{key:"_getForceContribution",value:function(e,t){if(e.childrenCount>0){var i=void 0,n=void 0,o=void 0;i=e.centerOfMass.x-t.x,n=e.centerOfMass.y-t.y,o=Math.sqrt(i*i+n*n),o*e.calcSize>this.thetaInversed?this._calculateForces(o,i,n,t,e):4===e.childrenCount?this._getForceContributions(e,t):e.children.data.id!=t.id&&this._calculateForces(o,i,n,t,e)}}},{key:"_calculateForces",value:function(e,t,i,n,o){0===e&&(e=.1,t=e),this.overlapAvoidanceFactor<1&&n.shape.radius&&(e=Math.max(.1+this.overlapAvoidanceFactor*n.shape.radius,e-n.shape.radius));var s=this.options.gravitationalConstant*o.mass*n.options.mass/Math.pow(e,3),r=t*s,a=i*s;this.physicsBody.forces[n.id].x+=r,this.physicsBody.forces[n.id].y+=a}},{key:"_formBarnesHutTree",value:function(e,t){for(var i=void 0,n=t.length,o=e[t[0]].x,s=e[t[0]].y,r=e[t[0]].x,a=e[t[0]].y,d=1;d0&&(lr&&(r=l),ua&&(a=u))}var c=Math.abs(r-o)-Math.abs(a-s);c>0?(s-=.5*c,a+=.5*c):(o+=.5*c,r-=.5*c);var f=Math.max(1e-5,Math.abs(r-o)),p=.5*f,v=.5*(o+r),g=.5*(s+a),y={root:{centerOfMass:{x:0,y:0},mass:0,range:{minX:v-p,maxX:v+p,minY:g-p,maxY:g+p},size:f,calcSize:1/f,children:{data:null},maxWidth:0,level:0,childrenCount:4}};this._splitBranch(y.root);for(var m=0;m0&&this._placeInTree(y.root,i);return y}},{key:"_updateBranchMass",value:function(e,t){var i=e.centerOfMass,n=e.mass+t.options.mass,o=1/n;i.x=i.x*e.mass+t.x*t.options.mass,i.x*=o,i.y=i.y*e.mass+t.y*t.options.mass,i.y*=o,e.mass=n;var s=Math.max(Math.max(t.height,t.radius),t.width);e.maxWidth=e.maxWidtht.x?n.maxY>t.y?"NW":"SW":n.maxY>t.y?"NE":"SE",this._placeInRegion(e,t,o)}},{key:"_placeInRegion",value:function(e,t,i){var n=e.children[i];switch(n.childrenCount){case 0:n.children.data=t,n.childrenCount=1,this._updateBranchMass(n,t);break;case 1:n.children.data.x===t.x&&n.children.data.y===t.y?(t.x+=this.seededRandom(),t.y+=this.seededRandom()):(this._splitBranch(n),this._placeInTree(n,t));break;case 4:this._placeInTree(n,t)}}},{key:"_splitBranch",value:function(e){var t=null;1===e.childrenCount&&(t=e.children.data,e.mass=0,e.centerOfMass.x=0,e.centerOfMass.y=0),e.childrenCount=4,e.children.data=null,this._insertRegion(e,"NW"),this._insertRegion(e,"NE"),this._insertRegion(e,"SW"),this._insertRegion(e,"SE"),null!=t&&this._placeInTree(e,t)}},{key:"_insertRegion",value:function(e,t){var i=void 0,n=void 0,o=void 0,s=void 0,r=.5*e.size;switch(t){case"NW":i=e.range.minX,n=e.range.minX+r,o=e.range.minY,s=e.range.minY+r;break;case"NE":i=e.range.minX+r,n=e.range.maxX,o=e.range.minY,s=e.range.minY+r;break;case"SW":i=e.range.minX,n=e.range.minX+r,o=e.range.minY+r,s=e.range.maxY;break;case"SE":i=e.range.minX+r,n=e.range.maxX,o=e.range.minY+r,s=e.range.maxY}e.children[t]={centerOfMass:{x:0,y:0},mass:0,range:{minX:i,maxX:n,minY:o,maxY:s},size:.5*e.size,calcSize:2*e.calcSize,children:{data:null},maxWidth:0,level:e.level+1,childrenCount:0}}},{key:"_debug",value:function(e,t){void 0!==this.barnesHutTree&&(e.lineWidth=1,this._drawBranch(this.barnesHutTree.root,e,t))}},{key:"_drawBranch",value:function(e,t,i){void 0===i&&(i="#FF0000"),4===e.childrenCount&&(this._drawBranch(e.children.NW,t),this._drawBranch(e.children.NE,t),this._drawBranch(e.children.SE,t),this._drawBranch(e.children.SW,t)),t.strokeStyle=i,t.beginPath(),t.moveTo(e.range.minX,e.range.minY),t.lineTo(e.range.maxX,e.range.minY),t.stroke(),t.beginPath(),t.moveTo(e.range.maxX,e.range.minY),t.lineTo(e.range.maxX,e.range.maxY),t.stroke(),t.beginPath(),t.moveTo(e.range.maxX,e.range.maxY),t.lineTo(e.range.minX,e.range.maxY),t.stroke(),t.beginPath(),t.moveTo(e.range.minX,e.range.maxY),t.lineTo(e.range.minX,e.range.minY),t.stroke()}}]),e}();t.default=d},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=function(){function e(t,i,n){(0,s.default)(this,e),this.body=t,this.physicsBody=i,this.setOptions(n)}return(0,a.default)(e,[{key:"setOptions",value:function(e){this.options=e}},{key:"solve",value:function(){for(var e=void 0,t=void 0,i=void 0,n=void 0,o=this.body.nodes,s=this.physicsBody.physicsNodeIndices,r=this.physicsBody.forces,a=0;a=e.length?(this._t=void 0,o(1)):"keys"==t?o(0,i):"values"==t?o(0,e[i]):o(0,[i,e[i]])},"values"),s.Arguments=s.Array,n("keys"),n("values"),n("entries")},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,i){var n=i(41),o=i(27),s=i(46),r={};i(18)(r,i(8)("iterator"),function(){return this}),e.exports=function(e,t,i){e.prototype=n(r,{next:o(1,i)}),s(e,t+" Iterator")}},function(e,t,i){var n=i(12),o=i(19),s=i(26);e.exports=i(13)?Object.defineProperties:function(e,t){o(e);for(var i,r=s(t),a=r.length,d=0;a>d;)n.f(e,i=r[d++],t[i]);return e}},function(e,t,i){var n=i(17),o=i(92),s=i(93);e.exports=function(e){return function(t,i,r){var a,d=n(t),h=o(d.length),l=s(r,h);if(e&&i!=i){for(;h>l;)if((a=d[l++])!=a)return!0}else for(;h>l;l++)if((e||l in d)&&d[l]===i)return e||l||0;return!e&&-1}}},function(e,t,i){var n=i(42),o=Math.min;e.exports=function(e){return e>0?o(n(e),9007199254740991):0}},function(e,t,i){var n=i(42),o=Math.max,s=Math.min;e.exports=function(e,t){return e=n(e),e<0?o(e+t,0):s(e,t)}},function(e,t,i){var n=i(9).document;e.exports=n&&n.documentElement},function(e,t,i){var n=i(42),o=i(38);e.exports=function(e){return function(t,i){var s,r,a=String(o(t)),d=n(i),h=a.length;return d<0||d>=h?e?"":void 0:(s=a.charCodeAt(d),s<55296||s>56319||d+1===h||(r=a.charCodeAt(d+1))<56320||r>57343?e?a.charAt(d):s:e?a.slice(d,d+2):r-56320+(s-55296<<10)+65536)}}},function(e,t,i){var n=i(19),o=i(97);e.exports=i(6).getIterator=function(e){var t=o(e);if("function"!=typeof t)throw TypeError(e+" is not iterable!");return n(t.call(e))}},function(e,t,i){var n=i(67),o=i(8)("iterator"),s=i(24);e.exports=i(6).getIteratorMethod=function(e){if(void 0!=e)return e[o]||e["@@iterator"]||s[n(e)]}},function(e,t,i){i(99);var n=i(6).Object;e.exports=function(e,t){return n.create(e,t)}},function(e,t,i){var n=i(11);n(n.S,"Object",{create:i(41)})},function(e,t,i){i(101),e.exports=i(6).Object.keys},function(e,t,i){var n=i(29),o=i(26);i(68)("keys",function(){return function(e){return o(n(e))}})},function(e,t,i){e.exports={default:i(103),__esModule:!0}},function(e,t,i){i(47),i(36),e.exports=i(48).f("iterator")},function(e,t,i){e.exports={default:i(105),__esModule:!0}},function(e,t,i){i(106),i(111),i(112),i(113),e.exports=i(6).Symbol},function(e,t,i){var n=i(9),o=i(14),s=i(13),r=i(11),a=i(64),d=i(107).KEY,h=i(20),l=i(44),u=i(46),c=i(28),f=i(8),p=i(48),v=i(49),g=i(108),y=i(109),m=i(19),b=i(17),_=i(40),w=i(27),k=i(41),x=i(110),O=i(70),M=i(12),E=i(26),S=O.f,D=M.f,C=x.f,T=n.Symbol,P=n.JSON,I=P&&P.stringify,F=f("_hidden"),N=f("toPrimitive"),B={}.propertyIsEnumerable,z=l("symbol-registry"),R=l("symbols"),A=l("op-symbols"),j=Object.prototype,L="function"==typeof T,H=n.QObject,W=!H||!H.prototype||!H.prototype.findChild,Y=s&&h(function(){return 7!=k(D({},"a",{get:function(){return D(this,"a",{value:7}).a}})).a})?function(e,t,i){var n=S(j,t);n&&delete j[t],D(e,t,i),n&&e!==j&&D(j,t,n)}:D,V=function(e){var t=R[e]=k(T.prototype);return t._k=e,t},U=L&&"symbol"==typeof T.iterator?function(e){return"symbol"==typeof e}:function(e){return e instanceof T},q=function(e,t,i){return e===j&&q(A,t,i),m(e),t=_(t,!0),m(i),o(R,t)?(i.enumerable?(o(e,F)&&e[F][t]&&(e[F][t]=!1),i=k(i,{enumerable:w(0,!1)})):(o(e,F)||D(e,F,w(1,{})),e[F][t]=!0),Y(e,t,i)):D(e,t,i)},G=function(e,t){m(e);for(var i,n=g(t=b(t)),o=0,s=n.length;s>o;)q(e,i=n[o++],t[i]);return e},X=function(e,t){return void 0===t?k(e):G(k(e),t)},K=function(e){var t=B.call(this,e=_(e,!0));return!(this===j&&o(R,e)&&!o(A,e))&&(!(t||!o(this,e)||!o(R,e)||o(this,F)&&this[F][e])||t)},Z=function(e,t){if(e=b(e),t=_(t,!0),e!==j||!o(R,t)||o(A,t)){var i=S(e,t);return!i||!o(R,t)||o(e,F)&&e[F][t]||(i.enumerable=!0),i}},$=function(e){for(var t,i=C(b(e)),n=[],s=0;i.length>s;)o(R,t=i[s++])||t==F||t==d||n.push(t);return n},Q=function(e){for(var t,i=e===j,n=C(i?A:b(e)),s=[],r=0;n.length>r;)!o(R,t=n[r++])||i&&!o(j,t)||s.push(R[t]);return s};L||(T=function(){if(this instanceof T)throw TypeError("Symbol is not a constructor!");var e=c(arguments.length>0?arguments[0]:void 0),t=function(i){this===j&&t.call(A,i),o(this,F)&&o(this[F],e)&&(this[F][e]=!1),Y(this,e,w(1,i))};return s&&W&&Y(j,e,{configurable:!0,set:t}),V(e)},a(T.prototype,"toString",function(){return this._k}),O.f=Z,M.f=q,i(69).f=x.f=$,i(31).f=K,i(50).f=Q,s&&!i(39)&&a(j,"propertyIsEnumerable",K,!0),p.f=function(e){return V(f(e))}),r(r.G+r.W+r.F*!L,{Symbol:T});for(var J="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),ee=0;J.length>ee;)f(J[ee++]);for(var te=E(f.store),ie=0;te.length>ie;)v(te[ie++]);r(r.S+r.F*!L,"Symbol",{for:function(e){return o(z,e+="")?z[e]:z[e]=T(e)},keyFor:function(e){if(!U(e))throw TypeError(e+" is not a symbol!");for(var t in z)if(z[t]===e)return t},useSetter:function(){W=!0},useSimple:function(){W=!1}}),r(r.S+r.F*!L,"Object",{create:X,defineProperty:q,defineProperties:G,getOwnPropertyDescriptor:Z,getOwnPropertyNames:$,getOwnPropertySymbols:Q}),P&&r(r.S+r.F*(!L||h(function(){var e=T();return"[null]"!=I([e])||"{}"!=I({a:e})||"{}"!=I(Object(e))})),"JSON",{stringify:function(e){if(void 0!==e&&!U(e)){for(var t,i,n=[e],o=1;arguments.length>o;)n.push(arguments[o++]);return t=n[1],"function"==typeof t&&(i=t),!i&&y(t)||(t=function(e,t){if(i&&(t=i.call(this,e,t)),!U(t))return t}),n[1]=t,I.apply(P,n)}}}),T.prototype[N]||i(18)(T.prototype,N,T.prototype.valueOf),u(T,"Symbol"),u(Math,"Math",!0),u(n.JSON,"JSON",!0)},function(e,t,i){var n=i(28)("meta"),o=i(25),s=i(14),r=i(12).f,a=0,d=Object.isExtensible||function(){return!0},h=!i(20)(function(){return d(Object.preventExtensions({}))}),l=function(e){r(e,n,{value:{i:"O"+ ++a,w:{}}})},u=function(e,t){if(!o(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!s(e,n)){if(!d(e))return"F";if(!t)return"E";l(e)}return e[n].i},c=function(e,t){if(!s(e,n)){if(!d(e))return!0;if(!t)return!1;l(e)}return e[n].w},f=function(e){return h&&p.NEED&&d(e)&&!s(e,n)&&l(e),e},p=e.exports={KEY:n,NEED:!1,fastKey:u,getWeak:c,onFreeze:f}},function(e,t,i){var n=i(26),o=i(50),s=i(31);e.exports=function(e){var t=n(e),i=o.f;if(i)for(var r,a=i(e),d=s.f,h=0;a.length>h;)d.call(e,r=a[h++])&&t.push(r);return t}},function(e,t,i){var n=i(37);e.exports=Array.isArray||function(e){return"Array"==n(e)}},function(e,t,i){var n=i(17),o=i(69).f,s={}.toString,r="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],a=function(e){try{return o(e)}catch(e){return r.slice()}};e.exports.f=function(e){return r&&"[object Window]"==s.call(e)?a(e):o(n(e))}},function(e,t){},function(e,t,i){i(49)("asyncIterator")},function(e,t,i){i(49)("observable")},function(e,t,i){(function(e){!function(t,i){e.exports=i()}(0,function(){function t(){return Sn.apply(null,arguments)}function i(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function n(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function o(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(e.hasOwnProperty(t))return!1;return!0}function s(e){return void 0===e}function r(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function a(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function d(e,t){var i,n=[];for(i=0;i0)for(i=0;i0?"future":"past"];return M(i)?i(t):i.replace(/%s/i,t)}function B(e,t){var i=e.toLowerCase();Rn[i]=Rn[i+"s"]=Rn[t]=e}function z(e){return"string"==typeof e?Rn[e]||Rn[e.toLowerCase()]:void 0}function R(e){var t,i,n={};for(i in e)h(e,i)&&(t=z(i))&&(n[t]=e[i]);return n}function A(e,t){An[e]=t}function j(e){var t=[];for(var i in e)t.push({unit:i,priority:An[i]});return t.sort(function(e,t){return e.priority-t.priority}),t}function L(e,t,i){var n=""+Math.abs(e),o=t-n.length;return(e>=0?i?"+":"":"-")+Math.pow(10,Math.max(0,o)).toString().substr(1)+n}function H(e,t,i,n){var o=n;"string"==typeof n&&(o=function(){return this[n]()}),e&&(Wn[e]=o),t&&(Wn[t[0]]=function(){return L(o.apply(this,arguments),t[1],t[2])}),i&&(Wn[i]=function(){return this.localeData().ordinal(o.apply(this,arguments),e)})}function W(e){return e.match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"")}function Y(e){var t,i,n=e.match(jn);for(t=0,i=n.length;t=0&&Ln.test(e);)e=e.replace(Ln,i),Ln.lastIndex=0,n-=1;return e}function q(e,t,i){ro[e]=M(t)?t:function(e,n){return e&&i?i:t}}function G(e,t){return h(ro,e)?ro[e](t._strict,t._locale):new RegExp(X(e))}function X(e){return K(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,i,n,o){return t||i||n||o}))}function K(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Z(e,t){var i,n=t;for("string"==typeof e&&(e=[e]),r(t)&&(n=function(e,i){i[t]=_(e)}),i=0;i=0&&isFinite(a.getFullYear())&&a.setFullYear(e),a}function _e(e){var t=new Date(Date.UTC.apply(null,arguments));return e<100&&e>=0&&isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e),t}function we(e,t,i){var n=7+t-i;return-(7+_e(e,0,n).getUTCDay()-t)%7+n-1}function ke(e,t,i,n,o){var s,r,a=(7+i-n)%7,d=we(e,n,o),h=1+7*(t-1)+a+d;return h<=0?(s=e-1,r=J(s)+h):h>J(e)?(s=e+1,r=h-J(e)):(s=e,r=h),{year:s,dayOfYear:r}}function xe(e,t,i){var n,o,s=we(e.year(),t,i),r=Math.floor((e.dayOfYear()-s-1)/7)+1;return r<1?(o=e.year()-1,n=r+Oe(o,t,i)):r>Oe(e.year(),t,i)?(n=r-Oe(e.year(),t,i),o=e.year()+1):(o=e.year(),n=r),{week:n,year:o}}function Oe(e,t,i){var n=we(e,t,i),o=we(e+1,t,i);return(J(e)-n+o)/7}function Me(e){return xe(e,this._week.dow,this._week.doy).week}function Ee(){return this._week.dow}function Se(){return this._week.doy}function De(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),"d")}function Ce(e){var t=xe(this,1,4).week;return null==e?t:this.add(7*(e-t),"d")}function Te(e,t){return"string"!=typeof e?e:isNaN(e)?(e=t.weekdaysParse(e),"number"==typeof e?e:null):parseInt(e,10)}function Pe(e,t){return"string"==typeof e?t.weekdaysParse(e)%7||7:isNaN(e)?null:e}function Ie(e,t){return e?i(this._weekdays)?this._weekdays[e.day()]:this._weekdays[this._weekdays.isFormat.test(t)?"format":"standalone"][e.day()]:i(this._weekdays)?this._weekdays:this._weekdays.standalone}function Fe(e){return e?this._weekdaysShort[e.day()]:this._weekdaysShort}function Ne(e){return e?this._weekdaysMin[e.day()]:this._weekdaysMin}function Be(e,t,i){var n,o,s,r=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],n=0;n<7;++n)s=u([2e3,1]).day(n),this._minWeekdaysParse[n]=this.weekdaysMin(s,"").toLocaleLowerCase(),this._shortWeekdaysParse[n]=this.weekdaysShort(s,"").toLocaleLowerCase(),this._weekdaysParse[n]=this.weekdays(s,"").toLocaleLowerCase();return i?"dddd"===t?(o=mo.call(this._weekdaysParse,r),-1!==o?o:null):"ddd"===t?(o=mo.call(this._shortWeekdaysParse,r),-1!==o?o:null):(o=mo.call(this._minWeekdaysParse,r),-1!==o?o:null):"dddd"===t?-1!==(o=mo.call(this._weekdaysParse,r))?o:-1!==(o=mo.call(this._shortWeekdaysParse,r))?o:(o=mo.call(this._minWeekdaysParse,r),-1!==o?o:null):"ddd"===t?-1!==(o=mo.call(this._shortWeekdaysParse,r))?o:-1!==(o=mo.call(this._weekdaysParse,r))?o:(o=mo.call(this._minWeekdaysParse,r),-1!==o?o:null):-1!==(o=mo.call(this._minWeekdaysParse,r))?o:-1!==(o=mo.call(this._weekdaysParse,r))?o:(o=mo.call(this._shortWeekdaysParse,r),-1!==o?o:null)}function ze(e,t,i){var n,o,s;if(this._weekdaysParseExact)return Be.call(this,e,t,i);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),n=0;n<7;n++){if(o=u([2e3,1]).day(n),i&&!this._fullWeekdaysParse[n]&&(this._fullWeekdaysParse[n]=new RegExp("^"+this.weekdays(o,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[n]=new RegExp("^"+this.weekdaysShort(o,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[n]=new RegExp("^"+this.weekdaysMin(o,"").replace(".",".?")+"$","i")),this._weekdaysParse[n]||(s="^"+this.weekdays(o,"")+"|^"+this.weekdaysShort(o,"")+"|^"+this.weekdaysMin(o,""),this._weekdaysParse[n]=new RegExp(s.replace(".",""),"i")),i&&"dddd"===t&&this._fullWeekdaysParse[n].test(e))return n;if(i&&"ddd"===t&&this._shortWeekdaysParse[n].test(e))return n;if(i&&"dd"===t&&this._minWeekdaysParse[n].test(e))return n;if(!i&&this._weekdaysParse[n].test(e))return n}}function Re(e){if(!this.isValid())return null!=e?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=Te(e,this.localeData()),this.add(e-t,"d")):t}function Ae(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,"d")}function je(e){if(!this.isValid())return null!=e?this:NaN;if(null!=e){var t=Pe(e,this.localeData());return this.day(this.day()%7?t:t-7)}return this.day()||7}function Le(e){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||Ye.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(h(this,"_weekdaysRegex")||(this._weekdaysRegex=Co),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)}function He(e){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||Ye.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(h(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=To),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function We(e){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||Ye.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(h(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=Po),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Ye(){function e(e,t){return t.length-e.length}var t,i,n,o,s,r=[],a=[],d=[],h=[];for(t=0;t<7;t++)i=u([2e3,1]).day(t),n=this.weekdaysMin(i,""),o=this.weekdaysShort(i,""),s=this.weekdays(i,""),r.push(n),a.push(o),d.push(s),h.push(n),h.push(o),h.push(s);for(r.sort(e),a.sort(e),d.sort(e),h.sort(e),t=0;t<7;t++)a[t]=K(a[t]),d[t]=K(d[t]),h[t]=K(h[t]);this._weekdaysRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+d.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Ve(){return this.hours()%12||12}function Ue(){return this.hours()||24}function qe(e,t){H(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function Ge(e,t){return t._meridiemParse}function Xe(e){return"p"===(e+"").toLowerCase().charAt(0)}function Ke(e,t,i){return e>11?i?"pm":"PM":i?"am":"AM"}function Ze(e){return e?e.toLowerCase().replace("_","-"):e}function $e(e){for(var t,i,n,o,s=0;s0;){if(n=Qe(o.slice(0,t).join("-")))return n;if(i&&i.length>=t&&w(o,i,!0)>=t-1)break;t--}s++}return null}function Qe(t){var i=null;if(!zo[t]&&void 0!==e&&e&&e.exports)try{i=Io._abbr;!function(){var e=new Error('Cannot find module "./locale"');throw e.code="MODULE_NOT_FOUND",e}(),Je(i)}catch(e){}return zo[t]}function Je(e,t){var i;return e&&(i=s(t)?it(e):et(e,t))&&(Io=i),Io._abbr}function et(e,t){if(null!==t){var i=Bo;if(t.abbr=e,null!=zo[e])O("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),i=zo[e]._config;else if(null!=t.parentLocale){if(null==zo[t.parentLocale])return Ro[t.parentLocale]||(Ro[t.parentLocale]=[]),Ro[t.parentLocale].push({name:e,config:t}),null;i=zo[t.parentLocale]._config}return zo[e]=new D(S(i,t)),Ro[e]&&Ro[e].forEach(function(e){et(e.name,e.config)}),Je(e),zo[e]}return delete zo[e],null}function tt(e,t){if(null!=t){var i,n=Bo;null!=zo[e]&&(n=zo[e]._config),t=S(n,t),i=new D(t),i.parentLocale=zo[e],zo[e]=i,Je(e)}else null!=zo[e]&&(null!=zo[e].parentLocale?zo[e]=zo[e].parentLocale:null!=zo[e]&&delete zo[e]);return zo[e]}function it(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return Io;if(!i(e)){if(t=Qe(e))return t;e=[e]}return $e(e)}function nt(){return In(zo)}function ot(e){var t,i=e._a;return i&&-2===f(e).overflow&&(t=i[lo]<0||i[lo]>11?lo:i[uo]<1||i[uo]>de(i[ho],i[lo])?uo:i[co]<0||i[co]>24||24===i[co]&&(0!==i[fo]||0!==i[po]||0!==i[vo])?co:i[fo]<0||i[fo]>59?fo:i[po]<0||i[po]>59?po:i[vo]<0||i[vo]>999?vo:-1,f(e)._overflowDayOfYear&&(tuo)&&(t=uo),f(e)._overflowWeeks&&-1===t&&(t=go),f(e)._overflowWeekday&&-1===t&&(t=yo),f(e).overflow=t),e}function st(e,t,i){return null!=e?e:null!=t?t:i}function rt(e){var i=new Date(t.now());return e._useUTC?[i.getUTCFullYear(),i.getUTCMonth(),i.getUTCDate()]:[i.getFullYear(),i.getMonth(),i.getDate()]}function at(e){var t,i,n,o,s=[];if(!e._d){for(n=rt(e),e._w&&null==e._a[uo]&&null==e._a[lo]&&dt(e),null!=e._dayOfYear&&(o=st(e._a[ho],n[ho]),(e._dayOfYear>J(o)||0===e._dayOfYear)&&(f(e)._overflowDayOfYear=!0),i=_e(o,0,e._dayOfYear),e._a[lo]=i.getUTCMonth(),e._a[uo]=i.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=s[t]=n[t];for(;t<7;t++)e._a[t]=s[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[co]&&0===e._a[fo]&&0===e._a[po]&&0===e._a[vo]&&(e._nextDay=!0,e._a[co]=0),e._d=(e._useUTC?_e:be).apply(null,s),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[co]=24),e._w&&void 0!==e._w.d&&e._w.d!==e._d.getDay()&&(f(e).weekdayMismatch=!0)}}function dt(e){var t,i,n,o,s,r,a,d;if(t=e._w,null!=t.GG||null!=t.W||null!=t.E)s=1,r=4,i=st(t.GG,e._a[ho],xe(Mt(),1,4).year),n=st(t.W,1),((o=st(t.E,1))<1||o>7)&&(d=!0);else{s=e._locale._week.dow,r=e._locale._week.doy;var h=xe(Mt(),s,r);i=st(t.gg,e._a[ho],h.year),n=st(t.w,h.week),null!=t.d?((o=t.d)<0||o>6)&&(d=!0):null!=t.e?(o=t.e+s,(t.e<0||t.e>6)&&(d=!0)):o=s}n<1||n>Oe(i,s,r)?f(e)._overflowWeeks=!0:null!=d?f(e)._overflowWeekday=!0:(a=ke(i,n,o,s,r),e._a[ho]=a.year,e._dayOfYear=a.dayOfYear)}function ht(e){var t,i,n,o,s,r,a=e._i,d=Ao.exec(a)||jo.exec(a);if(d){for(f(e).iso=!0,t=0,i=Ho.length;t0&&f(e).unusedInput.push(r),a=a.slice(a.indexOf(n)+n.length),h+=n.length),Wn[s]?(n?f(e).empty=!1:f(e).unusedTokens.push(s),Q(s,n,e)):e._strict&&!n&&f(e).unusedTokens.push(s);f(e).charsLeftOver=d-h,a.length>0&&f(e).unusedInput.push(a),e._a[co]<=12&&!0===f(e).bigHour&&e._a[co]>0&&(f(e).bigHour=void 0),f(e).parsedDateParts=e._a.slice(0),f(e).meridiem=e._meridiem,e._a[co]=mt(e._locale,e._a[co],e._meridiem),at(e),ot(e)}function mt(e,t,i){var n;return null==i?t:null!=e.meridiemHour?e.meridiemHour(t,i):null!=e.isPM?(n=e.isPM(i),n&&t<12&&(t+=12),n||12!==t||(t=0),t):t}function bt(e){var t,i,n,o,s;if(0===e._f.length)return f(e).invalidFormat=!0,void(e._d=new Date(NaN));for(o=0;othis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function qt(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e={};if(g(e,this),e=kt(e),e._a){var t=e._isUTC?u(e._a):Mt(e._a);this._isDSTShifted=this.isValid()&&w(e._a,t.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Gt(){return!!this.isValid()&&!this._isUTC}function Xt(){return!!this.isValid()&&this._isUTC}function Kt(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Zt(e,t){var i,n,o,s=e,a=null;return Ft(e)?s={ms:e._milliseconds,d:e._days,M:e._months}:r(e)?(s={},t?s[t]=e:s.milliseconds=e):(a=$o.exec(e))?(i="-"===a[1]?-1:1,s={y:0,d:_(a[uo])*i,h:_(a[co])*i,m:_(a[fo])*i,s:_(a[po])*i,ms:_(Nt(1e3*a[vo]))*i}):(a=Qo.exec(e))?(i="-"===a[1]?-1:(a[1],1),s={y:$t(a[2],i),M:$t(a[3],i),w:$t(a[4],i),d:$t(a[5],i),h:$t(a[6],i),m:$t(a[7],i),s:$t(a[8],i)}):null==s?s={}:"object"==typeof s&&("from"in s||"to"in s)&&(o=Jt(Mt(s.from),Mt(s.to)),s={},s.ms=o.milliseconds,s.M=o.months),n=new It(s),Ft(e)&&h(e,"_locale")&&(n._locale=e._locale),n}function $t(e,t){var i=e&&parseFloat(e.replace(",","."));return(isNaN(i)?0:i)*t}function Qt(e,t){var i={milliseconds:0,months:0};return i.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(i.months,"M").isAfter(t)&&--i.months,i.milliseconds=+t-+e.clone().add(i.months,"M"),i}function Jt(e,t){var i;return e.isValid()&&t.isValid()?(t=Rt(t,e),e.isBefore(t)?i=Qt(e,t):(i=Qt(t,e),i.milliseconds=-i.milliseconds,i.months=-i.months),i):{milliseconds:0,months:0}}function ei(e,t){return function(i,n){var o,s;return null===n||isNaN(+n)||(O(t,"moment()."+t+"(period, number) is deprecated. Please use moment()."+t+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),s=i,i=n,n=s),i="string"==typeof i?+i:i,o=Zt(i,n),ti(this,o,e),this}}function ti(e,i,n,o){var s=i._milliseconds,r=Nt(i._days),a=Nt(i._months);e.isValid()&&(o=null==o||o,a&&fe(e,ne(e,"Month")+a*n),r&&oe(e,"Date",ne(e,"Date")+r*n),s&&e._d.setTime(e._d.valueOf()+s*n),o&&t.updateOffset(e,r||a))}function ii(e,t){var i=e.diff(t,"days",!0);return i<-6?"sameElse":i<-1?"lastWeek":i<0?"lastDay":i<1?"sameDay":i<2?"nextDay":i<7?"nextWeek":"sameElse"}function ni(e,i){var n=e||Mt(),o=Rt(n,this).startOf("day"),s=t.calendarFormat(this,o)||"sameElse",r=i&&(M(i[s])?i[s].call(this,n):i[s]);return this.format(r||this.localeData().calendar(s,this,Mt(n)))}function oi(){return new y(this)}function si(e,t){var i=m(e)?e:Mt(e);return!(!this.isValid()||!i.isValid())&&(t=z(s(t)?"millisecond":t),"millisecond"===t?this.valueOf()>i.valueOf():i.valueOf()9999?V(e,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):M(Date.prototype.toISOString)?this.toDate().toISOString():V(e,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function vi(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",t="";this.isLocal()||(e=0===this.utcOffset()?"moment.utc":"moment.parseZone",t="Z");var i="["+e+'("]',n=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",o=t+'[")]';return this.format(i+n+"-MM-DD[T]HH:mm:ss.SSS"+o)}function gi(e){e||(e=this.isUtc()?t.defaultFormatUtc:t.defaultFormat);var i=V(this,e);return this.localeData().postformat(i)}function yi(e,t){return this.isValid()&&(m(e)&&e.isValid()||Mt(e).isValid())?Zt({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function mi(e){return this.from(Mt(),e)}function bi(e,t){return this.isValid()&&(m(e)&&e.isValid()||Mt(e).isValid())?Zt({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()}function _i(e){return this.to(Mt(),e)}function wi(e){var t;return void 0===e?this._locale._abbr:(t=it(e),null!=t&&(this._locale=t),this)}function ki(){return this._locale}function xi(e){switch(e=z(e)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===e&&this.weekday(0),"isoWeek"===e&&this.isoWeekday(1),"quarter"===e&&this.month(3*Math.floor(this.month()/3)),this}function Oi(e){return void 0===(e=z(e))||"millisecond"===e?this:("date"===e&&(e="day"),this.startOf(e).add(1,"isoWeek"===e?"week":e).subtract(1,"ms"))}function Mi(){return this._d.valueOf()-6e4*(this._offset||0)}function Ei(){return Math.floor(this.valueOf()/1e3)}function Si(){return new Date(this.valueOf())}function Di(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]}function Ci(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}}function Ti(){return this.isValid()?this.toISOString():null}function Pi(){return p(this)}function Ii(){return l({},f(this))}function Fi(){return f(this).overflow}function Ni(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Bi(e,t){H(0,[e,e.length],0,t)}function zi(e){return Li.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Ri(e){return Li.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)}function Ai(){return Oe(this.year(),1,4)}function ji(){var e=this.localeData()._week;return Oe(this.year(),e.dow,e.doy)}function Li(e,t,i,n,o){var s;return null==e?xe(this,n,o).year:(s=Oe(e,n,o),t>s&&(t=s),Hi.call(this,e,t,i,n,o))}function Hi(e,t,i,n,o){var s=ke(e,t,i,n,o),r=_e(s.year,0,s.dayOfYear);return this.year(r.getUTCFullYear()),this.month(r.getUTCMonth()),this.date(r.getUTCDate()),this}function Wi(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)}function Yi(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?t:this.add(e-t,"d")}function Vi(e,t){t[vo]=_(1e3*("0."+e))}function Ui(){return this._isUTC?"UTC":""}function qi(){return this._isUTC?"Coordinated Universal Time":""}function Gi(e){return Mt(1e3*e)}function Xi(){return Mt.apply(null,arguments).parseZone()}function Ki(e){return e}function Zi(e,t,i,n){var o=it(),s=u().set(n,t);return o[i](s,e)}function $i(e,t,i){if(r(e)&&(t=e,e=void 0),e=e||"",null!=t)return Zi(e,t,i,"month");var n,o=[];for(n=0;n<12;n++)o[n]=Zi(e,n,i,"month");return o}function Qi(e,t,i,n){"boolean"==typeof e?(r(t)&&(i=t,t=void 0),t=t||""):(t=e,i=t,e=!1,r(t)&&(i=t,t=void 0),t=t||"");var o=it(),s=e?o._week.dow:0;if(null!=i)return Zi(t,(i+s)%7,n,"day");var a,d=[];for(a=0;a<7;a++)d[a]=Zi(t,(a+s)%7,n,"day");return d}function Ji(e,t){return $i(e,t,"months")}function en(e,t){return $i(e,t,"monthsShort")}function tn(e,t,i){return Qi(e,t,i,"weekdays")}function nn(e,t,i){return Qi(e,t,i,"weekdaysShort")}function on(e,t,i){return Qi(e,t,i,"weekdaysMin")}function sn(){var e=this._data;return this._milliseconds=hs(this._milliseconds),this._days=hs(this._days),this._months=hs(this._months),e.milliseconds=hs(e.milliseconds),e.seconds=hs(e.seconds),e.minutes=hs(e.minutes),e.hours=hs(e.hours),e.months=hs(e.months),e.years=hs(e.years),this}function rn(e,t,i,n){var o=Zt(t,i);return e._milliseconds+=n*o._milliseconds,e._days+=n*o._days,e._months+=n*o._months,e._bubble()}function an(e,t){return rn(this,e,t,1)}function dn(e,t){return rn(this,e,t,-1)}function hn(e){return e<0?Math.floor(e):Math.ceil(e)}function ln(){var e,t,i,n,o,s=this._milliseconds,r=this._days,a=this._months,d=this._data;return s>=0&&r>=0&&a>=0||s<=0&&r<=0&&a<=0||(s+=864e5*hn(cn(a)+r),r=0,a=0),d.milliseconds=s%1e3,e=b(s/1e3),d.seconds=e%60,t=b(e/60),d.minutes=t%60,i=b(t/60),d.hours=i%24,r+=b(i/24),o=b(un(r)),a+=o,r-=hn(cn(o)),n=b(a/12),a%=12,d.days=r,d.months=a,d.years=n,this}function un(e){return 4800*e/146097}function cn(e){return 146097*e/4800}function fn(e){if(!this.isValid())return NaN;var t,i,n=this._milliseconds;if("month"===(e=z(e))||"year"===e)return t=this._days+n/864e5,i=this._months+un(t),"month"===e?i:i/12;switch(t=this._days+Math.round(cn(this._months)),e){case"week":return t/7+n/6048e5;case"day":return t+n/864e5;case"hour":return 24*t+n/36e5;case"minute":return 1440*t+n/6e4;case"second":return 86400*t+n/1e3;case"millisecond":return Math.floor(864e5*t)+n;default:throw new Error("Unknown unit "+e)}}function pn(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*_(this._months/12):NaN}function vn(e){return function(){return this.as(e)}}function gn(){return Zt(this)}function yn(e){return e=z(e),this.isValid()?this[e+"s"]():NaN}function mn(e){return function(){return this.isValid()?this._data[e]:NaN}}function bn(){return b(this.days()/7)}function _n(e,t,i,n,o){return o.relativeTime(t||1,!!i,e,n)}function wn(e,t,i){var n=Zt(e).abs(),o=Ms(n.as("s")),s=Ms(n.as("m")),r=Ms(n.as("h")),a=Ms(n.as("d")),d=Ms(n.as("M")),h=Ms(n.as("y")),l=o<=Es.ss&&["s",o]||o0,l[4]=i,_n.apply(null,l)}function kn(e){return void 0===e?Ms:"function"==typeof e&&(Ms=e,!0)}function xn(e,t){return void 0!==Es[e]&&(void 0===t?Es[e]:(Es[e]=t,"s"===e&&(Es.ss=t-1),!0))}function On(e){if(!this.isValid())return this.localeData().invalidDate();var t=this.localeData(),i=wn(this,!e,t);return e&&(i=t.pastFuture(+this,i)),t.postformat(i)}function Mn(e){return(e>0)-(e<0)||+e}function En(){if(!this.isValid())return this.localeData().invalidDate();var e,t,i,n=Ss(this._milliseconds)/1e3,o=Ss(this._days),s=Ss(this._months);e=b(n/60),t=b(e/60),n%=60,e%=60,i=b(s/12),s%=12;var r=i,a=s,d=o,h=t,l=e,u=n?n.toFixed(3).replace(/\.?0+$/,""):"",c=this.asSeconds();if(!c)return"P0D";var f=c<0?"-":"",p=Mn(this._months)!==Mn(c)?"-":"",v=Mn(this._days)!==Mn(c)?"-":"",g=Mn(this._milliseconds)!==Mn(c)?"-":"";return f+"P"+(r?p+r+"Y":"")+(a?p+a+"M":"")+(d?v+d+"D":"")+(h||l||u?"T":"")+(h?g+h+"H":"")+(l?g+l+"M":"")+(u?g+u+"S":"")}var Sn,Dn;Dn=Array.prototype.some?Array.prototype.some:function(e){for(var t=Object(this),i=t.length>>>0,n=0;n68?1900:2e3)};var mo,bo=ie("FullYear",!0);mo=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var t;for(t=0;tthis?this:e:v()}),Xo=function(){return Date.now?Date.now():+new Date},Ko=["year","quarter","month","week","day","hour","minute","second","millisecond"];Bt("Z",":"),Bt("ZZ",""),q("Z",no),q("ZZ",no),Z(["Z","ZZ"],function(e,t,i){i._useUTC=!0,i._tzm=zt(no,e)});var Zo=/([\+\-]|\d\d)/gi;t.updateOffset=function(){};var $o=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Qo=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;Zt.fn=It.prototype,Zt.invalid=Pt;var Jo=ei(1,"add"),es=ei(-1,"subtract");t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",t.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ts=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Bi("gggg","weekYear"),Bi("ggggg","weekYear"),Bi("GGGG","isoWeekYear"),Bi("GGGGG","isoWeekYear"),B("weekYear","gg"),B("isoWeekYear","GG"),A("weekYear",1),A("isoWeekYear",1),q("G",to),q("g",to),q("GG",Xn,Vn),q("gg",Xn,Vn),q("GGGG",Qn,qn),q("gggg",Qn,qn),q("GGGGG",Jn,Gn),q("ggggg",Jn,Gn),$(["gggg","ggggg","GGGG","GGGGG"],function(e,t,i,n){t[n.substr(0,2)]=_(e)}),$(["gg","GG"],function(e,i,n,o){i[o]=t.parseTwoDigitYear(e)}),H("Q",0,"Qo","quarter"),B("quarter","Q"),A("quarter",7),q("Q",Yn),Z("Q",function(e,t){t[lo]=3*(_(e)-1)}),H("D",["DD",2],"Do","date"),B("date","D"),A("date",9),q("D",Xn),q("DD",Xn,Vn),q("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),Z(["D","DD"],uo),Z("Do",function(e,t){t[uo]=_(e.match(Xn)[0],10)});var is=ie("Date",!0);H("DDD",["DDDD",3],"DDDo","dayOfYear"),B("dayOfYear","DDD"),A("dayOfYear",4),q("DDD",$n),q("DDDD",Un),Z(["DDD","DDDD"],function(e,t,i){i._dayOfYear=_(e)}),H("m",["mm",2],0,"minute"),B("minute","m"),A("minute",14),q("m",Xn),q("mm",Xn,Vn),Z(["m","mm"],fo);var ns=ie("Minutes",!1);H("s",["ss",2],0,"second"),B("second","s"),A("second",15),q("s",Xn),q("ss",Xn,Vn),Z(["s","ss"],po);var os=ie("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),B("millisecond","ms"),A("millisecond",16),q("S",$n,Yn),q("SS",$n,Vn),q("SSS",$n,Un);var ss;for(ss="SSSS";ss.length<=9;ss+="S")q(ss,eo);for(ss="S";ss.length<=9;ss+="S")Z(ss,Vi);var rs=ie("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var as=y.prototype;as.add=Jo,as.calendar=ni,as.clone=oi,as.diff=ui,as.endOf=Oi,as.format=gi,as.from=yi,as.fromNow=mi,as.to=bi,as.toNow=_i,as.get=se,as.invalidAt=Fi,as.isAfter=si,as.isBefore=ri,as.isBetween=ai,as.isSame=di,as.isSameOrAfter=hi,as.isSameOrBefore=li,as.isValid=Pi,as.lang=ts,as.locale=wi,as.localeData=ki,as.max=Go,as.min=qo,as.parsingFlags=Ii,as.set=re,as.startOf=xi,as.subtract=es,as.toArray=Di,as.toObject=Ci,as.toDate=Si,as.toISOString=pi,as.inspect=vi,as.toJSON=Ti,as.toString=fi,as.unix=Ei,as.valueOf=Mi,as.creationData=Ni,as.year=bo,as.isLeapYear=te,as.weekYear=zi,as.isoWeekYear=Ri,as.quarter=as.quarters=Wi,as.month=pe,as.daysInMonth=ve,as.week=as.weeks=De,as.isoWeek=as.isoWeeks=Ce,as.weeksInYear=ji,as.isoWeeksInYear=Ai,as.date=is,as.day=as.days=Re,as.weekday=Ae,as.isoWeekday=je,as.dayOfYear=Yi,as.hour=as.hours=No,as.minute=as.minutes=ns,as.second=as.seconds=os,as.millisecond=as.milliseconds=rs,as.utcOffset=jt,as.utc=Ht,as.local=Wt,as.parseZone=Yt,as.hasAlignedHourOffset=Vt,as.isDST=Ut,as.isLocal=Gt,as.isUtcOffset=Xt,as.isUtc=Kt,as.isUTC=Kt,as.zoneAbbr=Ui,as.zoneName=qi,as.dates=x("dates accessor is deprecated. Use date instead.",is),as.months=x("months accessor is deprecated. Use month instead",pe),as.years=x("years accessor is deprecated. Use year instead",bo),as.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Lt),as.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",qt);var ds=D.prototype;ds.calendar=C,ds.longDateFormat=T,ds.invalidDate=P,ds.ordinal=I,ds.preparse=Ki,ds.postformat=Ki,ds.relativeTime=F,ds.pastFuture=N,ds.set=E,ds.months=he,ds.monthsShort=le,ds.monthsParse=ce,ds.monthsRegex=ye,ds.monthsShortRegex=ge,ds.week=Me,ds.firstDayOfYear=Se,ds.firstDayOfWeek=Ee,ds.weekdays=Ie,ds.weekdaysMin=Ne,ds.weekdaysShort=Fe,ds.weekdaysParse=ze,ds.weekdaysRegex=Le,ds.weekdaysShortRegex=He,ds.weekdaysMinRegex=We,ds.isPM=Xe,ds.meridiem=Ke,Je("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===_(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")}}),t.lang=x("moment.lang is deprecated. Use moment.locale instead.",Je),t.langData=x("moment.langData is deprecated. Use moment.localeData instead.",it);var hs=Math.abs,ls=vn("ms"),us=vn("s"),cs=vn("m"),fs=vn("h"),ps=vn("d"),vs=vn("w"),gs=vn("M"),ys=vn("y"),ms=mn("milliseconds"),bs=mn("seconds"),_s=mn("minutes"),ws=mn("hours"),ks=mn("days"),xs=mn("months"),Os=mn("years"),Ms=Math.round,Es={ss:44,s:45,m:45,h:22,d:26,M:11},Ss=Math.abs,Ds=It.prototype;return Ds.isValid=Tt,Ds.abs=sn,Ds.add=an,Ds.subtract=dn,Ds.as=fn,Ds.asMilliseconds=ls,Ds.asSeconds=us,Ds.asMinutes=cs,Ds.asHours=fs,Ds.asDays=ps,Ds.asWeeks=vs,Ds.asMonths=gs,Ds.asYears=ys,Ds.valueOf=pn,Ds._bubble=ln,Ds.clone=gn,Ds.get=yn,Ds.milliseconds=ms,Ds.seconds=bs,Ds.minutes=_s,Ds.hours=ws,Ds.days=ks,Ds.weeks=bn,Ds.months=xs,Ds.years=Os,Ds.humanize=On,Ds.toISOString=En,Ds.toString=En,Ds.toJSON=En,Ds.locale=wi,Ds.localeData=ki,Ds.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",En),Ds.lang=ts,H("X",0,0,"unix"),H("x",0,0,"valueOf"),q("x",to),q("X",oo),Z("X",function(e,t,i){i._d=new Date(1e3*parseFloat(e,10))}),Z("x",function(e,t,i){i._d=new Date(_(e))}),t.version="2.19.1",function(e){Sn=e}(Mt),t.fn=as,t.min=St,t.max=Dt,t.now=Xo,t.utc=u,t.unix=Gi,t.months=Ji,t.isDate=a,t.locale=Je,t.invalid=v,t.duration=Zt,t.isMoment=m,t.weekdays=tn,t.parseZone=Xi,t.localeData=it,t.isDuration=Ft,t.monthsShort=en,t.weekdaysMin=on,t.defineLocale=et,t.updateLocale=tt,t.locales=nt,t.weekdaysShort=nn,t.normalizeUnits=z,t.relativeTimeRounding=kn,t.relativeTimeThreshold=xn,t.calendarFormat=ii,t.prototype=as,t})}).call(t,i(115)(e))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t){function i(e){throw new Error("Cannot find module '"+e+"'.")}i.keys=function(){return[]},i.resolve=i,e.exports=i,i.id=116},function(e,t,i){(function(t){function i(e,t,i){var n=t&&i||0,o=0;for(t=t||[],e.toLowerCase().replace(/[0-9a-f]{2}/g,function(e){o<16&&(t[n+o++]=u[e])});o<16;)t[n+o++]=0;return t}function n(e,t){var i=t||0,n=l;return n[e[i++]]+n[e[i++]]+n[e[i++]]+n[e[i++]]+"-"+n[e[i++]]+n[e[i++]]+"-"+n[e[i++]]+n[e[i++]]+"-"+n[e[i++]]+n[e[i++]]+"-"+n[e[i++]]+n[e[i++]]+n[e[i++]]+n[e[i++]]+n[e[i++]]+n[e[i++]]}function o(e,t,i){var o=t&&i||0,s=t||[];e=e||{};var r=void 0!==e.clockseq?e.clockseq:v,a=void 0!==e.msecs?e.msecs:(new Date).getTime(),d=void 0!==e.nsecs?e.nsecs:y+1,h=a-g+(d-y)/1e4;if(h<0&&void 0===e.clockseq&&(r=r+1&16383),(h<0||a>g)&&void 0===e.nsecs&&(d=0),d>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");g=a,y=d,v=r,a+=122192928e5;var l=(1e4*(268435455&a)+d)%4294967296;s[o++]=l>>>24&255,s[o++]=l>>>16&255,s[o++]=l>>>8&255,s[o++]=255&l;var u=a/4294967296*1e4&268435455;s[o++]=u>>>8&255,s[o++]=255&u,s[o++]=u>>>24&15|16,s[o++]=u>>>16&255,s[o++]=r>>>8|128,s[o++]=255&r;for(var c=e.node||p,f=0;f<6;f++)s[o+f]=c[f];return t||n(s)}function s(e,t,i){var o=t&&i||0;"string"==typeof e&&(t="binary"==e?new Array(16):null,e=null),e=e||{};var s=e.random||(e.rng||r)();if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,t)for(var a=0;a<16;a++)t[o+a]=s[a];return t||n(s)}var r,a="undefined"!=typeof window?window:void 0!==t?t:null;if(a&&a.crypto&&crypto.getRandomValues){var d=new Uint8Array(16);r=function(){return crypto.getRandomValues(d),d}}if(!r){var h=new Array(16);r=function(){for(var e,t=0;t<16;t++)0==(3&t)&&(e=4294967296*Math.random()),h[t]=e>>>((3&t)<<3)&255;return h}}for(var l=[],u={},c=0;c<256;c++)l[c]=(c+256).toString(16).substr(1),u[l[c]]=c;var f=r(),p=[1|f[0],f[1],f[2],f[3],f[4],f[5]],v=16383&(f[6]<<8|f[7]),g=0,y=0,m=s;m.v1=o,m.v4=s,m.parse=i,m.unparse=n,e.exports=m}).call(t,i(118))},function(e,t){var i;i=function(){return this}();try{i=i||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(i=window)}e.exports=i},function(e,t,i){t.prepareElements=function(e){for(var t in e)e.hasOwnProperty(t)&&(e[t].redundant=e[t].used,e[t].used=[])},t.cleanupElements=function(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t].redundant){for(var i=0;i0?(n=t[e].redundant[0],t[e].redundant.shift()):(n=document.createElementNS("http://www.w3.org/2000/svg",e),i.appendChild(n)):(n=document.createElementNS("http://www.w3.org/2000/svg",e),t[e]={used:[],redundant:[]},i.appendChild(n)),t[e].used.push(n),n},t.getDOMElement=function(e,t,i,n){var o;return t.hasOwnProperty(e)?t[e].redundant.length>0?(o=t[e].redundant[0],t[e].redundant.shift()):(o=document.createElement(e),void 0!==n?i.insertBefore(o,n):i.appendChild(o)):(o=document.createElement(e),t[e]={used:[],redundant:[]},void 0!==n?i.insertBefore(o,n):i.appendChild(o)),t[e].used.push(o),o},t.drawPoint=function(e,i,n,o,s,r){var a;if("circle"==n.style?(a=t.getSVGElement("circle",o,s),a.setAttributeNS(null,"cx",e),a.setAttributeNS(null,"cy",i),a.setAttributeNS(null,"r",.5*n.size)):(a=t.getSVGElement("rect",o,s),a.setAttributeNS(null,"x",e-.5*n.size),a.setAttributeNS(null,"y",i-.5*n.size),a.setAttributeNS(null,"width",n.size),a.setAttributeNS(null,"height",n.size)),void 0!==n.styles&&a.setAttributeNS(null,"style",n.styles),a.setAttributeNS(null,"class",n.className+" vis-point"),r){var d=t.getSVGElement("text",o,s);r.xOffset&&(e+=r.xOffset),r.yOffset&&(i+=r.yOffset),r.content&&(d.textContent=r.content),r.className&&d.setAttributeNS(null,"class",r.className+" vis-label"),d.setAttributeNS(null,"x",e),d.setAttributeNS(null,"y",i)}return a},t.drawBar=function(e,i,n,o,s,r,a,d){if(0!=o){o<0&&(o*=-1,i-=o);var h=t.getSVGElement("rect",r,a);h.setAttributeNS(null,"x",e-.5*n),h.setAttributeNS(null,"y",i),h.setAttributeNS(null,"width",n),h.setAttributeNS(null,"height",o),h.setAttributeNS(null,"class",s),d&&h.setAttributeNS(null,"style",d)}}},function(e,t,i){var n=i(6),o=n.JSON||(n.JSON={stringify:JSON.stringify});e.exports=function(e){return o.stringify.apply(o,arguments)}},function(e,t,i){function n(e,t,i){var o=this;if(!(this instanceof n))throw new SyntaxError("Constructor must be called with the new operator");this.options={},this.defaultOptions={locale:"en",locales:h,clickToUse:!1},s.extend(this.options,this.defaultOptions),this.body={container:e,nodes:{},nodeIndices:[],edges:{},edgeIndices:[],emitter:{on:this.on.bind(this),off:this.off.bind(this),emit:this.emit.bind(this),once:this.once.bind(this)},eventListeners:{onTap:function(){},onTouch:function(){},onDoubleTap:function(){},onHold:function(){},onDragStart:function(){},onDrag:function(){},onDragEnd:function(){},onMouseWheel:function(){},onPinch:function(){},onMouseMove:function(){},onRelease:function(){},onContext:function(){}},data:{nodes:null,edges:null},functions:{createNode:function(){},createEdge:function(){},getPointer:function(){}},modules:{},view:{scale:1,translation:{x:0,y:0}}},this.bindEventListeners(),this.images=new l(function(){return o.body.emitter.emit("_requestRedraw")}),this.groups=new u,this.canvas=new y(this.body),this.selectionHandler=new _(this.body,this.canvas),this.interactionHandler=new b(this.body,this.canvas,this.selectionHandler),this.view=new m(this.body,this.canvas),this.renderer=new g(this.body,this.canvas),this.physics=new p(this.body),this.layoutEngine=new w(this.body),this.clustering=new v(this.body),this.manipulation=new k(this.body,this.canvas,this.selectionHandler),this.nodesHandler=new c(this.body,this.images,this.groups,this.layoutEngine),this.edgesHandler=new f(this.body,this.images,this.groups),this.body.modules.kamadaKawai=new T(this.body,150,.05),this.body.modules.clustering=this.clustering,this.canvas._create(),this.setOptions(i),this.setData(t)}i(122);var o=i(73),s=i(5),r=i(74),a=i(75),d=i(123),h=i(126),l=i(76).default,u=i(131).default,c=i(132).default,f=i(163).default,p=i(169).default,v=i(176).default,g=i(178).default,y=i(179).default,m=i(180).default,b=i(181).default,_=i(184).default,w=i(185).default,k=i(187).default,x=i(188).default,O=i(54).default,M=i(54),E=M.printStyle,S=i(82),D=S.allOptions,C=S.configureOptions,T=i(190).default;o(n.prototype),n.prototype.setOptions=function(e){var t=this;if(void 0!==e){!0===O.validate(e,D)&&console.log("%cErrors have been found in the supplied options object.",E);var i=["locale","locales","clickToUse"];if(s.selectiveDeepExtend(i,this.options,e),e=this.layoutEngine.setOptions(e.layout,e),this.canvas.setOptions(e),this.groups.setOptions(e.groups),this.nodesHandler.setOptions(e.nodes),this.edgesHandler.setOptions(e.edges),this.physics.setOptions(e.physics),this.manipulation.setOptions(e.manipulation,e,this.options),this.interactionHandler.setOptions(e.interaction),this.renderer.setOptions(e.interaction),this.selectionHandler.setOptions(e.interaction),void 0!==e.groups&&this.body.emitter.emit("refreshNodes"),"configure"in e&&(this.configurator||(this.configurator=new x(this,this.body.container,C,this.canvas.pixelRatio)),this.configurator.setOptions(e.configure)),this.configurator&&!0===this.configurator.options.enabled){var n={nodes:{},edges:{},layout:{},interaction:{},manipulation:{},physics:{},global:{}};s.deepExtend(n.nodes,this.nodesHandler.options),s.deepExtend(n.edges,this.edgesHandler.options),s.deepExtend(n.layout,this.layoutEngine.options),s.deepExtend(n.interaction,this.selectionHandler.options),s.deepExtend(n.interaction,this.renderer.options),s.deepExtend(n.interaction,this.interactionHandler.options),s.deepExtend(n.manipulation,this.manipulation.options),s.deepExtend(n.physics,this.physics.options),s.deepExtend(n.global,this.canvas.options),s.deepExtend(n.global,this.options),this.configurator.setModuleOptions(n)}void 0!==e.clickToUse?!0===e.clickToUse?void 0===this.activator&&(this.activator=new d(this.canvas.frame),this.activator.on("change",function(){t.body.emitter.emit("activate")})):(void 0!==this.activator&&(this.activator.destroy(),delete this.activator),this.body.emitter.emit("activate")):this.body.emitter.emit("activate"),this.canvas.setSize(),this.body.emitter.emit("startSimulation")}},n.prototype._updateVisibleIndices=function(){var e=this.body.nodes,t=this.body.edges;this.body.nodeIndices=[],this.body.edgeIndices=[];for(var i in e)e.hasOwnProperty(i)&&(this.clustering._isClusteredNode(i)||!1!==e[i].options.hidden||this.body.nodeIndices.push(e[i].id));for(var n in t)if(t.hasOwnProperty(n)){var o=t[n],s=e[o.fromId],r=e[o.toId],a=void 0!==s&&void 0!==r,d=!this.clustering._isClusteredEdge(n)&&!1===o.options.hidden&&a&&!1===s.options.hidden&&!1===r.options.hidden;d&&this.body.edgeIndices.push(o.id)}},n.prototype.bindEventListeners=function(){var e=this;this.body.emitter.on("_dataChanged",function(){e.edgesHandler._updateState(),e.body.emitter.emit("_dataUpdated")}),this.body.emitter.on("_dataUpdated",function(){e.clustering._updateState(),e._updateVisibleIndices(),e._updateValueRange(e.body.nodes),e._updateValueRange(e.body.edges),e.body.emitter.emit("startSimulation"),e.body.emitter.emit("_requestRedraw")})},n.prototype.setData=function(e){if(this.body.emitter.emit("resetPhysics"),this.body.emitter.emit("_resetData"),this.selectionHandler.unselectAll(),e&&e.dot&&(e.nodes||e.edges))throw new SyntaxError('Data must contain either parameter "dot" or parameter pair "nodes" and "edges", but not both.');if(this.setOptions(e&&e.options),e&&e.dot){console.log("The dot property has been deprecated. Please use the static convertDot method to convert DOT into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertDot(dotString);");var t=r.DOTToGraph(e.dot);return void this.setData(t)}if(e&&e.gephi){console.log("The gephi property has been deprecated. Please use the static convertGephi method to convert gephi into vis.network format and use the normal data format with nodes and edges. This converter is used like this: var data = vis.network.convertGephi(gephiJson);");var i=a.parseGephi(e.gephi);return void this.setData(i)}this.nodesHandler.setData(e&&e.nodes,!0),this.edgesHandler.setData(e&&e.edges,!0),this.body.emitter.emit("_dataChanged"),this.body.emitter.emit("_dataLoaded"),this.body.emitter.emit("initPhysics")},n.prototype.destroy=function(){this.body.emitter.emit("destroy"),this.body.emitter.off(),this.off(),delete this.groups,delete this.canvas,delete this.selectionHandler,delete this.interactionHandler,delete this.view,delete this.renderer,delete this.physics,delete this.layoutEngine,delete this.clustering,delete this.manipulation,delete this.nodesHandler,delete this.edgesHandler,delete this.configurator,delete this.images;for(var e in this.body.nodes)this.body.nodes.hasOwnProperty(e)&&delete this.body.nodes[e];for(var t in this.body.edges)this.body.edges.hasOwnProperty(t)&&delete this.body.edges[t];s.recursiveDOMDelete(this.body.container)},n.prototype._updateValueRange=function(e){var t,i=void 0,n=void 0,o=0;for(t in e)if(e.hasOwnProperty(t)){var s=e[t].getValue();void 0!==s&&(i=void 0===i?s:Math.min(s,i),n=void 0===n?s:Math.max(s,n),o+=s)}if(void 0!==i&&void 0!==n)for(t in e)e.hasOwnProperty(t)&&e[t].setValueRange(i,n,o)},n.prototype.isActive=function(){return!this.activator||this.activator.active},n.prototype.setSize=function(){return this.canvas.setSize.apply(this.canvas,arguments)},n.prototype.canvasToDOM=function(){return this.canvas.canvasToDOM.apply(this.canvas,arguments)},n.prototype.DOMtoCanvas=function(){return this.canvas.DOMtoCanvas.apply(this.canvas,arguments)},n.prototype.findNode=function(){return this.clustering.findNode.apply(this.clustering,arguments)},n.prototype.isCluster=function(){return this.clustering.isCluster.apply(this.clustering,arguments)},n.prototype.openCluster=function(){return this.clustering.openCluster.apply(this.clustering,arguments)},n.prototype.cluster=function(){return this.clustering.cluster.apply(this.clustering,arguments)},n.prototype.getNodesInCluster=function(){return this.clustering.getNodesInCluster.apply(this.clustering,arguments)},n.prototype.clusterByConnection=function(){return this.clustering.clusterByConnection.apply(this.clustering,arguments)},n.prototype.clusterByHubsize=function(){return this.clustering.clusterByHubsize.apply(this.clustering,arguments)},n.prototype.clusterOutliers=function(){return this.clustering.clusterOutliers.apply(this.clustering,arguments)},n.prototype.getSeed=function(){return this.layoutEngine.getSeed.apply(this.layoutEngine,arguments)},n.prototype.enableEditMode=function(){return this.manipulation.enableEditMode.apply(this.manipulation,arguments)},n.prototype.disableEditMode=function(){return this.manipulation.disableEditMode.apply(this.manipulation,arguments)},n.prototype.addNodeMode=function(){return this.manipulation.addNodeMode.apply(this.manipulation,arguments)},n.prototype.editNode=function(){return this.manipulation.editNode.apply(this.manipulation,arguments)},n.prototype.editNodeMode=function(){return console.log("Deprecated: Please use editNode instead of editNodeMode."),this.manipulation.editNode.apply(this.manipulation,arguments)},n.prototype.addEdgeMode=function(){return this.manipulation.addEdgeMode.apply(this.manipulation,arguments)},n.prototype.editEdgeMode=function(){return this.manipulation.editEdgeMode.apply(this.manipulation,arguments)},n.prototype.deleteSelected=function(){return this.manipulation.deleteSelected.apply(this.manipulation,arguments)},n.prototype.getPositions=function(){return this.nodesHandler.getPositions.apply(this.nodesHandler,arguments)},n.prototype.storePositions=function(){return this.nodesHandler.storePositions.apply(this.nodesHandler,arguments)},n.prototype.moveNode=function(){return this.nodesHandler.moveNode.apply(this.nodesHandler,arguments)},n.prototype.getBoundingBox=function(){return this.nodesHandler.getBoundingBox.apply(this.nodesHandler,arguments)},n.prototype.getConnectedNodes=function(e){return void 0!==this.body.nodes[e]?this.nodesHandler.getConnectedNodes.apply(this.nodesHandler,arguments):this.edgesHandler.getConnectedNodes.apply(this.edgesHandler,arguments)},n.prototype.getConnectedEdges=function(){return this.nodesHandler.getConnectedEdges.apply(this.nodesHandler,arguments)},n.prototype.startSimulation=function(){return this.physics.startSimulation.apply(this.physics,arguments)},n.prototype.stopSimulation=function(){return this.physics.stopSimulation.apply(this.physics,arguments)},n.prototype.stabilize=function(){return this.physics.stabilize.apply(this.physics,arguments)},n.prototype.getSelection=function(){return this.selectionHandler.getSelection.apply(this.selectionHandler,arguments)},n.prototype.setSelection=function(){return this.selectionHandler.setSelection.apply(this.selectionHandler,arguments)},n.prototype.getSelectedNodes=function(){return this.selectionHandler.getSelectedNodes.apply(this.selectionHandler,arguments)},n.prototype.getSelectedEdges=function(){return this.selectionHandler.getSelectedEdges.apply(this.selectionHandler,arguments)},n.prototype.getNodeAt=function(){var e=this.selectionHandler.getNodeAt.apply(this.selectionHandler,arguments);return void 0!==e&&void 0!==e.id?e.id:e},n.prototype.getEdgeAt=function(){var e=this.selectionHandler.getEdgeAt.apply(this.selectionHandler,arguments);return void 0!==e&&void 0!==e.id?e.id:e},n.prototype.selectNodes=function(){return this.selectionHandler.selectNodes.apply(this.selectionHandler,arguments)},n.prototype.selectEdges=function(){return this.selectionHandler.selectEdges.apply(this.selectionHandler,arguments)},n.prototype.unselectAll=function(){this.selectionHandler.unselectAll.apply(this.selectionHandler,arguments),this.redraw()},n.prototype.redraw=function(){return this.renderer.redraw.apply(this.renderer,arguments)},n.prototype.getScale=function(){return this.view.getScale.apply(this.view,arguments)},n.prototype.getViewPosition=function(){return this.view.getViewPosition.apply(this.view,arguments)},n.prototype.fit=function(){return this.view.fit.apply(this.view,arguments)},n.prototype.moveTo=function(){return this.view.moveTo.apply(this.view,arguments)},n.prototype.focus=function(){return this.view.focus.apply(this.view,arguments)},n.prototype.releaseNode=function(){return this.view.releaseNode.apply(this.view,arguments)},n.prototype.getOptionsFromConfigurator=function(){var e={};return this.configurator&&(e=this.configurator.getOptions.apply(this.configurator)),e},e.exports=n},function(e,t,i){"undefined"!=typeof CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.circle=function(e,t,i){this.beginPath(),this.arc(e,t,i,0,2*Math.PI,!1),this.closePath()},CanvasRenderingContext2D.prototype.square=function(e,t,i){this.beginPath(),this.rect(e-i,t-i,2*i,2*i),this.closePath()},CanvasRenderingContext2D.prototype.triangle=function(e,t,i){this.beginPath(),i*=1.15,t+=.275*i;var n=2*i,o=n/2,s=Math.sqrt(3)/6*n,r=Math.sqrt(n*n-o*o);this.moveTo(e,t-(r-s)),this.lineTo(e+o,t+s),this.lineTo(e-o,t+s),this.lineTo(e,t-(r-s)),this.closePath()},CanvasRenderingContext2D.prototype.triangleDown=function(e,t,i){this.beginPath(),i*=1.15,t-=.275*i;var n=2*i,o=n/2,s=Math.sqrt(3)/6*n,r=Math.sqrt(n*n-o*o);this.moveTo(e,t+(r-s)),this.lineTo(e+o,t-s),this.lineTo(e-o,t-s),this.lineTo(e,t+(r-s)),this.closePath()},CanvasRenderingContext2D.prototype.star=function(e,t,i){this.beginPath(),i*=.82,t+=.1*i;for(var n=0;n<10;n++){var o=n%2==0?1.3*i:.5*i;this.lineTo(e+o*Math.sin(2*n*Math.PI/10),t-o*Math.cos(2*n*Math.PI/10))}this.closePath()},CanvasRenderingContext2D.prototype.diamond=function(e,t,i){this.beginPath(),this.lineTo(e,t+i),this.lineTo(e+i,t),this.lineTo(e,t-i),this.lineTo(e-i,t),this.closePath()},CanvasRenderingContext2D.prototype.roundRect=function(e,t,i,n,o){var s=Math.PI/180;i-2*o<0&&(o=i/2),n-2*o<0&&(o=n/2),this.beginPath(),this.moveTo(e+o,t),this.lineTo(e+i-o,t),this.arc(e+i-o,t+o,o,270*s,360*s,!1),this.lineTo(e+i,t+n-o),this.arc(e+i-o,t+n-o,o,0,90*s,!1),this.lineTo(e+o,t+n),this.arc(e+o,t+n-o,o,90*s,180*s,!1),this.lineTo(e,t+o),this.arc(e+o,t+o,o,180*s,270*s,!1),this.closePath()},CanvasRenderingContext2D.prototype.ellipse_vis=function(e,t,i,n){var o=i/2*.5522848,s=n/2*.5522848,r=e+i,a=t+n,d=e+i/2,h=t+n/2;this.beginPath(),this.moveTo(e,h),this.bezierCurveTo(e,h-s,d-o,t,d,t),this.bezierCurveTo(d+o,t,r,h-s,r,h),this.bezierCurveTo(r,h+s,d+o,a,d,a),this.bezierCurveTo(d-o,a,e,h+s,e,h),this.closePath()},CanvasRenderingContext2D.prototype.database=function(e,t,i,n){var o=i,s=n*(1/3),r=o/2*.5522848,a=s/2*.5522848,d=e+o,h=t+s,l=e+o/2,u=t+s/2,c=t+(n-s/2),f=t+n;this.beginPath(),this.moveTo(d,u),this.bezierCurveTo(d,u+a,l+r,h,l,h),this.bezierCurveTo(l-r,h,e,u+a,e,u),this.bezierCurveTo(e,u-a,l-r,t,l,t),this.bezierCurveTo(l+r,t,d,u-a,d,u),this.lineTo(d,c),this.bezierCurveTo(d,c+a,l+r,f,l,f),this.bezierCurveTo(l-r,f,e,c+a,e,c),this.lineTo(e,u)},CanvasRenderingContext2D.prototype.dashedLine=function(e,t,i,n,o){this.beginPath(),this.moveTo(e,t);for(var s=o.length,r=i-e,a=n-t,d=a/r,h=Math.sqrt(r*r+a*a),l=0,u=!0,c=0,f=o[0];h>=.1;)f=o[l++%s],f>h&&(f=h),c=Math.sqrt(f*f/(1+d*d)),c=r<0?-c:c,e+=c,t+=d*c,!0===u?this.lineTo(e,t):this.moveTo(e,t),h-=f,u=!u},CanvasRenderingContext2D.prototype.hexagon=function(e,t,i){this.beginPath();var n=2*Math.PI/6;this.moveTo(e+i,t);for(var o=1;o<6;o++)this.lineTo(e+i*Math.cos(n*o),t+i*Math.sin(n*o));this.closePath()})},function(e,t,i){function n(e){this.active=!1,this.dom={container:e},this.dom.overlay=document.createElement("div"),this.dom.overlay.className="vis-overlay",this.dom.container.appendChild(this.dom.overlay),this.hammer=a(this.dom.overlay),this.hammer.on("tap",this._onTapOverlay.bind(this));var t=this;["tap","doubletap","press","pinch","pan","panstart","panmove","panend"].forEach(function(e){t.hammer.on(e,function(e){e.stopPropagation()})}),document&&document.body&&(this.onClick=function(i){o(i.target,e)||t.deactivate()},document.body.addEventListener("click",this.onClick)),void 0!==this.keycharm&&this.keycharm.destroy(),this.keycharm=s(),this.escListener=this.deactivate.bind(this)}function o(e,t){for(;e;){if(e===t)return!0;e=e.parentNode}return!1}var s=i(52),r=i(73),a=i(22),d=i(5);r(n.prototype),n.current=null,n.prototype.destroy=function(){this.deactivate(),this.dom.overlay.parentNode.removeChild(this.dom.overlay),this.onClick&&document.body.removeEventListener("click",this.onClick),this.hammer.destroy(),this.hammer=null},n.prototype.activate=function(){n.current&&n.current.deactivate(),n.current=this,this.active=!0,this.dom.overlay.style.display="none",d.addClassName(this.dom.container,"vis-active"),this.emit("change"),this.emit("activate"),this.keycharm.bind("esc",this.escListener)},n.prototype.deactivate=function(){this.active=!1,this.dom.overlay.style.display="",d.removeClassName(this.dom.container,"vis-active"),this.keycharm.unbind("esc",this.escListener),this.emit("change"),this.emit("deactivate")},n.prototype._onTapOverlay=function(e){this.activate(),e.stopPropagation()},e.exports=n},function(e,t,i){var n,o,s;!function(i){o=[],n=i,void 0!==(s="function"==typeof n?n.apply(t,o):n)&&(e.exports=s)}(function(){var e=null;return function t(i,n){function o(e){return e.match(/[^ ]+/g)}function s(t){if("hammer.input"!==t.type){if(t.srcEvent._handled||(t.srcEvent._handled={}),t.srcEvent._handled[t.type])return;t.srcEvent._handled[t.type]=!0}var i=!1;t.stopPropagation=function(){i=!0};var n=t.srcEvent.stopPropagation.bind(t.srcEvent);"function"==typeof n&&(t.srcEvent.stopPropagation=function(){n(),t.stopPropagation()}),t.firstTarget=e;for(var o=e;o&&!i;){var s=o.hammer;if(s)for(var r,a=0;a0?h._handlers[e]=n:(i.off(e,s),delete h._handlers[e]))}),h},h.emit=function(t,n){e=n.target,i.emit(t,n)},h.destroy=function(){var e=i.element.hammer,t=e.indexOf(h);-1!==t&&e.splice(t,1),e.length||delete i.element.hammer,h._handlers={},i.destroy()},h}})},function(e,t,i){var n;/*! Hammer.JS - v2.0.7 - 2016-04-22 - * http://hammerjs.github.io/ - * - * Copyright (c) 2016 Jorik Tangelder; - * Licensed under the MIT license */ -!function(o,s,r,a){function d(e,t,i){return setTimeout(f(e,i),t)}function h(e,t,i){return!!Array.isArray(e)&&(l(e,i[t],i),!0)}function l(e,t,i){var n;if(e)if(e.forEach)e.forEach(t,i);else if(e.length!==a)for(n=0;n\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",s=o.console&&(o.console.warn||o.console.log);return s&&s.call(o.console,n,i),e.apply(this,arguments)}}function c(e,t,i){var n,o=t.prototype;n=e.prototype=Object.create(o),n.constructor=e,n._super=o,i&&pe(n,i)}function f(e,t){return function(){return e.apply(t,arguments)}}function p(e,t){return typeof e==ye?e.apply(t?t[0]||a:a,t):e}function v(e,t){return e===a?t:e}function g(e,t,i){l(_(t),function(t){e.addEventListener(t,i,!1)})}function y(e,t,i){l(_(t),function(t){e.removeEventListener(t,i,!1)})}function m(e,t){for(;e;){if(e==t)return!0;e=e.parentNode}return!1}function b(e,t){return e.indexOf(t)>-1}function _(e){return e.trim().split(/\s+/g)}function w(e,t,i){if(e.indexOf&&!i)return e.indexOf(t);for(var n=0;ni[t]}):n.sort()),n}function O(e,t){for(var i,n,o=t[0].toUpperCase()+t.slice(1),s=0;s1&&!i.firstMultiple?i.firstMultiple=F(t):1===o&&(i.firstMultiple=!1);var s=i.firstInput,r=i.firstMultiple,a=r?r.center:s.center,d=t.center=N(n);t.timeStamp=_e(),t.deltaTime=t.timeStamp-s.timeStamp,t.angle=A(a,d),t.distance=R(a,d),P(i,t),t.offsetDirection=z(t.deltaX,t.deltaY);var h=B(t.deltaTime,t.deltaX,t.deltaY);t.overallVelocityX=h.x,t.overallVelocityY=h.y,t.overallVelocity=be(h.x)>be(h.y)?h.x:h.y,t.scale=r?L(r.pointers,n):1,t.rotation=r?j(r.pointers,n):0,t.maxPointers=i.prevInput?t.pointers.length>i.prevInput.maxPointers?t.pointers.length:i.prevInput.maxPointers:t.pointers.length,I(i,t);var l=e.element;m(t.srcEvent.target,l)&&(l=t.srcEvent.target),t.target=l}function P(e,t){var i=t.center,n=e.offsetDelta||{},o=e.prevDelta||{},s=e.prevInput||{};t.eventType!==Ce&&s.eventType!==Pe||(o=e.prevDelta={x:s.deltaX||0,y:s.deltaY||0},n=e.offsetDelta={x:i.x,y:i.y}),t.deltaX=o.x+(i.x-n.x),t.deltaY=o.y+(i.y-n.y)}function I(e,t){var i,n,o,s,r=e.lastInterval||t,d=t.timeStamp-r.timeStamp;if(t.eventType!=Ie&&(d>De||r.velocity===a)){var h=t.deltaX-r.deltaX,l=t.deltaY-r.deltaY,u=B(d,h,l);n=u.x,o=u.y,i=be(u.x)>be(u.y)?u.x:u.y,s=z(h,l),e.lastInterval=t}else i=r.velocity,n=r.velocityX,o=r.velocityY,s=r.direction;t.velocity=i,t.velocityX=n,t.velocityY=o,t.direction=s}function F(e){for(var t=[],i=0;i=be(t)?e<0?Ne:Be:t<0?ze:Re}function R(e,t,i){i||(i=He);var n=t[i[0]]-e[i[0]],o=t[i[1]]-e[i[1]];return Math.sqrt(n*n+o*o)}function A(e,t,i){i||(i=He);var n=t[i[0]]-e[i[0]],o=t[i[1]]-e[i[1]];return 180*Math.atan2(o,n)/Math.PI}function j(e,t){return A(t[1],t[0],We)+A(e[1],e[0],We)}function L(e,t){return R(t[0],t[1],We)/R(e[0],e[1],We)}function H(){this.evEl=Ve,this.evWin=Ue,this.pressed=!1,S.apply(this,arguments)}function W(){this.evEl=Xe,this.evWin=Ke,S.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function Y(){this.evTarget=$e,this.evWin=Qe,this.started=!1,S.apply(this,arguments)}function V(e,t){var i=k(e.touches),n=k(e.changedTouches);return t&(Pe|Ie)&&(i=x(i.concat(n),"identifier",!0)),[i,n]}function U(){this.evTarget=et,this.targetIds={},S.apply(this,arguments)}function q(e,t){var i=k(e.touches),n=this.targetIds;if(t&(Ce|Te)&&1===i.length)return n[i[0].identifier]=!0,[i,i];var o,s,r=k(e.changedTouches),a=[],d=this.target;if(s=i.filter(function(e){return m(e.target,d)}),t===Ce)for(o=0;o-1&&n.splice(e,1)};setTimeout(o,tt)}}function Z(e){for(var t=e.srcEvent.clientX,i=e.srcEvent.clientY,n=0;n-1&&this.requireFail.splice(t,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(e){return!!this.simultaneous[e.id]},emit:function(e){function t(t){i.manager.emit(t,e)}var i=this,n=this.state;n=pt&&t(i.options.event+ee(n))},tryEmit:function(e){if(this.canEmit())return this.emit(e);this.state=32},canEmit:function(){for(var e=0;et.threshold&&o&t.direction},attrTest:function(e){return ne.prototype.attrTest.call(this,e)&&(this.state&ct||!(this.state&ct)&&this.directionTest(e))},emit:function(e){this.pX=e.deltaX,this.pY=e.deltaY;var t=te(e.direction);t&&(e.additionalEvent=this.options.event+t),this._super.emit.call(this,e)}}),c(se,ne,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[at]},attrTest:function(e){return this._super.attrTest.call(this,e)&&(Math.abs(e.scale-1)>this.options.threshold||this.state&ct)},emit:function(e){if(1!==e.scale){var t=e.scale<1?"in":"out";e.additionalEvent=this.options.event+t}this._super.emit.call(this,e)}}),c(re,J,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[st]},process:function(e){var t=this.options,i=e.pointers.length===t.pointers,n=e.distancet.time;if(this._input=e,!n||!i||e.eventType&(Pe|Ie)&&!o)this.reset();else if(e.eventType&Ce)this.reset(),this._timer=d(function(){this.state=vt,this.tryEmit()},t.time,this);else if(e.eventType&Pe)return vt;return 32},reset:function(){clearTimeout(this._timer)},emit:function(e){this.state===vt&&(e&&e.eventType&Pe?this.manager.emit(this.options.event+"up",e):(this._input.timeStamp=_e(),this.manager.emit(this.options.event,this._input)))}}),c(ae,ne,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[at]},attrTest:function(e){return this._super.attrTest.call(this,e)&&(Math.abs(e.rotation)>this.options.threshold||this.state&ct)}}),c(de,ne,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Ae|je,pointers:1},getTouchAction:function(){return oe.prototype.getTouchAction.call(this)},attrTest:function(e){var t,i=this.options.direction;return i&(Ae|je)?t=e.overallVelocity:i&Ae?t=e.overallVelocityX:i&je&&(t=e.overallVelocityY),this._super.attrTest.call(this,e)&&i&e.offsetDirection&&e.distance>this.options.threshold&&e.maxPointers==this.options.pointers&&be(t)>this.options.velocity&&e.eventType&Pe},emit:function(e){var t=te(e.offsetDirection);t&&this.manager.emit(this.options.event+t,e),this.manager.emit(this.options.event,e)}}),c(he,J,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[rt]},process:function(e){var t=this.options,i=e.pointers.length===t.pointers,n=e.distance2){t*=.5;for(var r=0;t>2&&r=this.NUM_ITERATIONS&&(r=this.NUM_ITERATIONS-1);var a=this.coordinates[r];e.drawImage(this.canvas,a[0],a[1],a[2],a[3],i,n,o,s)}else e.drawImage(this.image,i,n,o,s)}}]),e}();t.default=d},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=i(5),h=function(){function e(){(0,s.default)(this,e),this.clear(),this.defaultIndex=0,this.groupsArray=[],this.groupIndex=0,this.defaultGroups=[{border:"#2B7CE9",background:"#97C2FC",highlight:{border:"#2B7CE9",background:"#D2E5FF"},hover:{border:"#2B7CE9",background:"#D2E5FF"}},{border:"#FFA500",background:"#FFFF00",highlight:{border:"#FFA500",background:"#FFFFA3"},hover:{border:"#FFA500",background:"#FFFFA3"}},{border:"#FA0A10",background:"#FB7E81",highlight:{border:"#FA0A10",background:"#FFAFB1"},hover:{border:"#FA0A10",background:"#FFAFB1"}},{border:"#41A906",background:"#7BE141",highlight:{border:"#41A906",background:"#A1EC76"},hover:{border:"#41A906",background:"#A1EC76"}},{border:"#E129F0",background:"#EB7DF4",highlight:{border:"#E129F0",background:"#F0B3F5"},hover:{border:"#E129F0",background:"#F0B3F5"}},{border:"#7C29F0",background:"#AD85E4",highlight:{border:"#7C29F0",background:"#D3BDF0"},hover:{border:"#7C29F0",background:"#D3BDF0"}},{border:"#C37F00",background:"#FFA807",highlight:{border:"#C37F00",background:"#FFCA66"},hover:{border:"#C37F00",background:"#FFCA66"}},{border:"#4220FB",background:"#6E6EFD",highlight:{border:"#4220FB",background:"#9B9BFD"},hover:{border:"#4220FB",background:"#9B9BFD"}},{border:"#FD5A77",background:"#FFC0CB",highlight:{border:"#FD5A77",background:"#FFD1D9"},hover:{border:"#FD5A77",background:"#FFD1D9"}},{border:"#4AD63A",background:"#C2FABC",highlight:{border:"#4AD63A",background:"#E6FFE3"},hover:{border:"#4AD63A",background:"#E6FFE3"}},{border:"#990000",background:"#EE0000",highlight:{border:"#BB0000",background:"#FF3333"},hover:{border:"#BB0000",background:"#FF3333"}},{border:"#FF6000",background:"#FF6000",highlight:{border:"#FF6000",background:"#FF6000"},hover:{border:"#FF6000",background:"#FF6000"}},{border:"#97C2FC",background:"#2B7CE9",highlight:{border:"#D2E5FF",background:"#2B7CE9"},hover:{border:"#D2E5FF",background:"#2B7CE9"}},{border:"#399605",background:"#255C03",highlight:{border:"#399605",background:"#255C03"},hover:{border:"#399605",background:"#255C03"}},{border:"#B70054",background:"#FF007E",highlight:{border:"#B70054",background:"#FF007E"},hover:{border:"#B70054",background:"#FF007E"}},{border:"#AD85E4",background:"#7C29F0",highlight:{border:"#D3BDF0",background:"#7C29F0"},hover:{border:"#D3BDF0",background:"#7C29F0"}},{border:"#4557FA",background:"#000EA1",highlight:{border:"#6E6EFD",background:"#000EA1"},hover:{border:"#6E6EFD",background:"#000EA1"}},{border:"#FFC0CB",background:"#FD5A77",highlight:{border:"#FFD1D9",background:"#FD5A77"},hover:{border:"#FFD1D9",background:"#FD5A77"}},{border:"#C2FABC",background:"#74D66A",highlight:{border:"#E6FFE3",background:"#74D66A"},hover:{border:"#E6FFE3",background:"#74D66A"}},{border:"#EE0000",background:"#990000",highlight:{border:"#FF3333",background:"#BB0000"},hover:{border:"#FF3333",background:"#BB0000"}}],this.options={},this.defaultOptions={useDefaultGroups:!0},d.extend(this.options,this.defaultOptions)}return(0,a.default)(e,[{key:"setOptions",value:function(e){var t=["useDefaultGroups"];if(void 0!==e)for(var i in e)if(e.hasOwnProperty(i)&&-1===t.indexOf(i)){var n=e[i];this.add(i,n)}}},{key:"clear",value:function(){this.groups={},this.groupsArray=[]}},{key:"get",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],i=this.groups[e];if(void 0===i&&t)if(!1===this.options.useDefaultGroups&&this.groupsArray.length>0){var n=this.groupIndex%this.groupsArray.length;this.groupIndex++,i={},i.color=this.groups[this.groupsArray[n]],this.groups[e]=i}else{var o=this.defaultIndex%this.defaultGroups.length;this.defaultIndex++,i={},i.color=this.defaultGroups[o],this.groups[e]=i}return i}},{key:"add",value:function(e,t){return this.groups[e]=t,this.groupsArray.push(e),t}}]),e}();t.default=h},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=i(5),h=i(32),l=i(51),u=i(33).default,c=function(){function e(t,i,n,o){var r=this;if((0,s.default)(this,e),this.body=t,this.images=i,this.groups=n,this.layoutEngine=o,this.body.functions.createNode=this.create.bind(this),this.nodesListeners={add:function(e,t){r.add(t.items)},update:function(e,t){r.update(t.items,t.data,t.oldData)},remove:function(e,t){r.remove(t.items)}},this.defaultOptions={borderWidth:1,borderWidthSelected:2,brokenImage:void 0,color:{border:"#2B7CE9",background:"#97C2FC",highlight:{border:"#2B7CE9",background:"#D2E5FF"},hover:{border:"#2B7CE9",background:"#D2E5FF"}},fixed:{x:!1,y:!1},font:{color:"#343434",size:14,face:"arial",background:"none",strokeWidth:0,strokeColor:"#ffffff",align:"center",vadjust:0,multi:!1,bold:{mod:"bold"},boldital:{mod:"bold italic"},ital:{mod:"italic"},mono:{mod:"",size:15,face:"monospace",vadjust:2}},group:void 0,hidden:!1,icon:{face:"FontAwesome",code:void 0,size:50,color:"#2B7CE9"},image:void 0,label:void 0,labelHighlightBold:!0,level:void 0,margin:{top:5,right:5,bottom:5,left:5},mass:1,physics:!0,scaling:{min:10,max:30,label:{enabled:!1,min:14,max:30,maxVisible:30,drawThreshold:5},customScalingFunction:function(e,t,i,n){if(t===e)return.5;var o=1/(t-e);return Math.max(0,(n-e)*o)}},shadow:{enabled:!1,color:"rgba(0,0,0,0.5)",size:10,x:5,y:5},shape:"ellipse",shapeProperties:{borderDashes:!1,borderRadius:6,interpolation:!0,useImageSize:!1,useBorderWithImage:!1},size:25,title:void 0,value:void 0,x:void 0,y:void 0},this.defaultOptions.mass<=0)throw"Internal error: mass in defaultOptions of NodesHandler may not be zero or negative";this.options=d.bridgeObject(this.defaultOptions),this.bindEventListeners()}return(0,a.default)(e,[{key:"bindEventListeners",value:function(){var e=this;this.body.emitter.on("refreshNodes",this.refresh.bind(this)),this.body.emitter.on("refresh",this.refresh.bind(this)),this.body.emitter.on("destroy",function(){d.forEach(e.nodesListeners,function(t,i){e.body.data.nodes&&e.body.data.nodes.off(i,t)}),delete e.body.functions.createNode,delete e.nodesListeners.add,delete e.nodesListeners.update,delete e.nodesListeners.remove,delete e.nodesListeners})}},{key:"setOptions",value:function(e){if(void 0!==e){if(u.parseOptions(this.options,e),void 0!==e.shape)for(var t in this.body.nodes)this.body.nodes.hasOwnProperty(t)&&this.body.nodes[t].updateShape();if(void 0!==e.font)for(var i in this.body.nodes)this.body.nodes.hasOwnProperty(i)&&(this.body.nodes[i].updateLabelModule(),this.body.nodes[i].needsRefresh());if(void 0!==e.size)for(var n in this.body.nodes)this.body.nodes.hasOwnProperty(n)&&this.body.nodes[n].needsRefresh();void 0===e.hidden&&void 0===e.physics||this.body.emitter.emit("_dataChanged")}}},{key:"setData",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this.body.data.nodes;if(e instanceof h||e instanceof l)this.body.data.nodes=e;else if(Array.isArray(e))this.body.data.nodes=new h,this.body.data.nodes.add(e);else{if(e)throw new TypeError("Array or DataSet expected");this.body.data.nodes=new h}if(i&&d.forEach(this.nodesListeners,function(e,t){i.off(t,e)}),this.body.nodes={},this.body.data.nodes){var n=this;d.forEach(this.nodesListeners,function(e,t){n.body.data.nodes.on(t,e)});var o=this.body.data.nodes.getIds();this.add(o,!0)}!1===t&&this.body.emitter.emit("_dataChanged")}},{key:"add",value:function(e){for(var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=void 0,n=[],o=0;o1&&void 0!==arguments[1]?arguments[1]:u)(e,this.body,this.images,this.groups,this.options,this.defaultOptions)}},{key:"refresh",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];d.forEach(this.body.nodes,function(i,n){var o=e.body.data.nodes.get(n);void 0!==o&&(!0===t&&i.setOptions({x:null,y:null}),i.setOptions({fixed:!1}),i.setOptions(o))})}},{key:"getPositions",value:function(e){var t={};if(void 0!==e){if(!0===Array.isArray(e)){for(var i=0;i0)for(var r=0;r0)for(var f=0;f0&&void 0!==arguments[0]&&arguments[0];this.spacing&&(this.add(" "),this.spacing=!1),this.buffer.length>0&&(t.push({text:this.buffer,mod:this.modName()}),this.buffer="")},i.add=function(e){" "===e&&(i.spacing=!0),i.spacing&&(this.buffer+=" ",this.spacing=!1)," "!=e&&(this.buffer+=e)};i.position/.test(e.substr(i.position,3))?i.mono||i.ital||!//.test(e.substr(i.position,3))?!i.mono&&//.test(e.substr(i.position,6))?(i.emitBlock(),i.mono=!0,i.modStack.unshift("mono"),i.position+=5):!i.mono&&"bold"===i.mod()&&/<\/b>/.test(e.substr(i.position,4))?(i.emitBlock(),i.bold=!1,i.modStack.shift(),i.position+=3):!i.mono&&"ital"===i.mod()&&/<\/i>/.test(e.substr(i.position,4))?(i.emitBlock(),i.ital=!1,i.modStack.shift(),i.position+=3):"mono"===i.mod()&&/<\/code>/.test(e.substr(i.position,7))?(i.emitBlock(),i.mono=!1,i.modStack.shift(),i.position+=6):i.add(n):(i.emitBlock(),i.ital=!0,i.modStack.unshift("ital"),i.position+=2):(i.emitBlock(),i.bold=!0,i.modStack.unshift("bold"),i.position+=2):/&/.test(n)?/</.test(e.substr(i.position,4))?(i.add("<"),i.position+=3):/&/.test(e.substr(i.position,5))?(i.add("&"),i.position+=4):i.add("&"):i.add(n),i.position++}return i.emitBlock(),t}},{key:"splitMarkdownBlocks",value:function(e){var t=[],i={bold:!1,ital:!1,mono:!1,beginable:!0,spacing:!1,position:0,buffer:"",modStack:[]};for(i.mod=function(){return 0===this.modStack.length?"normal":this.modStack[0]},i.modName=function(){return 0===this.modStack.length?"normal":"mono"===this.modStack[0]?"mono":i.bold&&i.ital?"boldital":i.bold?"bold":i.ital?"ital":void 0},i.emitBlock=function(){arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.spacing&&(this.add(" "),this.spacing=!1),this.buffer.length>0&&(t.push({text:this.buffer,mod:this.modName()}),this.buffer="")},i.add=function(e){" "===e&&(i.spacing=!0),i.spacing&&(this.buffer+=" ",this.spacing=!1)," "!=e&&(this.buffer+=e)};i.positionthis.parent.fontOptions.maxWdt}},{key:"getLongestFit",value:function(e){for(var t="",i=0;i1&&void 0!==arguments[1]?arguments[1]:"normal",i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];e=e.replace(/^( +)/g,"$1\r"),e=e.replace(/([^\r][^ ]*)( +)/g,"$1\r$2\r");for(var n=e.split("\r");n.length>0;){var o=this.getLongestFit(n);if(0===o){var s=n[0],r=this.getLongestFitWord(s);this.lines.newLine(s.slice(0,r),t),n[0]=s.slice(r)}else{var a=o;" "===n[o-1]?o--:" "===n[a]&&a++;var d=n.slice(0,o).join("");o==n.length&&i?this.lines.append(d,t):this.lines.newLine(d,t),n=n.slice(a)}}}}]),e}();t.default=l},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(138),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=function(){function e(t){(0,a.default)(this,e),this.measureText=t,this.current=0,this.width=0,this.height=0,this.lines=[]}return(0,h.default)(e,[{key:"_add",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"normal";void 0===this.lines[e]&&(this.lines[e]={width:0,height:0,blocks:[]});var n=t;void 0!==t&&""!==t||(n=" ");var o=this.measureText(n,i),r=(0,s.default)({},o.values);r.text=t,r.width=o.width,r.mod=i,void 0!==t&&""!==t||(r.width=0),this.lines[e].blocks.push(r),this.lines[e].width+=r.width}},{key:"curWidth",value:function(){var e=this.lines[this.current];return void 0===e?0:e.width}},{key:"append",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"normal";this._add(this.current,e,t)}},{key:"newLine",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"normal";this._add(this.current,e,t),this.current++}},{key:"determineLineHeights",value:function(){for(var e=0;ee&&(e=n.width),t+=n.height}this.width=e,this.height=t}},{key:"removeEmptyBlocks",value:function(){for(var e=[],t=0;th;)for(var c,f=a(arguments[h++]),p=l?n(f).concat(l(f)):n(f),v=p.length,g=0;v>g;)u.call(f,c=p[g++])&&(i[c]=f[c]);return i}:d},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(15),v=n(p),g=function(e){function t(e,i,n){(0,a.default)(this,t);var o=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return o._setMargins(n),o}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(this.needsRefresh(t,i)){var n=this.getDimensionsFromLabel(e,t,i);this.width=n.width+this.margin.right+this.margin.left,this.height=n.height+this.margin.top+this.margin.bottom,this.radius=this.width/2}}},{key:"draw",value:function(e,t,i,n,o,s){this.resize(e,n,o),this.left=t-this.width/2,this.top=i-this.height/2,this.initContextForDraw(e,s),e.roundRect(this.left,this.top,this.width,this.height,s.borderRadius),this.performFill(e,s),this.updateBoundingBox(t,i,e,n,o),this.labelModule.draw(e,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,n,o)}},{key:"updateBoundingBox",value:function(e,t,i,n,o){this._updateBoundingBox(e,t,i,n,o);var s=this.options.shapeProperties.borderRadius;this._addBoundingBoxMargin(s)}},{key:"distanceToBorder",value:function(e,t){this.resize(e);var i=this.options.borderWidth;return Math.min(Math.abs(this.width/2/Math.cos(t)),Math.abs(this.height/2/Math.sin(t)))+i}}]),t}(v.default);t.default=g},function(e,t,i){i(144),e.exports=i(6).Object.getPrototypeOf},function(e,t,i){var n=i(29),o=i(66);i(68)("getPrototypeOf",function(){return function(e){return o(n(e))}})},function(e,t,i){e.exports={default:i(146),__esModule:!0}},function(e,t,i){i(147),e.exports=i(6).Object.setPrototypeOf},function(e,t,i){var n=i(11);n(n.S,"Object",{setPrototypeOf:i(148).set})},function(e,t,i){var n=i(25),o=i(19),s=function(e,t){if(o(e),!n(t)&&null!==t)throw TypeError(t+": can't set as prototype!")};e.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(e,t,n){try{n=i(61)(Function.call,i(70).f(Object.prototype,"__proto__").set,2),n(e,[]),t=!(e instanceof Array)}catch(e){t=!0}return function(e,i){return s(e,i),t?e.__proto__=i:n(e,i),e}}({},!1):void 0),check:s}},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(53),v=n(p),g=function(e){function t(e,i,n){(0,a.default)(this,t);var o=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return o._setMargins(n),o}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(this.needsRefresh(t,i)){var n=this.getDimensionsFromLabel(e,t,i),o=Math.max(n.width+this.margin.right+this.margin.left,n.height+this.margin.top+this.margin.bottom);this.options.size=o/2,this.width=o,this.height=o,this.radius=this.width/2}}},{key:"draw",value:function(e,t,i,n,o,s){this.resize(e,n,o),this.left=t-this.width/2,this.top=i-this.height/2,this._drawRawCircle(e,t,i,s),this.updateBoundingBox(t,i),this.labelModule.draw(e,this.left+this.textSize.width/2+this.margin.left,i,n,o)}},{key:"updateBoundingBox",value:function(e,t){this.boundingBox.top=t-this.options.size,this.boundingBox.left=e-this.options.size,this.boundingBox.right=e+this.options.size,this.boundingBox.bottom=t+this.options.size}},{key:"distanceToBorder",value:function(e,t){return this.resize(e),.5*this.width}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(53),v=n(p),g=function(e){function t(e,i,n,o,r){(0,a.default)(this,t);var d=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return d.setImages(o,r),d}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(void 0===this.imageObj.src||void 0===this.imageObj.width||void 0===this.imageObj.height){var n=2*this.options.size;return this.width=n,this.height=n,void(this.radius=.5*this.width)}this.needsRefresh(t,i)&&this._resizeImage()}},{key:"draw",value:function(e,t,i,n,o,s){this.switchImages(n),this.resize(),this.left=t-this.width/2,this.top=i-this.height/2,this._drawRawCircle(e,t,i,s),e.save(),e.clip(),this._drawImageAtPosition(e,s),e.restore(),this._drawImageLabel(e,t,i,n,o),this.updateBoundingBox(t,i)}},{key:"updateBoundingBox",value:function(e,t){this.boundingBox.top=t-this.options.size,this.boundingBox.left=e-this.options.size,this.boundingBox.right=e+this.options.size,this.boundingBox.bottom=t+this.options.size,this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelOffset)}},{key:"distanceToBorder",value:function(e,t){return this.resize(e),.5*this.width}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(15),v=n(p),g=function(e){function t(e,i,n){(0,a.default)(this,t);var o=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return o._setMargins(n),o}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e,t,i){if(this.needsRefresh(t,i)){var n=this.getDimensionsFromLabel(e,t,i),o=n.width+this.margin.right+this.margin.left;this.width=o,this.height=o,this.radius=this.width/2}}},{key:"draw",value:function(e,t,i,n,o,s){this.resize(e,n,o),this.left=t-this.width/2,this.top=i-this.height/2,this.initContextForDraw(e,s),e.database(t-this.width/2,i-this.height/2,this.width,this.height),this.performFill(e,s),this.updateBoundingBox(t,i,e,n,o),this.labelModule.draw(e,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,n,o)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"diamond",4,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"circle",2,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this.resize(e),this.options.size}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(15),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(this.needsRefresh(t,i)){var n=this.getDimensionsFromLabel(e,t,i);this.height=2*n.height,this.width=n.width+n.height,this.radius=.5*this.width}}},{key:"draw",value:function(e,t,i,n,o,s){this.resize(e,n,o),this.left=t-.5*this.width,this.top=i-.5*this.height,this.initContextForDraw(e,s),e.ellipse_vis(this.left,this.top,this.width,this.height),this.performFill(e,s),this.updateBoundingBox(t,i,e,n,o),this.labelModule.draw(e,t,i,n,o)}},{key:"distanceToBorder",value:function(e,t){this.resize(e);var i=.5*this.width,n=.5*this.height,o=Math.sin(t)*i,s=Math.cos(t)*n;return i*n/Math.sqrt(o*o+s*s)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(15),v=n(p),g=function(e){function t(e,i,n){(0,a.default)(this,t);var o=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return o._setMargins(n),o}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e,t,i){this.needsRefresh(t,i)&&(this.iconSize={width:Number(this.options.icon.size),height:Number(this.options.icon.size)},this.width=this.iconSize.width+this.margin.right+this.margin.left,this.height=this.iconSize.height+this.margin.top+this.margin.bottom,this.radius=.5*this.width)}},{key:"draw",value:function(e,t,i,n,o,s){if(this.resize(e,n,o),this.options.icon.size=this.options.icon.size||50,this.left=t-this.width/2,this.top=i-this.height/2,this._icon(e,t,i,n,o,s),void 0!==this.options.label){this.labelModule.draw(e,this.left+this.iconSize.width/2+this.margin.left,i+this.height/2+5,n)}this.updateBoundingBox(t,i)}},{key:"updateBoundingBox",value:function(e,t){if(this.boundingBox.top=t-.5*this.options.icon.size,this.boundingBox.left=e-.5*this.options.icon.size,this.boundingBox.right=e+.5*this.options.icon.size,this.boundingBox.bottom=t+.5*this.options.icon.size,void 0!==this.options.label&&this.labelModule.size.width>0){this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelModule.size.height+5)}}},{key:"_icon",value:function(e,t,i,n,o,s){var r=Number(this.options.icon.size);void 0!==this.options.icon.code?(e.font=(n?"bold ":"")+r+"px "+this.options.icon.face,e.fillStyle=this.options.icon.color||"black",e.textAlign="center",e.textBaseline="middle",this.enableShadow(e,s),e.fillText(this.options.icon.code,t,i),this.disableShadow(e,s)):console.error("When using the icon shape, you need to define the code in the icon options object. This can be done per node or globally.")}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(53),v=n(p),g=function(e){function t(e,i,n,o,r){(0,a.default)(this,t);var d=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return d.setImages(o,r),d}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.selected,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.hover;if(void 0===this.imageObj.src||void 0===this.imageObj.width||void 0===this.imageObj.height){var n=2*this.options.size;return this.width=n,void(this.height=n)}this.needsRefresh(t,i)&&this._resizeImage()}},{key:"draw",value:function(e,t,i,n,o,s){if(this.switchImages(n),this.resize(),this.left=t-this.width/2,this.top=i-this.height/2,!0===this.options.shapeProperties.useBorderWithImage){var r=this.options.borderWidth,a=this.options.borderWidthSelected||2*this.options.borderWidth,d=(n?a:r)/this.body.view.scale;e.lineWidth=Math.min(this.width,d),e.beginPath(),e.strokeStyle=n?this.options.color.highlight.border:o?this.options.color.hover.border:this.options.color.border,e.fillStyle=n?this.options.color.highlight.background:o?this.options.color.hover.background:this.options.color.background,e.rect(this.left-.5*e.lineWidth,this.top-.5*e.lineWidth,this.width+e.lineWidth,this.height+e.lineWidth),e.fill(),this.performStroke(e,s),e.closePath()}this._drawImageAtPosition(e,s),this._drawImageLabel(e,t,i,n,o),this.updateBoundingBox(t,i)}},{key:"updateBoundingBox",value:function(e,t){this.resize(),this._updateBoundingBox(e,t),void 0!==this.options.label&&this.labelModule.size.width>0&&(this.boundingBox.left=Math.min(this.boundingBox.left,this.labelModule.size.left),this.boundingBox.right=Math.max(this.boundingBox.right,this.labelModule.size.left+this.labelModule.size.width),this.boundingBox.bottom=Math.max(this.boundingBox.bottom,this.boundingBox.bottom+this.labelOffset))}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"square",2,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"hexagon",4,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"star",4,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(15),v=n(p),g=function(e){function t(e,i,n){(0,a.default)(this,t);var o=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n));return o._setMargins(n),o}return(0,f.default)(t,e),(0,h.default)(t,[{key:"resize",value:function(e,t,i){this.needsRefresh(t,i)&&(this.textSize=this.labelModule.getTextSize(e,t,i),this.width=this.textSize.width+this.margin.right+this.margin.left,this.height=this.textSize.height+this.margin.top+this.margin.bottom,this.radius=.5*this.width)}},{key:"draw",value:function(e,t,i,n,o,s){this.resize(e,n,o),this.left=t-this.width/2,this.top=i-this.height/2,this.enableShadow(e,s),this.labelModule.draw(e,this.left+this.textSize.width/2+this.margin.left,this.top+this.textSize.height/2+this.margin.top,n,o),this.disableShadow(e,s),this.updateBoundingBox(t,i,e,n,o)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"triangle",3,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(16),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"draw",value:function(e,t,i,n,o,s){this._drawShape(e,"triangleDown",3,t,i,n,o,s)}},{key:"distanceToBorder",value:function(e,t){return this._distanceToBorder(e,t)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=i(5),h=i(32),l=i(51),u=i(55).default,c=function(){function e(t,i,n){var o=this;(0,s.default)(this,e),this.body=t,this.images=i,this.groups=n,this.body.functions.createEdge=this.create.bind(this),this.edgesListeners={add:function(e,t){o.add(t.items)},update:function(e,t){o.update(t.items)},remove:function(e,t){o.remove(t.items)}},this.options={},this.defaultOptions={arrows:{to:{enabled:!1,scaleFactor:1,type:"arrow"},middle:{enabled:!1,scaleFactor:1,type:"arrow"},from:{enabled:!1,scaleFactor:1,type:"arrow"}},arrowStrikethrough:!0,color:{color:"#848484",highlight:"#848484",hover:"#848484",inherit:"from",opacity:1},dashes:!1,font:{color:"#343434",size:14,face:"arial",background:"none",strokeWidth:2,strokeColor:"#ffffff",align:"horizontal",multi:!1,vadjust:0,bold:{mod:"bold"},boldital:{mod:"bold italic"},ital:{mod:"italic"},mono:{mod:"",size:15,face:"courier new",vadjust:2}},hidden:!1,hoverWidth:1.5,label:void 0,labelHighlightBold:!0,length:void 0,physics:!0,scaling:{min:1,max:15,label:{enabled:!0,min:14,max:30,maxVisible:30,drawThreshold:5},customScalingFunction:function(e,t,i,n){if(t===e)return.5;var o=1/(t-e);return Math.max(0,(n-e)*o)}},selectionWidth:1.5,selfReferenceSize:20,shadow:{enabled:!1,color:"rgba(0,0,0,0.5)",size:10,x:5,y:5},smooth:{enabled:!0,type:"dynamic",forceDirection:"none",roundness:.5},title:void 0,width:1,value:void 0},d.deepExtend(this.options,this.defaultOptions),this.bindEventListeners()}return(0,a.default)(e,[{key:"bindEventListeners",value:function(){var e=this;this.body.emitter.on("_forceDisableDynamicCurves",function(t){var i=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];"dynamic"===t&&(t="continuous");var n=!1;for(var o in e.body.edges)if(e.body.edges.hasOwnProperty(o)){var s=e.body.edges[o],r=e.body.data.edges._data[o];if(void 0!==r){var a=r.smooth;void 0!==a&&!0===a.enabled&&"dynamic"===a.type&&(void 0===t?s.setOptions({smooth:!1}):s.setOptions({smooth:{type:t}}),n=!0)}}!0===i&&!0===n&&e.body.emitter.emit("_dataChanged")}),this.body.emitter.on("_dataUpdated",function(){e.reconnectEdges()}),this.body.emitter.on("refreshEdges",this.refresh.bind(this)),this.body.emitter.on("refresh",this.refresh.bind(this)),this.body.emitter.on("destroy",function(){d.forEach(e.edgesListeners,function(t,i){e.body.data.edges&&e.body.data.edges.off(i,t)}),delete e.body.functions.createEdge,delete e.edgesListeners.add,delete e.edgesListeners.update,delete e.edgesListeners.remove,delete e.edgesListeners})}},{key:"setOptions",value:function(e){if(void 0!==e){u.parseOptions(this.options,e,!0,this.defaultOptions,!0);var t=!1;if(void 0!==e.smooth)for(var i in this.body.edges)this.body.edges.hasOwnProperty(i)&&(t=this.body.edges[i].updateEdgeType()||t);if(void 0!==e.font)for(var n in this.body.edges)this.body.edges.hasOwnProperty(n)&&this.body.edges[n].updateLabelModule();void 0===e.hidden&&void 0===e.physics&&!0!==t||this.body.emitter.emit("_dataChanged")}}},{key:"setData",value:function(e){var t=this,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=this.body.data.edges;if(e instanceof h||e instanceof l)this.body.data.edges=e;else if(Array.isArray(e))this.body.data.edges=new h,this.body.data.edges.add(e);else{if(e)throw new TypeError("Array or DataSet expected");this.body.data.edges=new h}if(n&&d.forEach(this.edgesListeners,function(e,t){n.off(t,e)}),this.body.edges={},this.body.data.edges){d.forEach(this.edgesListeners,function(e,i){t.body.data.edges.on(i,e)});var o=this.body.data.edges.getIds();this.add(o,!0)}this.body.emitter.emit("_adjustEdgesForHierarchicalLayout"),!1===i&&this.body.emitter.emit("_dataChanged")}},{key:"add",value:function(e){for(var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this.body.edges,n=this.body.data.edges,o=0;o1&&void 0!==arguments[1])||arguments[1];if(0!==e.length){var i=this.body.edges;d.forEach(e,function(e){var t=i[e];void 0!==t&&t.remove()}),t&&this.body.emitter.emit("_dataChanged")}}},{key:"refresh",value:function(){var e=this;d.forEach(this.body.edges,function(t,i){var n=e.body.data.edges._data[i];void 0!==n&&t.setOptions(n)})}},{key:"create",value:function(e){return new u(e,this.body,this.options,this.defaultOptions)}},{key:"reconnectEdges",value:function(){var e,t=this.body.nodes,i=this.body.edges;for(e in t)t.hasOwnProperty(e)&&(t[e].edges=[]);for(e in i)if(i.hasOwnProperty(e)){var n=i[e];n.from=null,n.to=null,n.connect()}}},{key:"getConnectedNodes",value:function(e){var t=[];if(void 0!==this.body.edges[e]){var i=this.body.edges[e];void 0!==i.fromId&&t.push(i.fromId),void 0!==i.toId&&t.push(i.toId)}return t}},{key:"_updateState",value:function(){this._addMissingEdges(),this._removeInvalidEdges()}},{key:"_removeInvalidEdges",value:function(){var e=this,t=[];d.forEach(this.body.edges,function(i,n){var o=e.body.nodes[i.toId],s=e.body.nodes[i.fromId];void 0!==o&&!0===o.isCluster||void 0!==s&&!0===s.isCluster||void 0!==o&&void 0!==s||t.push(n)}),this.remove(t,!1)}},{key:"_addMissingEdges",value:function(){var e=this.body.edges,t=this.body.data.edges,i=[];t.forEach(function(t,n){void 0===e[n]&&i.push(n)}),this.add(i,!0)}}]),e}();t.default=c},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(23),s=n(o),r=i(2),a=n(r),d=i(0),h=n(d),l=i(1),u=n(l),c=i(3),f=n(c),p=i(4),v=n(p),g=i(165),y=n(g),m=function(e){function t(e,i,n){return(0,h.default)(this,t),(0,f.default)(this,(t.__proto__||(0,a.default)(t)).call(this,e,i,n))}return(0,v.default)(t,e),(0,u.default)(t,[{key:"_line",value:function(e,t,i){var n=i[0],o=i[1];this._bezierCurve(e,t,n,o)}},{key:"_getViaCoordinates",value:function(){var e=this.from.x-this.to.x,t=this.from.y-this.to.y,i=void 0,n=void 0,o=void 0,s=void 0,r=this.options.smooth.roundness;return(Math.abs(e)>Math.abs(t)||!0===this.options.smooth.forceDirection||"horizontal"===this.options.smooth.forceDirection)&&"vertical"!==this.options.smooth.forceDirection?(n=this.from.y,s=this.to.y,i=this.from.x-r*e,o=this.to.x+r*e):(n=this.from.y-r*t,s=this.to.y+r*t,i=this.from.x,o=this.to.x),[{x:i,y:n},{x:o,y:s}]}},{key:"getViaNode",value:function(){return this._getViaCoordinates()}},{key:"_findBorderPosition",value:function(e,t){return this._findBorderPositionBezier(e,t)}},{key:"_getDistanceToEdge",value:function(e,t,i,n,o,r){var a=arguments.length>6&&void 0!==arguments[6]?arguments[6]:this._getViaCoordinates(),d=(0,s.default)(a,2),h=d[0],l=d[1];return this._getDistanceToBezierEdge(e,t,i,n,o,r,h,l)}},{key:"getPoint",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this._getViaCoordinates(),i=(0,s.default)(t,2),n=i[0],o=i[1],r=e,a=[];return a[0]=Math.pow(1-r,3),a[1]=3*r*Math.pow(1-r,2),a[2]=3*Math.pow(r,2)*(1-r),a[3]=Math.pow(r,3),{x:a[0]*this.fromPoint.x+a[1]*n.x+a[2]*o.x+a[3]*this.toPoint.x,y:a[0]*this.fromPoint.y+a[1]*n.y+a[2]*o.y+a[3]*this.toPoint.y}}}]),t}(y.default);t.default=m},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(56),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"_getDistanceToBezierEdge",value:function(e,t,i,n,o,s,r,a){var d=1e9,h=void 0,l=void 0,u=void 0,c=void 0,f=void 0,p=e,v=t,g=[0,0,0,0];for(l=1;l<10;l++)u=.1*l,g[0]=Math.pow(1-u,3),g[1]=3*u*Math.pow(1-u,2),g[2]=3*Math.pow(u,2)*(1-u),g[3]=Math.pow(u,3),c=g[0]*e+g[1]*r.x+g[2]*a.x+g[3]*i,f=g[0]*t+g[1]*r.y+g[2]*a.y+g[3]*n,l>0&&(h=this._getDistanceToLine(p,v,c,f,o,s),d=h1&&void 0!==arguments[1]?arguments[1]:this.via,i=e,n=void 0,o=void 0;if(this.from===this.to){var r=this._getCircleData(this.from),a=(0,s.default)(r,3),d=a[0],h=a[1],l=a[2],u=2*Math.PI*(1-i);n=d+l*Math.sin(u),o=h+l-l*(1-Math.cos(u))}else n=Math.pow(1-i,2)*this.fromPoint.x+2*i*(1-i)*t.x+Math.pow(i,2)*this.toPoint.x,o=Math.pow(1-i,2)*this.fromPoint.y+2*i*(1-i)*t.y+Math.pow(i,2)*this.toPoint.y;return{x:n,y:o}}},{key:"_findBorderPosition",value:function(e,t){return this._findBorderPositionBezier(e,t,this.via)}},{key:"_getDistanceToEdge",value:function(e,t,i,n,o,s){return this._getDistanceToBezierEdge(e,t,i,n,o,s,this.via)}}]),t}(y.default);t.default=m},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(56),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"_line",value:function(e,t,i){this._bezierCurve(e,t,i)}},{key:"getViaNode",value:function(){return this._getViaCoordinates()}},{key:"_getViaCoordinates",value:function(){var e=void 0,t=void 0,i=this.options.smooth.roundness,n=this.options.smooth.type,o=Math.abs(this.from.x-this.to.x),s=Math.abs(this.from.y-this.to.y);if("discrete"===n||"diagonalCross"===n){var r=void 0,a=void 0;r=a=o<=s?i*s:i*o,this.from.x>this.to.x&&(r=-r),this.from.y>=this.to.y&&(a=-a),e=this.from.x+r,t=this.from.y+a,"discrete"===n&&(o<=s?e=othis.to.x&&(_=-_),this.from.y>=this.to.y&&(w=-w),e=this.from.x+_,t=this.from.y+w,o<=s?e=this.from.x<=this.to.x?this.to.xe?this.to.x:e:t=this.from.y>=this.to.y?this.to.y>t?this.to.y:t:this.to.y2&&void 0!==arguments[2]?arguments[2]:{};return this._findBorderPositionBezier(e,t,i.via)}},{key:"_getDistanceToEdge",value:function(e,t,i,n,o,s){var r=arguments.length>6&&void 0!==arguments[6]?arguments[6]:this._getViaCoordinates();return this._getDistanceToBezierEdge(e,t,i,n,o,s,r)}},{key:"getPoint",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this._getViaCoordinates(),i=e;return{x:Math.pow(1-i,2)*this.fromPoint.x+2*i*(1-i)*t.x+Math.pow(i,2)*this.toPoint.x,y:Math.pow(1-i,2)*this.fromPoint.y+2*i*(1-i)*t.y+Math.pow(i,2)*this.toPoint.y}}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(78),v=n(p),g=function(e){function t(e,i,n){return(0,a.default)(this,t),(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n))}return(0,f.default)(t,e),(0,h.default)(t,[{key:"_line",value:function(e,t){e.beginPath(),e.moveTo(this.fromPoint.x,this.fromPoint.y),e.lineTo(this.toPoint.x,this.toPoint.y),this.enableShadow(e,t),e.stroke(),this.disableShadow(e,t)}},{key:"getViaNode",value:function(){}},{key:"getPoint",value:function(e){return{x:(1-e)*this.fromPoint.x+e*this.toPoint.x,y:(1-e)*this.fromPoint.y+e*this.toPoint.y}}},{key:"_findBorderPosition",value:function(e,t){var i=this.to,n=this.from;e.id===this.from.id&&(i=this.from,n=this.to);var o=Math.atan2(i.y-n.y,i.x-n.x),s=i.x-n.x,r=i.y-n.y,a=Math.sqrt(s*s+r*r),d=e.distanceToBorder(t,o),h=(a-d)/a,l={};return l.x=(1-h)*n.x+h*i.x,l.y=(1-h)*n.y+h*i.y,l}},{key:"_getDistanceToEdge",value:function(e,t,i,n,o,s){return this._getDistanceToLine(e,t,i,n,o,s)}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(10),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(80).default,u=i(170).default,c=i(171).default,f=i(172).default,p=i(173).default,v=i(81).default,g=i(174).default,y=i(175).default,m=i(5),b=i(79).default,_=function(){function e(t){(0,a.default)(this,e),this.body=t,this.physicsBody={physicsNodeIndices:[],physicsEdgeIndices:[],forces:{},velocities:{}},this.physicsEnabled=!0,this.simulationInterval=1e3/60,this.requiresTimeout=!0,this.previousStates={},this.referenceState={},this.freezeCache={},this.renderTimer=void 0,this.adaptiveTimestep=!1,this.adaptiveTimestepEnabled=!1,this.adaptiveCounter=0,this.adaptiveInterval=3,this.stabilized=!1,this.startedStabilization=!1,this.stabilizationIterations=0,this.ready=!1,this.options={},this.defaultOptions={enabled:!0,barnesHut:{theta:.5,gravitationalConstant:-2e3,centralGravity:.3,springLength:95,springConstant:.04,damping:.09,avoidOverlap:0},forceAtlas2Based:{theta:.5,gravitationalConstant:-50,centralGravity:.01,springConstant:.08,springLength:100,damping:.4,avoidOverlap:0},repulsion:{centralGravity:.2,springLength:200,springConstant:.05,nodeDistance:100,damping:.09,avoidOverlap:0},hierarchicalRepulsion:{centralGravity:0,springLength:100,springConstant:.01,nodeDistance:120,damping:.09},maxVelocity:50,minVelocity:.75,solver:"barnesHut",stabilization:{enabled:!0,iterations:1e3,updateInterval:50,onlyDynamicEdges:!1,fit:!0},timestep:.5,adaptiveTimestep:!0},m.extend(this.options,this.defaultOptions),this.timestep=.5,this.layoutFailed=!1,this.bindEventListeners()}return(0,h.default)(e,[{key:"bindEventListeners",value:function(){var e=this;this.body.emitter.on("initPhysics",function(){e.initPhysics()}),this.body.emitter.on("_layoutFailed",function(){e.layoutFailed=!0}),this.body.emitter.on("resetPhysics",function(){e.stopSimulation(),e.ready=!1}),this.body.emitter.on("disablePhysics",function(){e.physicsEnabled=!1,e.stopSimulation()}),this.body.emitter.on("restorePhysics",function(){e.setOptions(e.options),!0===e.ready&&e.startSimulation()}),this.body.emitter.on("startSimulation",function(){!0===e.ready&&e.startSimulation()}),this.body.emitter.on("stopSimulation",function(){e.stopSimulation()}),this.body.emitter.on("destroy",function(){e.stopSimulation(!1),e.body.emitter.off()}),this.body.emitter.on("_dataChanged",function(){e.updatePhysicsData()})}},{key:"setOptions",value:function(e){void 0!==e&&(!1===e?(this.options.enabled=!1,this.physicsEnabled=!1,this.stopSimulation()):!0===e?(this.options.enabled=!0,this.physicsEnabled=!0,this.startSimulation()):(this.physicsEnabled=!0,m.selectiveNotDeepExtend(["stabilization"],this.options,e),m.mergeOptions(this.options,e,"stabilization"),void 0===e.enabled&&(this.options.enabled=!0),!1===this.options.enabled&&(this.physicsEnabled=!1,this.stopSimulation()),this.timestep=this.options.timestep)),this.init()}},{key:"init",value:function(){var e;"forceAtlas2Based"===this.options.solver?(e=this.options.forceAtlas2Based,this.nodesSolver=new g(this.body,this.physicsBody,e),this.edgesSolver=new f(this.body,this.physicsBody,e),this.gravitySolver=new y(this.body,this.physicsBody,e)):"repulsion"===this.options.solver?(e=this.options.repulsion,this.nodesSolver=new u(this.body,this.physicsBody,e),this.edgesSolver=new f(this.body,this.physicsBody,e),this.gravitySolver=new v(this.body,this.physicsBody,e)):"hierarchicalRepulsion"===this.options.solver?(e=this.options.hierarchicalRepulsion,this.nodesSolver=new c(this.body,this.physicsBody,e),this.edgesSolver=new p(this.body,this.physicsBody,e),this.gravitySolver=new v(this.body,this.physicsBody,e)):(e=this.options.barnesHut,this.nodesSolver=new l(this.body,this.physicsBody,e),this.edgesSolver=new f(this.body,this.physicsBody,e),this.gravitySolver=new v(this.body,this.physicsBody,e)),this.modelOptions=e}},{key:"initPhysics",value:function(){!0===this.physicsEnabled&&!0===this.options.enabled?!0===this.options.stabilization.enabled?this.stabilize():(this.stabilized=!1,this.ready=!0,this.body.emitter.emit("fit",{},this.layoutFailed),this.startSimulation()):(this.ready=!0,this.body.emitter.emit("fit"))}},{key:"startSimulation",value:function(){!0===this.physicsEnabled&&!0===this.options.enabled?(this.stabilized=!1,this.adaptiveTimestep=!1,this.body.emitter.emit("_resizeNodes"),void 0===this.viewFunction&&(this.viewFunction=this.simulationStep.bind(this),this.body.emitter.on("initRedraw",this.viewFunction),this.body.emitter.emit("_startRendering"))):this.body.emitter.emit("_redraw")}},{key:"stopSimulation",value:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.stabilized=!0,!0===e&&this._emitStabilized(),void 0!==this.viewFunction&&(this.body.emitter.off("initRedraw",this.viewFunction),this.viewFunction=void 0,!0===e&&this.body.emitter.emit("_stopRendering"))}},{key:"simulationStep",value:function(){var e=Date.now();this.physicsTick(),(Date.now()-e<.4*this.simulationInterval||!0===this.runDoubleSpeed)&&!1===this.stabilized&&(this.physicsTick(),this.runDoubleSpeed=!0),!0===this.stabilized&&this.stopSimulation()}},{key:"_emitStabilized",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.stabilizationIterations;(this.stabilizationIterations>1||!0===this.startedStabilization)&&setTimeout(function(){e.body.emitter.emit("stabilized",{iterations:t}),e.startedStabilization=!1,e.stabilizationIterations=0},0)}},{key:"physicsStep",value:function(){this.gravitySolver.solve(),this.nodesSolver.solve(),this.edgesSolver.solve(),this.moveNodes()}},{key:"adjustTimeStep",value:function(){!0===this._evaluateStepQuality()?this.timestep=1.2*this.timestep:this.timestep/1.2.3))return!1;return!0}},{key:"moveNodes",value:function(){for(var e=this.physicsBody.physicsNodeIndices,t=0,i=0,n=0;nn&&(e=e>0?n:-n),e}},{key:"_performStep",value:function(e){var t=this.body.nodes[e],i=this.physicsBody.forces[e],n=this.physicsBody.velocities[e];return this.previousStates[e]={x:t.x,y:t.y,vx:n.x,vy:n.y},!1===t.options.fixed.x?(n.x=this.calculateComponentVelocity(n.x,i.x,t.options.mass),t.x+=n.x*this.timestep):(i.x=0,n.x=0),!1===t.options.fixed.y?(n.y=this.calculateComponentVelocity(n.y,i.y,t.options.mass),t.y+=n.y*this.timestep):(i.y=0,n.y=0),Math.sqrt(Math.pow(n.x,2)+Math.pow(n.y,2))}},{key:"_freezeNodes",value:function(){var e=this.body.nodes;for(var t in e)if(e.hasOwnProperty(t)&&e[t].x&&e[t].y){var i=e[t].options.fixed;this.freezeCache[t]={x:i.x,y:i.y},i.x=!0,i.y=!0}}},{key:"_restoreFrozenNodes",value:function(){var e=this.body.nodes;for(var t in e)e.hasOwnProperty(t)&&void 0!==this.freezeCache[t]&&(e[t].options.fixed.x=this.freezeCache[t].x,e[t].options.fixed.y=this.freezeCache[t].y);this.freezeCache={}}},{key:"stabilize",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.stabilization.iterations;if("number"!=typeof t&&(t=this.options.stabilization.iterations,console.log("The stabilize method needs a numeric amount of iterations. Switching to default: ",t)),0===this.physicsBody.physicsNodeIndices.length)return void(this.ready=!0);this.adaptiveTimestep=this.options.adaptiveTimestep,this.body.emitter.emit("_resizeNodes"),this.stopSimulation(),this.stabilized=!1,this.body.emitter.emit("_blockRedraw"),this.targetIterations=t,!0===this.options.stabilization.onlyDynamicEdges&&this._freezeNodes(),this.stabilizationIterations=0,setTimeout(function(){return e._stabilizationBatch()},0)}},{key:"_startStabilizing",value:function(){return!0!==this.startedStabilization&&(this.body.emitter.emit("startStabilizing"),this.startedStabilization=!0,!0)}},{key:"_stabilizationBatch",value:function(){var e=this,t=function(){return!1===e.stabilized&&e.stabilizationIterations0){var s=o.edges.length+1,r=this.options.centralGravity*s*o.options.mass;n[o.id].x=t*r,n[o.id].y=i*r}}}]),t}(v.default);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(10),s=n(o),r=i(7),a=n(r),d=i(0),h=n(d),l=i(1),u=n(l),c=i(5),f=i(57).default,p=i(177).default,v=i(55).default,g=i(33).default,y=function(){function e(t){var i=this;(0,h.default)(this,e),this.body=t,this.clusteredNodes={},this.clusteredEdges={},this.options={},this.defaultOptions={},c.extend(this.options,this.defaultOptions),this.body.emitter.on("_resetData",function(){i.clusteredNodes={},i.clusteredEdges={}})}return(0,u.default)(e,[{key:"clusterByHubsize",value:function(e,t){void 0===e?e=this._getHubSize():"object"===(void 0===e?"undefined":(0,a.default)(e))&&(t=this._checkOptions(e),e=this._getHubSize());for(var i=[],n=0;n=e&&i.push(o.id)}for(var s=0;s0&&void 0!==arguments[0]?arguments[0]:{},i=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if(void 0===t.joinCondition)throw new Error("Cannot call clusterByNodeData without a joinCondition function in the options.");t=this._checkOptions(t);var n={},o={};c.forEach(this.body.nodes,function(i,s){var r=f.cloneOptions(i);!0===t.joinCondition(r)&&(n[s]=i,c.forEach(i.edges,function(t){void 0===e.clusteredEdges[t.id]&&(o[t.id]=t)}))}),this._cluster(n,o,t,i)}},{key:"clusterByEdgeCount",value:function(e,t){var i=this,n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];t=this._checkOptions(t);for(var o=[],r={},a=void 0,d=void 0,h=void 0,l=0;l0&&(0,s.default)(v).length>0&&!0===b)if(c=function(){for(var e=0;e1&&void 0!==arguments[1])||arguments[1];this.clusterByEdgeCount(1,e,t)}},{key:"clusterBridges",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];this.clusterByEdgeCount(2,e,t)}},{key:"clusterByConnection",value:function(e,t){var i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(void 0===e)throw new Error("No nodeId supplied to clusterByConnection!");if(void 0===this.body.nodes[e])throw new Error("The nodeId given to clusterByConnection does not exist!");var n=this.body.nodes[e];t=this._checkOptions(t,n),void 0===t.clusterNodeProperties.x&&(t.clusterNodeProperties.x=n.x),void 0===t.clusterNodeProperties.y&&(t.clusterNodeProperties.y=n.y),void 0===t.clusterNodeProperties.fixed&&(t.clusterNodeProperties.fixed={},t.clusterNodeProperties.fixed.x=n.options.fixed.x,t.clusterNodeProperties.fixed.y=n.options.fixed.y);var o={},r={},a=n.id,d=f.cloneOptions(n);o[a]=n;for(var h=0;h-1&&(r[y.id]=y)}this._cluster(o,r,t,i)}},{key:"_createClusterEdges",value:function(e,t,i,n){for(var o=void 0,r=void 0,a=void 0,d=void 0,h=void 0,l=void 0,u=(0,s.default)(e),c=[],f=0;f0&&void 0!==arguments[0]?arguments[0]:{};return void 0===e.clusterEdgeProperties&&(e.clusterEdgeProperties={}),void 0===e.clusterNodeProperties&&(e.clusterNodeProperties={}),e}},{key:"_cluster",value:function(e,t,i){var n=!(arguments.length>3&&void 0!==arguments[3])||arguments[3],o=[];for(var r in e)e.hasOwnProperty(r)&&void 0!==this.clusteredNodes[r]&&o.push(r);for(var a=0;an?a.x:n,o=a.yr?a.y:r;return{x:.5*(i+n),y:.5*(o+r)}}},{key:"openCluster",value:function(e,t){var i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(void 0===e)throw new Error("No clusterNodeId supplied to openCluster.");var n=this.body.nodes[e];if(void 0===n)throw new Error("The clusterNodeId supplied to openCluster does not exist.");if(!0!==n.isCluster||void 0===n.containedNodes||void 0===n.containedEdges)throw new Error("The node:"+e+" is not a valid cluster.");var o=this.findNode(e),s=o.indexOf(e)-1;if(s>=0){var r=o[s];return this.body.nodes[r]._openChildCluster(e),delete this.body.nodes[e],void(!0===i&&this.body.emitter.emit("_dataChanged"))}var a=n.containedNodes,d=n.containedEdges;if(void 0!==t&&void 0!==t.releaseFunction&&"function"==typeof t.releaseFunction){var h={},l={x:n.x,y:n.y};for(var u in a)if(a.hasOwnProperty(u)){var f=this.body.nodes[u];h[u]={x:f.x,y:f.y}}var p=t.releaseFunction(l,h);for(var v in a)if(a.hasOwnProperty(v)){var g=this.body.nodes[v];void 0!==p[v]&&(g.x=void 0===p[v].x?n.x:p[v].x,g.y=void 0===p[v].y?n.y:p[v].y)}}else c.forEach(a,function(e){!1===e.options.fixed.x&&(e.x=n.x),!1===e.options.fixed.y&&(e.y=n.y)});for(var y in a)if(a.hasOwnProperty(y)){var m=this.body.nodes[y];m.vx=n.vx,m.vy=n.vy,m.setOptions({physics:!0}),delete this.clusteredNodes[y]}for(var b=[],_=0;_0&&o<100;){var s=t.pop();if(void 0!==s){var r=this.body.edges[s];if(void 0!==r){o++;var a=r.clusteringEdgeReplacingIds;if(void 0===a)n.push(s);else for(var d=0;dn&&(n=s.edges.length),e+=s.edges.length,t+=Math.pow(s.edges.length,2),i+=1}e/=i,t/=i;var r=t-Math.pow(e,2),a=Math.sqrt(r),d=Math.floor(e+2*a);return d>n&&(d=n),d}},{key:"_createClusteredEdge",value:function(e,t,i,n,o){var s=f.cloneOptions(i,"edge");c.deepExtend(s,n),s.from=e,s.to=t,s.id="clusterEdge:"+c.randomUUID(),void 0!==o&&c.deepExtend(s,o);var r=this.body.functions.createEdge(s);return r.clusteringEdgeReplacingIds=[i.id],r.connect(),this.body.edges[r.id]=r,r}},{key:"_clusterEdges",value:function(e,t,i,n){if(t instanceof v){var o=t,s={};s[o.id]=o,t=s}if(e instanceof g){var r=e,a={};a[r.id]=r,e=a}if(void 0===i||null===i)throw new Error("_clusterEdges: parameter clusterNode required");void 0===n&&(n=i.clusterEdgeProperties),this._createClusterEdges(e,t,i,n);for(var d in t)if(t.hasOwnProperty(d)&&void 0!==this.body.edges[d]){var h=this.body.edges[d];this._backupEdgeOptions(h),h.setOptions({physics:!1})}for(var l in e)e.hasOwnProperty(l)&&(this.clusteredNodes[l]={clusterId:i.id,node:this.body.nodes[l]},this.body.nodes[l].setOptions({physics:!1}))}},{key:"_getClusterNodeForNode",value:function(e){if(void 0!==e){var t=this.clusteredNodes[e];if(void 0!==t){var i=t.clusterId;if(void 0!==i)return this.body.nodes[i]}}}},{key:"_filter",value:function(e,t){var i=[];return c.forEach(e,function(e){t(e)&&i.push(e)}),i}},{key:"_updateState",value:function(){var e=this,t=void 0,i=[],n=[],o=function(t){c.forEach(e.body.nodes,function(e){!0===e.isCluster&&t(e)})};for(t in this.clusteredNodes)if(this.clusteredNodes.hasOwnProperty(t)){var r=this.body.nodes[t];void 0===r&&i.push(t)}o(function(e){for(var t=0;t0}t.endPointsValid()&&o||n.push(i)}),o(function(t){c.forEach(n,function(i){delete t.containedEdges[i],c.forEach(t.edges,function(o,s){if(o.id===i)return void(t.edges[s]=null);o.clusteringEdgeReplacingIds=e._filter(o.clusteringEdgeReplacingIds,function(e){return-1===n.indexOf(e)})}),t.edges=e._filter(t.edges,function(e){return null!==e})})}),c.forEach(n,function(t){delete e.clusteredEdges[t]}),c.forEach(n,function(t){delete e.body.edges[t]});var d=(0,s.default)(this.body.edges);c.forEach(d,function(t){var i=e.body.edges[t],n=e._isClusteredNode(i.fromId)||e._isClusteredNode(i.toId);if(n!==e._isClusteredEdge(i.id)){if(!n)throw new Error("remove edge from clustering not implemented!");var o=e._getClusterNodeForNode(i.fromId);void 0!==o&&e._clusterEdges(e.body.nodes[i.fromId],i,o);var s=e._getClusterNodeForNode(i.toId);void 0!==s&&e._clusterEdges(e.body.nodes[i.toId],i,s)}});for(var h=!1,l=!0;l;)!function(){var t=[];o(function(e){var i=(0,s.default)(e.containedNodes).length,n=!0===e.options.allowSingleNodeCluster;(n&&i<1||!n&&i<2)&&t.push(e.id)});for(var i=0;i0,h=h||l}();h&&this._updateState()}},{key:"_isClusteredNode",value:function(e){return void 0!==this.clusteredNodes[e]}},{key:"_isClusteredEdge",value:function(e){return void 0!==this.clusteredEdges[e]}}]),e}();t.default=y},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(2),s=n(o),r=i(0),a=n(r),d=i(1),h=n(d),l=i(3),u=n(l),c=i(4),f=n(c),p=i(5),v=i(33).default,g=function(e){function t(e,i,n,o,r,d){(0,a.default)(this,t);var h=(0,u.default)(this,(t.__proto__||(0,s.default)(t)).call(this,e,i,n,o,r,d));return h.isCluster=!0,h.containedNodes={},h.containedEdges={},h}return(0,f.default)(t,e),(0,h.default)(t,[{key:"_openChildCluster",value:function(e){var t=this,i=this.body.nodes[e];if(void 0===this.containedNodes[e])throw new Error("node with id: "+e+" not in current cluster");if(!i.isCluster)throw new Error("node with id: "+e+" is not a cluster");delete this.containedNodes[e],p.forEach(i.edges,function(e){delete t.containedEdges[e.id]}),p.forEach(i.containedNodes,function(e,i){t.containedNodes[i]=e}),i.containedNodes={},p.forEach(i.containedEdges,function(e,i){t.containedEdges[i]=e}),i.containedEdges={},p.forEach(i.edges,function(e){p.forEach(t.edges,function(i){var n=i.clusteringEdgeReplacingIds.indexOf(e.id);-1!==n&&(p.forEach(e.clusteringEdgeReplacingIds,function(e){i.clusteringEdgeReplacingIds.push(e),t.body.edges[e].edgeReplacedById=i.id}),i.clusteringEdgeReplacingIds.splice(n,1))})}),i.edges=[]}}]),t}(v);t.default=g},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}function o(){var e;void 0!==window&&(e=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame),window.requestAnimationFrame=void 0===e?function(e){e()}:e}Object.defineProperty(t,"__esModule",{value:!0});var s=i(0),r=n(s),a=i(1),d=n(a),h=i(5),l=function(){function e(t,i){(0,r.default)(this,e),o(),this.body=t,this.canvas=i,this.redrawRequested=!1,this.renderTimer=void 0,this.requiresTimeout=!0,this.renderingActive=!1,this.renderRequests=0,this.allowRedraw=!0,this.dragging=!1,this.options={},this.defaultOptions={hideEdgesOnDrag:!1,hideNodesOnDrag:!1},h.extend(this.options,this.defaultOptions),this._determineBrowserMethod(),this.bindEventListeners()}return(0,d.default)(e,[{key:"bindEventListeners",value:function(){var e=this;this.body.emitter.on("dragStart",function(){e.dragging=!0}),this.body.emitter.on("dragEnd",function(){e.dragging=!1}),this.body.emitter.on("_resizeNodes",function(){e._resizeNodes()}),this.body.emitter.on("_redraw",function(){!1===e.renderingActive&&e._redraw()}),this.body.emitter.on("_blockRedraw",function(){e.allowRedraw=!1}),this.body.emitter.on("_allowRedraw",function(){e.allowRedraw=!0,e.redrawRequested=!1}),this.body.emitter.on("_requestRedraw",this._requestRedraw.bind(this)),this.body.emitter.on("_startRendering",function(){e.renderRequests+=1,e.renderingActive=!0,e._startRendering()}),this.body.emitter.on("_stopRendering",function(){e.renderRequests-=1,e.renderingActive=e.renderRequests>0,e.renderTimer=void 0}),this.body.emitter.on("destroy",function(){e.renderRequests=0,e.allowRedraw=!1,e.renderingActive=!1,!0===e.requiresTimeout?clearTimeout(e.renderTimer):window.cancelAnimationFrame(e.renderTimer),e.body.emitter.off()})}},{key:"setOptions",value:function(e){if(void 0!==e){var t=["hideEdgesOnDrag","hideNodesOnDrag"];h.selectiveDeepExtend(t,this.options,e)}}},{key:"_requestNextFrame",value:function(e,t){if("undefined"!=typeof window){var i=void 0,n=window;return!0===this.requiresTimeout?i=n.setTimeout(e,t):n.requestAnimationFrame&&(i=n.requestAnimationFrame(e)),i}}},{key:"_startRendering",value:function(){!0===this.renderingActive&&void 0===this.renderTimer&&(this.renderTimer=this._requestNextFrame(this._renderStep.bind(this),this.simulationInterval))}},{key:"_renderStep",value:function(){!0===this.renderingActive&&(this.renderTimer=void 0,!0===this.requiresTimeout&&this._startRendering(),this._redraw(),!1===this.requiresTimeout&&this._startRendering())}},{key:"redraw",value:function(){this.body.emitter.emit("setSize"),this._redraw()}},{key:"_requestRedraw",value:function(){var e=this;!0!==this.redrawRequested&&!1===this.renderingActive&&!0===this.allowRedraw&&(this.redrawRequested=!0,this._requestNextFrame(function(){e._redraw(!1)},0))}},{key:"_redraw",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(!0===this.allowRedraw){this.body.emitter.emit("initRedraw"),this.redrawRequested=!1,0!==this.canvas.frame.canvas.width&&0!==this.canvas.frame.canvas.height||this.canvas.setSize(),this.canvas.setTransform();var t=this.canvas.getContext(),i=this.canvas.frame.canvas.clientWidth,n=this.canvas.frame.canvas.clientHeight;if(t.clearRect(0,0,i,n),0===this.canvas.frame.clientWidth)return;t.save(),t.translate(this.body.view.translation.x,this.body.view.translation.y),t.scale(this.body.view.scale,this.body.view.scale),t.beginPath(),this.body.emitter.emit("beforeDrawing",t),t.closePath(),!1===e&&(!1===this.dragging||!0===this.dragging&&!1===this.options.hideEdgesOnDrag)&&this._drawEdges(t),(!1===this.dragging||!0===this.dragging&&!1===this.options.hideNodesOnDrag)&&this._drawNodes(t,e),t.beginPath(),this.body.emitter.emit("afterDrawing",t),t.closePath(),t.restore(),!0===e&&t.clearRect(0,0,i,n)}}},{key:"_resizeNodes",value:function(){this.canvas.setTransform();var e=this.canvas.getContext();e.save(),e.translate(this.body.view.translation.x,this.body.view.translation.y),e.scale(this.body.view.scale,this.body.view.scale);var t=this.body.nodes,i=void 0;for(var n in t)t.hasOwnProperty(n)&&(i=t[n],i.resize(e),i.updateBoundingBox(e,i.selected));e.restore()}},{key:"_drawNodes",value:function(e){for(var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=this.body.nodes,n=this.body.nodeIndices,o=void 0,s=[],r=this.canvas.DOMtoCanvas({x:-20,y:-20}),a=this.canvas.DOMtoCanvas({x:this.canvas.frame.canvas.clientWidth+20,y:this.canvas.frame.canvas.clientHeight+20}),d={top:r.y,left:r.x,bottom:a.y,right:a.x},h=0;h0&&void 0!==arguments[0]?arguments[0]:this.pixelRatio;!0===this.initialized&&(this.cameraState.previousWidth=this.frame.canvas.width/e,this.cameraState.previousHeight=this.frame.canvas.height/e,this.cameraState.scale=this.body.view.scale,this.cameraState.position=this.DOMtoCanvas({x:.5*this.frame.canvas.width/e,y:.5*this.frame.canvas.height/e}))}},{key:"_setCameraState",value:function(){if(void 0!==this.cameraState.scale&&0!==this.frame.canvas.clientWidth&&0!==this.frame.canvas.clientHeight&&0!==this.pixelRatio&&this.cameraState.previousWidth>0){var e=this.frame.canvas.width/this.pixelRatio/this.cameraState.previousWidth,t=this.frame.canvas.height/this.pixelRatio/this.cameraState.previousHeight,i=this.cameraState.scale;1!=e&&1!=t?i=.5*this.cameraState.scale*(e+t):1!=e?i=this.cameraState.scale*e:1!=t&&(i=this.cameraState.scale*t),this.body.view.scale=i;var n=this.DOMtoCanvas({x:.5*this.frame.canvas.clientWidth,y:.5*this.frame.canvas.clientHeight}),o={x:n.x-this.cameraState.position.x,y:n.y-this.cameraState.position.y};this.body.view.translation.x+=o.x*this.body.view.scale,this.body.view.translation.y+=o.y*this.body.view.scale}}},{key:"_prepareValue",value:function(e){if("number"==typeof e)return e+"px";if("string"==typeof e){if(-1!==e.indexOf("%")||-1!==e.indexOf("px"))return e;if(-1===e.indexOf("%"))return e+"px"}throw new Error("Could not use the value supplied for width or height:"+e)}},{key:"_create",value:function(){for(;this.body.container.hasChildNodes();)this.body.container.removeChild(this.body.container.firstChild);if(this.frame=document.createElement("div"),this.frame.className="vis-network",this.frame.style.position="relative",this.frame.style.overflow="hidden",this.frame.tabIndex=900,this.frame.canvas=document.createElement("canvas"),this.frame.canvas.style.position="relative",this.frame.appendChild(this.frame.canvas),this.frame.canvas.getContext)this._setPixelRatio(),this.setTransform();else{var e=document.createElement("DIV");e.style.color="red",e.style.fontWeight="bold",e.style.padding="10px",e.innerHTML="Error: your browser does not support HTML canvas",this.frame.canvas.appendChild(e)}this.body.container.appendChild(this.frame),this.body.view.scale=1,this.body.view.translation={x:.5*this.frame.canvas.clientWidth,y:.5*this.frame.canvas.clientHeight},this._bindHammer()}},{key:"_bindHammer",value:function(){var e=this;void 0!==this.hammer&&this.hammer.destroy(),this.drag={},this.pinch={},this.hammer=new d(this.frame.canvas),this.hammer.get("pinch").set({enable:!0}),this.hammer.get("pan").set({threshold:5,direction:d.DIRECTION_ALL}),h.onTouch(this.hammer,function(t){e.body.eventListeners.onTouch(t)}),this.hammer.on("tap",function(t){e.body.eventListeners.onTap(t)}),this.hammer.on("doubletap",function(t){e.body.eventListeners.onDoubleTap(t)}),this.hammer.on("press",function(t){e.body.eventListeners.onHold(t)}),this.hammer.on("panstart",function(t){e.body.eventListeners.onDragStart(t)}),this.hammer.on("panmove",function(t){e.body.eventListeners.onDrag(t)}),this.hammer.on("panend",function(t){e.body.eventListeners.onDragEnd(t)}),this.hammer.on("pinch",function(t){e.body.eventListeners.onPinch(t)}),this.frame.canvas.addEventListener("mousewheel",function(t){e.body.eventListeners.onMouseWheel(t)}),this.frame.canvas.addEventListener("DOMMouseScroll",function(t){e.body.eventListeners.onMouseWheel(t)}),this.frame.canvas.addEventListener("mousemove",function(t){e.body.eventListeners.onMouseMove(t)}),this.frame.canvas.addEventListener("contextmenu",function(t){e.body.eventListeners.onContext(t)}),this.hammerFrame=new d(this.frame),h.onRelease(this.hammerFrame,function(t){e.body.eventListeners.onRelease(t)})}},{key:"setSize",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.width,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.options.height;e=this._prepareValue(e),t=this._prepareValue(t);var i=!1,n=this.frame.canvas.width,o=this.frame.canvas.height,s=this.pixelRatio;if(this._setPixelRatio(),e!=this.options.width||t!=this.options.height||this.frame.style.width!=e||this.frame.style.height!=t)this._getCameraState(s),this.frame.style.width=e,this.frame.style.height=t,this.frame.canvas.style.width="100%",this.frame.canvas.style.height="100%",this.frame.canvas.width=Math.round(this.frame.canvas.clientWidth*this.pixelRatio),this.frame.canvas.height=Math.round(this.frame.canvas.clientHeight*this.pixelRatio),this.options.width=e,this.options.height=t,this.canvasViewCenter={x:.5*this.frame.clientWidth,y:.5*this.frame.clientHeight},i=!0;else{var r=Math.round(this.frame.canvas.clientWidth*this.pixelRatio),a=Math.round(this.frame.canvas.clientHeight*this.pixelRatio);this.frame.canvas.width===r&&this.frame.canvas.height===a||this._getCameraState(s),this.frame.canvas.width!==r&&(this.frame.canvas.width=r,i=!0),this.frame.canvas.height!==a&&(this.frame.canvas.height=a,i=!0)}return!0===i&&(this.body.emitter.emit("resize",{width:Math.round(this.frame.canvas.width/this.pixelRatio),height:Math.round(this.frame.canvas.height/this.pixelRatio),oldWidth:Math.round(n/this.pixelRatio),oldHeight:Math.round(o/this.pixelRatio)}),this._setCameraState()),this.initialized=!0,i}},{key:"getContext",value:function(){return this.frame.canvas.getContext("2d")}},{key:"_determinePixelRatio",value:function(){var e=this.getContext();if(void 0===e)throw new Error("Could not get canvax context");var t=1;return"undefined"!=typeof window&&(t=window.devicePixelRatio||1),t/(e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1)}},{key:"_setPixelRatio",value:function(){this.pixelRatio=this._determinePixelRatio()}},{key:"setTransform",value:function(){var e=this.getContext();if(void 0===e)throw new Error("Could not get canvax context");e.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}},{key:"_XconvertDOMtoCanvas",value:function(e){return(e-this.body.view.translation.x)/this.body.view.scale}},{key:"_XconvertCanvasToDOM",value:function(e){return e*this.body.view.scale+this.body.view.translation.x}},{key:"_YconvertDOMtoCanvas",value:function(e){return(e-this.body.view.translation.y)/this.body.view.scale}},{key:"_YconvertCanvasToDOM",value:function(e){return e*this.body.view.scale+this.body.view.translation.y}},{key:"canvasToDOM",value:function(e){return{x:this._XconvertCanvasToDOM(e.x),y:this._YconvertCanvasToDOM(e.y)}}},{key:"DOMtoCanvas",value:function(e){return{x:this._XconvertDOMtoCanvas(e.x),y:this._YconvertDOMtoCanvas(e.y)}}}]),e}();t.default=u},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=i(5),h=i(57).default,l=function(){function e(t,i){var n=this;(0,s.default)(this,e),this.body=t,this.canvas=i,this.animationSpeed=1/this.renderRefreshRate,this.animationEasingFunction="easeInOutQuint",this.easingTime=0,this.sourceScale=0,this.targetScale=0,this.sourceTranslation=0,this.targetTranslation=0,this.lockedOnNodeId=void 0,this.lockedOnNodeOffset=void 0,this.touchTime=0,this.viewFunction=void 0,this.body.emitter.on("fit",this.fit.bind(this)),this.body.emitter.on("animationFinished",function(){n.body.emitter.emit("_stopRendering")}),this.body.emitter.on("unlockNode",this.releaseNode.bind(this))}return(0,a.default)(e,[{key:"setOptions",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.options=e}},{key:"fit",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{nodes:[]},t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=void 0,n=void 0;if(void 0!==e.nodes&&0!==e.nodes.length||(e.nodes=this.body.nodeIndices),!0===t){var o=0;for(var s in this.body.nodes)if(this.body.nodes.hasOwnProperty(s)){var r=this.body.nodes[s];!0===r.predefinedPosition&&(o+=1)}if(o>.5*this.body.nodeIndices.length)return void this.fit(e,!1);i=h.getRange(this.body.nodes,e.nodes);n=12.662/(this.body.nodeIndices.length+7.4147)+.0964822;n*=Math.min(this.canvas.frame.canvas.clientWidth/600,this.canvas.frame.canvas.clientHeight/600)}else{this.body.emitter.emit("_resizeNodes"),i=h.getRange(this.body.nodes,e.nodes);var a=1.1*Math.abs(i.maxX-i.minX),d=1.1*Math.abs(i.maxY-i.minY),l=this.canvas.frame.canvas.clientWidth/a,u=this.canvas.frame.canvas.clientHeight/d;n=l<=u?l:u}n>1?n=1:0===n&&(n=1);var c=h.findCenter(i),f={position:c,scale:n,animation:e.animation};this.moveTo(f)}},{key:"focus",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(void 0!==this.body.nodes[e]){var i={x:this.body.nodes[e].x,y:this.body.nodes[e].y};t.position=i,t.lockedOnNode=e,this.moveTo(t)}else console.log("Node: "+e+" cannot be found.")}},{key:"moveTo",value:function(e){if(void 0===e)return void(e={});void 0===e.offset&&(e.offset={x:0,y:0}),void 0===e.offset.x&&(e.offset.x=0),void 0===e.offset.y&&(e.offset.y=0),void 0===e.scale&&(e.scale=this.body.view.scale),void 0===e.position&&(e.position=this.getViewPosition()),void 0===e.animation&&(e.animation={duration:0}),!1===e.animation&&(e.animation={duration:0}),!0===e.animation&&(e.animation={}),void 0===e.animation.duration&&(e.animation.duration=1e3),void 0===e.animation.easingFunction&&(e.animation.easingFunction="easeInOutQuad"),this.animateView(e)}},{key:"animateView",value:function(e){if(void 0!==e){this.animationEasingFunction=e.animation.easingFunction,this.releaseNode(),!0===e.locked&&(this.lockedOnNodeId=e.lockedOnNode,this.lockedOnNodeOffset=e.offset),0!=this.easingTime&&this._transitionRedraw(!0),this.sourceScale=this.body.view.scale,this.sourceTranslation=this.body.view.translation,this.targetScale=e.scale,this.body.view.scale=this.targetScale;var t=this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.canvas.clientWidth,y:.5*this.canvas.frame.canvas.clientHeight}),i={x:t.x-e.position.x,y:t.y-e.position.y};this.targetTranslation={x:this.sourceTranslation.x+i.x*this.targetScale+e.offset.x,y:this.sourceTranslation.y+i.y*this.targetScale+e.offset.y},0===e.animation.duration?void 0!=this.lockedOnNodeId?(this.viewFunction=this._lockedRedraw.bind(this),this.body.emitter.on("initRedraw",this.viewFunction)):(this.body.view.scale=this.targetScale,this.body.view.translation=this.targetTranslation,this.body.emitter.emit("_requestRedraw")):(this.animationSpeed=1/(60*e.animation.duration*.001)||1/60,this.animationEasingFunction=e.animation.easingFunction,this.viewFunction=this._transitionRedraw.bind(this),this.body.emitter.on("initRedraw",this.viewFunction),this.body.emitter.emit("_startRendering"))}}},{key:"_lockedRedraw",value:function(){var e={x:this.body.nodes[this.lockedOnNodeId].x,y:this.body.nodes[this.lockedOnNodeId].y},t=this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.canvas.clientWidth,y:.5*this.canvas.frame.canvas.clientHeight}),i={x:t.x-e.x,y:t.y-e.y},n=this.body.view.translation,o={x:n.x+i.x*this.body.view.scale+this.lockedOnNodeOffset.x,y:n.y+i.y*this.body.view.scale+this.lockedOnNodeOffset.y};this.body.view.translation=o}},{key:"releaseNode",value:function(){void 0!==this.lockedOnNodeId&&void 0!==this.viewFunction&&(this.body.emitter.off("initRedraw",this.viewFunction),this.lockedOnNodeId=void 0,this.lockedOnNodeOffset=void 0)}},{key:"_transitionRedraw",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.easingTime+=this.animationSpeed,this.easingTime=!0===e?1:this.easingTime;var t=d.easingFunctions[this.animationEasingFunction](this.easingTime);this.body.view.scale=this.sourceScale+(this.targetScale-this.sourceScale)*t,this.body.view.translation={x:this.sourceTranslation.x+(this.targetTranslation.x-this.sourceTranslation.x)*t,y:this.sourceTranslation.y+(this.targetTranslation.y-this.sourceTranslation.y)*t},this.easingTime>=1&&(this.body.emitter.off("initRedraw",this.viewFunction),this.easingTime=0,void 0!=this.lockedOnNodeId&&(this.viewFunction=this._lockedRedraw.bind(this),this.body.emitter.on("initRedraw",this.viewFunction)),this.body.emitter.emit("animationFinished"))}},{key:"getScale",value:function(){return this.body.view.scale}},{key:"getViewPosition",value:function(){return this.canvas.DOMtoCanvas({x:.5*this.canvas.frame.canvas.clientWidth,y:.5*this.canvas.frame.canvas.clientHeight})}}]),e}();t.default=l},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=i(5),h=i(182).default,l=i(183).default,u=function(){function e(t,i,n){(0,s.default)(this,e),this.body=t,this.canvas=i,this.selectionHandler=n,this.navigationHandler=new h(t,i),this.body.eventListeners.onTap=this.onTap.bind(this),this.body.eventListeners.onTouch=this.onTouch.bind(this),this.body.eventListeners.onDoubleTap=this.onDoubleTap.bind(this),this.body.eventListeners.onHold=this.onHold.bind(this),this.body.eventListeners.onDragStart=this.onDragStart.bind(this),this.body.eventListeners.onDrag=this.onDrag.bind(this),this.body.eventListeners.onDragEnd=this.onDragEnd.bind(this),this.body.eventListeners.onMouseWheel=this.onMouseWheel.bind(this),this.body.eventListeners.onPinch=this.onPinch.bind(this),this.body.eventListeners.onMouseMove=this.onMouseMove.bind(this),this.body.eventListeners.onRelease=this.onRelease.bind(this),this.body.eventListeners.onContext=this.onContext.bind(this),this.touchTime=0,this.drag={},this.pinch={},this.popup=void 0,this.popupObj=void 0,this.popupTimer=void 0,this.body.functions.getPointer=this.getPointer.bind(this),this.options={},this.defaultOptions={dragNodes:!0,dragView:!0,hover:!1,keyboard:{enabled:!1,speed:{x:10,y:10,zoom:.02},bindToWindow:!0},navigationButtons:!1,tooltipDelay:300,zoomView:!0},d.extend(this.options,this.defaultOptions),this.bindEventListeners()}return(0,a.default)(e,[{key:"bindEventListeners",value:function(){var e=this;this.body.emitter.on("destroy",function(){clearTimeout(e.popupTimer),delete e.body.functions.getPointer})}},{key:"setOptions",value:function(e){if(void 0!==e){var t=["hideEdgesOnDrag","hideNodesOnDrag","keyboard","multiselect","selectable","selectConnectedEdges"];d.selectiveNotDeepExtend(t,this.options,e),d.mergeOptions(this.options,e,"keyboard"),e.tooltip&&(d.extend(this.options.tooltip,e.tooltip),e.tooltip.color&&(this.options.tooltip.color=d.parseColor(e.tooltip.color)))}this.navigationHandler.setOptions(this.options)}},{key:"getPointer",value:function(e){return{x:e.x-d.getAbsoluteLeft(this.canvas.frame.canvas),y:e.y-d.getAbsoluteTop(this.canvas.frame.canvas)}}},{key:"onTouch",value:function(e){(new Date).valueOf()-this.touchTime>50&&(this.drag.pointer=this.getPointer(e.center),this.drag.pinched=!1,this.pinch.scale=this.body.view.scale,this.touchTime=(new Date).valueOf())}},{key:"onTap",value:function(e){var t=this.getPointer(e.center),i=this.selectionHandler.options.multiselect&&(e.changedPointers[0].ctrlKey||e.changedPointers[0].metaKey);this.checkSelectionChanges(t,e,i),this.selectionHandler._generateClickEvent("click",e,t)}},{key:"onDoubleTap",value:function(e){var t=this.getPointer(e.center);this.selectionHandler._generateClickEvent("doubleClick",e,t)}},{key:"onHold",value:function(e){var t=this.getPointer(e.center),i=this.selectionHandler.options.multiselect;this.checkSelectionChanges(t,e,i),this.selectionHandler._generateClickEvent("click",e,t),this.selectionHandler._generateClickEvent("hold",e,t)}},{key:"onRelease",value:function(e){if((new Date).valueOf()-this.touchTime>10){var t=this.getPointer(e.center);this.selectionHandler._generateClickEvent("release",e,t),this.touchTime=(new Date).valueOf()}}},{key:"onContext",value:function(e){var t=this.getPointer({x:e.clientX,y:e.clientY});this.selectionHandler._generateClickEvent("oncontext",e,t)}},{key:"checkSelectionChanges",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=this.selectionHandler.getSelection(),o=!1;o=!0===i?this.selectionHandler.selectAdditionalOnPoint(e):this.selectionHandler.selectOnPoint(e);var s=this.selectionHandler.getSelection(),r=this._determineDifference(n,s),a=this._determineDifference(s,n);r.edges.length>0&&(this.selectionHandler._generateClickEvent("deselectEdge",t,e,n),o=!0),r.nodes.length>0&&(this.selectionHandler._generateClickEvent("deselectNode",t,e,n),o=!0),a.nodes.length>0&&(this.selectionHandler._generateClickEvent("selectNode",t,e),o=!0),a.edges.length>0&&(this.selectionHandler._generateClickEvent("selectEdge",t,e),o=!0),!0===o&&this.selectionHandler._generateClickEvent("select",t,e)}},{key:"_determineDifference",value:function(e,t){var i=function(e,t){for(var i=[],n=0;n10&&(e=10);var n=void 0;void 0!==this.drag&&!0===this.drag.dragging&&(n=this.canvas.DOMtoCanvas(this.drag.pointer));var o=this.body.view.translation,s=e/i,r=(1-s)*t.x+o.x*s,a=(1-s)*t.y+o.y*s;if(this.body.view.scale=e,this.body.view.translation={x:r,y:a},void 0!=n){var d=this.canvas.canvasToDOM(n);this.drag.pointer.x=d.x,this.drag.pointer.y=d.y}this.body.emitter.emit("_requestRedraw"),i0&&(this.popupObj=d[u[u.length-1]],s=!0)}if(void 0===this.popupObj&&!1===s){for(var f=this.body.edgeIndices,p=this.body.edges,v=void 0,g=[],y=0;y0&&(this.popupObj=p[g[g.length-1]],r="edge")}void 0!==this.popupObj?this.popupObj.id!==o&&(void 0===this.popup&&(this.popup=new l(this.canvas.frame)),this.popup.popupTargetType=r,this.popup.popupTargetId=this.popupObj.id,this.popup.setPosition(e.x+3,e.y-5),this.popup.setText(this.popupObj.getTitle()),this.popup.show(),this.body.emitter.emit("showPopup",this.popupObj.id)):void 0!==this.popup&&(this.popup.hide(),this.body.emitter.emit("hidePopup"))}},{key:"_checkHidePopup",value:function(e){var t=this.selectionHandler._pointerToPositionObject(e),i=!1;if("node"===this.popup.popupTargetType){if(void 0!==this.body.nodes[this.popup.popupTargetId]&&!0===(i=this.body.nodes[this.popup.popupTargetId].isOverlappingWith(t))){var n=this.selectionHandler.getNodeAt(e);i=void 0!==n&&n.id===this.popup.popupTargetId}}else void 0===this.selectionHandler.getNodeAt(e)&&void 0!==this.body.edges[this.popup.popupTargetId]&&(i=this.body.edges[this.popup.popupTargetId].isOverlappingWith(t));!1===i&&(this.popupObj=void 0,this.popup.hide(),this.body.emitter.emit("hidePopup"))}}]),e}();t.default=u},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=i(22),h=i(35),l=i(52),u=function(){function e(t,i){var n=this;(0,s.default)(this,e),this.body=t,this.canvas=i,this.iconsCreated=!1,this.navigationHammers=[],this.boundFunctions={},this.touchTime=0,this.activated=!1,this.body.emitter.on("activate",function(){n.activated=!0,n.configureKeyboardBindings()}),this.body.emitter.on("deactivate",function(){n.activated=!1,n.configureKeyboardBindings()}),this.body.emitter.on("destroy",function(){void 0!==n.keycharm&&n.keycharm.destroy()}),this.options={}}return(0,a.default)(e,[{key:"setOptions",value:function(e){void 0!==e&&(this.options=e,this.create())}},{key:"create",value:function(){!0===this.options.navigationButtons?!1===this.iconsCreated&&this.loadNavigationElements():!0===this.iconsCreated&&this.cleanNavigation(),this.configureKeyboardBindings()}},{key:"cleanNavigation",value:function(){if(0!=this.navigationHammers.length){for(var e=0;e700&&(this.body.emitter.emit("fit",{duration:700}),this.touchTime=(new Date).valueOf())}},{key:"_stopMovement",value:function(){for(var e in this.boundFunctions)this.boundFunctions.hasOwnProperty(e)&&(this.body.emitter.off("initRedraw",this.boundFunctions[e]),this.body.emitter.emit("_stopRendering"));this.boundFunctions={}}},{key:"_moveUp",value:function(){this.body.view.translation.y+=this.options.keyboard.speed.y}},{key:"_moveDown",value:function(){this.body.view.translation.y-=this.options.keyboard.speed.y}},{key:"_moveLeft",value:function(){this.body.view.translation.x+=this.options.keyboard.speed.x}},{key:"_moveRight",value:function(){this.body.view.translation.x-=this.options.keyboard.speed.x}},{key:"_zoomIn",value:function(){var e=this.body.view.scale,t=this.body.view.scale*(1+this.options.keyboard.speed.zoom),i=this.body.view.translation,n=t/e,o=(1-n)*this.canvas.canvasViewCenter.x+i.x*n,s=(1-n)*this.canvas.canvasViewCenter.y+i.y*n;this.body.view.scale=t,this.body.view.translation={x:o,y:s},this.body.emitter.emit("zoom",{direction:"+",scale:this.body.view.scale,pointer:null})}},{key:"_zoomOut",value:function(){var e=this.body.view.scale,t=this.body.view.scale/(1+this.options.keyboard.speed.zoom),i=this.body.view.translation,n=t/e,o=(1-n)*this.canvas.canvasViewCenter.x+i.x*n,s=(1-n)*this.canvas.canvasViewCenter.y+i.y*n;this.body.view.scale=t,this.body.view.translation={x:o,y:s},this.body.emitter.emit("zoom",{direction:"-",scale:this.body.view.scale,pointer:null})}},{key:"configureKeyboardBindings",value:function(){var e=this;void 0!==this.keycharm&&this.keycharm.destroy(),!0===this.options.keyboard.enabled&&(!0===this.options.keyboard.bindToWindow?this.keycharm=l({container:window,preventDefault:!0}):this.keycharm=l({container:this.canvas.frame,preventDefault:!0}),this.keycharm.reset(),!0===this.activated&&(this.keycharm.bind("up",function(){e.bindToRedraw("_moveUp")},"keydown"),this.keycharm.bind("down",function(){e.bindToRedraw("_moveDown")},"keydown"),this.keycharm.bind("left",function(){e.bindToRedraw("_moveLeft")},"keydown"),this.keycharm.bind("right",function(){e.bindToRedraw("_moveRight")},"keydown"),this.keycharm.bind("=",function(){e.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("num+",function(){e.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("num-",function(){e.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("-",function(){e.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("[",function(){e.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("]",function(){e.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("pageup",function(){e.bindToRedraw("_zoomIn")},"keydown"),this.keycharm.bind("pagedown",function(){e.bindToRedraw("_zoomOut")},"keydown"),this.keycharm.bind("up",function(){e.unbindFromRedraw("_moveUp")},"keyup"),this.keycharm.bind("down",function(){e.unbindFromRedraw("_moveDown")},"keyup"),this.keycharm.bind("left",function(){e.unbindFromRedraw("_moveLeft")},"keyup"),this.keycharm.bind("right",function(){e.unbindFromRedraw("_moveRight")},"keyup"),this.keycharm.bind("=",function(){e.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("num+",function(){e.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("num-",function(){e.unbindFromRedraw("_zoomOut")},"keyup"),this.keycharm.bind("-",function(){e.unbindFromRedraw("_zoomOut")},"keyup"),this.keycharm.bind("[",function(){e.unbindFromRedraw("_zoomOut")},"keyup"),this.keycharm.bind("]",function(){e.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("pageup",function(){e.unbindFromRedraw("_zoomIn")},"keyup"),this.keycharm.bind("pagedown",function(){e.unbindFromRedraw("_zoomOut")},"keyup")))}}]),e}();t.default=u},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(0),s=n(o),r=i(1),a=n(r),d=function(){function e(t,i){(0,s.default)(this,e),this.container=t,this.overflowMethod=i||"cap",this.x=0,this.y=0,this.padding=5,this.hidden=!1,this.frame=document.createElement("div"),this.frame.className="vis-tooltip",this.container.appendChild(this.frame)}return(0,a.default)(e,[{key:"setPosition",value:function(e,t){this.x=parseInt(e),this.y=parseInt(t)}},{key:"setText",value:function(e){e instanceof Element?(this.frame.innerHTML="",this.frame.appendChild(e)):this.frame.innerHTML=e}},{key:"show",value:function(e){if(void 0===e&&(e=!0),!0===e){var t=this.frame.clientHeight,i=this.frame.clientWidth,n=this.frame.parentNode.clientHeight,o=this.frame.parentNode.clientWidth,s=0,r=0;if("flip"==this.overflowMethod){var a=!1,d=!0;this.y-to-this.padding&&(a=!0),s=a?this.x-i:this.x,r=d?this.y-t:this.y}else r=this.y-t,r+t+this.padding>n&&(r=n-t-this.padding),ro&&(s=o-i-this.padding),s4&&void 0!==arguments[4]&&arguments[4],s=this._initBaseEvent(t,i);if(!0===o)s.nodes=[],s.edges=[];else{var r=this.getSelection();s.nodes=r.nodes,s.edges=r.edges}void 0!==n&&(s.previousSelection=n),"click"==e&&(s.items=this.getClickedItems(i)),this.body.emitter.emit(e,s)}},{key:"selectObject",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.options.selectConnectedEdges;return void 0!==e&&(e instanceof d&&!0===t&&this._selectConnectedEdges(e),e.select(),this._addToSelection(e),!0)}},{key:"deselectObject",value:function(e){!0===e.isSelected()&&(e.selected=!1,this._removeFromSelection(e))}},{key:"_getAllNodesOverlappingWith",value:function(e){for(var t=[],i=this.body.nodes,n=0;n1&&void 0!==arguments[1])||arguments[1],i=this._pointerToPositionObject(e),n=this._getAllNodesOverlappingWith(i);return n.length>0?!0===t?this.body.nodes[n[n.length-1]]:n[n.length-1]:void 0}},{key:"_getEdgesOverlappingWith",value:function(e,t){for(var i=this.body.edges,n=0;n1&&void 0!==arguments[1])||arguments[1],i=this.canvas.DOMtoCanvas(e),n=10,o=null,s=this.body.edges,r=0;r1)return!0;return!1}},{key:"_selectConnectedEdges",value:function(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:{},i=void 0,n=void 0;if(!e||!e.nodes&&!e.edges)throw"Selection must be an object with nodes and/or edges properties";if((t.unselectAll||void 0===t.unselectAll)&&this.unselectAll(),e.nodes)for(i=0;i1&&void 0!==arguments[1])||arguments[1];if(!e||void 0===e.length)throw"Selection must be an array with ids";this.setSelection({nodes:e},{highlightEdges:t})}},{key:"selectEdges",value:function(e){if(!e||void 0===e.length)throw"Selection must be an array with ids";this.setSelection({edges:e})}},{key:"updateSelection",value:function(){for(var e in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(e)&&(this.body.nodes.hasOwnProperty(e)||delete this.selectionObj.nodes[e]);for(var t in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(t)&&(this.body.edges.hasOwnProperty(t)||delete this.selectionObj.edges[t])}},{key:"getClickedItems",value:function(e){for(var t=this.canvas.DOMtoCanvas(e),i=[],n=this.body.nodeIndices,o=this.body.nodes,s=n.length-1;s>=0;s--){var r=o[n[s]],a=r.getItemsOnPoint(t);i.push.apply(i,a)}for(var d=this.body.edgeIndices,h=this.body.edges,l=d.length-1;l>=0;l--){var u=h[d[l]],c=u.getItemsOnPoint(t);i.push.apply(i,c)}return i}}]),e}();t.default=u},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(23),s=n(o),r=i(7),a=n(r),d=i(10),h=n(d),l=i(0),u=n(l),c=i(1),f=n(c),p=i(5),v=i(57).default,g=i(186),y=g.HorizontalStrategy,m=g.VerticalStrategy,b=function(){function e(){(0,u.default)(this,e),this.childrenReference={},this.parentReference={},this.trees={},this.distributionOrdering={},this.levels={},this.distributionIndex={},this.isTree=!1,this.treeIndex=-1}return(0,f.default)(e,[{key:"addRelation",value:function(e,t){void 0===this.childrenReference[e]&&(this.childrenReference[e]=[]),this.childrenReference[e].push(t),void 0===this.parentReference[t]&&(this.parentReference[t]=[]),this.parentReference[t].push(e)}},{key:"checkIfTree",value:function(){for(var e in this.parentReference)if(this.parentReference[e].length>1)return void(this.isTree=!1);this.isTree=!0}},{key:"numTrees",value:function(){return this.treeIndex+1}},{key:"setTreeIndex",value:function(e,t){void 0!==t&&void 0===this.trees[e.id]&&(this.trees[e.id]=t,this.treeIndex=Math.max(t,this.treeIndex))}},{key:"ensureLevel",value:function(e){void 0===this.levels[e]&&(this.levels[e]=0)}},{key:"getMaxLevel",value:function(e){var t=this,i={};return function e(n){if(void 0!==i[n])return i[n];var o=t.levels[n];if(t.childrenReference[n]){var s=t.childrenReference[n];if(s.length>0)for(var r=0;r0&&(i.levelSeparation*=-1):i.levelSeparation<0&&(i.levelSeparation*=-1),this.setDirectionStrategy(),this.body.emitter.emit("_resetHierarchicalLayout"),this.adaptAllOptionsForHierarchicalLayout(t);if(!0===n)return this.body.emitter.emit("refresh"),p.deepExtend(t,this.optionsBackup)}return t}},{key:"adaptAllOptionsForHierarchicalLayout",value:function(e){if(!0===this.options.hierarchical.enabled){var t=this.optionsBackup.physics;void 0===e.physics||!0===e.physics?(e.physics={enabled:void 0===t.enabled||t.enabled,solver:"hierarchicalRepulsion"},t.enabled=void 0===t.enabled||t.enabled,t.solver=t.solver||"barnesHut"):"object"===(0,a.default)(e.physics)?(t.enabled=void 0===e.physics.enabled||e.physics.enabled,t.solver=e.physics.solver||"barnesHut",e.physics.solver="hierarchicalRepulsion"):!1!==e.physics&&(t.solver="barnesHut",e.physics={solver:"hierarchicalRepulsion"});var i=this.direction.curveType();if(void 0===e.edges)this.optionsBackup.edges={smooth:{enabled:!0,type:"dynamic"}},e.edges={smooth:!1};else if(void 0===e.edges.smooth)this.optionsBackup.edges={smooth:{enabled:!0,type:"dynamic"}},e.edges.smooth=!1;else if("boolean"==typeof e.edges.smooth)this.optionsBackup.edges={smooth:e.edges.smooth},e.edges.smooth={enabled:e.edges.smooth,type:i};else{var n=e.edges.smooth;void 0!==n.type&&"dynamic"!==n.type&&(i=n.type),this.optionsBackup.edges={smooth:void 0===n.enabled||n.enabled,type:void 0===n.type?"dynamic":n.type,roundness:void 0===n.roundness?.5:n.roundness,forceDirection:void 0!==n.forceDirection&&n.forceDirection},e.edges.smooth={enabled:void 0===n.enabled||n.enabled,type:i,roundness:void 0===n.roundness?.5:n.roundness,forceDirection:void 0!==n.forceDirection&&n.forceDirection}}this.body.emitter.emit("_forceDisableDynamicCurves",i)}return e}},{key:"seededRandom",value:function(){var e=1e4*Math.sin(this.randomSeed++);return e-Math.floor(e)}},{key:"positionInitially",value:function(e){if(!0!==this.options.hierarchical.enabled){this.randomSeed=this.initialRandomSeed;for(var t=e.length+50,i=0;i150){for(var s=e.length;e.length>150&&n<=10;){n+=1;var r=e.length;n%3==0?this.body.modules.clustering.clusterBridges(o):this.body.modules.clustering.clusterOutliers(o);if(r==e.length&&n%3!=0)return this._declusterAll(),this.body.emitter.emit("_layoutFailed"),void console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance.")}this.body.modules.kamadaKawai.setOptions({springLength:Math.max(150,2*s)})}n>10&&console.info("The clustering didn't succeed within the amount of interations allowed, progressing with partial result."),this.body.modules.kamadaKawai.solve(e,this.body.edgeIndices,!0),this._shiftToCenter();for(var a=0;a0){var e=void 0,t=void 0,i=!1,n=!1;this.lastNodeOnLevel={},this.hierarchical=new b;for(t in this.body.nodes)this.body.nodes.hasOwnProperty(t)&&(e=this.body.nodes[t],void 0!==e.options.level?(i=!0,this.hierarchical.levels[t]=e.options.level):n=!0);if(!0===n&&!0===i)throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");if(!0===n){var o=this.options.hierarchical.sortMethod;"hubsize"===o?this._determineLevelsByHubsize():"directed"===o?this._determineLevelsDirected():"custom"===o&&this._determineLevelsCustomCallback()}for(var s in this.body.nodes)this.body.nodes.hasOwnProperty(s)&&this.hierarchical.ensureLevel(s);var r=this._getDistribution();this._generateMap(),this._placeNodesByHierarchy(r),this._condenseHierarchy(),this._shiftToCenter()}}},{key:"_condenseHierarchy",value:function(){var e=this,t=!1,i={},n=function(t,i){var n=e.hierarchical.trees;for(var o in n)n.hasOwnProperty(o)&&n[o]===t&&e.direction.shift(o,i)},o=function(){for(var t=[],i=0;i0)for(var s=0;s1&&void 0!==arguments[1]?arguments[1]:1e9,n=1e9,o=1e9,r=1e9,a=-1e9;for(var d in t)if(t.hasOwnProperty(d)){var h=e.body.nodes[d],l=e.hierarchical.levels[h.id],u=e.direction.getPosition(h),c=e._getSpaceAroundNode(h,t),f=(0,s.default)(c,2),p=f[0],v=f[1];n=Math.min(p,n),o=Math.min(v,o),l<=i&&(r=Math.min(u,r),a=Math.max(u,a))}return[r,a,n,o]},d=function(t,i){var n=e.hierarchical.getMaxLevel(t.id),o=e.hierarchical.getMaxLevel(i.id);return Math.min(n,o)},h=function(t,i,n){for(var o=e.hierarchical,s=0;s1)for(var d=0;d2&&void 0!==arguments[2]&&arguments[2],s=e.direction.getPosition(i),h=e.direction.getPosition(n),l=Math.abs(h-s),u=e.options.hierarchical.nodeSpacing;if(l>u){var c={},f={};r(i,c),r(n,f);var p=d(i,n),v=a(c,p),g=a(f,p),y=v[1],m=g[0],b=g[2];if(Math.abs(y-m)>u){var _=y-m+u;_<-b+u&&(_=-b+u),_<0&&(e._shiftBlock(n.id,_),t=!0,!0===o&&e._centerParent(n))}}},u=function(n,o){for(var d=o.id,h=o.edges,l=e.hierarchical.levels[o.id],u=e.options.hierarchical.levelSeparation*e.options.hierarchical.levelSeparation,c={},f=[],p=0;p0?f=Math.min(c,u-e.options.hierarchical.nodeSpacing):c<0&&(f=-Math.min(-c,l-e.options.hierarchical.nodeSpacing)),0!=f&&(e._shiftBlock(o.id,f),t=!0)}(_),_=b(n,h),function(i){var n=e.direction.getPosition(o),r=e._getSpaceAroundNode(o),a=(0,s.default)(r,2),d=a[0],h=a[1],l=i-n,u=n;l>0?u=Math.min(n+(h-e.options.hierarchical.nodeSpacing),i):l<0&&(u=Math.max(n-(d-e.options.hierarchical.nodeSpacing),i)),u!==n&&(e.direction.setPosition(o,u),t=!0)}(_)};!0===this.options.hierarchical.blockShifting&&(function(i){var n=e.hierarchical.getLevels();n=n.reverse();for(var o=0;o0&&Math.abs(f)0&&(a=this.direction.getPosition(i[o-1])+r),this.direction.setPosition(s,a,t),this._validatePositionAndContinue(s,t,a),n++}}}}},{key:"_placeBranchNodes",value:function(e,t){var i=this.hierarchical.childrenReference[e];if(void 0!==i){for(var n=[],o=0;ot&&void 0===this.positionedNodes[r.id]))return;var d=this.options.hierarchical.nodeSpacing,h=void 0;h=0===s?this.direction.getPosition(this.body.nodes[e]):this.direction.getPosition(n[s-1])+d,this.direction.setPosition(r,h,a),this._validatePositionAndContinue(r,a,h)}var l=this._getCenterPosition(n);this.direction.setPosition(this.body.nodes[e],l,t)}}},{key:"_validatePositionAndContinue",value:function(e,t,i){if(this.hierarchical.isTree){if(void 0!==this.lastNodeOnLevel[t]){var n=this.direction.getPosition(this.body.nodes[this.lastNodeOnLevel[t]]);if(i-ne.hierarchical.levels[t.id]&&e.hierarchical.addRelation(t.id,i.id)};this._crawlNetwork(t),this.hierarchical.checkIfTree()}},{key:"_crawlNetwork",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){},i=arguments[1],n={},o=function i(o,s){if(void 0===n[o.id]){e.hierarchical.setTreeIndex(o,s),n[o.id]=!0;for(var r=void 0,a=e._getActiveEdges(o),d=0;d2&&void 0!==arguments[2]?arguments[2]:void 0;this.fake_use(e,t,i),this.abstract()}},{key:"getTreeSize",value:function(e){return this.fake_use(e),this.abstract()}},{key:"sort",value:function(e){this.fake_use(e),this.abstract()}},{key:"fix",value:function(e,t){this.fake_use(e,t),this.abstract()}},{key:"shift",value:function(e,t){this.fake_use(e,t),this.abstract()}}]),e}(),v=function(e){function t(e){(0,u.default)(this,t);var i=(0,a.default)(this,(t.__proto__||(0,s.default)(t)).call(this));return i.layout=e,i}return(0,h.default)(t,e),(0,f.default)(t,[{key:"curveType",value:function(){return"horizontal"}},{key:"getPosition",value:function(e){return e.x}},{key:"setPosition",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;void 0!==i&&this.layout.hierarchical.addToOrdering(e,i),e.x=t}},{key:"getTreeSize",value:function(e){var t=this.layout.hierarchical.getTreeSize(this.layout.body.nodes,e);return{min:t.min_x,max:t.max_x}}},{key:"sort",value:function(e){e.sort(function(e,t){return void 0===e.x||void 0===t.x?0:e.x-t.x})}},{key:"fix",value:function(e,t){e.y=this.layout.options.hierarchical.levelSeparation*t,e.options.fixed.y=!0}},{key:"shift",value:function(e,t){this.layout.body.nodes[e].x+=t}}]),t}(p),g=function(e){function t(e){(0,u.default)(this,t);var i=(0,a.default)(this,(t.__proto__||(0,s.default)(t)).call(this));return i.layout=e,i}return(0,h.default)(t,e),(0,f.default)(t,[{key:"curveType",value:function(){return"vertical"}},{key:"getPosition",value:function(e){return e.y}},{key:"setPosition",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;void 0!==i&&this.layout.hierarchical.addToOrdering(e,i),e.y=t}},{key:"getTreeSize",value:function(e){var t=this.layout.hierarchical.getTreeSize(this.layout.body.nodes,e);return{min:t.min_y,max:t.max_y}}},{key:"sort",value:function(e){e.sort(function(e,t){return void 0===e.y||void 0===t.y?0:e.y-t.y})}},{key:"fix",value:function(e,t){e.x=this.layout.options.hierarchical.levelSeparation*t,e.options.fixed.x=!0}},{key:"shift",value:function(e,t){this.layout.body.nodes[e].y+=t}}]),t}(p);t.HorizontalStrategy=g,t.VerticalStrategy=v},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(10),s=n(o),r=i(21),a=n(r),d=i(7),h=n(d),l=i(0),u=n(l),c=i(1),f=n(c),p=i(5),v=i(22),g=i(35),y=function(){function e(t,i,n){var o=this;(0,u.default)(this,e),this.body=t,this.canvas=i,this.selectionHandler=n,this.editMode=!1,this.manipulationDiv=void 0,this.editModeDiv=void 0,this.closeDiv=void 0,this.manipulationHammers=[],this.temporaryUIFunctions={},this.temporaryEventFunctions=[],this.touchTime=0,this.temporaryIds={nodes:[],edges:[]},this.guiEnabled=!1,this.inMode=!1,this.selectedControlNode=void 0,this.options={},this.defaultOptions={enabled:!1,initiallyActive:!1,addNode:!0,addEdge:!0,editNode:void 0,editEdge:!0,deleteNode:!0,deleteEdge:!0,controlNodeStyle:{shape:"dot",size:6,color:{background:"#ff0000",border:"#3c3c3c",highlight:{background:"#07f968",border:"#3c3c3c"}},borderWidth:2,borderWidthSelected:2}},p.extend(this.options,this.defaultOptions),this.body.emitter.on("destroy",function(){o._clean()}),this.body.emitter.on("_dataChanged",this._restore.bind(this)),this.body.emitter.on("_resetData",this._restore.bind(this))}return(0,f.default)(e,[{key:"_restore",value:function(){!1!==this.inMode&&(!0===this.options.initiallyActive?this.enableEditMode():this.disableEditMode())}},{key:"setOptions",value:function(e,t,i){void 0!==t&&(void 0!==t.locale?this.options.locale=t.locale:this.options.locale=i.locale,void 0!==t.locales?this.options.locales=t.locales:this.options.locales=i.locales),void 0!==e&&("boolean"==typeof e?this.options.enabled=e:(this.options.enabled=!0,p.deepExtend(this.options,e)),!0===this.options.initiallyActive&&(this.editMode=!0),this._setup())}},{key:"toggleEditMode",value:function(){!0===this.editMode?this.disableEditMode():this.enableEditMode()}},{key:"enableEditMode",value:function(){this.editMode=!0,this._clean(),!0===this.guiEnabled&&(this.manipulationDiv.style.display="block",this.closeDiv.style.display="block",this.editModeDiv.style.display="none",this.showManipulatorToolbar())}},{key:"disableEditMode",value:function(){this.editMode=!1,this._clean(),!0===this.guiEnabled&&(this.manipulationDiv.style.display="none",this.closeDiv.style.display="none",this.editModeDiv.style.display="block",this._createEditButton())}},{key:"showManipulatorToolbar",value:function(){if(this._clean(),this.manipulationDOM={},!0===this.guiEnabled){this.editMode=!0,this.manipulationDiv.style.display="block",this.closeDiv.style.display="block";var e=this.selectionHandler._getSelectedNodeCount(),t=this.selectionHandler._getSelectedEdgeCount(),i=e+t,n=this.options.locales[this.options.locale],o=!1;!1!==this.options.addNode&&(this._createAddNodeButton(n),o=!0),!1!==this.options.addEdge&&(!0===o?this._createSeperator(1):o=!0,this._createAddEdgeButton(n)),1===e&&"function"==typeof this.options.editNode?(!0===o?this._createSeperator(2):o=!0,this._createEditNodeButton(n)):1===t&&0===e&&!1!==this.options.editEdge&&(!0===o?this._createSeperator(3):o=!0,this._createEditEdgeButton(n)),0!==i&&(e>0&&!1!==this.options.deleteNode?(!0===o&&this._createSeperator(4),this._createDeleteButton(n)):0===e&&!1!==this.options.deleteEdge&&(!0===o&&this._createSeperator(4),this._createDeleteButton(n))),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this)),this._temporaryBindEvent("select",this.showManipulatorToolbar.bind(this))}this.body.emitter.emit("_redraw")}},{key:"addNodeMode",value:function(){if(!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="addNode",!0===this.guiEnabled){var e=this.options.locales[this.options.locale];this.manipulationDOM={},this._createBackButton(e),this._createSeperator(),this._createDescription(e.addDescription||this.options.locales.en.addDescription),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this))}this._temporaryBindEvent("click",this._performAddNode.bind(this))}},{key:"editNode",value:function(){var e=this;!0!==this.editMode&&this.enableEditMode(),this._clean();var t=this.selectionHandler._getSelectedNode();if(void 0!==t){if(this.inMode="editNode","function"!=typeof this.options.editNode)throw new Error("No function has been configured to handle the editing of nodes.");if(!0!==t.isCluster){var i=p.deepExtend({},t.options,!1);if(i.x=t.x,i.y=t.y,2!==this.options.editNode.length)throw new Error("The function for edit does not support two arguments (data, callback)");this.options.editNode(i,function(t){null!==t&&void 0!==t&&"editNode"===e.inMode&&e.body.data.nodes.getDataSet().update(t),e.showManipulatorToolbar()})}else alert(this.options.locales[this.options.locale].editClusterError||this.options.locales.en.editClusterError)}else this.showManipulatorToolbar()}},{key:"addEdgeMode",value:function(){if(!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="addEdge",!0===this.guiEnabled){var e=this.options.locales[this.options.locale];this.manipulationDOM={},this._createBackButton(e),this._createSeperator(),this._createDescription(e.edgeDescription||this.options.locales.en.edgeDescription),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this))}this._temporaryBindUI("onTouch",this._handleConnect.bind(this)),this._temporaryBindUI("onDragEnd",this._finishConnect.bind(this)),this._temporaryBindUI("onDrag",this._dragControlNode.bind(this)),this._temporaryBindUI("onRelease",this._finishConnect.bind(this)),this._temporaryBindUI("onDragStart",this._dragStartEdge.bind(this)),this._temporaryBindUI("onHold",function(){})}},{key:"editEdgeMode",value:function(){if(!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="editEdge","object"===(0,h.default)(this.options.editEdge)&&"function"==typeof this.options.editEdge.editWithoutDrag&&(this.edgeBeingEditedId=this.selectionHandler.getSelectedEdges()[0],void 0!==this.edgeBeingEditedId)){var e=this.body.edges[this.edgeBeingEditedId];return void this._performEditEdge(e.from,e.to)}if(!0===this.guiEnabled){var t=this.options.locales[this.options.locale];this.manipulationDOM={},this._createBackButton(t),this._createSeperator(),this._createDescription(t.editEdgeDescription||this.options.locales.en.editEdgeDescription),this._bindHammerToDiv(this.closeDiv,this.toggleEditMode.bind(this))}if(this.edgeBeingEditedId=this.selectionHandler.getSelectedEdges()[0],void 0!==this.edgeBeingEditedId){var i=this.body.edges[this.edgeBeingEditedId],n=this._getNewTargetNode(i.from.x,i.from.y),o=this._getNewTargetNode(i.to.x,i.to.y);this.temporaryIds.nodes.push(n.id),this.temporaryIds.nodes.push(o.id),this.body.nodes[n.id]=n,this.body.nodeIndices.push(n.id),this.body.nodes[o.id]=o,this.body.nodeIndices.push(o.id),this._temporaryBindUI("onTouch",this._controlNodeTouch.bind(this)),this._temporaryBindUI("onTap",function(){}),this._temporaryBindUI("onHold",function(){}),this._temporaryBindUI("onDragStart",this._controlNodeDragStart.bind(this)),this._temporaryBindUI("onDrag",this._controlNodeDrag.bind(this)),this._temporaryBindUI("onDragEnd",this._controlNodeDragEnd.bind(this)),this._temporaryBindUI("onMouseMove",function(){}),this._temporaryBindEvent("beforeDrawing",function(e){var t=i.edgeType.findBorderPositions(e);!1===n.selected&&(n.x=t.from.x,n.y=t.from.y),!1===o.selected&&(o.x=t.to.x,o.y=t.to.y)}),this.body.emitter.emit("_redraw")}else this.showManipulatorToolbar()}},{key:"deleteSelected",value:function(){var e=this;!0!==this.editMode&&this.enableEditMode(),this._clean(),this.inMode="delete";var t=this.selectionHandler.getSelectedNodes(),i=this.selectionHandler.getSelectedEdges(),n=void 0;if(t.length>0){for(var o=0;o0&&"function"==typeof this.options.deleteEdge&&(n=this.options.deleteEdge);if("function"==typeof n){var s={nodes:t,edges:i};if(2!==n.length)throw new Error("The function for delete does not support two arguments (data, callback)");n(s,function(t){null!==t&&void 0!==t&&"delete"===e.inMode?(e.body.data.edges.getDataSet().remove(t.edges),e.body.data.nodes.getDataSet().remove(t.nodes),e.body.emitter.emit("startSimulation"),e.showManipulatorToolbar()):(e.body.emitter.emit("startSimulation"),e.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().remove(i),this.body.data.nodes.getDataSet().remove(t),this.body.emitter.emit("startSimulation"),this.showManipulatorToolbar()}},{key:"_setup",value:function(){!0===this.options.enabled?(this.guiEnabled=!0,this._createWrappers(),!1===this.editMode?this._createEditButton():this.showManipulatorToolbar()):(this._removeManipulationDOM(),this.guiEnabled=!1)}},{key:"_createWrappers",value:function(){void 0===this.manipulationDiv&&(this.manipulationDiv=document.createElement("div"),this.manipulationDiv.className="vis-manipulation",!0===this.editMode?this.manipulationDiv.style.display="block":this.manipulationDiv.style.display="none",this.canvas.frame.appendChild(this.manipulationDiv)),void 0===this.editModeDiv&&(this.editModeDiv=document.createElement("div"),this.editModeDiv.className="vis-edit-mode",!0===this.editMode?this.editModeDiv.style.display="none":this.editModeDiv.style.display="block",this.canvas.frame.appendChild(this.editModeDiv)),void 0===this.closeDiv&&(this.closeDiv=document.createElement("div"),this.closeDiv.className="vis-close",this.closeDiv.style.display=this.manipulationDiv.style.display,this.canvas.frame.appendChild(this.closeDiv))}},{key:"_getNewTargetNode",value:function(e,t){var i=p.deepExtend({},this.options.controlNodeStyle);i.id="targetNode"+p.randomUUID(),i.hidden=!1,i.physics=!1,i.x=e,i.y=t;var n=this.body.functions.createNode(i);return n.shape.boundingBox={left:e,right:e,top:t,bottom:t},n}},{key:"_createEditButton",value:function(){this._clean(),this.manipulationDOM={},p.recursiveDOMDelete(this.editModeDiv);var e=this.options.locales[this.options.locale],t=this._createButton("editMode","vis-button vis-edit vis-edit-mode",e.edit||this.options.locales.en.edit);this.editModeDiv.appendChild(t),this._bindHammerToDiv(t,this.toggleEditMode.bind(this))}},{key:"_clean",value:function(){this.inMode=!1,!0===this.guiEnabled&&(p.recursiveDOMDelete(this.editModeDiv),p.recursiveDOMDelete(this.manipulationDiv),this._cleanManipulatorHammers()),this._cleanupTemporaryNodesAndEdges(),this._unbindTemporaryUIs(),this._unbindTemporaryEvents(),this.body.emitter.emit("restorePhysics")}},{key:"_cleanManipulatorHammers",value:function(){if(0!=this.manipulationHammers.length){for(var e=0;e0&&void 0!==arguments[0]?arguments[0]:1;this.manipulationDOM["seperatorLineDiv"+e]=document.createElement("div"),this.manipulationDOM["seperatorLineDiv"+e].className="vis-separator-line",this.manipulationDiv.appendChild(this.manipulationDOM["seperatorLineDiv"+e])}},{key:"_createAddNodeButton",value:function(e){var t=this._createButton("addNode","vis-button vis-add",e.addNode||this.options.locales.en.addNode);this.manipulationDiv.appendChild(t),this._bindHammerToDiv(t,this.addNodeMode.bind(this))}},{key:"_createAddEdgeButton",value:function(e){var t=this._createButton("addEdge","vis-button vis-connect",e.addEdge||this.options.locales.en.addEdge);this.manipulationDiv.appendChild(t),this._bindHammerToDiv(t,this.addEdgeMode.bind(this))}},{key:"_createEditNodeButton",value:function(e){var t=this._createButton("editNode","vis-button vis-edit",e.editNode||this.options.locales.en.editNode);this.manipulationDiv.appendChild(t),this._bindHammerToDiv(t,this.editNode.bind(this))}},{key:"_createEditEdgeButton",value:function(e){var t=this._createButton("editEdge","vis-button vis-edit",e.editEdge||this.options.locales.en.editEdge);this.manipulationDiv.appendChild(t),this._bindHammerToDiv(t,this.editEdgeMode.bind(this))}},{key:"_createDeleteButton",value:function(e){var t;t=this.options.rtl?"vis-button vis-delete-rtl":"vis-button vis-delete";var i=this._createButton("delete",t,e.del||this.options.locales.en.del);this.manipulationDiv.appendChild(i),this._bindHammerToDiv(i,this.deleteSelected.bind(this))}},{key:"_createBackButton",value:function(e){var t=this._createButton("back","vis-button vis-back",e.back||this.options.locales.en.back);this.manipulationDiv.appendChild(t),this._bindHammerToDiv(t,this.showManipulatorToolbar.bind(this))}},{key:"_createButton",value:function(e,t,i){var n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"vis-label";return this.manipulationDOM[e+"Div"]=document.createElement("div"),this.manipulationDOM[e+"Div"].className=t,this.manipulationDOM[e+"Label"]=document.createElement("div"),this.manipulationDOM[e+"Label"].className=n,this.manipulationDOM[e+"Label"].innerHTML=i,this.manipulationDOM[e+"Div"].appendChild(this.manipulationDOM[e+"Label"]),this.manipulationDOM[e+"Div"]}},{key:"_createDescription",value:function(e){this.manipulationDiv.appendChild(this._createButton("description","vis-button vis-none",e))}},{key:"_temporaryBindEvent",value:function(e,t){this.temporaryEventFunctions.push({event:e,boundFunction:t}),this.body.emitter.on(e,t)}},{key:"_temporaryBindUI",value:function(e,t){if(void 0===this.body.eventListeners[e])throw new Error("This UI function does not exist. Typo? You tried: "+e+" possible are: "+(0,a.default)((0,s.default)(this.body.eventListeners)));this.temporaryUIFunctions[e]=this.body.eventListeners[e],this.body.eventListeners[e]=t}},{key:"_unbindTemporaryUIs",value:function(){for(var e in this.temporaryUIFunctions)this.temporaryUIFunctions.hasOwnProperty(e)&&(this.body.eventListeners[e]=this.temporaryUIFunctions[e],delete this.temporaryUIFunctions[e]);this.temporaryUIFunctions={}}},{key:"_unbindTemporaryEvents",value:function(){for(var e=0;e=0;r--)if(o[r]!==this.selectedControlNode.id){s=this.body.nodes[o[r]];break}if(void 0!==s&&void 0!==this.selectedControlNode)if(!0===s.isCluster)alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError);else{var a=this.body.nodes[this.temporaryIds.nodes[0]];this.selectedControlNode.id===a.id?this._performEditEdge(s.id,n.to.id):this._performEditEdge(n.from.id,s.id)}else n.updateEdgeType(),this.body.emitter.emit("restorePhysics");this.body.emitter.emit("_redraw")}}},{key:"_handleConnect",value:function(e){if((new Date).valueOf()-this.touchTime>100){this.lastTouch=this.body.functions.getPointer(e.center),this.lastTouch.translation=p.extend({},this.body.view.translation);var t=this.lastTouch,i=this.selectionHandler.getNodeAt(t);if(void 0!==i)if(!0===i.isCluster)alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError);else{var n=this._getNewTargetNode(i.x,i.y);this.body.nodes[n.id]=n,this.body.nodeIndices.push(n.id);var o=this.body.functions.createEdge({id:"connectionEdge"+p.randomUUID(),from:i.id,to:n.id,physics:!1,smooth:{enabled:!0,type:"continuous",roundness:.5}});this.body.edges[o.id]=o,this.body.edgeIndices.push(o.id),this.temporaryIds.nodes.push(n.id),this.temporaryIds.edges.push(o.id)}this.touchTime=(new Date).valueOf()}}},{key:"_dragControlNode",value:function(e){var t=this.body.functions.getPointer(e.center);if(void 0!==this.temporaryIds.nodes[0]){var i=this.body.nodes[this.temporaryIds.nodes[0]];i.x=this.canvas._XconvertDOMtoCanvas(t.x),i.y=this.canvas._YconvertDOMtoCanvas(t.y),this.body.emitter.emit("_redraw")}else{var n=t.x-this.lastTouch.x,o=t.y-this.lastTouch.y;this.body.view.translation={x:this.lastTouch.translation.x+n,y:this.lastTouch.translation.y+o}}}},{key:"_finishConnect",value:function(e){var t=this.body.functions.getPointer(e.center),i=this.selectionHandler._pointerToPositionObject(t),n=void 0;void 0!==this.temporaryIds.edges[0]&&(n=this.body.edges[this.temporaryIds.edges[0]].fromId);for(var o=this.selectionHandler._getAllNodesOverlappingWith(i),s=void 0,r=o.length-1;r>=0;r--)if(-1===this.temporaryIds.nodes.indexOf(o[r])){s=this.body.nodes[o[r]];break}this._cleanupTemporaryNodesAndEdges(),void 0!==s&&(!0===s.isCluster?alert(this.options.locales[this.options.locale].createEdgeError||this.options.locales.en.createEdgeError):void 0!==this.body.nodes[n]&&void 0!==this.body.nodes[s.id]&&this._performAddEdge(n,s.id)),this.body.emitter.emit("_redraw")}},{key:"_dragStartEdge",value:function(e){var t=this.lastTouch;this.selectionHandler._generateClickEvent("dragStart",e,t,void 0,!0)}},{key:"_performAddNode",value:function(e){var t=this,i={id:p.randomUUID(),x:e.pointer.canvas.x,y:e.pointer.canvas.y,label:"new"};if("function"==typeof this.options.addNode){if(2!==this.options.addNode.length)throw this.showManipulatorToolbar(),new Error("The function for add does not support two arguments (data,callback)");this.options.addNode(i,function(e){null!==e&&void 0!==e&&"addNode"===t.inMode&&(t.body.data.nodes.getDataSet().add(e),t.showManipulatorToolbar())})}else this.body.data.nodes.getDataSet().add(i),this.showManipulatorToolbar()}},{key:"_performAddEdge",value:function(e,t){var i=this,n={from:e,to:t};if("function"==typeof this.options.addEdge){if(2!==this.options.addEdge.length)throw new Error("The function for connect does not support two arguments (data,callback)");this.options.addEdge(n,function(e){null!==e&&void 0!==e&&"addEdge"===i.inMode&&(i.body.data.edges.getDataSet().add(e),i.selectionHandler.unselectAll(),i.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().add(n),this.selectionHandler.unselectAll(),this.showManipulatorToolbar()}},{key:"_performEditEdge",value:function(e,t){var i=this,n={id:this.edgeBeingEditedId,from:e,to:t,label:this.body.data.edges._data[this.edgeBeingEditedId].label},o=this.options.editEdge;if("object"===(void 0===o?"undefined":(0,h.default)(o))&&(o=o.editWithoutDrag),"function"==typeof o){if(2!==o.length)throw new Error("The function for edit does not support two arguments (data, callback)");o(n,function(e){null===e||void 0===e||"editEdge"!==i.inMode?(i.body.edges[n.id].updateEdgeType(),i.body.emitter.emit("_redraw"),i.showManipulatorToolbar()):(i.body.data.edges.getDataSet().update(e),i.selectionHandler.unselectAll(),i.showManipulatorToolbar())})}else this.body.data.edges.getDataSet().update(n),this.selectionHandler.unselectAll(),this.showManipulatorToolbar()}}]),e}();t.default=y},function(e,t,i){function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=i(21),s=n(o),r=i(7),a=n(r),d=i(0),h=n(d),l=i(1),u=n(l),c=i(5),f=i(189).default,p=function(){function e(t,i,n){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:1;(0,h.default)(this,e),this.parent=t,this.changedOptions=[],this.container=i,this.allowCreation=!1,this.options={},this.initialized=!1,this.popupCounter=0,this.defaultOptions={enabled:!1,filter:!0,container:void 0,showButton:!0},c.extend(this.options,this.defaultOptions),this.configureOptions=n,this.moduleOptions={},this.domElements=[],this.popupDiv={},this.popupLimit=5,this.popupHistory={},this.colorPicker=new f(o),this.wrapper=void 0}return(0,u.default)(e,[{key:"setOptions",value:function(e){if(void 0!==e){this.popupHistory={},this._removePopup();var t=!0;"string"==typeof e?this.options.filter=e:e instanceof Array?this.options.filter=e.join():"object"===(void 0===e?"undefined":(0,a.default)(e))?(void 0!==e.container&&(this.options.container=e.container),void 0!==e.filter&&(this.options.filter=e.filter),void 0!==e.showButton&&(this.options.showButton=e.showButton),void 0!==e.enabled&&(t=e.enabled)):"boolean"==typeof e?(this.options.filter=!0,t=e):"function"==typeof e&&(this.options.filter=e,t=!0),!1===this.options.filter&&(t=!1),this.options.enabled=t}this._clean()}},{key:"setModuleOptions",value:function(e){this.moduleOptions=e,!0===this.options.enabled&&(this._clean(),void 0!==this.options.container&&(this.container=this.options.container),this._create())}},{key:"_create",value:function(){var e=this;this._clean(),this.changedOptions=[];var t=this.options.filter,i=0,n=!1;for(var o in this.configureOptions)this.configureOptions.hasOwnProperty(o)&&(this.allowCreation=!1,n=!1,"function"==typeof t?(n=t(o,[]),n=n||this._handleObject(this.configureOptions[o],[o],!0)):!0!==t&&-1===t.indexOf(o)||(n=!0),!1!==n&&(this.allowCreation=!0,i>0&&this._makeItem([]),this._makeHeader(o),this._handleObject(this.configureOptions[o],[o])),i++);if(!0===this.options.showButton){var s=document.createElement("div");s.className="vis-configuration vis-config-button",s.innerHTML="generate options",s.onclick=function(){e._printOptions()},s.onmouseover=function(){s.className="vis-configuration vis-config-button hover"},s.onmouseout=function(){s.className="vis-configuration vis-config-button"},this.optionsContainer=document.createElement("div"),this.optionsContainer.className="vis-configuration vis-config-option-container",this.domElements.push(this.optionsContainer),this.domElements.push(s)}this._push()}},{key:"_push",value:function(){this.wrapper=document.createElement("div"),this.wrapper.className="vis-configuration-wrapper",this.container.appendChild(this.wrapper);for(var e=0;e1?i-1:0),o=1;o2&&void 0!==arguments[2]&&arguments[2],n=document.createElement("div");return n.className="vis-configuration vis-config-label vis-config-s"+t.length,n.innerHTML=!0===i?""+e+":":e+":",n}},{key:"_makeDropdown",value:function(e,t,i){var n=document.createElement("select");n.className="vis-configuration vis-config-select";var o=0;void 0!==t&&-1!==e.indexOf(t)&&(o=e.indexOf(t));for(var s=0;ss&&1!==s&&(a.max=Math.ceil(1.2*t),h=a.max,d="range increased"),a.value=t}else a.value=n;var l=document.createElement("input");l.className="vis-configuration vis-config-rangeinput",l.value=a.value;var u=this;a.onchange=function(){l.value=this.value,u._update(Number(this.value),i)},a.oninput=function(){l.value=this.value};var c=this._makeLabel(i[i.length-1],i),f=this._makeItem(i,c,a,l);""!==d&&this.popupHistory[f]!==h&&(this.popupHistory[f]=h,this._setupPopup(d,f))}},{key:"_setupPopup",value:function(e,t){var i=this;if(!0===this.initialized&&!0===this.allowCreation&&this.popupCounter1&&void 0!==arguments[1]?arguments[1]:[],i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=!1,o=this.options.filter,s=!1;for(var r in e)if(e.hasOwnProperty(r)){n=!0;var a=e[r],d=c.copyAndExtendArray(t,r);if("function"==typeof o&&!1===(n=o(r,t))&&!(a instanceof Array)&&"string"!=typeof a&&"boolean"!=typeof a&&a instanceof Object&&(this.allowCreation=!1,n=this._handleObject(a,d,!0),this.allowCreation=!1===i),!1!==n){s=!0;var h=this._getValue(d);if(a instanceof Array)this._handleArray(a,h,d);else if("string"==typeof a)this._makeTextInput(a,h,d);else if("boolean"==typeof a)this._makeCheckbox(a,h,d);else if(a instanceof Object){var l=!0;if(-1!==t.indexOf("physics")&&this.moduleOptions.physics.solver!==r&&(l=!1),!0===l)if(void 0!==a.enabled){var u=c.copyAndExtendArray(d,"enabled"),f=this._getValue(u);if(!0===f){var p=this._makeLabel(r,d,!0);this._makeItem(d,p),s=this._handleObject(a,d)||s}else this._makeCheckbox(a,f,d)}else{var v=this._makeLabel(r,d,!0);this._makeItem(d,v),s=this._handleObject(a,d)||s}}else console.error("dont know how to handle",a,r,d)}}return s}},{key:"_handleArray",value:function(e,t,i){"string"==typeof e[0]&&"color"===e[0]?(this._makeColorField(e,t,i),e[1]!==t&&this.changedOptions.push({path:i,value:t})):"string"==typeof e[0]?(this._makeDropdown(e,t,i),e[0]!==t&&this.changedOptions.push({path:i,value:t})):"number"==typeof e[0]&&(this._makeRange(e,t,i),e[0]!==t&&this.changedOptions.push({path:i,value:Number(t)}))}},{key:"_update",value:function(e,t){var i=this._constructOptions(e,t);this.parent.body&&this.parent.body.emitter&&this.parent.body.emitter.emit&&this.parent.body.emitter.emit("configChange",i),this.initialized=!0,this.parent.setOptions(i)}},{key:"_constructOptions",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=i;e="true"===e||e,e="false"!==e&&e;for(var o=0;ovar options = "+(0,s.default)(e,null,2)+""}},{key:"getOptions",value:function(){for(var e={},t=0;t0&&void 0!==arguments[0]?arguments[0]:1;(0,a.default)(this,e),this.pixelRatio=t,this.generated=!1,this.centerCoordinates={x:144.5,y:144.5},this.r=289*.49,this.color={r:255,g:255,b:255,a:1},this.hueCircle=void 0,this.initialColor={r:255,g:255,b:255,a:1},this.previousColor=void 0,this.applied=!1,this.updateCallback=function(){},this.closeCallback=function(){},this._create()}return(0,h.default)(e,[{key:"insertTo",value:function(e){void 0!==this.hammer&&(this.hammer.destroy(),this.hammer=void 0),this.container=e,this.container.appendChild(this.frame),this._bindHammer(),this._setSize()}},{key:"setUpdateCallback",value:function(e){if("function"!=typeof e)throw new Error("Function attempted to set as colorPicker update callback is not a function.");this.updateCallback=e}},{key:"setCloseCallback",value:function(e){if("function"!=typeof e)throw new Error("Function attempted to set as colorPicker closing callback is not a function.");this.closeCallback=e}},{key:"_isColorString",value:function(e){var t={black:"#000000",navy:"#000080",darkblue:"#00008B",mediumblue:"#0000CD",blue:"#0000FF",darkgreen:"#006400",green:"#008000",teal:"#008080",darkcyan:"#008B8B",deepskyblue:"#00BFFF",darkturquoise:"#00CED1",mediumspringgreen:"#00FA9A",lime:"#00FF00",springgreen:"#00FF7F",aqua:"#00FFFF",cyan:"#00FFFF",midnightblue:"#191970",dodgerblue:"#1E90FF",lightseagreen:"#20B2AA",forestgreen:"#228B22",seagreen:"#2E8B57",darkslategray:"#2F4F4F",limegreen:"#32CD32",mediumseagreen:"#3CB371",turquoise:"#40E0D0",royalblue:"#4169E1",steelblue:"#4682B4",darkslateblue:"#483D8B",mediumturquoise:"#48D1CC",indigo:"#4B0082",darkolivegreen:"#556B2F",cadetblue:"#5F9EA0",cornflowerblue:"#6495ED",mediumaquamarine:"#66CDAA",dimgray:"#696969",slateblue:"#6A5ACD",olivedrab:"#6B8E23",slategray:"#708090",lightslategray:"#778899",mediumslateblue:"#7B68EE",lawngreen:"#7CFC00",chartreuse:"#7FFF00",aquamarine:"#7FFFD4",maroon:"#800000",purple:"#800080",olive:"#808000",gray:"#808080",skyblue:"#87CEEB",lightskyblue:"#87CEFA",blueviolet:"#8A2BE2",darkred:"#8B0000",darkmagenta:"#8B008B",saddlebrown:"#8B4513",darkseagreen:"#8FBC8F",lightgreen:"#90EE90",mediumpurple:"#9370D8",darkviolet:"#9400D3",palegreen:"#98FB98",darkorchid:"#9932CC",yellowgreen:"#9ACD32",sienna:"#A0522D",brown:"#A52A2A",darkgray:"#A9A9A9",lightblue:"#ADD8E6",greenyellow:"#ADFF2F",paleturquoise:"#AFEEEE",lightsteelblue:"#B0C4DE",powderblue:"#B0E0E6",firebrick:"#B22222",darkgoldenrod:"#B8860B",mediumorchid:"#BA55D3",rosybrown:"#BC8F8F",darkkhaki:"#BDB76B",silver:"#C0C0C0",mediumvioletred:"#C71585",indianred:"#CD5C5C",peru:"#CD853F",chocolate:"#D2691E",tan:"#D2B48C",lightgrey:"#D3D3D3",palevioletred:"#D87093",thistle:"#D8BFD8",orchid:"#DA70D6",goldenrod:"#DAA520",crimson:"#DC143C",gainsboro:"#DCDCDC",plum:"#DDA0DD",burlywood:"#DEB887",lightcyan:"#E0FFFF",lavender:"#E6E6FA",darksalmon:"#E9967A",violet:"#EE82EE",palegoldenrod:"#EEE8AA",lightcoral:"#F08080",khaki:"#F0E68C",aliceblue:"#F0F8FF",honeydew:"#F0FFF0",azure:"#F0FFFF",sandybrown:"#F4A460",wheat:"#F5DEB3",beige:"#F5F5DC",whitesmoke:"#F5F5F5",mintcream:"#F5FFFA",ghostwhite:"#F8F8FF",salmon:"#FA8072",antiquewhite:"#FAEBD7",linen:"#FAF0E6",lightgoldenrodyellow:"#FAFAD2",oldlace:"#FDF5E6",red:"#FF0000",fuchsia:"#FF00FF",magenta:"#FF00FF",deeppink:"#FF1493",orangered:"#FF4500",tomato:"#FF6347",hotpink:"#FF69B4",coral:"#FF7F50",darkorange:"#FF8C00",lightsalmon:"#FFA07A",orange:"#FFA500",lightpink:"#FFB6C1",pink:"#FFC0CB",gold:"#FFD700",peachpuff:"#FFDAB9",navajowhite:"#FFDEAD",moccasin:"#FFE4B5",bisque:"#FFE4C4",mistyrose:"#FFE4E1",blanchedalmond:"#FFEBCD",papayawhip:"#FFEFD5",lavenderblush:"#FFF0F5",seashell:"#FFF5EE",cornsilk:"#FFF8DC",lemonchiffon:"#FFFACD",floralwhite:"#FFFAF0",snow:"#FFFAFA",yellow:"#FFFF00",lightyellow:"#FFFFE0",ivory:"#FFFFF0",white:"#FFFFFF"};if("string"==typeof e)return t[e]}},{key:"setColor",value:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if("none"!==e){var i=void 0,n=this._isColorString(e);if(void 0!==n&&(e=n),!0===c.isString(e)){if(!0===c.isValidRGB(e)){var o=e.substr(4).substr(0,e.length-5).split(",");i={r:o[0],g:o[1],b:o[2],a:1}}else if(!0===c.isValidRGBA(e)){var r=e.substr(5).substr(0,e.length-6).split(",");i={r:r[0],g:r[1],b:r[2],a:r[3]}}else if(!0===c.isValidHex(e)){var a=c.hexToRGB(e);i={r:a.r,g:a.g,b:a.b,a:1}}}else if(e instanceof Object&&void 0!==e.r&&void 0!==e.g&&void 0!==e.b){var d=void 0!==e.a?e.a:"1.0";i={r:e.r,g:e.g,b:e.b,a:d}}if(void 0===i)throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: "+(0,s.default)(e));this._setColor(i,t)}}},{key:"show",value:function(){void 0!==this.closeCallback&&(this.closeCallback(),this.closeCallback=void 0),this.applied=!1,this.frame.style.display="block",this._generateHueCircle()}},{key:"_hide",value:function(){var e=this;!0===(!(arguments.length>0&&void 0!==arguments[0])||arguments[0])&&(this.previousColor=c.extend({},this.color)),!0===this.applied&&this.updateCallback(this.initialColor),this.frame.style.display="none",setTimeout(function(){void 0!==e.closeCallback&&(e.closeCallback(),e.closeCallback=void 0)},0)}},{key:"_save",value:function(){this.updateCallback(this.color),this.applied=!1,this._hide()}},{key:"_apply",value:function(){this.applied=!0,this.updateCallback(this.color),this._updatePicker(this.color)}},{key:"_loadLast",value:function(){void 0!==this.previousColor?this.setColor(this.previousColor,!1):alert("There is no last color to load...")}},{key:"_setColor",value:function(e){!0===(!(arguments.length>1&&void 0!==arguments[1])||arguments[1])&&(this.initialColor=c.extend({},e)),this.color=e;var t=c.RGBToHSV(e.r,e.g,e.b),i=2*Math.PI,n=this.r*t.s,o=this.centerCoordinates.x+n*Math.sin(i*t.h),s=this.centerCoordinates.y+n*Math.cos(i*t.h);this.colorPickerSelector.style.left=o-.5*this.colorPickerSelector.clientWidth+"px",this.colorPickerSelector.style.top=s-.5*this.colorPickerSelector.clientHeight+"px",this._updatePicker(e)}},{key:"_setOpacity",value:function(e){this.color.a=e/100,this._updatePicker(this.color)}},{key:"_setBrightness",value:function(e){var t=c.RGBToHSV(this.color.r,this.color.g,this.color.b);t.v=e/100;var i=c.HSVToRGB(t.h,t.s,t.v);i.a=this.color.a,this.color=i,this._updatePicker()}},{key:"_updatePicker",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.color,t=c.RGBToHSV(e.r,e.g,e.b),i=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1)),i.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var n=this.colorPickerCanvas.clientWidth,o=this.colorPickerCanvas.clientHeight;i.clearRect(0,0,n,o),i.putImageData(this.hueCircle,0,0),i.fillStyle="rgba(0,0,0,"+(1-t.v)+")",i.circle(this.centerCoordinates.x,this.centerCoordinates.y,this.r),i.fill(),this.brightnessRange.value=100*t.v,this.opacityRange.value=100*e.a,this.initialColorDiv.style.backgroundColor="rgba("+this.initialColor.r+","+this.initialColor.g+","+this.initialColor.b+","+this.initialColor.a+")",this.newColorDiv.style.backgroundColor="rgba("+this.color.r+","+this.color.g+","+this.color.b+","+this.color.a+")"}},{key:"_setSize",value:function(){this.colorPickerCanvas.style.width="100%",this.colorPickerCanvas.style.height="100%",this.colorPickerCanvas.width=289*this.pixelRatio,this.colorPickerCanvas.height=289*this.pixelRatio}},{key:"_create",value:function(){if(this.frame=document.createElement("div"),this.frame.className="vis-color-picker",this.colorPickerDiv=document.createElement("div"),this.colorPickerSelector=document.createElement("div"),this.colorPickerSelector.className="vis-selector",this.colorPickerDiv.appendChild(this.colorPickerSelector),this.colorPickerCanvas=document.createElement("canvas"),this.colorPickerDiv.appendChild(this.colorPickerCanvas),this.colorPickerCanvas.getContext){var e=this.colorPickerCanvas.getContext("2d");this.pixelRatio=(window.devicePixelRatio||1)/(e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1),this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}else{var t=document.createElement("DIV");t.style.color="red",t.style.fontWeight="bold",t.style.padding="10px",t.innerHTML="Error: your browser does not support HTML canvas",this.colorPickerCanvas.appendChild(t)}this.colorPickerDiv.className="vis-color",this.opacityDiv=document.createElement("div"),this.opacityDiv.className="vis-opacity",this.brightnessDiv=document.createElement("div"),this.brightnessDiv.className="vis-brightness",this.arrowDiv=document.createElement("div"),this.arrowDiv.className="vis-arrow",this.opacityRange=document.createElement("input");try{this.opacityRange.type="range",this.opacityRange.min="0",this.opacityRange.max="100"}catch(e){}this.opacityRange.value="100",this.opacityRange.className="vis-range",this.brightnessRange=document.createElement("input");try{this.brightnessRange.type="range",this.brightnessRange.min="0",this.brightnessRange.max="100"}catch(e){}this.brightnessRange.value="100",this.brightnessRange.className="vis-range",this.opacityDiv.appendChild(this.opacityRange),this.brightnessDiv.appendChild(this.brightnessRange);var i=this;this.opacityRange.onchange=function(){i._setOpacity(this.value)},this.opacityRange.oninput=function(){i._setOpacity(this.value)},this.brightnessRange.onchange=function(){i._setBrightness(this.value)},this.brightnessRange.oninput=function(){i._setBrightness(this.value)},this.brightnessLabel=document.createElement("div"),this.brightnessLabel.className="vis-label vis-brightness",this.brightnessLabel.innerHTML="brightness:",this.opacityLabel=document.createElement("div"),this.opacityLabel.className="vis-label vis-opacity",this.opacityLabel.innerHTML="opacity:",this.newColorDiv=document.createElement("div"),this.newColorDiv.className="vis-new-color",this.newColorDiv.innerHTML="new",this.initialColorDiv=document.createElement("div"),this.initialColorDiv.className="vis-initial-color",this.initialColorDiv.innerHTML="initial",this.cancelButton=document.createElement("div"),this.cancelButton.className="vis-button vis-cancel",this.cancelButton.innerHTML="cancel",this.cancelButton.onclick=this._hide.bind(this,!1),this.applyButton=document.createElement("div"),this.applyButton.className="vis-button vis-apply",this.applyButton.innerHTML="apply",this.applyButton.onclick=this._apply.bind(this),this.saveButton=document.createElement("div"),this.saveButton.className="vis-button vis-save",this.saveButton.innerHTML="save",this.saveButton.onclick=this._save.bind(this),this.loadButton=document.createElement("div"),this.loadButton.className="vis-button vis-load",this.loadButton.innerHTML="load last",this.loadButton.onclick=this._loadLast.bind(this),this.frame.appendChild(this.colorPickerDiv),this.frame.appendChild(this.arrowDiv),this.frame.appendChild(this.brightnessLabel),this.frame.appendChild(this.brightnessDiv),this.frame.appendChild(this.opacityLabel),this.frame.appendChild(this.opacityDiv),this.frame.appendChild(this.newColorDiv),this.frame.appendChild(this.initialColorDiv),this.frame.appendChild(this.cancelButton),this.frame.appendChild(this.applyButton),this.frame.appendChild(this.saveButton),this.frame.appendChild(this.loadButton)}},{key:"_bindHammer",value:function(){var e=this;this.drag={},this.pinch={},this.hammer=new l(this.colorPickerCanvas),this.hammer.get("pinch").set({enable:!0}),u.onTouch(this.hammer,function(t){e._moveSelector(t)}),this.hammer.on("tap",function(t){e._moveSelector(t)}),this.hammer.on("panstart",function(t){e._moveSelector(t)}),this.hammer.on("panmove",function(t){e._moveSelector(t)}),this.hammer.on("panend",function(t){e._moveSelector(t)})}},{key:"_generateHueCircle",value:function(){if(!1===this.generated){var e=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(e.webkitBackingStorePixelRatio||e.mozBackingStorePixelRatio||e.msBackingStorePixelRatio||e.oBackingStorePixelRatio||e.backingStorePixelRatio||1)),e.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var t=this.colorPickerCanvas.clientWidth,i=this.colorPickerCanvas.clientHeight;e.clearRect(0,0,t,i);var n=void 0,o=void 0,s=void 0,r=void 0;this.centerCoordinates={x:.5*t,y:.5*i},this.r=.49*t;var a=2*Math.PI/360,d=1/this.r,h=void 0;for(s=0;s<360;s++)for(r=0;r2&&void 0!==arguments[2]&&arguments[2],n=this.distanceSolver.getDistances(this.body,e,t);this._createL_matrix(n),this._createK_matrix(n),this._createE_matrix();for(var o=0,r=Math.max(1e3,Math.min(10*this.body.nodeIndices.length,6e3)),a=1e9,d=0,h=0,l=0,u=0,c=0;a>.01&&o1&&c<5;){c+=1,this._moveNode(d,h,l);var v=this._getEnergy(d),g=(0,s.default)(v,3);u=g[0],h=g[1],l=g[2]}}}},{key:"_getHighestEnergyNode",value:function(e){for(var t=this.body.nodeIndices,i=this.body.nodes,n=0,o=t[0],r=0,a=0,d=0;d0?Y:U)(t)},G=Math.min,K=function(t){return t>0?G(X(t),9007199254740991):0},Q=Math.max,$=Math.min,Z=function(t,e){var i=X(t);return i<0?Q(i+e,0):$(i,e)},J=function(t){return function(e,i,n){var o,r=y(e),s=K(r.length),a=Z(n,s);if(t&&i!=i){for(;s>a;)if((o=r[a++])!=o)return!0}else for(;s>a;a++)if((t||a in r)&&r[a]===i)return t||a||0;return!t&&-1}},tt={includes:J(!0),indexOf:J(!1)},et={},it=tt.indexOf,nt=function(t,e){var i,n=y(t),o=0,r=[];for(i in n)!k(et,i)&&k(n,i)&&r.push(i);for(;e.length>o;)k(n,i=e[o++])&&(~it(r,i)||r.push(i));return r},ot=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],rt=Object.keys||function(t){return nt(t,ot)},st={f:Object.getOwnPropertySymbols},at=function(t){return Object(g(t))},ht=Object.assign,lt=Object.defineProperty,dt=!ht||s((function(){if(a&&1!==ht({b:1},ht(lt({},"a",{enumerable:!0,get:function(){lt(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var t={},e={},i=Symbol(),n="abcdefghijklmnopqrst";return t[i]=7,n.split("").forEach((function(t){e[t]=t})),7!=ht({},t)[i]||rt(ht({},e)).join("")!=n}))?function(t,e){for(var i=at(t),n=arguments.length,o=1,r=st.f,s=d.f;n>o;)for(var h,l=v(arguments[o++]),c=r?rt(l).concat(r(l)):rt(l),u=c.length,f=0;u>f;)h=c[f++],a&&!s.call(l,h)||(i[h]=l[h]);return i}:ht;V({target:"Object",stat:!0,forced:Object.assign!==dt},{assign:dt});var ct=N.Object.assign,ut=[].slice,ft={},pt=function(t,e,i){if(!(e in ft)){for(var n=[],o=0;o=.1;)(p=+r[c++%s])>d&&(p=d),f=Math.sqrt(p*p/(1+l*l)),e+=f=a<0?-f:f,i+=l*f,!0===u?t.lineTo(e,i):t.moveTo(e,i),d-=p,u=!u}var Ot={circle:wt,dashedLine:Et,database:xt,diamond:function(t,e,i,n){t.beginPath(),t.lineTo(e,i+n),t.lineTo(e+n,i),t.lineTo(e,i-n),t.lineTo(e-n,i),t.closePath()},ellipse:_t,ellipse_vis:_t,hexagon:function(t,e,i,n){t.beginPath();var o=2*Math.PI/6;t.moveTo(e+n,i);for(var r=1;r<6;r++)t.lineTo(e+n*Math.cos(o*r),i+n*Math.sin(o*r));t.closePath()},roundRect:kt,square:function(t,e,i,n){t.beginPath(),t.rect(e-n,i-n,2*n,2*n),t.closePath()},star:function(t,e,i,n){t.beginPath(),i+=.1*(n*=.82);for(var o=0;o<10;o++){var r=o%2==0?1.3*n:.5*n;t.lineTo(e+r*Math.sin(2*o*Math.PI/10),i-r*Math.cos(2*o*Math.PI/10))}t.closePath()},triangle:function(t,e,i,n){t.beginPath(),i+=.275*(n*=1.15);var o=2*n,r=o/2,s=Math.sqrt(3)/6*o,a=Math.sqrt(o*o-r*r);t.moveTo(e,i-(a-s)),t.lineTo(e+r,i+s),t.lineTo(e-r,i+s),t.lineTo(e,i-(a-s)),t.closePath()},triangleDown:function(t,e,i,n){t.beginPath(),i-=.275*(n*=1.15);var o=2*n,r=o/2,s=Math.sqrt(3)/6*o,a=Math.sqrt(o*o-r*r);t.moveTo(e,i+(a-s)),t.lineTo(e+r,i-s),t.lineTo(e-r,i-s),t.lineTo(e,i+(a-s)),t.closePath()}};var Ct=n((function(t){function e(t){if(t)return function(t){for(var i in e.prototype)t[i]=e.prototype[i];return t}(t)}t.exports=e,e.prototype.on=e.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},e.prototype.once=function(t,e){function i(){this.off(t,i),e.apply(this,arguments)}return i.fn=e,this.on(t,i),this},e.prototype.off=e.prototype.removeListener=e.prototype.removeAllListeners=e.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var i,n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var o=0;o=a?t?"":void 0:(n=r.charCodeAt(s))<55296||n>56319||s+1===a||(o=r.charCodeAt(s+1))<56320||o>57343?t?r.charAt(s):n:t?r.slice(s,s+2):o-56320+(n-55296<<10)+65536}},Tt={codeAt:St(!1),charAt:St(!0)},Mt="__core-js_shared__",Pt=r[Mt]||function(t,e){try{H(r,t,e)}catch(i){r[t]=e}return e}(Mt,{}),Dt=Function.toString;"function"!=typeof Pt.inspectSource&&(Pt.inspectSource=function(t){return Dt.call(t)});var It,Bt,zt,Nt=Pt.inspectSource,At=r.WeakMap,Ft="function"==typeof At&&/native code/.test(Nt(At)),jt=n((function(t){(t.exports=function(t,e){return Pt[t]||(Pt[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.9.1",mode:"pure",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})})),Rt=0,Lt=Math.random(),Ht=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++Rt+Lt).toString(36)},Wt=jt("keys"),qt=function(t){return Wt[t]||(Wt[t]=Ht(t))},Vt=r.WeakMap;if(Ft){var Ut=Pt.state||(Pt.state=new Vt),Yt=Ut.get,Xt=Ut.has,Gt=Ut.set;It=function(t,e){return e.facade=t,Gt.call(Ut,t,e),e},Bt=function(t){return Yt.call(Ut,t)||{}},zt=function(t){return Xt.call(Ut,t)}}else{var Kt=qt("state");et[Kt]=!0,It=function(t,e){return e.facade=t,H(t,Kt,e),e},Bt=function(t){return k(t,Kt)?t[Kt]:{}},zt=function(t){return k(t,Kt)}}var Qt,$t,Zt={set:It,get:Bt,has:zt,enforce:function(t){return zt(t)?Bt(t):It(t,{})},getterFor:function(t){return function(e){var i;if(!m(e)||(i=Bt(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return i}}},Jt=!s((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype})),te=qt("IE_PROTO"),ee=Object.prototype,ie=Jt?Object.getPrototypeOf:function(t){return t=at(t),k(t,te)?t[te]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?ee:null},ne="process"==f(r.process),oe=function(t){return"function"==typeof t?t:void 0},re=function(t,e){return arguments.length<2?oe(N[t])||oe(r[t]):N[t]&&N[t][e]||r[t]&&r[t][e]},se=re("navigator","userAgent")||"",ae=r.process,he=ae&&ae.versions,le=he&&he.v8;le?$t=(Qt=le.split("."))[0]+Qt[1]:se&&(!(Qt=se.match(/Edge\/(\d+)/))||Qt[1]>=74)&&(Qt=se.match(/Chrome\/(\d+)/))&&($t=Qt[1]);var de,ce,ue,fe=$t&&+$t,pe=!!Object.getOwnPropertySymbols&&!s((function(){return!Symbol.sham&&(ne?38===fe:fe>37&&fe<41)})),ve=pe&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,ge=jt("wks"),ye=r.Symbol,me=ve?ye:ye&&ye.withoutSetter||Ht,be=function(t){return k(ge,t)&&(pe||"string"==typeof ge[t])||(pe&&k(ye,t)?ge[t]=ye[t]:ge[t]=me("Symbol."+t)),ge[t]},we=be("iterator"),ke=!1;[].keys&&("next"in(ue=[].keys())?(ce=ie(ie(ue)))!==Object.prototype&&(de=ce):ke=!0);var _e=null==de||s((function(){var t={};return de[we].call(t)!==t}));_e&&(de={}),_e&&!k(de,we)&&H(de,we,(function(){return this}));var xe,Ee={IteratorPrototype:de,BUGGY_SAFARI_ITERATORS:ke},Oe=a?Object.defineProperties:function(t,e){j(t);for(var i,n=rt(e),o=n.length,r=0;o>r;)L.f(t,i=n[r++],e[i]);return t},Ce=re("document","documentElement"),Se=qt("IE_PROTO"),Te=function(){},Me=function(t){return" - - - - From 94bd236fe4a5e1a4472b7357154b8dd0cac1bb3e Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 24 Oct 2022 18:05:03 +0200 Subject: [PATCH 62/88] Apply pre-commit on migration of jobs graph --- queue_job/controllers/main.py | 60 +++-- queue_job/delay.py | 112 +++++---- queue_job/job.py | 36 ++- queue_job/jobrunner/channels.py | 5 +- queue_job/static/src/js/queue_job_fields.js | 266 ++++++++++---------- queue_job/tests/common.py | 52 ++-- queue_job/tests/test_delayable.py | 122 ++++----- queue_job/views/queue_job_views.xml | 58 +++-- 8 files changed, 365 insertions(+), 346 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index 0abcc1dd0c..bcf5d06cec 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -2,8 +2,8 @@ # Copyright 2013-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) -import random import logging +import random import time import traceback from io import StringIO @@ -52,19 +52,18 @@ def _enqueue_dependent_jobs(self, env, job): raise if tries >= DEPENDS_MAX_TRIES_ON_CONCURRENCY_FAILURE: _logger.info( - "%s, maximum number of tries reached to" - " update dependencies", - errorcodes.lookup(err.pgcode) + "%s, maximum number of tries reached to update dependencies", + errorcodes.lookup(err.pgcode), ) raise - wait_time = random.uniform(0.0, 2 ** tries) + wait_time = random.uniform(0.0, 2**tries) tries += 1 _logger.info( "%s, retry %d/%d in %.04f sec...", errorcodes.lookup(err.pgcode), tries, DEPENDS_MAX_TRIES_ON_CONCURRENCY_FAILURE, - wait_time + wait_time, ) time.sleep(wait_time) else: @@ -146,9 +145,9 @@ def retry_postpone(job, message, seconds=None): buff.close() raise - _logger.debug('%s enqueue depends started', job) + _logger.debug("%s enqueue depends started", job) self._enqueue_dependent_jobs(env, job) - _logger.debug('%s enqueue depends done', job) + _logger.debug("%s enqueue depends done", job) return "" @@ -164,9 +163,16 @@ def _get_failure_values(self, job, traceback_txt, orig_exception): "exc_message": exc_message, } + # flake8: noqa: C901 @http.route("/queue_job/create_test_job", type="http", auth="user") def create_test_job( - self, priority=None, max_retries=None, channel=None, description="Test job", size=1, failure_rate=0 + self, + priority=None, + max_retries=None, + channel=None, + description="Test job", + size=1, + failure_rate=0, ): if not http.request.env.user.has_group("base.group_erp_manager"): raise Forbidden(_("Access Denied")) @@ -204,7 +210,7 @@ def create_test_job( max_retries=max_retries, channel=channel, description=description, - failure_rate=failure_rate + failure_rate=failure_rate, ) if size > 1: @@ -214,14 +220,18 @@ def create_test_job( max_retries=max_retries, channel=channel, description=description, - failure_rate=failure_rate + failure_rate=failure_rate, ) - return '' + return "" def _create_single_test_job( - self, priority=None, max_retries=None, - channel=None, description="Test job", size=1, - failure_rate=0 + self, + priority=None, + max_retries=None, + channel=None, + description="Test job", + size=1, + failure_rate=0, ): delayed = ( http.request.env["queue.job"] @@ -233,14 +243,18 @@ def _create_single_test_job( ) ._test_job(failure_rate=failure_rate) ) - return 'job uuid: %s' % (delayed.db_record().uuid,) + return "job uuid: %s" % (delayed.db_record().uuid,) TEST_GRAPH_MAX_PER_GROUP = 5 def _create_graph_test_jobs( - self, size, priority=None, max_retries=None, - channel=None, description="Test job", - failure_rate=0 + self, + size, + priority=None, + max_retries=None, + channel=None, + description="Test job", + failure_rate=0, ): model = http.request.env["queue.job"] current_count = 0 @@ -250,7 +264,9 @@ def _create_graph_test_jobs( tails = [] # we can connect new graph chains/groups to tails root_delayable = None while current_count < size: - jobs_count = min(size - current_count, random.randint(1, self.TEST_GRAPH_MAX_PER_GROUP)) + jobs_count = min( + size - current_count, random.randint(1, self.TEST_GRAPH_MAX_PER_GROUP) + ) jobs = [] for __ in range(jobs_count): @@ -275,4 +291,6 @@ def _create_graph_test_jobs( root_delayable.delay() - return 'graph uuid: %s' % (list(root_delayable._head())[0]._generated_job.graph_uuid,) + return "graph uuid: %s" % ( + list(root_delayable._head())[0]._generated_job.graph_uuid, + ) diff --git a/queue_job/delay.py b/queue_job/delay.py index ca5542077d..f93fb5cc9d 100644 --- a/queue_job/delay.py +++ b/queue_job/delay.py @@ -6,7 +6,6 @@ import logging import os import uuid - from collections import defaultdict, deque from .job import Job @@ -58,7 +57,8 @@ class Graph: This graph is not specifically designed to hold :class:`~Delayable` instances, although ultimately it is used for this purpose. """ - __slots__ = ('_graph') + + __slots__ = "_graph" def __init__(self, graph=None): if graph: @@ -104,8 +104,8 @@ def paths(self, vertex): >>> sorted(self.paths(3)) [[3, 1, 2, 4]] """ - path = [vertex] # path traversed so far - seen = {vertex} # set of vertices in path + path = [vertex] # path traversed so far + seen = {vertex} # set of vertices in path def search(): dead_end = True @@ -119,6 +119,7 @@ def search(): seen.remove(neighbour) if dead_end: yield list(path) + yield from search() def topological_sort(self): @@ -154,14 +155,11 @@ def root_vertices(self): return set(self._graph.keys()) - dependency_vertices def __repr__(self): - paths = [ - path for vertex in self.root_vertices() - for path in self.paths(vertex) - ] + paths = [path for vertex in self.root_vertices() for path in self.paths(vertex)] lines = [] for path in paths: - lines.append(' → '.join(repr(vertex) for vertex in path)) - return '\n'.join(lines) + lines.append(" → ".join(repr(vertex) for vertex in path)) + return "\n".join(lines) class DelayableGraph(Graph): @@ -224,7 +222,7 @@ def _has_to_execute_directly(self, vertices): "`TEST_QUEUE_JOB_NO_DELAY` env var found. NO JOB scheduled." ) return True - envs = set(vertex.recordset.env for vertex in vertices) + envs = {vertex.recordset.env for vertex in vertices} for env in envs: if env.context.get("test_queue_job_no_delay"): _logger.warning( @@ -246,11 +244,9 @@ def _ensure_same_graph_uuid(jobs): " have a graph uuid" % (jobs[0],) ) else: - graph_uuids = set(job.graph_uuid for job in jobs if job.graph_uuid) + graph_uuids = {job.graph_uuid for job in jobs if job.graph_uuid} if len(graph_uuids) > 1: - raise ValueError( - "Jobs cannot have dependencies between several graphs" - ) + raise ValueError("Jobs cannot have dependencies between several graphs") elif len(graph_uuids) == 1: graph_uuid = graph_uuids.pop() else: @@ -267,9 +263,7 @@ def delay(self): for vertex in vertices: vertex._build_job() - self._ensure_same_graph_uuid( - [vertex._generated_job for vertex in vertices] - ) + self._ensure_same_graph_uuid([vertex._generated_job for vertex in vertices]) if self._has_to_execute_directly(vertices): self._execute_graph_direct(graph) @@ -328,7 +322,8 @@ class DelayableChain: Important: :meth:`~delay` must be called on the top-level delayable/chain/group object of the graph. """ - __slots__ = ('_graph', '__head', '__tail') + + __slots__ = ("_graph", "__head", "__tail") def __init__(self, *delayables): self._graph = DelayableGraph() @@ -349,7 +344,7 @@ def _tail(self): def __repr__(self): inner_graph = "\n\t".join(repr(self._graph).split("\n")) - return 'DelayableChain(\n\t{}\n)'.format(inner_graph) + return "DelayableChain(\n\t{}\n)".format(inner_graph) def on_done(self, *delayables): """Connects the current chain to other delayables/chains/groups @@ -384,7 +379,8 @@ class DelayableGroup: Important: :meth:`~delay` must be called on the top-level delayable/chain/group object of the graph. """ - __slots__ = ('_graph', '_delayables') + + __slots__ = ("_graph", "_delayables") def __init__(self, *delayables): self._graph = DelayableGraph() @@ -393,18 +389,14 @@ def __init__(self, *delayables): self._graph.add_vertex(delayable) def _head(self): - return itertools.chain.from_iterable( - node._head() for node in self._delayables - ) + return itertools.chain.from_iterable(node._head() for node in self._delayables) def _tail(self): - return itertools.chain.from_iterable( - node._tail() for node in self._delayables - ) + return itertools.chain.from_iterable(node._tail() for node in self._delayables) def __repr__(self): inner_graph = "\n\t".join(repr(self._graph).split("\n")) - return 'DelayableGroup(\n\t{}\n)'.format(inner_graph) + return "DelayableGroup(\n\t{}\n)".format(inner_graph) def on_done(self, *delayables): """Connects the current group to other delayables/chains/groups @@ -447,18 +439,34 @@ class Delayable: Important: :meth:`delay()` must be called on the top-level delayable/chain/group object of the graph. """ + _properties = ( - 'priority', 'eta', 'max_retries', 'description', - 'channel', 'identity_key' + "priority", + "eta", + "max_retries", + "description", + "channel", + "identity_key", ) __slots__ = _properties + ( - 'recordset', '_graph', '_job_method', '_job_args', '_job_kwargs', - '_generated_job', + "recordset", + "_graph", + "_job_method", + "_job_args", + "_job_kwargs", + "_generated_job", ) - def __init__(self, recordset, priority=None, eta=None, - max_retries=None, description=None, channel=None, - identity_key=None): + def __init__( + self, + recordset, + priority=None, + eta=None, + max_retries=None, + description=None, + channel=None, + identity_key=None, + ): self._graph = DelayableGraph() self._graph.add_vertex(self) @@ -484,21 +492,18 @@ def _tail(self): return [self] def __repr__(self): - return 'Delayable({}.{}({}, {}))'.format( - self.recordset, self._job_method.__name__, - self._job_args, self._job_kwargs + return "Delayable({}.{}({}, {}))".format( + self.recordset, self._job_method.__name__, self._job_args, self._job_kwargs ) def __del__(self): if not self._generated_job: - _logger.warning( - 'Delayable %s was prepared but never delayed', self - ) + _logger.warning("Delayable %s was prepared but never delayed", self) def _set_from_dict(self, properties): for key, value in properties.items(): if key not in self._properties: - raise ValueError('No property %s' % (key,)) + raise ValueError("No property %s" % (key,)) setattr(self, key, value) def set(self, *args, **kwargs): @@ -552,8 +557,7 @@ def __getattr__(self, name): return super().__getattr__(name) if name in self.recordset: raise AttributeError( - 'only methods can be delayed (%s called on %s)' % - (name, self.recordset) + "only methods can be delayed (%s called on %s)" % (name, self.recordset) ) recordset_method = getattr(self.recordset, name) self._job_method = recordset_method @@ -579,11 +583,18 @@ class DelayableRecordset(object): by :meth:`~odoo.addons.queue_job.models.base.Base.with_delay` """ - __slots__ = ('delayable',) - - def __init__(self, recordset, priority=None, eta=None, - max_retries=None, description=None, channel=None, - identity_key=None): + __slots__ = ("delayable",) + + def __init__( + self, + recordset, + priority=None, + eta=None, + max_retries=None, + description=None, + channel=None, + identity_key=None, + ): self.delayable = Delayable( recordset, priority=priority, @@ -602,12 +613,13 @@ def __getattr__(self, name): def _delay_delayable(*args, **kwargs): getattr(self.delayable, name)(*args, **kwargs).delay() return self.delayable._generated_job + return _delay_delayable def __str__(self): return "DelayableRecordset(%s%s)" % ( self.delayable.recordset._name, - getattr(self.delayable.recordset, '_ids', "") + getattr(self.delayable.recordset, "_ids", ""), ) __repr__ = __str__ diff --git a/queue_job/job.py b/queue_job/job.py index 4530451cdd..f824020a27 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -8,16 +8,15 @@ import sys import uuid import weakref - from datetime import datetime, timedelta -from random import randint from functools import total_ordering +from random import randint import odoo from .exception import FailedJobError, NoSuchJobError, RetryableJobError -WAIT_DEPENDENCIES = 'wait_dependencies' +WAIT_DEPENDENCIES = "wait_dependencies" PENDING = "pending" ENQUEUED = "enqueued" CANCELLED = "cancelled" @@ -26,7 +25,7 @@ FAILED = "failed" STATES = [ - (WAIT_DEPENDENCIES, 'Wait Dependencies'), + (WAIT_DEPENDENCIES, "Wait Dependencies"), (PENDING, "Pending"), (ENQUEUED, "Enqueued"), (STARTED, "Started"), @@ -47,8 +46,11 @@ def DelayableRecordset(*args, **kwargs): # prevent circular import from .delay import DelayableRecordset as dr - _logger.debug("DelayableRecordset moved from the queue_job.job" - " to the queue_job.delay python module") + + _logger.debug( + "DelayableRecordset moved from the queue_job.job" + " to the queue_job.delay python module" + ) return dr(*args, **kwargs) @@ -284,11 +286,9 @@ def _load_from_db_record(cls, job_db_record): job_.identity_key = stored.identity_key job_.worker_pid = stored.worker_pid - job_.__depends_on_uuids.update( - stored.dependencies.get('depends_on', []) - ) + job_.__depends_on_uuids.update(stored.dependencies.get("depends_on", [])) job_.__reverse_depends_on_uuids.update( - stored.dependencies.get('reverse_depends_on', []) + stored.dependencies.get("reverse_depends_on", []) ) return job_ @@ -362,7 +362,7 @@ def _enqueue_job(self): self.method_name, self.args, self.kwargs, - self.uuid + self.uuid, ) return self @@ -498,7 +498,7 @@ def __init__( def add_depends(self, jobs): if self in jobs: - raise ValueError('job cannot depend on itself') + raise ValueError("job cannot depend on itself") self.__depends_on_uuids |= {j.uuid for j in jobs} self._depends_on.update(jobs) for parent in jobs: @@ -621,14 +621,12 @@ def _store_values(self, create=False): vals["identity_key"] = self.identity_key dependencies = { - 'depends_on': [ - parent.uuid for parent in self.depends_on - ], - 'reverse_depends_on': [ + "depends_on": [parent.uuid for parent in self.depends_on], + "reverse_depends_on": [ children.uuid for children in self.reverse_depends_on ], } - vals['dependencies'] = dependencies + vals["dependencies"] = dependencies if create: vals.update( @@ -727,9 +725,7 @@ def identity_key(self, value): @property def depends_on(self): if not self._depends_on: - self._depends_on = Job.load_many( - self.env, self.__depends_on_uuids - ) + self._depends_on = Job.load_many(self.env, self.__depends_on_uuids) return self._depends_on @property diff --git a/queue_job/jobrunner/channels.py b/queue_job/jobrunner/channels.py index 9108ffdad6..75d895156a 100644 --- a/queue_job/jobrunner/channels.py +++ b/queue_job/jobrunner/channels.py @@ -7,9 +7,8 @@ from weakref import WeakValueDictionary from ..exception import ChannelNotFound -from ..job import ( - PENDING, ENQUEUED, STARTED, FAILED, DONE, WAIT_DEPENDENCIES -) +from ..job import DONE, ENQUEUED, FAILED, PENDING, STARTED, WAIT_DEPENDENCIES + NOT_DONE = (WAIT_DEPENDENCIES, PENDING, ENQUEUED, STARTED, FAILED) _logger = logging.getLogger(__name__) diff --git a/queue_job/static/src/js/queue_job_fields.js b/queue_job/static/src/js/queue_job_fields.js index a4a05a007a..3829ad7145 100644 --- a/queue_job/static/src/js/queue_job_fields.js +++ b/queue_job/static/src/js/queue_job_fields.js @@ -1,150 +1,148 @@ -odoo.define('queue_job.fields', function (require) { -"use strict"; +odoo.define("queue_job.fields", function (require) { + "use strict"; -/** - * This module contains field widgets for the job queue. - */ - -var AbstractField = require('web.AbstractField'); -var core = require('web.core'); -var framework = require('web.framework'); -var field_registry = require('web.field_registry'); + /** + * This module contains field widgets for the job queue. + */ -var qweb = core.qweb; -var _t = core._t; + var AbstractField = require("web.AbstractField"); + var core = require("web.core"); + var field_registry = require("web.field_registry"); -var JobDirectedGraph = AbstractField.extend({ - className: "o_field_job_directed_graph", - cssLibs: [ - '/queue_job/static/lib/vis/vis-network.min.css' - ], - jsLibs: [ - '/queue_job/static/lib/vis/vis-network.min.js' - ], - init: function () { - this._super.apply(this, arguments); - this.network = null; - this.tabListenerInstalled = false; - }, - start: function () { - var def = this._super(); + var JobDirectedGraph = AbstractField.extend({ + /* global vis */ + className: "o_field_job_directed_graph", + cssLibs: ["/queue_job/static/lib/vis/vis-network.min.css"], + jsLibs: ["/queue_job/static/lib/vis/vis-network.min.js"], + init: function () { + this._super.apply(this, arguments); + this.network = null; + this.tabListenerInstalled = false; + }, + start: function () { + var def = this._super(); - core.bus.on('DOM_updated', this, function () { - this._installTabListener(); - }.bind(this)); + core.bus.on( + "DOM_updated", + this, + function () { + this._installTabListener(); + }.bind(this) + ); - return def; - }, - _fitNetwork: function () { - if (this.network) { - this.network.fit(this.network.body.nodeIndices); - } - }, - /* - * Add a listener on tabs if any: when the widget is render inside a tab, - * it does not view the view. Install a listener that will fit the network - * graph to show all the nodes when we switch tab. - */ - _installTabListener: function () { - if (this.tabListenerInstalled) { - return; - } - this.tabListenerInstalled = true; - - var tab = this.$el.closest('div.tab-pane'); - if (!tab.length) { - return; - } - $('a[href="#' + tab[0].id + '"]').on('shown.bs.tab', function (e) { - this._fitNetwork(); - }.bind(this)); - }, - htmlTitle: function(html) { - const container = document.createElement("div"); - container.innerHTML = html; - return container; - }, - _render: function () { - var self = this; - this.$el.empty(); + return def; + }, + _fitNetwork: function () { + if (this.network) { + this.network.fit(this.network.body.nodeIndices); + } + }, + /* + * Add a listener on tabs if any: when the widget is render inside a tab, + * it does not view the view. Install a listener that will fit the network + * graph to show all the nodes when we switch tab. + */ + _installTabListener: function () { + if (this.tabListenerInstalled) { + return; + } + this.tabListenerInstalled = true; - var nodes = this.value.nodes || []; + var tab = this.$el.closest("div.tab-pane"); + if (!tab.length) { + return; + } + $('a[href="#' + tab[0].id + '"]').on( + "shown.bs.tab", + function () { + this._fitNetwork(); + }.bind(this) + ); + }, + htmlTitle: function (html) { + const container = document.createElement("div"); + container.innerHTML = html; + return container; + }, + _render: function () { + var self = this; + this.$el.empty(); - if (!nodes.length) { - return; - } - nodes = _.map(nodes, function(node) { - node.title = self.htmlTitle(node.title || ''); - return node; - }); + var nodes = this.value.nodes || []; - var edges = []; - _.each(this.value.edges || [], function (edge){ - var edgeFrom = edge[0]; - var edgeTo = edge[1]; - edges.push({ - from: edgeFrom, - to: edgeTo, - arrows: 'to' + if (!nodes.length) { + return; + } + nodes = _.map(nodes, function (node) { + node.title = self.htmlTitle(node.title || ""); + return node; }); - }); - var data = { - nodes: new vis.DataSet(nodes), - edges: new vis.DataSet(edges) - }; - var options = { - // fix the seed to have always the same result for the same graph - layout: {randomSeed: 1} - }; - // Arbitrary threshold, generation becomes very slow at some - // point, and disabling the stabilization helps to have a fast result. - // Actually, it stabilizes, but is displayed while stabilizing, rather - // than showing a blank canvas. - if (nodes.length > 100) { - options.physics = { stabilization: false }; - } - var network = new vis.Network(this.$el[0], data, options); - network.selectNodes([this.res_id]); + var edges = []; + _.each(this.value.edges || [], function (edge) { + var edgeFrom = edge[0]; + var edgeTo = edge[1]; + edges.push({ + from: edgeFrom, + to: edgeTo, + arrows: "to", + }); + }); - network.on("dragging", function () { - // by default, dragging changes the selected node - // to the dragged one, we want to keep the current - // job selected - network.selectNodes([self.res_id]); - }); - network.on("click", function (params) { - if (params.nodes.length > 0) { - var jobId = params.nodes[0]; - if (jobId !== self.res_id) { - self.openDependencyJob(jobId); - } - } else { - // clicked outside of the nodes, we want to - // keep the current job selected - network.selectNodes([self.res_id]); + var data = { + nodes: new vis.DataSet(nodes), + edges: new vis.DataSet(edges), + }; + var options = { + // Fix the seed to have always the same result for the same graph + layout: {randomSeed: 1}, + }; + // Arbitrary threshold, generation becomes very slow at some + // point, and disabling the stabilization helps to have a fast result. + // Actually, it stabilizes, but is displayed while stabilizing, rather + // than showing a blank canvas. + if (nodes.length > 100) { + options.physics = {stabilization: false}; } - }); - this.network = network; - }, - openDependencyJob: function (res_id) { - var self = this; - this._rpc({ - model: this.model, - method: 'get_formview_action', - args: [[res_id]], - context: this.record.getContext(this.recordParams), - }) - .then(function (action) { - self.trigger_up('do_action', {action: action}); - }); - } -}); + var network = new vis.Network(this.$el[0], data, options); + network.selectNodes([this.res_id]); -field_registry.add('job_directed_graph', JobDirectedGraph); + network.on("dragging", function () { + // By default, dragging changes the selected node + // to the dragged one, we want to keep the current + // job selected + network.selectNodes([self.res_id]); + }); + network.on("click", function (params) { + if (params.nodes.length > 0) { + var jobId = params.nodes[0]; + if (jobId !== self.res_id) { + self.openDependencyJob(jobId); + } + } else { + // Clicked outside of the nodes, we want to + // keep the current job selected + network.selectNodes([self.res_id]); + } + }); + this.network = network; + }, + openDependencyJob: function (res_id) { + var self = this; + this._rpc({ + model: this.model, + method: "get_formview_action", + args: [[res_id]], + context: this.record.getContext(this.recordParams), + }).then(function (action) { + self.trigger_up("do_action", {action: action}); + }); + }, + }); -return { - JobDirectedGraph: JobDirectedGraph, -}; + field_registry.add("job_directed_graph", JobDirectedGraph); + return { + JobDirectedGraph: JobDirectedGraph, + }; }); diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py index aa70a6cf01..0c461db230 100644 --- a/queue_job/tests/common.py +++ b/queue_job/tests/common.py @@ -4,15 +4,15 @@ import logging import sys import typing - from contextlib import contextmanager from itertools import groupby from operator import attrgetter -from unittest import mock, TestCase +from unittest import TestCase, mock + +from odoo.addons.queue_job.delay import Graph # pylint: disable=odoo-addons-relative-import from odoo.addons.queue_job.job import Job -from odoo.addons.queue_job.delay import Graph @contextmanager @@ -135,6 +135,7 @@ class JobsTrap: You can also access the list of calls that were made to enqueue the jobs in the ``calls`` attribute, and the generated jobs in the ``enqueued_jobs``. """ + def __init__(self, job_mock): self.job_mock = job_mock self.job_mock.side_effect = self._add_job @@ -160,8 +161,7 @@ def assert_jobs_count(self, expected, only=None): """ self._test_case.assertEqual(self.jobs_count(only=only), expected) - def assert_enqueued_job(self, method, args=None, kwargs=None, - properties=None): + def assert_enqueued_job(self, method, args=None, kwargs=None, properties=None): """Raise an assertion error if the expected method has not been enqueued * ``method`` is the method (as method object) delayed as job @@ -191,7 +191,8 @@ def assert_enqueued_job(self, method, args=None, kwargs=None, actual_calls = [] for call in self.calls: checked_properties = { - key: value for key, value in call.properties.items() + key: value + for key, value in call.properties.items() if key in properties } # build copy of calls with only the properties that we want to @@ -201,24 +202,29 @@ def assert_enqueued_job(self, method, args=None, kwargs=None, method=call.method, args=call.args, kwargs=call.kwargs, - properties=checked_properties + properties=checked_properties, ) ) if expected_call not in actual_calls: raise AssertionError( "Job %s was not enqueued.\n" - "Actual enqueued jobs:\n%s" % ( + "Actual enqueued jobs:\n%s" + % ( self._format_job_call(expected_call), - "\n".join(" * %s" % (self._format_job_call(call),) - for call in actual_calls) + "\n".join( + " * %s" % (self._format_job_call(call),) + for call in actual_calls + ), ) ) def perform_enqueued_jobs(self): """Perform the enqueued jobs synchronously""" + def by_graph(job): return job.graph_uuid or "" + sorted_jobs = sorted(self.enqueued_jobs, key=by_graph) for graph_uuid, jobs in groupby(sorted_jobs, key=by_graph): if graph_uuid: @@ -246,18 +252,20 @@ def _add_job(self, *args, **kwargs): job = Job(*args, **kwargs) self.enqueued_jobs.append(job) - patcher = mock.patch.object(job, 'store') + patcher = mock.patch.object(job, "store") self._store_patchers.append(patcher) patcher.start() job_args = kwargs.pop("args", None) or () job_kwargs = kwargs.pop("kwargs", None) or {} - self.calls.append(JobCall( - method=args[0], - args=job_args, - kwargs=job_kwargs, - properties=kwargs, - )) + self.calls.append( + JobCall( + method=args[0], + args=job_args, + kwargs=job_kwargs, + properties=kwargs, + ) + ) return job def __enter__(self): @@ -278,20 +286,16 @@ def _filtered_enqueued_jobs(self, job_method): def _format_job_call(self, call): method_all_args = [] if call.args: - method_all_args.append( - ", ".join("%s" % (arg,) for arg in call.args) - ) + method_all_args.append(", ".join("%s" % (arg,) for arg in call.args)) if call.kwargs: method_all_args.append( - ", ".join("%s=%s" % (key, value) for key, value - in call.kwargs.items()) + ", ".join("%s=%s" % (key, value) for key, value in call.kwargs.items()) ) return "<%s>.%s(%s) with properties (%s)" % ( call.method.__self__.__class__._name, call.method.__name__, ", ".join(method_all_args), - ", ".join("%s=%s" % (key, value) for key, value - in call.properties.items()), + ", ".join("%s=%s" % (key, value) for key, value in call.properties.items()), ) def __repr__(self): diff --git a/queue_job/tests/test_delayable.py b/queue_job/tests/test_delayable.py index b5fc86f0f8..097c29f25e 100644 --- a/queue_job/tests/test_delayable.py +++ b/queue_job/tests/test_delayable.py @@ -1,25 +1,24 @@ # copyright 2019 Camptocamp # license agpl-3.0 or later (http://www.gnu.org/licenses/agpl.html) -import mock import unittest +from unittest import mock from odoo.addons.queue_job.delay import Delayable, DelayableGraph class TestDelayable(unittest.TestCase): - def setUp(self): super().setUp() - self.recordset = mock.MagicMock(name='recordset') + self.recordset = mock.MagicMock(name="recordset") def test_delayable_set(self): dl = Delayable(self.recordset) dl.set(priority=15) self.assertEqual(dl.priority, 15) - dl.set({'priority': 20, 'description': 'test'}) + dl.set({"priority": 20, "description": "test"}) self.assertEqual(dl.priority, 20) - self.assertEqual(dl.description, 'test') + self.assertEqual(dl.description, "test") def test_delayable_set_unknown(self): dl = Delayable(self.recordset) @@ -28,33 +27,31 @@ def test_delayable_set_unknown(self): def test_graph_add_vertex_edge(self): graph = DelayableGraph() - graph.add_vertex('a') - self.assertEqual(graph._graph, {'a': set()}) - graph.add_edge('a', 'b') - self.assertEqual(graph._graph, {'a': {'b'}, 'b': set()}) - graph.add_edge('b', 'c') - self.assertEqual(graph._graph, {'a': {'b'}, 'b': {'c'}, 'c': set()}) + graph.add_vertex("a") + self.assertEqual(graph._graph, {"a": set()}) + graph.add_edge("a", "b") + self.assertEqual(graph._graph, {"a": {"b"}, "b": set()}) + graph.add_edge("b", "c") + self.assertEqual(graph._graph, {"a": {"b"}, "b": {"c"}, "c": set()}) def test_graph_vertices(self): - graph = DelayableGraph({'a': {'b'}, 'b': {'c'}, 'c': set()}) - self.assertEqual(graph.vertices(), {'a', 'b', 'c'}) + graph = DelayableGraph({"a": {"b"}, "b": {"c"}, "c": set()}) + self.assertEqual(graph.vertices(), {"a", "b", "c"}) def test_graph_edges(self): - graph = DelayableGraph({ - 'a': {'b'}, - 'b': {'c', 'd'}, - 'c': {'e'}, - 'd': set(), - 'e': set() - }) + graph = DelayableGraph( + {"a": {"b"}, "b": {"c", "d"}, "c": {"e"}, "d": set(), "e": set()} + ) self.assertEqual( sorted(graph.edges()), - sorted([ - ('a', 'b'), - ('b', 'c'), - ('b', 'd'), - ('c', 'e'), - ]) + sorted( + [ + ("a", "b"), + ("b", "c"), + ("b", "d"), + ("c", "e"), + ] + ), ) def test_graph_connect(self): @@ -73,63 +70,48 @@ def test_graph_connect(self): node_tail2: set(), node_middle: {node_tail, node_tail2}, node_top: {node_middle}, - } + }, ) def test_graph_paths(self): - graph = DelayableGraph({ - 'a': {'b'}, - 'b': {'c', 'd'}, - 'c': {'e'}, - 'd': set(), - 'e': set() - }) - paths = list(graph.paths('a')) - self.assertEqual( - sorted(paths), - sorted([['a', 'b', 'd'], ['a', 'b', 'c', 'e']]) - ) - paths = list(graph.paths('b')) - self.assertEqual( - sorted(paths), - sorted([['b', 'd'], ['b', 'c', 'e']]) + graph = DelayableGraph( + {"a": {"b"}, "b": {"c", "d"}, "c": {"e"}, "d": set(), "e": set()} ) - paths = list(graph.paths('c')) - self.assertEqual(paths, [['c', 'e']]) - paths = list(graph.paths('d')) - self.assertEqual(paths, [['d']]) - paths = list(graph.paths('e')) - self.assertEqual(paths, [['e']]) + paths = list(graph.paths("a")) + self.assertEqual(sorted(paths), sorted([["a", "b", "d"], ["a", "b", "c", "e"]])) + paths = list(graph.paths("b")) + self.assertEqual(sorted(paths), sorted([["b", "d"], ["b", "c", "e"]])) + paths = list(graph.paths("c")) + self.assertEqual(paths, [["c", "e"]]) + paths = list(graph.paths("d")) + self.assertEqual(paths, [["d"]]) + paths = list(graph.paths("e")) + self.assertEqual(paths, [["e"]]) def test_graph_repr(self): - graph = DelayableGraph({ - 'a': {'b'}, - 'b': {'c', 'd'}, - 'c': {'e'}, - 'd': set(), - 'e': set() - }) + graph = DelayableGraph( + {"a": {"b"}, "b": {"c", "d"}, "c": {"e"}, "d": set(), "e": set()} + ) actual = repr(graph) - expected = [ - "'a' → 'b' → 'c' → 'e'", - "'a' → 'b' → 'd'" - ] + expected = ["'a' → 'b' → 'c' → 'e'", "'a' → 'b' → 'd'"] self.assertEqual(sorted(actual.split("\n")), expected) def test_graph_topological_sort(self): # the graph is an example from # https://en.wikipedia.org/wiki/Topological_sorting # if you want a visual representation - graph = DelayableGraph({ - 5: {11}, - 7: {11, 8}, - 3: {8, 10}, - 11: {2, 9, 10}, - 2: set(), - 8: {9}, - 9: set(), - 10: set(), - }) + graph = DelayableGraph( + { + 5: {11}, + 7: {11, 8}, + 3: {8, 10}, + 11: {2, 9, 10}, + 2: set(), + 8: {9}, + 9: set(), + 10: set(), + } + ) # these are all the pre-computed combinations that # respect the dependencies order diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index 2eb55ba2d5..d58739a82e 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -46,8 +46,13 @@ type="object" class="oe_stat_button" icon="fa-sitemap" - attrs="{'invisible':[('graph_uuid', '=', False)]}"> - + attrs="{'invisible':[('graph_uuid', '=', False)]}" + > +

@@ -73,7 +78,7 @@ - + @@ -92,22 +97,23 @@ - + + name="exc_info" + string="Exception Information" + attrs="{'invisible': [('exc_info', '=', False)]}" + colspan="4" + >
-

Known issues / Roadmap

+

Known issues / Roadmap

  • After creating a new database or installing queue_job on an existing database, Odoo must be restarted for the runner to detect it.
  • @@ -634,7 +875,7 @@

    Known issues / Roadmap

-

Changelog

+

Changelog

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -664,16 +905,16 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Camptocamp
  • ACSONE SA/NV
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose From d2a47fb87ebc20eebfc2ad50430bedc73dab2aed Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 15 Nov 2022 14:18:36 +0000 Subject: [PATCH 65/88] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: queue-15.0/queue-15.0-queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-15-0/queue-15-0-queue_job/ --- queue_job/i18n/de.po | 66 +++++++++++++++++++++++++++++++++++++---- queue_job/i18n/es.po | 66 +++++++++++++++++++++++++++++++++++++---- queue_job/i18n/zh_CN.po | 66 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 183 insertions(+), 15 deletions(-) diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index 87e8bb8c8e..d006e81144 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -132,11 +132,6 @@ msgstr "Der Root-Kanal kann nicht entfernt werden" msgid "Channel" msgstr "Kanal" -#. module: queue_job -#: model:ir.model.fields,field_description:queue_job.field_queue_job__channel_method_name -msgid "Channel Method Name" -msgstr "Kanal-Methodenname" - #. module: queue_job #: model:ir.model.constraint,message:queue_job.constraint_queue_job_channel_name_uniq msgid "Channel complete name must be unique" @@ -155,6 +150,11 @@ msgstr "Kanäle" msgid "Company" msgstr "Unternehmen" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__channel_method_name +msgid "Complete Method Name" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__complete_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__channel @@ -202,6 +202,17 @@ msgstr "" msgid "Date Done" msgstr "Erledigt am" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__dependencies +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Dependencies" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__dependency_graph +msgid "Dependency Graph" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__name msgid "Description" @@ -306,6 +317,26 @@ msgstr "Abonnenten (Partner)" msgid "Font awesome icon e.g. fa-tasks" msgstr "Font Awesome Icon z.B. fa-tasks" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Graph" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Graph Jobs" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__graph_jobs_count +msgid "Graph Jobs Count" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__graph_uuid +msgid "Graph UUID" +msgstr "" + #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_function_search #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search @@ -441,6 +472,12 @@ msgstr "Jobs" msgid "Jobs Garbage Collector" msgstr "" +#. module: queue_job +#: code:addons/queue_job/models/queue_job.py:0 +#, python-format +msgid "Jobs for graph %s" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__kwargs msgid "Kwargs" @@ -714,6 +751,11 @@ msgstr "Verantwortlicher Benutzer" msgid "Result" msgstr "Ergebnis" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Results" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__edit_retry_pattern msgid "Retry Pattern" @@ -754,6 +796,11 @@ msgstr "Als Erledigt markieren" msgid "Set to done" msgstr "Als Erledigt markieren" +#. module: queue_job +#: model:ir.model.fields,help:queue_job.field_queue_job__graph_uuid +msgid "Single shared identifier of a Graph. Empty for a single job." +msgstr "" + #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, python-format @@ -878,6 +925,12 @@ msgstr "" msgid "User ID" msgstr "Benutzer" +#. module: queue_job +#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__wait_dependencies +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Wait Dependencies" +msgstr "" + #. module: queue_job #: model:ir.model,name:queue_job.model_queue_requeue_job msgid "Wizard to requeue a selection of jobs" @@ -888,6 +941,9 @@ msgstr "Assistent zur erneuten Einreihung einer Job-Auswahl" msgid "Worker Pid" msgstr "" +#~ msgid "Channel Method Name" +#~ msgstr "Kanal-Methodenname" + #~ msgid "Followers (Channels)" #~ msgstr "Abonnenten (Kanäle)" diff --git a/queue_job/i18n/es.po b/queue_job/i18n/es.po index e568132980..dc336ca88e 100644 --- a/queue_job/i18n/es.po +++ b/queue_job/i18n/es.po @@ -133,11 +133,6 @@ msgstr "No se puede eliminar el canal raíz" msgid "Channel" msgstr "Canal" -#. module: queue_job -#: model:ir.model.fields,field_description:queue_job.field_queue_job__channel_method_name -msgid "Channel Method Name" -msgstr "Nombre del método del canal" - #. module: queue_job #: model:ir.model.constraint,message:queue_job.constraint_queue_job_channel_name_uniq msgid "Channel complete name must be unique" @@ -156,6 +151,11 @@ msgstr "Canales" msgid "Company" msgstr "Empresa" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__channel_method_name +msgid "Complete Method Name" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__complete_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__channel @@ -203,6 +203,17 @@ msgstr "" msgid "Date Done" msgstr "Fecha de realización" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__dependencies +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Dependencies" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__dependency_graph +msgid "Dependency Graph" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__name msgid "Description" @@ -307,6 +318,26 @@ msgstr "Seguidores (Partners)" msgid "Font awesome icon e.g. fa-tasks" msgstr "Icono de Font Awesome ej. fa-tasks" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Graph" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Graph Jobs" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__graph_jobs_count +msgid "Graph Jobs Count" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__graph_uuid +msgid "Graph UUID" +msgstr "" + #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_function_search #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search @@ -442,6 +473,12 @@ msgstr "Trabajos" msgid "Jobs Garbage Collector" msgstr "Recolector de basura de trabajos" +#. module: queue_job +#: code:addons/queue_job/models/queue_job.py:0 +#, python-format +msgid "Jobs for graph %s" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__kwargs msgid "Kwargs" @@ -720,6 +757,11 @@ msgstr "Usuario responsable" msgid "Result" msgstr "Resultado" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Results" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__edit_retry_pattern msgid "Retry Pattern" @@ -760,6 +802,11 @@ msgstr "Marcar como 'Hecho'" msgid "Set to done" msgstr "Marcar como hecho" +#. module: queue_job +#: model:ir.model.fields,help:queue_job.field_queue_job__graph_uuid +msgid "Single shared identifier of a Graph. Empty for a single job." +msgstr "" + #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, python-format @@ -894,6 +941,12 @@ msgstr "" msgid "User ID" msgstr "ID de usuario" +#. module: queue_job +#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__wait_dependencies +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Wait Dependencies" +msgstr "" + #. module: queue_job #: model:ir.model,name:queue_job.model_queue_requeue_job msgid "Wizard to requeue a selection of jobs" @@ -914,6 +967,9 @@ msgstr "Pid del trabajador" #~ msgid "Unread Messages Counter" #~ msgstr "Nº de mensajes sin leer" +#~ msgid "Channel Method Name" +#~ msgstr "Nombre del método del canal" + #~ msgid "Override Channel" #~ msgstr "Sobreescribir canal" diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po index d164f2fa6f..e60022b5d2 100644 --- a/queue_job/i18n/zh_CN.po +++ b/queue_job/i18n/zh_CN.po @@ -132,11 +132,6 @@ msgstr "无法删除root频道" msgid "Channel" msgstr "频道" -#. module: queue_job -#: model:ir.model.fields,field_description:queue_job.field_queue_job__channel_method_name -msgid "Channel Method Name" -msgstr "频道方法名称" - #. module: queue_job #: model:ir.model.constraint,message:queue_job.constraint_queue_job_channel_name_uniq msgid "Channel complete name must be unique" @@ -155,6 +150,11 @@ msgstr "频道" msgid "Company" msgstr "公司" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__channel_method_name +msgid "Complete Method Name" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__complete_name #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__channel @@ -202,6 +202,17 @@ msgstr "" msgid "Date Done" msgstr "完成日期" +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__dependencies +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Dependencies" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__dependency_graph +msgid "Dependency Graph" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__name msgid "Description" @@ -306,6 +317,26 @@ msgstr "关注者(业务伙伴)" msgid "Font awesome icon e.g. fa-tasks" msgstr "" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Graph" +msgstr "" + +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Graph Jobs" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__graph_jobs_count +msgid "Graph Jobs Count" +msgstr "" + +#. module: queue_job +#: model:ir.model.fields,field_description:queue_job.field_queue_job__graph_uuid +msgid "Graph UUID" +msgstr "" + #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_function_search #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search @@ -439,6 +470,12 @@ msgstr "作业" msgid "Jobs Garbage Collector" msgstr "" +#. module: queue_job +#: code:addons/queue_job/models/queue_job.py:0 +#, python-format +msgid "Jobs for graph %s" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__kwargs msgid "Kwargs" @@ -713,6 +750,11 @@ msgstr "负责的用户" msgid "Result" msgstr "结果" +#. module: queue_job +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form +msgid "Results" +msgstr "" + #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__edit_retry_pattern msgid "Retry Pattern" @@ -753,6 +795,11 @@ msgstr "设置为“完成”" msgid "Set to done" msgstr "设置为完成" +#. module: queue_job +#: model:ir.model.fields,help:queue_job.field_queue_job__graph_uuid +msgid "Single shared identifier of a Graph. Empty for a single job." +msgstr "" + #. module: queue_job #: code:addons/queue_job/models/queue_job.py:0 #, python-format @@ -874,6 +921,12 @@ msgstr "" msgid "User ID" msgstr "用户" +#. module: queue_job +#: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__wait_dependencies +#: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search +msgid "Wait Dependencies" +msgstr "" + #. module: queue_job #: model:ir.model,name:queue_job.model_queue_requeue_job msgid "Wizard to requeue a selection of jobs" @@ -884,6 +937,9 @@ msgstr "重新排队向导所选的作业" msgid "Worker Pid" msgstr "" +#~ msgid "Channel Method Name" +#~ msgstr "频道方法名称" + #~ msgid "Followers (Channels)" #~ msgstr "关注者(频道)" From 1cc2f8c89c12f0c9f4360a42ec6e8faeafb335ac Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 1 Feb 2021 10:32:56 +0100 Subject: [PATCH 66/88] Remove initial create notification and follower Everytime a job is created, a mail.message "Queue Job created" is created. This is useless, as we already have the creation date and user, and nobody will ever want to receive a notification for a created job anyway. Removing the on creation auto-subscription of the user that created the job makes sense as well since we automatically subscribe the queue job managers for failures. Add the owner of the jobs to the followers on failures only as well. It allows to remove a lot of insertions of records (and of deletions when autovacuuming jobs). --- test_queue_job/tests/test_job.py | 47 ++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index ad9d2b37f5..ba2eea71a8 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -6,7 +6,6 @@ from unittest import mock import odoo.tests.common as common -from odoo import SUPERUSER_ID from odoo.addons.queue_job import identity_exact from odoo.addons.queue_job.exception import ( @@ -480,7 +479,7 @@ def test_message_when_write_fail(self): stored.write({"state": "failed"}) self.assertEqual(stored.state, FAILED) messages = stored.message_ids - self.assertEqual(len(messages), 2) + self.assertEqual(len(messages), 1) def test_follower_when_write_fail(self): """Check that inactive users doesn't are not followers even if @@ -539,6 +538,22 @@ def setUp(self): User = self.env["res.users"] Company = self.env["res.company"] Partner = self.env["res.partner"] + + main_company = self.env.ref("base.main_company") + + self.partner_user = Partner.create( + {"name": "Simple User", "email": "simple.user@example.com"} + ) + self.simple_user = User.create( + { + "partner_id": self.partner_user.id, + "company_ids": [(4, main_company.id)], + "login": "simple_user", + "name": "simple user", + "groups_id": [], + } + ) + self.other_partner_a = Partner.create( {"name": "My Company a", "is_company": True, "email": "test@tes.ttest"} ) @@ -555,7 +570,7 @@ def setUp(self): "company_id": self.other_company_a.id, "company_ids": [(4, self.other_company_a.id)], "login": "my_login a", - "name": "my user", + "name": "my user A", "groups_id": [(4, grp_queue_job_manager)], } ) @@ -575,16 +590,11 @@ def setUp(self): "company_id": self.other_company_b.id, "company_ids": [(4, self.other_company_b.id)], "login": "my_login_b", - "name": "my user 1", + "name": "my user B", "groups_id": [(4, grp_queue_job_manager)], } ) - def _subscribe_users(self, stored): - domain = stored._subscribe_users_domain() - users = self.env["res.users"].search(domain) - stored.message_subscribe(partner_ids=users.mapped("partner_id").ids) - def _create_job(self, env): self.cr.execute("delete from queue_job") env["test.queue.job"].with_delay().testing_method() @@ -630,11 +640,14 @@ def test_job_subscription(self): # queue_job.group_queue_job_manager must be followers User = self.env["res.users"] no_company_context = dict(self.env.context, company_id=None) - no_company_env = self.env(context=no_company_context) + no_company_env = self.env(user=self.simple_user, context=no_company_context) stored = self._create_job(no_company_env) - self._subscribe_users(stored) - users = User.with_context(active_test=False).search( - [("groups_id", "=", self.ref("queue_job.group_queue_job_manager"))] + stored._message_post_on_failure() + users = ( + User.search( + [("groups_id", "=", self.ref("queue_job.group_queue_job_manager"))] + ) + + stored.user_id ) self.assertEqual(len(stored.message_follower_ids), len(users)) expected_partners = [u.partner_id for u in users] @@ -648,13 +661,13 @@ def test_job_subscription(self): # jobs created for a specific company_id are followed only by # company's members company_a_context = dict(self.env.context, company_id=self.other_company_a.id) - company_a_env = self.env(context=company_a_context) + company_a_env = self.env(user=self.simple_user, context=company_a_context) stored = self._create_job(company_a_env) stored.with_user(self.other_user_a.id) - self._subscribe_users(stored) - # 2 because admin + self.other_partner_a + stored._message_post_on_failure() + # 2 because simple_user (creator of job) + self.other_partner_a self.assertEqual(len(stored.message_follower_ids), 2) - users = User.browse([SUPERUSER_ID, self.other_user_a.id]) + users = self.simple_user + self.other_user_a expected_partners = [u.partner_id for u in users] self.assertSetEqual( set(stored.message_follower_ids.mapped("partner_id")), From 70bab16c0b9a0adba5d7874b7f01e20c4fe28fd7 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Feb 2021 16:43:43 +0100 Subject: [PATCH 67/88] queue_job: add exec time to view some stats --- test_queue_job/tests/test_job.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index ba2eea71a8..8aed632a38 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -148,14 +148,16 @@ def test_worker_pid(self): def test_set_done(self): job_a = Job(self.method) + job_a.date_started = datetime(2015, 3, 15, 16, 40, 0) datetime_path = "odoo.addons.queue_job.job.datetime" with mock.patch(datetime_path, autospec=True) as mock_datetime: mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0) job_a.set_done(result="test") - self.assertEqual(job_a.state, DONE) - self.assertEqual(job_a.result, "test") - self.assertEqual(job_a.date_done, datetime(2015, 3, 15, 16, 41, 0)) + self.assertEquals(job_a.state, DONE) + self.assertEquals(job_a.result, "test") + self.assertEquals(job_a.date_done, datetime(2015, 3, 15, 16, 41, 0)) + self.assertEquals(job_a.exec_time, 60.0) self.assertFalse(job_a.exc_info) def test_set_failed(self): @@ -232,6 +234,7 @@ def test_read(self): self.assertAlmostEqual(job_read.date_started, test_date, delta=delta) self.assertAlmostEqual(job_read.date_enqueued, test_date, delta=delta) self.assertAlmostEqual(job_read.date_done, test_date, delta=delta) + self.assertAlmostEqual(job_read.exec_time, 0.0) def test_job_unlinked(self): test_job = Job(self.method, args=("o", "k"), kwargs={"c": "!"}) From 0b2f48fbeeb94f7c47f1adee9436da87cbd9f652 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 10:06:32 +0200 Subject: [PATCH 68/88] queue_job: add hook to customize stored values --- test_queue_job/models/test_models.py | 8 ++++++++ test_queue_job/tests/test_job.py | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index 389ad32832..4cff25c380 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -10,6 +10,8 @@ class QueueJob(models.Model): _inherit = "queue.job" + additional_info = fields.Char() + def testing_related_method(self, **kwargs): return self, kwargs @@ -93,6 +95,12 @@ def _register_hook(self): ) return super()._register_hook() + def _job_store_values(self, job): + value = "JUST_TESTING" + if job.state == "failed": + value += "_BUT_FAILED" + return {"additional_info": value} + class TestQueueChannel(models.Model): diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 8aed632a38..039c3c3d38 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -183,6 +183,16 @@ def test_store(self): stored = self.queue_job.search([("uuid", "=", test_job.uuid)]) self.assertEqual(len(stored), 1) + def test_store_extra_data(self): + test_job = Job(self.method) + test_job.store() + stored = self.queue_job.search([("uuid", "=", test_job.uuid)]) + self.assertEqual(stored.additional_info, "JUST_TESTING") + test_job.set_failed(exc_info="failed test", exc_name="FailedTest") + test_job.store() + stored.invalidate_cache() + self.assertEqual(stored.additional_info, "JUST_TESTING_BUT_FAILED") + def test_read(self): eta = datetime.now() + timedelta(hours=5) test_job = Job( From 5fb0a9a06b43fe51f9acfbf41da31d15fe47e10a Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 29 Mar 2021 09:12:07 +0200 Subject: [PATCH 69/88] queue_job: store exception name and message --- test_queue_job/tests/test_job.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 039c3c3d38..cdcdec01ce 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -154,17 +154,23 @@ def test_set_done(self): mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0) job_a.set_done(result="test") - self.assertEquals(job_a.state, DONE) - self.assertEquals(job_a.result, "test") - self.assertEquals(job_a.date_done, datetime(2015, 3, 15, 16, 41, 0)) - self.assertEquals(job_a.exec_time, 60.0) + self.assertEqual(job_a.state, DONE) + self.assertEqual(job_a.result, "test") + self.assertEqual(job_a.date_done, datetime(2015, 3, 15, 16, 41, 0)) + self.assertEqual(job_a.exec_time, 60.0) self.assertFalse(job_a.exc_info) def test_set_failed(self): job_a = Job(self.method) - job_a.set_failed(exc_info="failed test") + job_a.set_failed( + exc_info="failed test", + exc_name="FailedTest", + exc_message="Sadly this job failed", + ) self.assertEqual(job_a.state, FAILED) self.assertEqual(job_a.exc_info, "failed test") + self.assertEqual(job_a.exc_name, "FailedTest") + self.assertEqual(job_a.exc_message, "Sadly this job failed") def test_postpone(self): job_a = Job(self.method) From 076fd7ee3f98d33b8ff724911aca28aea7572802 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Mon, 31 Oct 2022 16:34:06 +0000 Subject: [PATCH 70/88] [UPD] Update test_queue_job.pot --- test_queue_job/i18n/test_queue_job.pot | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test_queue_job/i18n/test_queue_job.pot b/test_queue_job/i18n/test_queue_job.pot index 506932ccd9..6e40557462 100644 --- a/test_queue_job/i18n/test_queue_job.pot +++ b/test_queue_job/i18n/test_queue_job.pot @@ -13,6 +13,11 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: test_queue_job +#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info +msgid "Additional Info" +msgstr "" + #. module: test_queue_job #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid From e32e21974e688b35950d6ca58b3e6942582a6dab Mon Sep 17 00:00:00 2001 From: Weblate Date: Mon, 31 Oct 2022 16:36:35 +0000 Subject: [PATCH 71/88] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: queue-15.0/queue-15.0-test_queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-15-0/queue-15-0-test_queue_job/ --- test_queue_job/i18n/zh_CN.po | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test_queue_job/i18n/zh_CN.po b/test_queue_job/i18n/zh_CN.po index 72b0758613..8cac393e3c 100644 --- a/test_queue_job/i18n/zh_CN.po +++ b/test_queue_job/i18n/zh_CN.po @@ -16,6 +16,11 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.8\n" +#. module: test_queue_job +#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info +msgid "Additional Info" +msgstr "" + #. module: test_queue_job #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid #: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid From 8a22667eed21b0b8a4d7e9c000d3a008947fccc6 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 2 Jul 2019 21:41:24 +0200 Subject: [PATCH 72/88] Store dependencies --- test_queue_job/tests/__init__.py | 1 + test_queue_job/tests/test_dependencies.py | 111 ++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 test_queue_job/tests/test_dependencies.py diff --git a/test_queue_job/tests/__init__.py b/test_queue_job/tests/__init__.py index 502a0752fd..0984a41db0 100644 --- a/test_queue_job/tests/__init__.py +++ b/test_queue_job/tests/__init__.py @@ -1,4 +1,5 @@ from . import test_autovacuum +from . import test_dependencies from . import test_job from . import test_job_auto_delay from . import test_job_channels diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py new file mode 100644 index 0000000000..159e949d28 --- /dev/null +++ b/test_queue_job/tests/test_dependencies.py @@ -0,0 +1,111 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) + +import odoo.tests.common as common + +from odoo.addons.queue_job.job import ( + Job, +) + + +class TestJobDependencies(common.TransactionCase): + + def setUp(self): + super().setUp() + self.queue_job = self.env['queue.job'] + self.method = self.env['test.queue.job'].testing_method + + def test_depends_store(self): + job_root = Job(self.method) + job_lvl1_a = Job(self.method) + job_lvl1_a.add_depends({job_root}) + job_lvl1_b = Job(self.method) + job_lvl1_b.add_depends({job_root}) + job_lvl2_a = Job(self.method) + job_lvl2_a.add_depends({job_lvl1_a}) + + # Jobs must be stored after the dependencies are set up. + # (Or if not, a new store must be called on the parent) + job_root.store() + job_lvl1_a.store() + job_lvl1_b.store() + job_lvl2_a.store() + + # test properties + self.assertFalse(job_root.depends_on) + + self.assertEqual(job_lvl1_a.depends_on, {job_root}) + self.assertEqual(job_lvl1_b.depends_on, {job_root}) + + self.assertEqual(job_lvl2_a.depends_on, {job_lvl1_a}) + + self.assertEqual(job_root.reverse_depends_on, {job_lvl1_a, job_lvl1_b}) + + self.assertEqual(job_lvl1_a.reverse_depends_on, {job_lvl2_a}) + self.assertFalse(job_lvl1_b.reverse_depends_on) + + self.assertFalse(job_lvl2_a.reverse_depends_on) + + # test DB state + self.assertEqual(job_root.db_record().dependencies['depends_on'], []) + self.assertEqual( + sorted(job_root.db_record().dependencies['reverse_depends_on']), + sorted([job_lvl1_a.uuid, job_lvl1_b.uuid]) + ) + + self.assertEqual( + job_lvl1_a.db_record().dependencies['depends_on'], [job_root.uuid] + ) + self.assertEqual( + job_lvl1_a.db_record().dependencies['reverse_depends_on'], + [job_lvl2_a.uuid] + ) + + self.assertEqual( + job_lvl1_b.db_record().dependencies['depends_on'], [job_root.uuid] + ) + self.assertEqual( + job_lvl1_b.db_record().dependencies['reverse_depends_on'], [] + ) + + self.assertEqual( + job_lvl2_a.db_record().dependencies['depends_on'], + [job_lvl1_a.uuid] + ) + self.assertEqual( + job_lvl2_a.db_record().dependencies['reverse_depends_on'], [] + ) + + def test_depends_store_after(self): + job_root = Job(self.method) + job_root.store() + job_a = Job(self.method) + job_a.add_depends({job_root}) + job_a.store() + + # as the reverse dependency has been added after the root job has been + # stored, it is not reflected in DB + self.assertEqual( + job_root.db_record().dependencies['reverse_depends_on'], [] + ) + + # a new store will write it + job_root.store() + self.assertEqual( + job_root.db_record().dependencies['reverse_depends_on'], + [job_a.uuid] + ) + + def test_depends_load(self): + job_root = Job(self.method) + job_a = Job(self.method) + job_a.add_depends({job_root}) + + job_root.store() + job_a.store() + + read_job_root = Job.load(self.env, job_root.uuid) + self.assertEqual(read_job_root.reverse_depends_on, {job_a}) + + read_job_a = Job.load(self.env, job_a.uuid) + self.assertEqual(read_job_a.depends_on, {job_root}) From 3b8ad1223951fd39866f71f9d017b58f95a64b6b Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 2 Jul 2019 22:39:26 +0200 Subject: [PATCH 73/88] Add wait dependencies state --- test_queue_job/tests/test_dependencies.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 159e949d28..c334839e9a 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -5,6 +5,8 @@ from odoo.addons.queue_job.job import ( Job, + WAIT_DEPENDENCIES, + PENDING, ) @@ -109,3 +111,23 @@ def test_depends_load(self): read_job_a = Job.load(self.env, job_a.uuid) self.assertEqual(read_job_a.depends_on, {job_root}) + + def test_depends_enqueue_waiting_single(self): + job_root = Job(self.method) + job_a = Job(self.method) + job_a.add_depends({job_root}) + + job_root.store() + job_a.store() + + self.assertEqual(job_a.state, WAIT_DEPENDENCIES) + + # these are the steps run by RunJobController + job_root.perform() + job_root.set_done() + job_root.store() + + job_root.enqueue_waiting() + + # will be picked up by the jobrunner + self.assertEqual(job_a.state, PENDING) From 67555d8c3e0bdd5addb534afc1965cc6315d59db Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 5 Jul 2019 23:48:31 +0200 Subject: [PATCH 74/88] Add API for Delayables --- test_queue_job/tests/__init__.py | 1 + test_queue_job/tests/test_delayable.py | 251 ++++++++++++++++++++++ test_queue_job/tests/test_dependencies.py | 7 +- 3 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 test_queue_job/tests/test_delayable.py diff --git a/test_queue_job/tests/__init__.py b/test_queue_job/tests/__init__.py index 0984a41db0..bf5295e359 100644 --- a/test_queue_job/tests/__init__.py +++ b/test_queue_job/tests/__init__.py @@ -1,4 +1,5 @@ from . import test_autovacuum +from . import test_delayable from . import test_dependencies from . import test_job from . import test_job_auto_delay diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py new file mode 100644 index 0000000000..2456bc4ef0 --- /dev/null +++ b/test_queue_job/tests/test_delayable.py @@ -0,0 +1,251 @@ +# copyright 2019 Camptocamp +# Copyright 2019 Guewen Baconnier +# license agpl-3.0 or later (http://www.gnu.org/licenses/agpl.html) + + +import odoo.tests.common as common + +from odoo.addons.queue_job.delay import ( + Delayable, + DelayableGroup, + DelayableChain, + chain, + group, +) + + +class TestDelayable(common.TransactionCase): + + def setUp(self): + super().setUp() + self.queue_job = self.env['queue.job'] + self.test_model = self.env['test.queue.job'] + self.method = self.env['test.queue.job'].testing_method + self.node = Delayable(self.test_model).testing_method(1) + self.node2 = Delayable(self.test_model).testing_method(2) + self.node3 = Delayable(self.test_model).testing_method(3) + self.node4 = Delayable(self.test_model).testing_method(4) + self.node5 = Delayable(self.test_model).testing_method(5) + self.node6 = Delayable(self.test_model).testing_method(6) + self.node7 = Delayable(self.test_model).testing_method(7) + self.node8 = Delayable(self.test_model).testing_method(8) + + def test_delayable_delay_single(self): + self.node.delay() + self.assert_generated_job(self.node) + + def assert_generated_job(self, *nodes): + for node in nodes: + self.assertTrue(node._generated_job) + job = node._generated_job + self.assertTrue(job.db_record().id) + + def assert_depends_on(self, delayable, parent_delayables): + self.assertEqual( + delayable._generated_job._depends_on, + {parent._generated_job for parent in parent_delayables} + ) + + def assert_reverse_depends_on(self, delayable, child_delayables): + self.assertEqual( + set(delayable._generated_job._reverse_depends_on), + {child._generated_job for child in child_delayables} + ) + + def assert_dependencies(self, nodes): + reverse_dependencies = {} + for child, parents in nodes.items(): + self.assert_depends_on(child, parents) + for parent in parents: + reverse_dependencies.setdefault(parent, set()).add(child) + for parent, children in reverse_dependencies.items(): + self.assert_reverse_depends_on(parent, children) + + def test_delayable_delay_done(self): + self.node.done(self.node2).delay() + self.assert_generated_job(self.node, self.node2) + self.assert_dependencies({self.node: {}, self.node2: {self.node}}) + + def test_delayable_delay_done_multi(self): + self.node.done(self.node2, self.node3).delay() + self.assert_generated_job(self.node, self.node2, self.node3) + self.assert_dependencies({ + self.node: {}, self.node2: {self.node}, self.node3: {self.node} + }) + + def test_delayable_delay_group(self): + DelayableGroup(self.node, self.node2, self.node3).delay() + self.assert_generated_job(self.node, self.node2, self.node3) + self.assert_dependencies( + {self.node: {}, self.node2: {}, self.node3: {}} + ) + + def test_group_function(self): + group(self.node, self.node2, self.node3).delay() + self.assert_generated_job(self.node, self.node2, self.node3) + self.assert_dependencies( + {self.node: {}, self.node2: {}, self.node3: {}} + ) + + def test_delayable_delay_job_after_group(self): + DelayableGroup(self.node, self.node2).done(self.node3).delay() + self.assert_generated_job(self.node, self.node2, self.node3) + self.assert_dependencies({ + self.node: {}, self.node2: {}, self.node3: {self.node, self.node2} + }) + + def test_delayable_delay_group_after_group(self): + g1 = DelayableGroup(self.node, self.node2) + g2 = DelayableGroup(self.node3, self.node4) + g1.done(g2).delay() + self.assert_generated_job( + self.node, self.node2, self.node3, self.node4 + ) + self.assert_dependencies({ + self.node: {}, self.node2: {}, + self.node3: {self.node, self.node2}, + self.node4: {self.node, self.node2}, + }) + + def test_delayable_delay_implicit_group_after_group(self): + g1 = DelayableGroup(self.node, self.node2).done(self.node3, self.node4) + g1.delay() + self.assert_generated_job( + self.node, self.node2, self.node3, self.node4 + ) + self.assert_dependencies({ + self.node: {}, self.node2: {}, + self.node3: {self.node, self.node2}, + self.node4: {self.node, self.node2}, + }) + + def test_delayable_delay_group_after_group_after_group(self): + g1 = DelayableGroup(self.node) + g2 = DelayableGroup(self.node2) + g3 = DelayableGroup(self.node3) + g4 = DelayableGroup(self.node4) + g1.done(g2.done(g3.done(g4))).delay() + self.assert_generated_job( + self.node, self.node2, self.node3, self.node4 + ) + self.assert_dependencies({ + self.node: {}, + self.node2: {self.node}, + self.node3: {self.node2}, + self.node4: {self.node3}, + }) + + def test_delayable_diamond(self): + g1 = DelayableGroup(self.node2, self.node3) + g1.done(self.node4) + self.node.done(g1) + self.node.delay() + self.assert_generated_job( + self.node, self.node2, self.node3, self.node4 + ) + self.assert_dependencies({ + self.node: {}, + self.node2: {self.node}, + self.node3: {self.node}, + self.node4: {self.node2, self.node3}, + }) + + def test_delayable_chain(self): + c1 = DelayableChain(self.node, self.node2, self.node3) + c1.delay() + self.assert_generated_job( + self.node, self.node2, self.node3 + ) + self.assert_dependencies({ + self.node: {}, + self.node2: {self.node}, + self.node3: {self.node2}, + }) + + def test_chain_function(self): + c1 = chain(self.node, self.node2, self.node3) + c1.delay() + self.assert_generated_job( + self.node, self.node2, self.node3 + ) + self.assert_dependencies({ + self.node: {}, + self.node2: {self.node}, + self.node3: {self.node2}, + }) + + def test_delayable_chain_after_job(self): + c1 = DelayableChain(self.node2, self.node3, self.node4) + self.node.done(c1) + self.node.delay() + self.assert_generated_job( + self.node, self.node2, self.node3, self.node4 + ) + self.assert_dependencies({ + self.node: {}, + self.node2: {self.node}, + self.node3: {self.node2}, + self.node4: {self.node3}, + }) + + def test_delayable_chain_after_chain(self): + chain1 = DelayableChain(self.node, self.node2, self.node3) + chain2 = DelayableChain(self.node4, self.node5, self.node6) + chain1.done(chain2) + chain1.delay() + self.assert_generated_job( + self.node, self.node2, self.node3, + self.node4, self.node5, self.node6, + ) + self.assert_dependencies({ + self.node: {}, + self.node2: {self.node}, + self.node3: {self.node2}, + self.node4: {self.node3}, + self.node5: {self.node4}, + self.node6: {self.node5}, + }) + + def test_delayable_group_of_chain(self): + chain1 = DelayableChain(self.node, self.node2) + chain2 = DelayableChain(self.node3, self.node4) + chain3 = DelayableChain(self.node5, self.node6) + chain4 = DelayableChain(self.node7, self.node8) + g1 = DelayableGroup(chain1, chain2).done(chain3, chain4) + g1.delay() + self.assert_generated_job( + self.node, self.node2, self.node3, self.node4, + self.node5, self.node6, self.node7, self.node8, + ) + self.assert_dependencies({ + self.node: {}, + self.node3: {}, + self.node2: {self.node}, + self.node4: {self.node3}, + self.node5: {self.node4, self.node2}, + self.node7: {self.node4, self.node2}, + self.node6: {self.node5}, + self.node8: {self.node7}, + }) + + def test_log_not_delayed(self): + logger_name = 'odoo.addons.queue_job' + with self.assertLogs(logger_name, level='WARN') as test: + # When a Delayable never gets a delay() call, + # when the GC collects it and calls __del__, a warning + # will be displayed. We cannot test this is a scenario + # using the GC as it isn't predictable. Call __del__ + # directly + self.node.__del__() + expected = ( + 'WARNING:odoo.addons.queue_job.delay:Delayable ' + 'Delayable(test.queue.job().testing_method((1,), {}))' + ' was prepared but never delayed' + ) + self.assertEqual(test.output, [expected]) + + def test_delay_job_already_exists(self): + self.node2.delay() + self.node.done(self.node2).delay() + self.assert_generated_job(self.node, self.node2) + self.assert_dependencies({self.node: {}, self.node2: {self.node}}) diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index c334839e9a..0f4b56fc4a 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -129,5 +129,8 @@ def test_depends_enqueue_waiting_single(self): job_root.enqueue_waiting() - # will be picked up by the jobrunner - self.assertEqual(job_a.state, PENDING) + # Will be picked up by the jobrunner. + # Warning: as the state has been changed in memory but + # not in the job_a instance, here, we re-read it. + # In practice, it won't be an issue for the jobrunner. + self.assertEqual(Job.load(self.env, job_a.uuid).state, PENDING) From 8f1dc777ad1175d13353a5ae48c4019ae48f29ce Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 26 May 2021 20:33:08 +0200 Subject: [PATCH 75/88] Add a graph UUID A graph of jobs always share the same graph_uuid, which can be used to group jobs, but is also a faster way to find all the jobs of the graph. Then we can use the dependencies field to build the whole graph from the pre-selection of jobs. --- test_queue_job/tests/test_dependencies.py | 111 +++++++++++++++++++++- 1 file changed, 106 insertions(+), 5 deletions(-) diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 0f4b56fc4a..cadf1246b7 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -10,12 +10,13 @@ ) -class TestJobDependencies(common.TransactionCase): +class TestJobDependencies(common.SavepointCase): - def setUp(self): - super().setUp() - self.queue_job = self.env['queue.job'] - self.method = self.env['test.queue.job'].testing_method + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.queue_job = cls.env['queue.job'] + cls.method = cls.env['test.queue.job'].testing_method def test_depends_store(self): job_root = Job(self.method) @@ -134,3 +135,103 @@ def test_depends_enqueue_waiting_single(self): # not in the job_a instance, here, we re-read it. # In practice, it won't be an issue for the jobrunner. self.assertEqual(Job.load(self.env, job_a.uuid).state, PENDING) + + def test_dependency_graph(self): + job_root = Job(self.method) + job_lvl1_a = Job(self.method) + job_lvl1_a.add_depends({job_root}) + job_lvl1_b = Job(self.method) + job_lvl1_b.add_depends({job_root}) + job_lvl2_a = Job(self.method) + job_lvl2_a.add_depends({job_lvl1_a}) + + job_2_root = Job(self.method) + job_2_child = Job(self.method) + job_2_child.add_depends({job_2_root}) + + # Jobs must be stored after the dependencies are set up. + # (Or if not, a new store must be called on the parent) + job_root.store() + job_lvl1_a.store() + job_lvl1_b.store() + job_lvl2_a.store() + + job_2_root.store() + job_2_child.store() + + record_root = job_root.db_record() + record_lvl1_a = job_lvl1_a.db_record() + record_lvl1_b = job_lvl1_b.db_record() + record_lvl2_a = job_lvl2_a.db_record() + + record_2_root = job_2_root.db_record() + record_2_child = job_2_child.db_record() + + expected_nodes = sorted( + [record_root.id, record_lvl1_a.id, + record_lvl1_b.id, record_lvl2_a.id] + ) + expected_edges = sorted( + [ + (record_root.id, record_lvl1_a.id), + (record_lvl1_a.id, record_lvl2_a.id), + (record_root.id, record_lvl1_b.id), + ] + ) + + records = [record_root, record_lvl1_a, record_lvl1_b, record_lvl2_a] + for record in records: + self.assertEqual( + sorted(record.dependency_graph['nodes']), + expected_nodes + ) + self.assertEqual( + sorted(record.dependency_graph['edges']), + expected_edges + ) + + expected_nodes = sorted([record_2_root.id, record_2_child.id]) + expected_edges = sorted([(record_2_root.id, record_2_child.id)]) + + for record in [record_2_root, record_2_child]: + self.assertEqual( + sorted(record.dependency_graph['nodes']), + expected_nodes + ) + self.assertEqual( + sorted(record.dependency_graph['edges']), + expected_edges + ) + + def test_no_dependency_graph_single_job(self): + job_root = Job(self.method) + job_root.store() + self.assertEqual(job_root.db_record().dependency_graph, {}) + + def test_depends_graph_uuid(self): + """All jobs with dependencies share the same graph uuid""" + job_root = Job(self.method) + job_lvl1_a = Job(self.method) + job_lvl1_a.add_depends({job_root}) + job_lvl1_b = Job(self.method) + job_lvl1_b.add_depends({job_root}) + job_lvl2_a = Job(self.method) + job_lvl2_a.add_depends({job_lvl1_a}) + + # Jobs must be stored after the dependencies are set up. + # (Or if not, a new store must be called on the parent) + job_root.store() + job_lvl1_a.store() + job_lvl1_b.store() + job_lvl2_a.store() + + jobs = [job_root, job_lvl1_a, job_lvl1_b, job_lvl2_a] + self.assertTrue(job_root.graph_uuid) + self.assertEqual(len(set(j.graph_uuid for j in jobs)), 1) + self.assertEqual(job_root.graph_uuid, job_root.db_record().graph_uuid) + self.assertEqual(job_lvl1_a.graph_uuid, + job_lvl1_a.db_record().graph_uuid) + self.assertEqual(job_lvl1_b.graph_uuid, + job_lvl1_b.db_record().graph_uuid) + self.assertEqual(job_lvl2_a.graph_uuid, + job_lvl2_a.db_record().graph_uuid) From 4230c8502f84479b9d7f579a719f9683d759fd36 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 26 May 2021 22:34:16 +0200 Subject: [PATCH 76/88] Ignore requeues on dependency jobs waiting on parent jobs Jobs waiting that their dependencies are executed cannot be requeued. They have to keep waiting their turn. --- test_queue_job/tests/test_job.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index cdcdec01ce..2133f3fc73 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -21,6 +21,7 @@ RETRY_INTERVAL, STARTED, Job, + WAIT_DEPENDENCIES, ) from .common import JobCommonCase @@ -493,6 +494,23 @@ def test_requeue(self): stored.requeue() self.assertEqual(stored.state, PENDING) + def test_requeue_wait_dependencies_not_touched(self): + job_root = Job(self.env['test.queue.job'].testing_method) + job_child = Job(self.env['test.queue.job'].testing_method) + job_child.add_depends({job_root}) + job_root.store() + job_child.store() + + record_root = job_root.db_record() + record_child = job_child.db_record() + self.assertEqual(record_root.state, PENDING) + self.assertEqual(record_child.state, WAIT_DEPENDENCIES) + record_root.write({'state': 'failed'}) + + (record_root + record_child).requeue() + self.assertEqual(record_root.state, PENDING) + self.assertEqual(record_child.state, WAIT_DEPENDENCIES) + def test_message_when_write_fail(self): stored = self._create_job() stored.write({"state": "failed"}) From 4276b1c47b07e52352e21f6a1afab05e0fe384c3 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 27 May 2021 21:08:03 +0200 Subject: [PATCH 77/88] Fix warnings in tests As jobs are created in the setup, but not always delayed, the __del__ warning that the job was not delayed is triggered. Create only the jobs necessary for each test. --- test_queue_job/tests/test_delayable.py | 277 ++++++++++++++----------- 1 file changed, 155 insertions(+), 122 deletions(-) diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py index 2456bc4ef0..b2cfa76a7a 100644 --- a/test_queue_job/tests/test_delayable.py +++ b/test_queue_job/tests/test_delayable.py @@ -2,6 +2,7 @@ # Copyright 2019 Guewen Baconnier # license agpl-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import odoo import odoo.tests.common as common @@ -21,18 +22,9 @@ def setUp(self): self.queue_job = self.env['queue.job'] self.test_model = self.env['test.queue.job'] self.method = self.env['test.queue.job'].testing_method - self.node = Delayable(self.test_model).testing_method(1) - self.node2 = Delayable(self.test_model).testing_method(2) - self.node3 = Delayable(self.test_model).testing_method(3) - self.node4 = Delayable(self.test_model).testing_method(4) - self.node5 = Delayable(self.test_model).testing_method(5) - self.node6 = Delayable(self.test_model).testing_method(6) - self.node7 = Delayable(self.test_model).testing_method(7) - self.node8 = Delayable(self.test_model).testing_method(8) - def test_delayable_delay_single(self): - self.node.delay() - self.assert_generated_job(self.node) + def job_node(self, id_): + return Delayable(self.test_model).testing_method(id_) def assert_generated_job(self, *nodes): for node in nodes: @@ -61,171 +53,209 @@ def assert_dependencies(self, nodes): for parent, children in reverse_dependencies.items(): self.assert_reverse_depends_on(parent, children) + def test_delayable_delay_single(self): + node = self.job_node(1) + node.delay() + self.assert_generated_job(node) + def test_delayable_delay_done(self): - self.node.done(self.node2).delay() - self.assert_generated_job(self.node, self.node2) - self.assert_dependencies({self.node: {}, self.node2: {self.node}}) + node = self.job_node(1) + node2 = self.job_node(2) + node.done(node2).delay() + self.assert_generated_job(node, node2) + self.assert_dependencies({node: {}, node2: {node}}) def test_delayable_delay_done_multi(self): - self.node.done(self.node2, self.node3).delay() - self.assert_generated_job(self.node, self.node2, self.node3) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node.done(node2, node3).delay() + self.assert_generated_job(node, node2, node3) self.assert_dependencies({ - self.node: {}, self.node2: {self.node}, self.node3: {self.node} + node: {}, node2: {node}, node3: {node} }) def test_delayable_delay_group(self): - DelayableGroup(self.node, self.node2, self.node3).delay() - self.assert_generated_job(self.node, self.node2, self.node3) - self.assert_dependencies( - {self.node: {}, self.node2: {}, self.node3: {}} - ) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + DelayableGroup(node, node2, node3).delay() + self.assert_generated_job(node, node2, node3) + self.assert_dependencies({node: {}, node2: {}, node3: {}}) def test_group_function(self): - group(self.node, self.node2, self.node3).delay() - self.assert_generated_job(self.node, self.node2, self.node3) - self.assert_dependencies( - {self.node: {}, self.node2: {}, self.node3: {}} - ) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + group(node, node2, node3).delay() + self.assert_generated_job(node, node2, node3) + self.assert_dependencies({node: {}, node2: {}, node3: {}}) def test_delayable_delay_job_after_group(self): - DelayableGroup(self.node, self.node2).done(self.node3).delay() - self.assert_generated_job(self.node, self.node2, self.node3) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + DelayableGroup(node, node2).done(node3).delay() + self.assert_generated_job(node, node2, node3) self.assert_dependencies({ - self.node: {}, self.node2: {}, self.node3: {self.node, self.node2} + node: {}, node2: {}, node3: {node, node2} }) def test_delayable_delay_group_after_group(self): - g1 = DelayableGroup(self.node, self.node2) - g2 = DelayableGroup(self.node3, self.node4) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + g1 = DelayableGroup(node, node2) + g2 = DelayableGroup(node3, node4) g1.done(g2).delay() - self.assert_generated_job( - self.node, self.node2, self.node3, self.node4 - ) + self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ - self.node: {}, self.node2: {}, - self.node3: {self.node, self.node2}, - self.node4: {self.node, self.node2}, + node: {}, node2: {}, + node3: {node, node2}, + node4: {node, node2}, }) def test_delayable_delay_implicit_group_after_group(self): - g1 = DelayableGroup(self.node, self.node2).done(self.node3, self.node4) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + g1 = DelayableGroup(node, node2).done(node3, node4) g1.delay() - self.assert_generated_job( - self.node, self.node2, self.node3, self.node4 - ) + self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ - self.node: {}, self.node2: {}, - self.node3: {self.node, self.node2}, - self.node4: {self.node, self.node2}, + node: {}, node2: {}, + node3: {node, node2}, + node4: {node, node2}, }) def test_delayable_delay_group_after_group_after_group(self): - g1 = DelayableGroup(self.node) - g2 = DelayableGroup(self.node2) - g3 = DelayableGroup(self.node3) - g4 = DelayableGroup(self.node4) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + g1 = DelayableGroup(node) + g2 = DelayableGroup(node2) + g3 = DelayableGroup(node3) + g4 = DelayableGroup(node4) g1.done(g2.done(g3.done(g4))).delay() - self.assert_generated_job( - self.node, self.node2, self.node3, self.node4 - ) + self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ - self.node: {}, - self.node2: {self.node}, - self.node3: {self.node2}, - self.node4: {self.node3}, + node: {}, + node2: {node}, + node3: {node2}, + node4: {node3}, }) def test_delayable_diamond(self): - g1 = DelayableGroup(self.node2, self.node3) - g1.done(self.node4) - self.node.done(g1) - self.node.delay() - self.assert_generated_job( - self.node, self.node2, self.node3, self.node4 - ) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + g1 = DelayableGroup(node2, node3) + g1.done(node4) + node.done(g1) + node.delay() + self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ - self.node: {}, - self.node2: {self.node}, - self.node3: {self.node}, - self.node4: {self.node2, self.node3}, + node: {}, + node2: {node}, + node3: {node}, + node4: {node2, node3}, }) def test_delayable_chain(self): - c1 = DelayableChain(self.node, self.node2, self.node3) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + c1 = DelayableChain(node, node2, node3) c1.delay() - self.assert_generated_job( - self.node, self.node2, self.node3 - ) + self.assert_generated_job(node, node2, node3) self.assert_dependencies({ - self.node: {}, - self.node2: {self.node}, - self.node3: {self.node2}, + node: {}, + node2: {node}, + node3: {node2}, }) def test_chain_function(self): - c1 = chain(self.node, self.node2, self.node3) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + c1 = chain(node, node2, node3) c1.delay() - self.assert_generated_job( - self.node, self.node2, self.node3 - ) + self.assert_generated_job(node, node2, node3) self.assert_dependencies({ - self.node: {}, - self.node2: {self.node}, - self.node3: {self.node2}, + node: {}, + node2: {node}, + node3: {node2}, }) def test_delayable_chain_after_job(self): - c1 = DelayableChain(self.node2, self.node3, self.node4) - self.node.done(c1) - self.node.delay() - self.assert_generated_job( - self.node, self.node2, self.node3, self.node4 - ) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + c1 = DelayableChain(node2, node3, node4) + node.done(c1) + node.delay() + self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ - self.node: {}, - self.node2: {self.node}, - self.node3: {self.node2}, - self.node4: {self.node3}, + node: {}, + node2: {node}, + node3: {node2}, + node4: {node3}, }) def test_delayable_chain_after_chain(self): - chain1 = DelayableChain(self.node, self.node2, self.node3) - chain2 = DelayableChain(self.node4, self.node5, self.node6) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + node5 = self.job_node(5) + node6 = self.job_node(6) + chain1 = DelayableChain(node, node2, node3) + chain2 = DelayableChain(node4, node5, node6) chain1.done(chain2) chain1.delay() - self.assert_generated_job( - self.node, self.node2, self.node3, - self.node4, self.node5, self.node6, - ) + self.assert_generated_job(node, node2, node3, node4, node5, node6) self.assert_dependencies({ - self.node: {}, - self.node2: {self.node}, - self.node3: {self.node2}, - self.node4: {self.node3}, - self.node5: {self.node4}, - self.node6: {self.node5}, + node: {}, + node2: {node}, + node3: {node2}, + node4: {node3}, + node5: {node4}, + node6: {node5}, }) def test_delayable_group_of_chain(self): - chain1 = DelayableChain(self.node, self.node2) - chain2 = DelayableChain(self.node3, self.node4) - chain3 = DelayableChain(self.node5, self.node6) - chain4 = DelayableChain(self.node7, self.node8) + node = self.job_node(1) + node2 = self.job_node(2) + node3 = self.job_node(3) + node4 = self.job_node(4) + node5 = self.job_node(5) + node6 = self.job_node(6) + node7 = self.job_node(7) + node8 = self.job_node(8) + chain1 = DelayableChain(node, node2) + chain2 = DelayableChain(node3, node4) + chain3 = DelayableChain(node5, node6) + chain4 = DelayableChain(node7, node8) g1 = DelayableGroup(chain1, chain2).done(chain3, chain4) g1.delay() self.assert_generated_job( - self.node, self.node2, self.node3, self.node4, - self.node5, self.node6, self.node7, self.node8, + node, node2, node3, node4, + node5, node6, node7, node8, ) self.assert_dependencies({ - self.node: {}, - self.node3: {}, - self.node2: {self.node}, - self.node4: {self.node3}, - self.node5: {self.node4, self.node2}, - self.node7: {self.node4, self.node2}, - self.node6: {self.node5}, - self.node8: {self.node7}, + node: {}, + node3: {}, + node2: {node}, + node4: {node3}, + node5: {node4, node2}, + node7: {node4, node2}, + node6: {node5}, + node8: {node7}, }) def test_log_not_delayed(self): @@ -236,7 +266,8 @@ def test_log_not_delayed(self): # will be displayed. We cannot test this is a scenario # using the GC as it isn't predictable. Call __del__ # directly - self.node.__del__() + node = self.job_node(1) + node.__del__() expected = ( 'WARNING:odoo.addons.queue_job.delay:Delayable ' 'Delayable(test.queue.job().testing_method((1,), {}))' @@ -245,7 +276,9 @@ def test_log_not_delayed(self): self.assertEqual(test.output, [expected]) def test_delay_job_already_exists(self): - self.node2.delay() - self.node.done(self.node2).delay() - self.assert_generated_job(self.node, self.node2) - self.assert_dependencies({self.node: {}, self.node2: {self.node}}) + node = self.job_node(1) + node2 = self.job_node(2) + node2.delay() + node.done(node2).delay() + self.assert_generated_job(node, node2) + self.assert_dependencies({node: {}, node2: {node}}) From 5d7a9ebef2b271d258ec4438f7323452e52e4cc7 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 27 May 2021 22:17:33 +0200 Subject: [PATCH 78/88] Improve display of jobs graph widget --- test_queue_job/tests/test_dependencies.py | 60 ++++++++++++++++++++--- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index cadf1246b7..61298ac7e1 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -167,10 +167,37 @@ def test_dependency_graph(self): record_2_root = job_2_root.db_record() record_2_child = job_2_child.db_record() - expected_nodes = sorted( - [record_root.id, record_lvl1_a.id, - record_lvl1_b.id, record_lvl2_a.id] - ) + expected_nodes = [ + { + 'id': record_root.id, + 'title': 'Method used for tests
' + 'test.queue.job().testing_method()', + 'color': '#D2E5FF', + 'border': '#2B7CE9', + 'shadow': True + }, { + 'id': record_lvl1_a.id, + 'title': 'Method used for tests
' + 'test.queue.job().testing_method()', + 'color': '#D2E5FF', + 'border': '#2B7CE9', + 'shadow': True + }, { + 'id': record_lvl1_b.id, + 'title': 'Method used for tests
' + 'test.queue.job().testing_method()', + 'color': '#D2E5FF', + 'border': '#2B7CE9', + 'shadow': True + }, { + 'id': record_lvl2_a.id, + 'title': 'Method used for tests
' + 'test.queue.job().testing_method()', + 'color': '#D2E5FF', + 'border': '#2B7CE9', + 'shadow': True + } + ] expected_edges = sorted( [ (record_root.id, record_lvl1_a.id), @@ -180,9 +207,11 @@ def test_dependency_graph(self): ) records = [record_root, record_lvl1_a, record_lvl1_b, record_lvl2_a] + for record in records: self.assertEqual( - sorted(record.dependency_graph['nodes']), + sorted(record.dependency_graph['nodes'], + key=lambda d: d["id"]), expected_nodes ) self.assertEqual( @@ -190,12 +219,29 @@ def test_dependency_graph(self): expected_edges ) - expected_nodes = sorted([record_2_root.id, record_2_child.id]) + expected_nodes = [ + { + 'id': record_2_root.id, + 'title': 'Method used for tests
' + 'test.queue.job().testing_method()', + 'color': '#D2E5FF', + 'border': '#2B7CE9', + 'shadow': True + }, { + 'id': record_2_child.id, + 'title': 'Method used for tests
' + 'test.queue.job().testing_method()', + 'color': '#D2E5FF', + 'border': '#2B7CE9', + 'shadow': True + } + ] expected_edges = sorted([(record_2_root.id, record_2_child.id)]) for record in [record_2_root, record_2_child]: self.assertEqual( - sorted(record.dependency_graph['nodes']), + sorted(record.dependency_graph['nodes'], + key=lambda d: d["id"]), expected_nodes ) self.assertEqual( From bccc555053bc3ef83df45059c2b5e9a0d48d608e Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 28 May 2021 17:59:28 +0200 Subject: [PATCH 79/88] Add powerful context manager for running tests on jobs * mock_jobs context manager with assert methods to check enqueued jobs * jobs are not stored in database, yet they can be inspected and performed during the test * the context manager works with graphs enqueued by delayable and with with_delay() * direct execution of jobs works with graphs by executing them synchronously following their topological sort --- test_queue_job/models/test_models.py | 38 ++++ test_queue_job/tests/__init__.py | 1 + test_queue_job/tests/test_delay_mocks.py | 226 +++++++++++++++++++++++ test_queue_job/tests/test_delayable.py | 17 +- 4 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 test_queue_job/tests/test_delay_mocks.py diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index 4cff25c380..d37a6fc49d 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -2,6 +2,8 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) from odoo import api, fields, models +from odoo.addons.queue_job.delay import chain +from odoo.addons.queue_job.job import identity_exact from odoo.addons.queue_job.exception import RetryableJobError @@ -51,6 +53,17 @@ def testing_method(self, *args, **kwargs): return self.env.context return args, kwargs + def create_ir_logging(self, message, level="info"): + return self.env["ir.logging"].create({ + "name": "test_queue_job", + "type": "server", + "dbname": self.env.cr.dbname, + "message": message, + "path": "job", + "func": "create_ir_logging", + "line": 1, + }) + def no_description(self): return @@ -101,6 +114,31 @@ def _job_store_values(self, job): value += "_BUT_FAILED" return {"additional_info": value} + def button_that_uses_with_delay(self): + self.with_delay( + channel="root.test", + description="Test", + eta=15, + identity_key=identity_exact, + max_retries=1, + priority=15, + ).testing_method(1, foo=2) + + def button_that_uses_delayable_chain(self): + delayables = chain( + self.delayable( + channel="root.test", + description="Test", + eta=15, + identity_key=identity_exact, + max_retries=1, + priority=15, + ).testing_method(1, foo=2), + self.delayable().testing_method('x', foo='y'), + self.delayable().no_description(), + ) + delayables.delay() + class TestQueueChannel(models.Model): diff --git a/test_queue_job/tests/__init__.py b/test_queue_job/tests/__init__.py index bf5295e359..dc59429e71 100644 --- a/test_queue_job/tests/__init__.py +++ b/test_queue_job/tests/__init__.py @@ -5,3 +5,4 @@ from . import test_job_auto_delay from . import test_job_channels from . import test_related_actions +from . import test_delay_mocks diff --git a/test_queue_job/tests/test_delay_mocks.py b/test_queue_job/tests/test_delay_mocks.py new file mode 100644 index 0000000000..4385840404 --- /dev/null +++ b/test_queue_job/tests/test_delay_mocks.py @@ -0,0 +1,226 @@ +# Copyright 2021 Guewen Baconnier +# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html) + +import os + +from unittest import mock + +import odoo.tests.common as common + +from odoo.addons.queue_job.delay import Delayable +from odoo.addons.queue_job.job import identity_exact +from odoo.addons.queue_job.tests.common import mock_with_delay, mock_jobs + + +class TestDelayMocks(common.SavepointCase): + + def test_mock_jobs_on_with_delay(self): + with mock_jobs() as enqueued_jobs: + self.env['test.queue.job'].button_that_uses_with_delay() + enqueued_jobs.assert_jobs_count(1) + enqueued_jobs.assert_jobs_count( + 1, only=self.env['test.queue.job'].testing_method + ) + + enqueued_jobs.assert_enqueued_job( + self.env['test.queue.job'].testing_method, + args=(1,), + kwargs={"foo": 2}, + properties=dict( + channel="root.test", + description="Test", + eta=15, + identity_key=identity_exact, + max_retries=1, + priority=15, + ) + ) + + def test_mock_jobs_on_graph(self): + with mock_jobs() as jobs_tester: + self.env['test.queue.job'].button_that_uses_delayable_chain() + jobs_tester.assert_jobs_count(3) + jobs_tester.assert_jobs_count( + 2, only=self.env['test.queue.job'].testing_method + ) + jobs_tester.assert_jobs_count( + 1, only=self.env['test.queue.job'].no_description + ) + + jobs_tester.assert_enqueued_job( + self.env['test.queue.job'].testing_method, + args=(1,), + kwargs={"foo": 2}, + properties=dict( + channel="root.test", + description="Test", + eta=15, + identity_key=identity_exact, + max_retries=1, + priority=15, + ) + ) + jobs_tester.assert_enqueued_job( + self.env['test.queue.job'].testing_method, + args=("x",), + kwargs={"foo": "y"}, + ) + jobs_tester.assert_enqueued_job( + self.env['test.queue.job'].no_description, + ) + + jobs_tester.perform_enqueued_jobs() + + def test_mock_jobs_perform(self): + with mock_jobs() as jobs_tester: + model = self.env["test.queue.job"] + model.with_delay(priority=1).create_ir_logging( + "test_mock_jobs_perform single" + ) + node = Delayable(model).create_ir_logging( + "test_mock_jobs_perform graph 1" + ) + node2 = Delayable(model).create_ir_logging( + "test_mock_jobs_perform graph 2" + ) + node3 = Delayable(model).create_ir_logging( + "test_mock_jobs_perform graph 3" + ) + node2.done(node3) + node3.done(node) + node2.delay() + + # jobs are not executed + logs = self.env["ir.logging"].search( + [ + ("name", "=", "test_queue_job"), + ("func", "=", "create_ir_logging"), + ], + order="id asc", + ) + self.assertEqual(len(logs), 0) + + jobs_tester.assert_jobs_count(4) + + # perform the jobs + jobs_tester.perform_enqueued_jobs() + + logs = self.env["ir.logging"].search( + [ + ("name", "=", "test_queue_job"), + ("func", "=", "create_ir_logging"), + ], + order="id asc", + ) + self.assertEqual(len(logs), 4) + + # check if they are executed in order + self.assertEqual(logs[0].message, "test_mock_jobs_perform single") + self.assertEqual(logs[1].message, "test_mock_jobs_perform graph 2") + self.assertEqual(logs[2].message, "test_mock_jobs_perform graph 3") + self.assertEqual(logs[3].message, "test_mock_jobs_perform graph 1") + + def test_mock_with_delay(self): + with mock_with_delay() as (delayable_cls, delayable): + self.env['test.queue.job'].button_that_uses_with_delay() + + self.assertEqual(delayable_cls.call_count, 1) + # arguments passed in 'with_delay()' + delay_args, delay_kwargs = delayable_cls.call_args + self.assertEqual( + delay_args, (self.env["test.queue.job"],) + ) + self.assertDictEqual(delay_kwargs, { + "channel": "root.test", + "description": "Test", + "eta": 15, + "identity_key": identity_exact, + "max_retries": 1, + "priority": 15, + }) + + # check what's passed to the job method 'testing_method' + self.assertEqual(delayable.testing_method.call_count, 1) + delay_args, delay_kwargs = delayable.testing_method.call_args + self.assertEqual(delay_args, (1,)) + self.assertDictEqual(delay_kwargs, {"foo": 2}) + + @mock.patch.dict(os.environ, {"TEST_QUEUE_JOB_NO_DELAY": "1"}) + def test_delay_graph_direct_exec_env_var(self): + node = Delayable(self.env["test.queue.job"]).create_ir_logging( + "test_delay_graph_direct_exec 1" + ) + node2 = Delayable(self.env["test.queue.job"]).create_ir_logging( + "test_delay_graph_direct_exec 2" + ) + node2.done(node) + node2.delay() + # jobs are executed directly + logs = self.env["ir.logging"].search( + [ + ("name", "=", "test_queue_job"), + ("func", "=", "create_ir_logging"), + ], + order="id asc", + ) + self.assertEqual(len(logs), 2) + # check if they are executed in order + self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2") + self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1") + + def test_delay_graph_direct_exec_context_key(self): + node = Delayable( + self.env["test.queue.job"].with_context( + test_queue_job_no_delay=True + ) + ).create_ir_logging( + "test_delay_graph_direct_exec 1" + ) + node2 = Delayable(self.env["test.queue.job"]).create_ir_logging( + "test_delay_graph_direct_exec 2" + ) + node2.done(node) + node2.delay() + # jobs are executed directly + logs = self.env["ir.logging"].search( + [ + ("name", "=", "test_queue_job"), + ("func", "=", "create_ir_logging"), + ], + order="id asc", + ) + self.assertEqual(len(logs), 2) + # check if they are executed in order + self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2") + self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1") + + @mock.patch.dict(os.environ, {"TEST_QUEUE_JOB_NO_DELAY": "1"}) + def test_delay_with_delay_direct_exec_env_var(self): + model = self.env["test.queue.job"] + model.with_delay().create_ir_logging("test_delay_graph_direct_exec 1") + # jobs are executed directly + logs = self.env["ir.logging"].search( + [ + ("name", "=", "test_queue_job"), + ("func", "=", "create_ir_logging"), + ], + order="id asc", + ) + self.assertEqual(len(logs), 1) + self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 1") + + def test_delay_with_delay_direct_exec_context_key(self): + model = self.env["test.queue.job"].with_context( + test_queue_job_no_delay=True + ) + model.with_delay().create_ir_logging("test_delay_graph_direct_exec 1") + # jobs are executed directly + logs = self.env["ir.logging"].search( + [ + ("name", "=", "test_queue_job"), + ("func", "=", "create_ir_logging"), + ], + order="id asc", + ) + self.assertEqual(len(logs), 1) + self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 1") diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py index b2cfa76a7a..c57d3e8f47 100644 --- a/test_queue_job/tests/test_delayable.py +++ b/test_queue_job/tests/test_delayable.py @@ -1,8 +1,6 @@ # copyright 2019 Camptocamp # Copyright 2019 Guewen Baconnier -# license agpl-3.0 or later (http://www.gnu.org/licenses/agpl.html) - -import odoo +# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html) import odoo.tests.common as common @@ -15,13 +13,14 @@ ) -class TestDelayable(common.TransactionCase): +class TestDelayable(common.SavepointCase): - def setUp(self): - super().setUp() - self.queue_job = self.env['queue.job'] - self.test_model = self.env['test.queue.job'] - self.method = self.env['test.queue.job'].testing_method + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.queue_job = cls.env['queue.job'] + cls.test_model = cls.env['test.queue.job'] + cls.method = cls.env['test.queue.job'].testing_method def job_node(self, id_): return Delayable(self.test_model).testing_method(id_) From 2723dafb403ca3b1780d2e335f9630522451f389 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Sun, 30 May 2021 20:35:38 +0200 Subject: [PATCH 80/88] Set graph_uuid only once in DelayableGraph The previous implementation was incomplete, as it was setting the graph_uuid when `Job.add_depends()` was called. It had the advantage of being correct when (for e.g. a test) manually created a Job and called `add_depends()`, but it could not work when in some situations involving a group where one of the job has no job depending on it. Also, remove ``Job.add_reverse_depends()`` which is not used. --- test_queue_job/tests/test_dependencies.py | 72 ++++++++++++++--------- test_queue_job/tests/test_job.py | 3 + 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 61298ac7e1..2b259cc5cd 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -8,6 +8,7 @@ WAIT_DEPENDENCIES, PENDING, ) +from odoo.addons.queue_job.delay import chain, DelayableGraph, group class TestJobDependencies(common.SavepointCase): @@ -118,6 +119,8 @@ def test_depends_enqueue_waiting_single(self): job_a = Job(self.method) job_a.add_depends({job_root}) + DelayableGraph._ensure_same_graph_uuid([job_root, job_a]) + job_root.store() job_a.store() @@ -145,10 +148,16 @@ def test_dependency_graph(self): job_lvl2_a = Job(self.method) job_lvl2_a.add_depends({job_lvl1_a}) + DelayableGraph._ensure_same_graph_uuid([ + job_root, job_lvl1_a, job_lvl1_b, job_lvl2_a, + ]) + job_2_root = Job(self.method) job_2_child = Job(self.method) job_2_child.add_depends({job_2_root}) + DelayableGraph._ensure_same_graph_uuid([job_2_root, job_2_child]) + # Jobs must be stored after the dependencies are set up. # (Or if not, a new store must be called on the parent) job_root.store() @@ -250,34 +259,43 @@ def test_dependency_graph(self): ) def test_no_dependency_graph_single_job(self): - job_root = Job(self.method) - job_root.store() - self.assertEqual(job_root.db_record().dependency_graph, {}) + """A single job has no graph""" + job = self.env["test.queue.job"].with_delay().testing_method() + self.assertEqual(job.db_record().dependency_graph, {}) + self.assertIsNone(job.graph_uuid) def test_depends_graph_uuid(self): - """All jobs with dependencies share the same graph uuid""" - job_root = Job(self.method) - job_lvl1_a = Job(self.method) - job_lvl1_a.add_depends({job_root}) - job_lvl1_b = Job(self.method) - job_lvl1_b.add_depends({job_root}) - job_lvl2_a = Job(self.method) - job_lvl2_a.add_depends({job_lvl1_a}) - - # Jobs must be stored after the dependencies are set up. - # (Or if not, a new store must be called on the parent) - job_root.store() - job_lvl1_a.store() - job_lvl1_b.store() - job_lvl2_a.store() + """All jobs in a graph share the same graph uuid""" + model = self.env["test.queue.job"] + delayable1 = model.delayable().testing_method(1) + delayable2 = model.delayable().testing_method(2) + delayable3 = model.delayable().testing_method(3) + delayable4 = model.delayable().testing_method(4) + group1 = group(delayable1, delayable2) + group2 = group(delayable3, delayable4) + chain_root = chain(group1, group2) + chain_root.delay() + + jobs = [ + delayable._generated_job for delayable + in [delayable1, delayable2, delayable3, delayable4] + ] - jobs = [job_root, job_lvl1_a, job_lvl1_b, job_lvl2_a] - self.assertTrue(job_root.graph_uuid) + self.assertTrue(jobs[0].graph_uuid) self.assertEqual(len(set(j.graph_uuid for j in jobs)), 1) - self.assertEqual(job_root.graph_uuid, job_root.db_record().graph_uuid) - self.assertEqual(job_lvl1_a.graph_uuid, - job_lvl1_a.db_record().graph_uuid) - self.assertEqual(job_lvl1_b.graph_uuid, - job_lvl1_b.db_record().graph_uuid) - self.assertEqual(job_lvl2_a.graph_uuid, - job_lvl2_a.db_record().graph_uuid) + for job in jobs: + self.assertEqual(job.graph_uuid, job.db_record().graph_uuid) + + def test_depends_graph_uuid_group(self): + """All jobs in a group share the same graph uuid""" + g = group( + self.env['test.queue.job'].delayable().testing_method(), + self.env['test.queue.job'].delayable().testing_method(), + ) + g.delay() + + jobs = [delayable._generated_job for delayable in g._delayables] + + self.assertTrue(jobs[0].graph_uuid) + self.assertTrue(jobs[1].graph_uuid) + self.assertEqual(jobs[0].graph_uuid, jobs[1].graph_uuid) diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 2133f3fc73..23754cfa82 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -13,6 +13,7 @@ NoSuchJobError, RetryableJobError, ) +from odoo.addons.queue_job.delay import DelayableGraph from odoo.addons.queue_job.job import ( DONE, ENQUEUED, @@ -501,6 +502,8 @@ def test_requeue_wait_dependencies_not_touched(self): job_root.store() job_child.store() + DelayableGraph._ensure_same_graph_uuid([job_root, job_child]) + record_root = job_root.db_record() record_child = job_child.db_record() self.assertEqual(record_root.state, PENDING) From b2d5995645c4363675b4d73bfe6966420e40d2dd Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Sun, 30 May 2021 22:07:55 +0200 Subject: [PATCH 81/88] Add docstrings on the new delayable classes --- test_queue_job/tests/test_delay_mocks.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test_queue_job/tests/test_delay_mocks.py b/test_queue_job/tests/test_delay_mocks.py index 4385840404..f4898ee3ea 100644 --- a/test_queue_job/tests/test_delay_mocks.py +++ b/test_queue_job/tests/test_delay_mocks.py @@ -11,6 +11,8 @@ from odoo.addons.queue_job.job import identity_exact from odoo.addons.queue_job.tests.common import mock_with_delay, mock_jobs +from odoo.tools import mute_logger + class TestDelayMocks(common.SavepointCase): @@ -145,6 +147,7 @@ def test_mock_with_delay(self): self.assertEqual(delay_args, (1,)) self.assertDictEqual(delay_kwargs, {"foo": 2}) + @mute_logger('odoo.addons.queue_job.models.base') @mock.patch.dict(os.environ, {"TEST_QUEUE_JOB_NO_DELAY": "1"}) def test_delay_graph_direct_exec_env_var(self): node = Delayable(self.env["test.queue.job"]).create_ir_logging( @@ -168,6 +171,7 @@ def test_delay_graph_direct_exec_env_var(self): self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2") self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1") + @mute_logger('odoo.addons.queue_job.models.base') def test_delay_graph_direct_exec_context_key(self): node = Delayable( self.env["test.queue.job"].with_context( @@ -194,6 +198,7 @@ def test_delay_graph_direct_exec_context_key(self): self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2") self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1") + @mute_logger('odoo.addons.queue_job.models.base') @mock.patch.dict(os.environ, {"TEST_QUEUE_JOB_NO_DELAY": "1"}) def test_delay_with_delay_direct_exec_env_var(self): model = self.env["test.queue.job"] @@ -209,6 +214,7 @@ def test_delay_with_delay_direct_exec_env_var(self): self.assertEqual(len(logs), 1) self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 1") + @mute_logger('odoo.addons.queue_job.models.base') def test_delay_with_delay_direct_exec_context_key(self): model = self.env["test.queue.job"].with_context( test_queue_job_no_delay=True From 82a6cd5a1e1fdff6bbae1bd40130272f44a168c4 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Wed, 26 Oct 2022 08:58:56 +0200 Subject: [PATCH 82/88] Fix things required by odoo 14.0 or python 3.9 * Renaming the TestXxx models in test_queue_job is because of pytest that complains, confusing them for test classes * logger.warn is deprecated in favor of logger.warning * Add invalidation of cache in SQL update that enqueue dependent jobs * Flush in a test, the same is done currently in the controller * Weird one: we can no longer compare the bound methods of the models, compare their __func__ which is equal. --- test_queue_job/models/test_models.py | 8 ++++---- test_queue_job/tests/test_dependencies.py | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index d37a6fc49d..ee81286df6 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -30,7 +30,7 @@ def testing_related__url(self, **kwargs): } -class TestQueueJob(models.Model): +class ModelTestQueueJob(models.Model): _name = "test.queue.job" _description = "Test model for queue.job" @@ -74,7 +74,7 @@ def job_with_retry_pattern__no_zero(self): return def mapped(self, func): - return super(TestQueueJob, self).mapped(func) + return super(ModelTestQueueJob, self).mapped(func) def job_alter_mutable(self, mutable_arg, mutable_kwarg=None): mutable_arg.append(2) @@ -140,7 +140,7 @@ def button_that_uses_delayable_chain(self): delayables.delay() -class TestQueueChannel(models.Model): +class ModelTestQueueChannel(models.Model): _name = "test.queue.channel" _description = "Test model for queue.channel" @@ -155,7 +155,7 @@ def job_sub_channel(self): return -class TestRelatedAction(models.Model): +class ModelTestRelatedAction(models.Model): _name = "test.related.action" _description = "Test model for related actions" diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 2b259cc5cd..0e41588710 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -130,6 +130,7 @@ def test_depends_enqueue_waiting_single(self): job_root.perform() job_root.set_done() job_root.store() + self.env["base"].flush() job_root.enqueue_waiting() @@ -209,9 +210,9 @@ def test_dependency_graph(self): ] expected_edges = sorted( [ - (record_root.id, record_lvl1_a.id), - (record_lvl1_a.id, record_lvl2_a.id), - (record_root.id, record_lvl1_b.id), + [record_root.id, record_lvl1_a.id], + [record_lvl1_a.id, record_lvl2_a.id], + [record_root.id, record_lvl1_b.id], ] ) @@ -245,7 +246,7 @@ def test_dependency_graph(self): 'shadow': True } ] - expected_edges = sorted([(record_2_root.id, record_2_child.id)]) + expected_edges = sorted([[record_2_root.id, record_2_child.id]]) for record in [record_2_root, record_2_child]: self.assertEqual( From f6c0e7e4405c771abfaf3f15d54cc7f6a759fb35 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 3 Feb 2022 12:58:57 +0100 Subject: [PATCH 83/88] Rename mock_jobs() to trap_jobs() The fact that mocks are used is an implementation detail, and is actually hidden to the end user of the test API. --- test_queue_job/tests/test_delay_mocks.py | 83 ++++++++++-------------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/test_queue_job/tests/test_delay_mocks.py b/test_queue_job/tests/test_delay_mocks.py index f4898ee3ea..f067a88855 100644 --- a/test_queue_job/tests/test_delay_mocks.py +++ b/test_queue_job/tests/test_delay_mocks.py @@ -9,23 +9,18 @@ from odoo.addons.queue_job.delay import Delayable from odoo.addons.queue_job.job import identity_exact -from odoo.addons.queue_job.tests.common import mock_with_delay, mock_jobs +from odoo.addons.queue_job.tests.common import mock_with_delay, trap_jobs -from odoo.tools import mute_logger +class TestDelayMocks(common.TransactionCase): + def test_trap_jobs_on_with_delay(self): + with trap_jobs() as trap: + self.env["test.queue.job"].button_that_uses_with_delay() + trap.assert_jobs_count(1) + trap.assert_jobs_count(1, only=self.env["test.queue.job"].testing_method) -class TestDelayMocks(common.SavepointCase): - - def test_mock_jobs_on_with_delay(self): - with mock_jobs() as enqueued_jobs: - self.env['test.queue.job'].button_that_uses_with_delay() - enqueued_jobs.assert_jobs_count(1) - enqueued_jobs.assert_jobs_count( - 1, only=self.env['test.queue.job'].testing_method - ) - - enqueued_jobs.assert_enqueued_job( - self.env['test.queue.job'].testing_method, + trap.assert_enqueued_job( + self.env["test.queue.job"].testing_method, args=(1,), kwargs={"foo": 2}, properties=dict( @@ -38,19 +33,15 @@ def test_mock_jobs_on_with_delay(self): ) ) - def test_mock_jobs_on_graph(self): - with mock_jobs() as jobs_tester: - self.env['test.queue.job'].button_that_uses_delayable_chain() - jobs_tester.assert_jobs_count(3) - jobs_tester.assert_jobs_count( - 2, only=self.env['test.queue.job'].testing_method - ) - jobs_tester.assert_jobs_count( - 1, only=self.env['test.queue.job'].no_description - ) + def test_trap_jobs_on_graph(self): + with trap_jobs() as trap: + self.env["test.queue.job"].button_that_uses_delayable_chain() + trap.assert_jobs_count(3) + trap.assert_jobs_count(2, only=self.env["test.queue.job"].testing_method) + trap.assert_jobs_count(1, only=self.env["test.queue.job"].no_description) - jobs_tester.assert_enqueued_job( - self.env['test.queue.job'].testing_method, + trap.assert_enqueued_job( + self.env["test.queue.job"].testing_method, args=(1,), kwargs={"foo": 2}, properties=dict( @@ -62,32 +53,26 @@ def test_mock_jobs_on_graph(self): priority=15, ) ) - jobs_tester.assert_enqueued_job( - self.env['test.queue.job'].testing_method, + trap.assert_enqueued_job( + self.env["test.queue.job"].testing_method, args=("x",), kwargs={"foo": "y"}, ) - jobs_tester.assert_enqueued_job( - self.env['test.queue.job'].no_description, + trap.assert_enqueued_job( + self.env["test.queue.job"].no_description, ) - jobs_tester.perform_enqueued_jobs() + trap.perform_enqueued_jobs() - def test_mock_jobs_perform(self): - with mock_jobs() as jobs_tester: + def test_trap_jobs_perform(self): + with trap_jobs() as trap: model = self.env["test.queue.job"] model.with_delay(priority=1).create_ir_logging( - "test_mock_jobs_perform single" - ) - node = Delayable(model).create_ir_logging( - "test_mock_jobs_perform graph 1" - ) - node2 = Delayable(model).create_ir_logging( - "test_mock_jobs_perform graph 2" - ) - node3 = Delayable(model).create_ir_logging( - "test_mock_jobs_perform graph 3" + "test_trap_jobs_perform single" ) + node = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 1") + node2 = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 2") + node3 = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 3") node2.done(node3) node3.done(node) node2.delay() @@ -102,10 +87,10 @@ def test_mock_jobs_perform(self): ) self.assertEqual(len(logs), 0) - jobs_tester.assert_jobs_count(4) + trap.assert_jobs_count(4) # perform the jobs - jobs_tester.perform_enqueued_jobs() + trap.perform_enqueued_jobs() logs = self.env["ir.logging"].search( [ @@ -117,10 +102,10 @@ def test_mock_jobs_perform(self): self.assertEqual(len(logs), 4) # check if they are executed in order - self.assertEqual(logs[0].message, "test_mock_jobs_perform single") - self.assertEqual(logs[1].message, "test_mock_jobs_perform graph 2") - self.assertEqual(logs[2].message, "test_mock_jobs_perform graph 3") - self.assertEqual(logs[3].message, "test_mock_jobs_perform graph 1") + self.assertEqual(logs[0].message, "test_trap_jobs_perform single") + self.assertEqual(logs[1].message, "test_trap_jobs_perform graph 2") + self.assertEqual(logs[2].message, "test_trap_jobs_perform graph 3") + self.assertEqual(logs[3].message, "test_trap_jobs_perform graph 1") def test_mock_with_delay(self): with mock_with_delay() as (delayable_cls, delayable): From 50a8d1282726b0bca46aa573961a0d2fa4d9266a Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 3 Feb 2022 13:18:42 +0100 Subject: [PATCH 84/88] Rename dependency method done() to on_done() on_done() is maybe a better choice, it is less ambiguous than then() (clearer on the fact it happens on done()) and done() (less confusion with set_done()). I'll do the renaming soon, unless you have other suggestions. Ref: https://github.com/OCA/queue/pull/154#issuecomment-1025127060 --- test_queue_job/tests/test_delay_mocks.py | 8 ++++---- test_queue_job/tests/test_delayable.py | 26 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test_queue_job/tests/test_delay_mocks.py b/test_queue_job/tests/test_delay_mocks.py index f067a88855..8544cf0b00 100644 --- a/test_queue_job/tests/test_delay_mocks.py +++ b/test_queue_job/tests/test_delay_mocks.py @@ -73,8 +73,8 @@ def test_trap_jobs_perform(self): node = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 1") node2 = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 2") node3 = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 3") - node2.done(node3) - node3.done(node) + node2.on_done(node3) + node3.on_done(node) node2.delay() # jobs are not executed @@ -141,7 +141,7 @@ def test_delay_graph_direct_exec_env_var(self): node2 = Delayable(self.env["test.queue.job"]).create_ir_logging( "test_delay_graph_direct_exec 2" ) - node2.done(node) + node2.on_done(node) node2.delay() # jobs are executed directly logs = self.env["ir.logging"].search( @@ -168,7 +168,7 @@ def test_delay_graph_direct_exec_context_key(self): node2 = Delayable(self.env["test.queue.job"]).create_ir_logging( "test_delay_graph_direct_exec 2" ) - node2.done(node) + node2.on_done(node) node2.delay() # jobs are executed directly logs = self.env["ir.logging"].search( diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py index c57d3e8f47..5abc3fe87a 100644 --- a/test_queue_job/tests/test_delayable.py +++ b/test_queue_job/tests/test_delayable.py @@ -57,10 +57,10 @@ def test_delayable_delay_single(self): node.delay() self.assert_generated_job(node) - def test_delayable_delay_done(self): + def test_delayable_delay_on_done(self): node = self.job_node(1) node2 = self.job_node(2) - node.done(node2).delay() + node.on_done(node2).delay() self.assert_generated_job(node, node2) self.assert_dependencies({node: {}, node2: {node}}) @@ -68,7 +68,7 @@ def test_delayable_delay_done_multi(self): node = self.job_node(1) node2 = self.job_node(2) node3 = self.job_node(3) - node.done(node2, node3).delay() + node.on_done(node2, node3).delay() self.assert_generated_job(node, node2, node3) self.assert_dependencies({ node: {}, node2: {node}, node3: {node} @@ -94,7 +94,7 @@ def test_delayable_delay_job_after_group(self): node = self.job_node(1) node2 = self.job_node(2) node3 = self.job_node(3) - DelayableGroup(node, node2).done(node3).delay() + DelayableGroup(node, node2).on_done(node3).delay() self.assert_generated_job(node, node2, node3) self.assert_dependencies({ node: {}, node2: {}, node3: {node, node2} @@ -107,7 +107,7 @@ def test_delayable_delay_group_after_group(self): node4 = self.job_node(4) g1 = DelayableGroup(node, node2) g2 = DelayableGroup(node3, node4) - g1.done(g2).delay() + g1.on_done(g2).delay() self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ node: {}, node2: {}, @@ -120,7 +120,7 @@ def test_delayable_delay_implicit_group_after_group(self): node2 = self.job_node(2) node3 = self.job_node(3) node4 = self.job_node(4) - g1 = DelayableGroup(node, node2).done(node3, node4) + g1 = DelayableGroup(node, node2).on_done(node3, node4) g1.delay() self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ @@ -138,7 +138,7 @@ def test_delayable_delay_group_after_group_after_group(self): g2 = DelayableGroup(node2) g3 = DelayableGroup(node3) g4 = DelayableGroup(node4) - g1.done(g2.done(g3.done(g4))).delay() + g1.on_done(g2.on_done(g3.on_done(g4))).delay() self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ node: {}, @@ -153,8 +153,8 @@ def test_delayable_diamond(self): node3 = self.job_node(3) node4 = self.job_node(4) g1 = DelayableGroup(node2, node3) - g1.done(node4) - node.done(g1) + g1.on_done(node4) + node.on_done(g1) node.delay() self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ @@ -196,7 +196,7 @@ def test_delayable_chain_after_job(self): node3 = self.job_node(3) node4 = self.job_node(4) c1 = DelayableChain(node2, node3, node4) - node.done(c1) + node.on_done(c1) node.delay() self.assert_generated_job(node, node2, node3, node4) self.assert_dependencies({ @@ -215,7 +215,7 @@ def test_delayable_chain_after_chain(self): node6 = self.job_node(6) chain1 = DelayableChain(node, node2, node3) chain2 = DelayableChain(node4, node5, node6) - chain1.done(chain2) + chain1.on_done(chain2) chain1.delay() self.assert_generated_job(node, node2, node3, node4, node5, node6) self.assert_dependencies({ @@ -240,7 +240,7 @@ def test_delayable_group_of_chain(self): chain2 = DelayableChain(node3, node4) chain3 = DelayableChain(node5, node6) chain4 = DelayableChain(node7, node8) - g1 = DelayableGroup(chain1, chain2).done(chain3, chain4) + g1 = DelayableGroup(chain1, chain2).on_done(chain3, chain4) g1.delay() self.assert_generated_job( node, node2, node3, node4, @@ -278,6 +278,6 @@ def test_delay_job_already_exists(self): node = self.job_node(1) node2 = self.job_node(2) node2.delay() - node.done(node2).delay() + node.on_done(node2).delay() self.assert_generated_job(node, node2) self.assert_dependencies({node: {}, node2: {node}}) From b3352e107c7165dac0b815ef23bab7d559aa76c7 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 24 Oct 2022 18:05:03 +0200 Subject: [PATCH 85/88] Apply pre-commit on migration of jobs graph --- test_queue_job/models/test_models.py | 25 ++-- test_queue_job/tests/test_delay_mocks.py | 49 +++--- test_queue_job/tests/test_delayable.py | 173 ++++++++++++---------- test_queue_job/tests/test_dependencies.py | 173 ++++++++++------------ test_queue_job/tests/test_job.py | 10 +- 5 files changed, 217 insertions(+), 213 deletions(-) diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index ee81286df6..0d3f9d6b33 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -3,9 +3,8 @@ from odoo import api, fields, models from odoo.addons.queue_job.delay import chain -from odoo.addons.queue_job.job import identity_exact - from odoo.addons.queue_job.exception import RetryableJobError +from odoo.addons.queue_job.job import identity_exact class QueueJob(models.Model): @@ -54,15 +53,17 @@ def testing_method(self, *args, **kwargs): return args, kwargs def create_ir_logging(self, message, level="info"): - return self.env["ir.logging"].create({ - "name": "test_queue_job", - "type": "server", - "dbname": self.env.cr.dbname, - "message": message, - "path": "job", - "func": "create_ir_logging", - "line": 1, - }) + return self.env["ir.logging"].create( + { + "name": "test_queue_job", + "type": "server", + "dbname": self.env.cr.dbname, + "message": message, + "path": "job", + "func": "create_ir_logging", + "line": 1, + } + ) def no_description(self): return @@ -134,7 +135,7 @@ def button_that_uses_delayable_chain(self): max_retries=1, priority=15, ).testing_method(1, foo=2), - self.delayable().testing_method('x', foo='y'), + self.delayable().testing_method("x", foo="y"), self.delayable().no_description(), ) delayables.delay() diff --git a/test_queue_job/tests/test_delay_mocks.py b/test_queue_job/tests/test_delay_mocks.py index 8544cf0b00..a4f9861e33 100644 --- a/test_queue_job/tests/test_delay_mocks.py +++ b/test_queue_job/tests/test_delay_mocks.py @@ -2,10 +2,10 @@ # license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html) import os - from unittest import mock import odoo.tests.common as common +from odoo.tools import mute_logger from odoo.addons.queue_job.delay import Delayable from odoo.addons.queue_job.job import identity_exact @@ -30,7 +30,7 @@ def test_trap_jobs_on_with_delay(self): identity_key=identity_exact, max_retries=1, priority=15, - ) + ), ) def test_trap_jobs_on_graph(self): @@ -51,7 +51,7 @@ def test_trap_jobs_on_graph(self): identity_key=identity_exact, max_retries=1, priority=15, - ) + ), ) trap.assert_enqueued_job( self.env["test.queue.job"].testing_method, @@ -109,22 +109,23 @@ def test_trap_jobs_perform(self): def test_mock_with_delay(self): with mock_with_delay() as (delayable_cls, delayable): - self.env['test.queue.job'].button_that_uses_with_delay() + self.env["test.queue.job"].button_that_uses_with_delay() self.assertEqual(delayable_cls.call_count, 1) # arguments passed in 'with_delay()' delay_args, delay_kwargs = delayable_cls.call_args - self.assertEqual( - delay_args, (self.env["test.queue.job"],) + self.assertEqual(delay_args, (self.env["test.queue.job"],)) + self.assertDictEqual( + delay_kwargs, + { + "channel": "root.test", + "description": "Test", + "eta": 15, + "identity_key": identity_exact, + "max_retries": 1, + "priority": 15, + }, ) - self.assertDictEqual(delay_kwargs, { - "channel": "root.test", - "description": "Test", - "eta": 15, - "identity_key": identity_exact, - "max_retries": 1, - "priority": 15, - }) # check what's passed to the job method 'testing_method' self.assertEqual(delayable.testing_method.call_count, 1) @@ -132,7 +133,7 @@ def test_mock_with_delay(self): self.assertEqual(delay_args, (1,)) self.assertDictEqual(delay_kwargs, {"foo": 2}) - @mute_logger('odoo.addons.queue_job.models.base') + @mute_logger("odoo.addons.queue_job.models.base") @mock.patch.dict(os.environ, {"TEST_QUEUE_JOB_NO_DELAY": "1"}) def test_delay_graph_direct_exec_env_var(self): node = Delayable(self.env["test.queue.job"]).create_ir_logging( @@ -156,15 +157,11 @@ def test_delay_graph_direct_exec_env_var(self): self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2") self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1") - @mute_logger('odoo.addons.queue_job.models.base') + @mute_logger("odoo.addons.queue_job.models.base") def test_delay_graph_direct_exec_context_key(self): node = Delayable( - self.env["test.queue.job"].with_context( - test_queue_job_no_delay=True - ) - ).create_ir_logging( - "test_delay_graph_direct_exec 1" - ) + self.env["test.queue.job"].with_context(test_queue_job_no_delay=True) + ).create_ir_logging("test_delay_graph_direct_exec 1") node2 = Delayable(self.env["test.queue.job"]).create_ir_logging( "test_delay_graph_direct_exec 2" ) @@ -183,7 +180,7 @@ def test_delay_graph_direct_exec_context_key(self): self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2") self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1") - @mute_logger('odoo.addons.queue_job.models.base') + @mute_logger("odoo.addons.queue_job.models.base") @mock.patch.dict(os.environ, {"TEST_QUEUE_JOB_NO_DELAY": "1"}) def test_delay_with_delay_direct_exec_env_var(self): model = self.env["test.queue.job"] @@ -199,11 +196,9 @@ def test_delay_with_delay_direct_exec_env_var(self): self.assertEqual(len(logs), 1) self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 1") - @mute_logger('odoo.addons.queue_job.models.base') + @mute_logger("odoo.addons.queue_job.models.base") def test_delay_with_delay_direct_exec_context_key(self): - model = self.env["test.queue.job"].with_context( - test_queue_job_no_delay=True - ) + model = self.env["test.queue.job"].with_context(test_queue_job_no_delay=True) model.with_delay().create_ir_logging("test_delay_graph_direct_exec 1") # jobs are executed directly logs = self.env["ir.logging"].search( diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py index 5abc3fe87a..57044abc7d 100644 --- a/test_queue_job/tests/test_delayable.py +++ b/test_queue_job/tests/test_delayable.py @@ -6,21 +6,20 @@ from odoo.addons.queue_job.delay import ( Delayable, - DelayableGroup, DelayableChain, + DelayableGroup, chain, group, ) class TestDelayable(common.SavepointCase): - @classmethod def setUpClass(cls): super().setUpClass() - cls.queue_job = cls.env['queue.job'] - cls.test_model = cls.env['test.queue.job'] - cls.method = cls.env['test.queue.job'].testing_method + cls.queue_job = cls.env["queue.job"] + cls.test_model = cls.env["test.queue.job"] + cls.method = cls.env["test.queue.job"].testing_method def job_node(self, id_): return Delayable(self.test_model).testing_method(id_) @@ -34,13 +33,13 @@ def assert_generated_job(self, *nodes): def assert_depends_on(self, delayable, parent_delayables): self.assertEqual( delayable._generated_job._depends_on, - {parent._generated_job for parent in parent_delayables} + {parent._generated_job for parent in parent_delayables}, ) def assert_reverse_depends_on(self, delayable, child_delayables): self.assertEqual( set(delayable._generated_job._reverse_depends_on), - {child._generated_job for child in child_delayables} + {child._generated_job for child in child_delayables}, ) def assert_dependencies(self, nodes): @@ -70,9 +69,7 @@ def test_delayable_delay_done_multi(self): node3 = self.job_node(3) node.on_done(node2, node3).delay() self.assert_generated_job(node, node2, node3) - self.assert_dependencies({ - node: {}, node2: {node}, node3: {node} - }) + self.assert_dependencies({node: {}, node2: {node}, node3: {node}}) def test_delayable_delay_group(self): node = self.job_node(1) @@ -96,9 +93,7 @@ def test_delayable_delay_job_after_group(self): node3 = self.job_node(3) DelayableGroup(node, node2).on_done(node3).delay() self.assert_generated_job(node, node2, node3) - self.assert_dependencies({ - node: {}, node2: {}, node3: {node, node2} - }) + self.assert_dependencies({node: {}, node2: {}, node3: {node, node2}}) def test_delayable_delay_group_after_group(self): node = self.job_node(1) @@ -109,11 +104,14 @@ def test_delayable_delay_group_after_group(self): g2 = DelayableGroup(node3, node4) g1.on_done(g2).delay() self.assert_generated_job(node, node2, node3, node4) - self.assert_dependencies({ - node: {}, node2: {}, - node3: {node, node2}, - node4: {node, node2}, - }) + self.assert_dependencies( + { + node: {}, + node2: {}, + node3: {node, node2}, + node4: {node, node2}, + } + ) def test_delayable_delay_implicit_group_after_group(self): node = self.job_node(1) @@ -123,11 +121,14 @@ def test_delayable_delay_implicit_group_after_group(self): g1 = DelayableGroup(node, node2).on_done(node3, node4) g1.delay() self.assert_generated_job(node, node2, node3, node4) - self.assert_dependencies({ - node: {}, node2: {}, - node3: {node, node2}, - node4: {node, node2}, - }) + self.assert_dependencies( + { + node: {}, + node2: {}, + node3: {node, node2}, + node4: {node, node2}, + } + ) def test_delayable_delay_group_after_group_after_group(self): node = self.job_node(1) @@ -140,12 +141,14 @@ def test_delayable_delay_group_after_group_after_group(self): g4 = DelayableGroup(node4) g1.on_done(g2.on_done(g3.on_done(g4))).delay() self.assert_generated_job(node, node2, node3, node4) - self.assert_dependencies({ - node: {}, - node2: {node}, - node3: {node2}, - node4: {node3}, - }) + self.assert_dependencies( + { + node: {}, + node2: {node}, + node3: {node2}, + node4: {node3}, + } + ) def test_delayable_diamond(self): node = self.job_node(1) @@ -157,12 +160,14 @@ def test_delayable_diamond(self): node.on_done(g1) node.delay() self.assert_generated_job(node, node2, node3, node4) - self.assert_dependencies({ - node: {}, - node2: {node}, - node3: {node}, - node4: {node2, node3}, - }) + self.assert_dependencies( + { + node: {}, + node2: {node}, + node3: {node}, + node4: {node2, node3}, + } + ) def test_delayable_chain(self): node = self.job_node(1) @@ -171,11 +176,13 @@ def test_delayable_chain(self): c1 = DelayableChain(node, node2, node3) c1.delay() self.assert_generated_job(node, node2, node3) - self.assert_dependencies({ - node: {}, - node2: {node}, - node3: {node2}, - }) + self.assert_dependencies( + { + node: {}, + node2: {node}, + node3: {node2}, + } + ) def test_chain_function(self): node = self.job_node(1) @@ -184,11 +191,13 @@ def test_chain_function(self): c1 = chain(node, node2, node3) c1.delay() self.assert_generated_job(node, node2, node3) - self.assert_dependencies({ - node: {}, - node2: {node}, - node3: {node2}, - }) + self.assert_dependencies( + { + node: {}, + node2: {node}, + node3: {node2}, + } + ) def test_delayable_chain_after_job(self): node = self.job_node(1) @@ -199,12 +208,14 @@ def test_delayable_chain_after_job(self): node.on_done(c1) node.delay() self.assert_generated_job(node, node2, node3, node4) - self.assert_dependencies({ - node: {}, - node2: {node}, - node3: {node2}, - node4: {node3}, - }) + self.assert_dependencies( + { + node: {}, + node2: {node}, + node3: {node2}, + node4: {node3}, + } + ) def test_delayable_chain_after_chain(self): node = self.job_node(1) @@ -218,14 +229,16 @@ def test_delayable_chain_after_chain(self): chain1.on_done(chain2) chain1.delay() self.assert_generated_job(node, node2, node3, node4, node5, node6) - self.assert_dependencies({ - node: {}, - node2: {node}, - node3: {node2}, - node4: {node3}, - node5: {node4}, - node6: {node5}, - }) + self.assert_dependencies( + { + node: {}, + node2: {node}, + node3: {node2}, + node4: {node3}, + node5: {node4}, + node6: {node5}, + } + ) def test_delayable_group_of_chain(self): node = self.job_node(1) @@ -243,23 +256,31 @@ def test_delayable_group_of_chain(self): g1 = DelayableGroup(chain1, chain2).on_done(chain3, chain4) g1.delay() self.assert_generated_job( - node, node2, node3, node4, - node5, node6, node7, node8, + node, + node2, + node3, + node4, + node5, + node6, + node7, + node8, + ) + self.assert_dependencies( + { + node: {}, + node3: {}, + node2: {node}, + node4: {node3}, + node5: {node4, node2}, + node7: {node4, node2}, + node6: {node5}, + node8: {node7}, + } ) - self.assert_dependencies({ - node: {}, - node3: {}, - node2: {node}, - node4: {node3}, - node5: {node4, node2}, - node7: {node4, node2}, - node6: {node5}, - node8: {node7}, - }) def test_log_not_delayed(self): - logger_name = 'odoo.addons.queue_job' - with self.assertLogs(logger_name, level='WARN') as test: + logger_name = "odoo.addons.queue_job" + with self.assertLogs(logger_name, level="WARN") as test: # When a Delayable never gets a delay() call, # when the GC collects it and calls __del__, a warning # will be displayed. We cannot test this is a scenario @@ -268,9 +289,9 @@ def test_log_not_delayed(self): node = self.job_node(1) node.__del__() expected = ( - 'WARNING:odoo.addons.queue_job.delay:Delayable ' - 'Delayable(test.queue.job().testing_method((1,), {}))' - ' was prepared but never delayed' + "WARNING:odoo.addons.queue_job.delay:Delayable " + "Delayable(test.queue.job().testing_method((1,), {}))" + " was prepared but never delayed" ) self.assertEqual(test.output, [expected]) diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 0e41588710..09a96e3864 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -3,21 +3,16 @@ import odoo.tests.common as common -from odoo.addons.queue_job.job import ( - Job, - WAIT_DEPENDENCIES, - PENDING, -) -from odoo.addons.queue_job.delay import chain, DelayableGraph, group +from odoo.addons.queue_job.delay import DelayableGraph, chain, group +from odoo.addons.queue_job.job import PENDING, WAIT_DEPENDENCIES, Job class TestJobDependencies(common.SavepointCase): - @classmethod def setUpClass(cls): super().setUpClass() - cls.queue_job = cls.env['queue.job'] - cls.method = cls.env['test.queue.job'].testing_method + cls.queue_job = cls.env["queue.job"] + cls.method = cls.env["test.queue.job"].testing_method def test_depends_store(self): job_root = Job(self.method) @@ -51,34 +46,28 @@ def test_depends_store(self): self.assertFalse(job_lvl2_a.reverse_depends_on) # test DB state - self.assertEqual(job_root.db_record().dependencies['depends_on'], []) + self.assertEqual(job_root.db_record().dependencies["depends_on"], []) self.assertEqual( - sorted(job_root.db_record().dependencies['reverse_depends_on']), - sorted([job_lvl1_a.uuid, job_lvl1_b.uuid]) + sorted(job_root.db_record().dependencies["reverse_depends_on"]), + sorted([job_lvl1_a.uuid, job_lvl1_b.uuid]), ) self.assertEqual( - job_lvl1_a.db_record().dependencies['depends_on'], [job_root.uuid] + job_lvl1_a.db_record().dependencies["depends_on"], [job_root.uuid] ) self.assertEqual( - job_lvl1_a.db_record().dependencies['reverse_depends_on'], - [job_lvl2_a.uuid] + job_lvl1_a.db_record().dependencies["reverse_depends_on"], [job_lvl2_a.uuid] ) self.assertEqual( - job_lvl1_b.db_record().dependencies['depends_on'], [job_root.uuid] - ) - self.assertEqual( - job_lvl1_b.db_record().dependencies['reverse_depends_on'], [] + job_lvl1_b.db_record().dependencies["depends_on"], [job_root.uuid] ) + self.assertEqual(job_lvl1_b.db_record().dependencies["reverse_depends_on"], []) self.assertEqual( - job_lvl2_a.db_record().dependencies['depends_on'], - [job_lvl1_a.uuid] - ) - self.assertEqual( - job_lvl2_a.db_record().dependencies['reverse_depends_on'], [] + job_lvl2_a.db_record().dependencies["depends_on"], [job_lvl1_a.uuid] ) + self.assertEqual(job_lvl2_a.db_record().dependencies["reverse_depends_on"], []) def test_depends_store_after(self): job_root = Job(self.method) @@ -89,15 +78,12 @@ def test_depends_store_after(self): # as the reverse dependency has been added after the root job has been # stored, it is not reflected in DB - self.assertEqual( - job_root.db_record().dependencies['reverse_depends_on'], [] - ) + self.assertEqual(job_root.db_record().dependencies["reverse_depends_on"], []) # a new store will write it job_root.store() self.assertEqual( - job_root.db_record().dependencies['reverse_depends_on'], - [job_a.uuid] + job_root.db_record().dependencies["reverse_depends_on"], [job_a.uuid] ) def test_depends_load(self): @@ -149,9 +135,14 @@ def test_dependency_graph(self): job_lvl2_a = Job(self.method) job_lvl2_a.add_depends({job_lvl1_a}) - DelayableGraph._ensure_same_graph_uuid([ - job_root, job_lvl1_a, job_lvl1_b, job_lvl2_a, - ]) + DelayableGraph._ensure_same_graph_uuid( + [ + job_root, + job_lvl1_a, + job_lvl1_b, + job_lvl2_a, + ] + ) job_2_root = Job(self.method) job_2_child = Job(self.method) @@ -179,34 +170,37 @@ def test_dependency_graph(self): expected_nodes = [ { - 'id': record_root.id, - 'title': 'Method used for tests
' - 'test.queue.job().testing_method()', - 'color': '#D2E5FF', - 'border': '#2B7CE9', - 'shadow': True - }, { - 'id': record_lvl1_a.id, - 'title': 'Method used for tests
' - 'test.queue.job().testing_method()', - 'color': '#D2E5FF', - 'border': '#2B7CE9', - 'shadow': True - }, { - 'id': record_lvl1_b.id, - 'title': 'Method used for tests
' - 'test.queue.job().testing_method()', - 'color': '#D2E5FF', - 'border': '#2B7CE9', - 'shadow': True - }, { - 'id': record_lvl2_a.id, - 'title': 'Method used for tests
' - 'test.queue.job().testing_method()', - 'color': '#D2E5FF', - 'border': '#2B7CE9', - 'shadow': True - } + "id": record_root.id, + "title": "Method used for tests
" + "test.queue.job().testing_method()", + "color": "#D2E5FF", + "border": "#2B7CE9", + "shadow": True, + }, + { + "id": record_lvl1_a.id, + "title": "Method used for tests
" + "test.queue.job().testing_method()", + "color": "#D2E5FF", + "border": "#2B7CE9", + "shadow": True, + }, + { + "id": record_lvl1_b.id, + "title": "Method used for tests
" + "test.queue.job().testing_method()", + "color": "#D2E5FF", + "border": "#2B7CE9", + "shadow": True, + }, + { + "id": record_lvl2_a.id, + "title": "Method used for tests
" + "test.queue.job().testing_method()", + "color": "#D2E5FF", + "border": "#2B7CE9", + "shadow": True, + }, ] expected_edges = sorted( [ @@ -220,44 +214,37 @@ def test_dependency_graph(self): for record in records: self.assertEqual( - sorted(record.dependency_graph['nodes'], - key=lambda d: d["id"]), - expected_nodes - ) - self.assertEqual( - sorted(record.dependency_graph['edges']), - expected_edges + sorted(record.dependency_graph["nodes"], key=lambda d: d["id"]), + expected_nodes, ) + self.assertEqual(sorted(record.dependency_graph["edges"]), expected_edges) expected_nodes = [ { - 'id': record_2_root.id, - 'title': 'Method used for tests
' - 'test.queue.job().testing_method()', - 'color': '#D2E5FF', - 'border': '#2B7CE9', - 'shadow': True - }, { - 'id': record_2_child.id, - 'title': 'Method used for tests
' - 'test.queue.job().testing_method()', - 'color': '#D2E5FF', - 'border': '#2B7CE9', - 'shadow': True - } + "id": record_2_root.id, + "title": "Method used for tests
" + "test.queue.job().testing_method()", + "color": "#D2E5FF", + "border": "#2B7CE9", + "shadow": True, + }, + { + "id": record_2_child.id, + "title": "Method used for tests
" + "test.queue.job().testing_method()", + "color": "#D2E5FF", + "border": "#2B7CE9", + "shadow": True, + }, ] expected_edges = sorted([[record_2_root.id, record_2_child.id]]) for record in [record_2_root, record_2_child]: self.assertEqual( - sorted(record.dependency_graph['nodes'], - key=lambda d: d["id"]), - expected_nodes - ) - self.assertEqual( - sorted(record.dependency_graph['edges']), - expected_edges + sorted(record.dependency_graph["nodes"], key=lambda d: d["id"]), + expected_nodes, ) + self.assertEqual(sorted(record.dependency_graph["edges"]), expected_edges) def test_no_dependency_graph_single_job(self): """A single job has no graph""" @@ -278,20 +265,20 @@ def test_depends_graph_uuid(self): chain_root.delay() jobs = [ - delayable._generated_job for delayable - in [delayable1, delayable2, delayable3, delayable4] + delayable._generated_job + for delayable in [delayable1, delayable2, delayable3, delayable4] ] self.assertTrue(jobs[0].graph_uuid) - self.assertEqual(len(set(j.graph_uuid for j in jobs)), 1) + self.assertEqual(len({j.graph_uuid for j in jobs}), 1) for job in jobs: self.assertEqual(job.graph_uuid, job.db_record().graph_uuid) def test_depends_graph_uuid_group(self): """All jobs in a group share the same graph uuid""" g = group( - self.env['test.queue.job'].delayable().testing_method(), - self.env['test.queue.job'].delayable().testing_method(), + self.env["test.queue.job"].delayable().testing_method(), + self.env["test.queue.job"].delayable().testing_method(), ) g.delay() diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 23754cfa82..094a56165b 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -8,12 +8,12 @@ import odoo.tests.common as common from odoo.addons.queue_job import identity_exact +from odoo.addons.queue_job.delay import DelayableGraph from odoo.addons.queue_job.exception import ( FailedJobError, NoSuchJobError, RetryableJobError, ) -from odoo.addons.queue_job.delay import DelayableGraph from odoo.addons.queue_job.job import ( DONE, ENQUEUED, @@ -21,8 +21,8 @@ PENDING, RETRY_INTERVAL, STARTED, - Job, WAIT_DEPENDENCIES, + Job, ) from .common import JobCommonCase @@ -496,8 +496,8 @@ def test_requeue(self): self.assertEqual(stored.state, PENDING) def test_requeue_wait_dependencies_not_touched(self): - job_root = Job(self.env['test.queue.job'].testing_method) - job_child = Job(self.env['test.queue.job'].testing_method) + job_root = Job(self.env["test.queue.job"].testing_method) + job_child = Job(self.env["test.queue.job"].testing_method) job_child.add_depends({job_root}) job_root.store() job_child.store() @@ -508,7 +508,7 @@ def test_requeue_wait_dependencies_not_touched(self): record_child = job_child.db_record() self.assertEqual(record_root.state, PENDING) self.assertEqual(record_child.state, WAIT_DEPENDENCIES) - record_root.write({'state': 'failed'}) + record_root.write({"state": "failed"}) (record_root + record_child).requeue() self.assertEqual(record_root.state, PENDING) From 844b1fd8f25ab00121cec64d8c127ac62d4b5a57 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 24 Oct 2022 18:06:27 +0200 Subject: [PATCH 86/88] Use new TransactionCase instead of deprecated SavepointCase --- test_queue_job/tests/test_delayable.py | 2 +- test_queue_job/tests/test_dependencies.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py index 57044abc7d..416638f37b 100644 --- a/test_queue_job/tests/test_delayable.py +++ b/test_queue_job/tests/test_delayable.py @@ -13,7 +13,7 @@ ) -class TestDelayable(common.SavepointCase): +class TestDelayable(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 09a96e3864..11084b681e 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -7,7 +7,7 @@ from odoo.addons.queue_job.job import PENDING, WAIT_DEPENDENCIES, Job -class TestJobDependencies(common.SavepointCase): +class TestJobDependencies(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() From 2a2be55491d563c225a6270f6f5045ed3e3f3558 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Wed, 16 Nov 2022 12:53:09 +0100 Subject: [PATCH 87/88] [MIG] queue_job, test_queue_job: Forward port queue from 15.0 This forward port takes all the changes from 15.0 not yet into 16.0 up to https://github.com/OCA/queue/commit/173b6ca7026dc8042cc8e750767c3267c67f4f43 This FP contains the new queue dependencies functionality. The dedicated widget used to display the dependecies graph on the job form is also migrated to owl --- queue_job/__manifest__.py | 7 +- queue_job/i18n/de.po | 1 + queue_job/i18n/es.po | 2 +- queue_job/i18n/queue_job.pot | 1 + queue_job/i18n/zh_CN.po | 1 + queue_job/job.py | 2 +- queue_job/models/queue_job.py | 2 +- queue_job/static/src/js/queue_job_fields.js | 148 ------------------ .../job_direct_graph/job_direct_graph.esm.js | 143 +++++++++++++++++ .../job_direct_graph/job_direct_graph.scss} | 5 + .../job_direct_graph/job_direct_graph.xml | 8 + queue_job/tests/common.py | 1 + queue_job/views/queue_job_views.xml | 2 +- test_queue_job/models/test_models.py | 1 + test_queue_job/tests/test_dependencies.py | 2 +- test_queue_job/tests/test_job.py | 2 +- 16 files changed, 169 insertions(+), 159 deletions(-) delete mode 100644 queue_job/static/src/js/queue_job_fields.js create mode 100644 queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.esm.js rename queue_job/static/src/{scss/queue_job_fields.scss => views/fields/job_direct_graph/job_direct_graph.scss} (59%) create mode 100644 queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.xml diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 11c3c42793..039857ddb4 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -7,7 +7,7 @@ "website": "https://github.com/OCA/queue", "license": "LGPL-3", "category": "Generic Modules", - "depends": ["mail", "base_sparse_field"], + "depends": ["mail", "base_sparse_field", "web"], "external_dependencies": {"python": ["requests"]}, "data": [ "security/security.xml", @@ -24,10 +24,7 @@ ], "assets": { "web.assets_backend": [ - "/queue_job/static/lib/vis/vis-network.min.css", - "/queue_job/static/src/scss/queue_job_fields.scss", - "/queue_job/static/lib/vis/vis-network.min.js", - "/queue_job/static/src/js/queue_job_fields.js", + "/queue_job/static/src/views/**/*", ], }, "installable": True, diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index d006e81144..0f065bce8d 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -621,6 +621,7 @@ msgstr "Das ist die Anzahl von Nachrichten mit Übermittlungsfehler" msgid "Override Channel" msgstr "Kanal überschreiben" +#. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter msgid "Number of unread messages" msgstr "Das ist die Anzahl von ungelesenen Nachrichten" diff --git a/queue_job/i18n/es.po b/queue_job/i18n/es.po index dc336ca88e..2ae2060da1 100644 --- a/queue_job/i18n/es.po +++ b/queue_job/i18n/es.po @@ -1,4 +1,3 @@ -msgstr "" # Translation of Odoo Server. # This file contains the translation of the following modules: # * queue_job @@ -622,6 +621,7 @@ msgstr "Número de mensajes con error de envío" msgid "Override Channel" msgstr "Sobreescribir canal" +#. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter msgid "Number of unread messages" msgstr "Número de mensajes no leidos" diff --git a/queue_job/i18n/queue_job.pot b/queue_job/i18n/queue_job.pot index fa2f248b87..8dbf46bb04 100644 --- a/queue_job/i18n/queue_job.pot +++ b/queue_job/i18n/queue_job.pot @@ -613,6 +613,7 @@ msgstr "" msgid "Override Channel" msgstr "" +#. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter msgid "Number of unread messages" msgstr "" diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po index e60022b5d2..ca7fd2bb98 100644 --- a/queue_job/i18n/zh_CN.po +++ b/queue_job/i18n/zh_CN.po @@ -620,6 +620,7 @@ msgstr "递送错误消息数量" msgid "Override Channel" msgstr "覆盖频道" +#. module: queue_job #: model:ir.model.fields,help:queue_job.field_queue_job__message_unread_counter msgid "Number of unread messages" msgstr "未读消息数量" diff --git a/queue_job/job.py b/queue_job/job.py index f824020a27..f8e04cebe7 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -564,7 +564,7 @@ def enqueue_waiting(self): AND state = %s; """ self.env.cr.execute(sql, (PENDING, self.uuid, DONE, WAIT_DEPENDENCIES)) - self.env["queue.job"].invalidate_cache(["state"]) + self.env["queue.job"].invalidate_model(["state"]) def store(self): """Store the Job""" diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index c276287084..d9336181b4 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -319,7 +319,7 @@ def _change_job_state(self, state, result=None): if state == DONE: job_.set_done(result=result) job_.store() - record.env["queue.job"].flush() + record.env["queue.job"].flush_model() job_.enqueue_waiting() elif state == PENDING: job_.set_pending(result=result) diff --git a/queue_job/static/src/js/queue_job_fields.js b/queue_job/static/src/js/queue_job_fields.js deleted file mode 100644 index 3829ad7145..0000000000 --- a/queue_job/static/src/js/queue_job_fields.js +++ /dev/null @@ -1,148 +0,0 @@ -odoo.define("queue_job.fields", function (require) { - "use strict"; - - /** - * This module contains field widgets for the job queue. - */ - - var AbstractField = require("web.AbstractField"); - var core = require("web.core"); - var field_registry = require("web.field_registry"); - - var JobDirectedGraph = AbstractField.extend({ - /* global vis */ - className: "o_field_job_directed_graph", - cssLibs: ["/queue_job/static/lib/vis/vis-network.min.css"], - jsLibs: ["/queue_job/static/lib/vis/vis-network.min.js"], - init: function () { - this._super.apply(this, arguments); - this.network = null; - this.tabListenerInstalled = false; - }, - start: function () { - var def = this._super(); - - core.bus.on( - "DOM_updated", - this, - function () { - this._installTabListener(); - }.bind(this) - ); - - return def; - }, - _fitNetwork: function () { - if (this.network) { - this.network.fit(this.network.body.nodeIndices); - } - }, - /* - * Add a listener on tabs if any: when the widget is render inside a tab, - * it does not view the view. Install a listener that will fit the network - * graph to show all the nodes when we switch tab. - */ - _installTabListener: function () { - if (this.tabListenerInstalled) { - return; - } - this.tabListenerInstalled = true; - - var tab = this.$el.closest("div.tab-pane"); - if (!tab.length) { - return; - } - $('a[href="#' + tab[0].id + '"]').on( - "shown.bs.tab", - function () { - this._fitNetwork(); - }.bind(this) - ); - }, - htmlTitle: function (html) { - const container = document.createElement("div"); - container.innerHTML = html; - return container; - }, - _render: function () { - var self = this; - this.$el.empty(); - - var nodes = this.value.nodes || []; - - if (!nodes.length) { - return; - } - nodes = _.map(nodes, function (node) { - node.title = self.htmlTitle(node.title || ""); - return node; - }); - - var edges = []; - _.each(this.value.edges || [], function (edge) { - var edgeFrom = edge[0]; - var edgeTo = edge[1]; - edges.push({ - from: edgeFrom, - to: edgeTo, - arrows: "to", - }); - }); - - var data = { - nodes: new vis.DataSet(nodes), - edges: new vis.DataSet(edges), - }; - var options = { - // Fix the seed to have always the same result for the same graph - layout: {randomSeed: 1}, - }; - // Arbitrary threshold, generation becomes very slow at some - // point, and disabling the stabilization helps to have a fast result. - // Actually, it stabilizes, but is displayed while stabilizing, rather - // than showing a blank canvas. - if (nodes.length > 100) { - options.physics = {stabilization: false}; - } - var network = new vis.Network(this.$el[0], data, options); - network.selectNodes([this.res_id]); - - network.on("dragging", function () { - // By default, dragging changes the selected node - // to the dragged one, we want to keep the current - // job selected - network.selectNodes([self.res_id]); - }); - network.on("click", function (params) { - if (params.nodes.length > 0) { - var jobId = params.nodes[0]; - if (jobId !== self.res_id) { - self.openDependencyJob(jobId); - } - } else { - // Clicked outside of the nodes, we want to - // keep the current job selected - network.selectNodes([self.res_id]); - } - }); - this.network = network; - }, - openDependencyJob: function (res_id) { - var self = this; - this._rpc({ - model: this.model, - method: "get_formview_action", - args: [[res_id]], - context: this.record.getContext(this.recordParams), - }).then(function (action) { - self.trigger_up("do_action", {action: action}); - }); - }, - }); - - field_registry.add("job_directed_graph", JobDirectedGraph); - - return { - JobDirectedGraph: JobDirectedGraph, - }; -}); diff --git a/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.esm.js b/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.esm.js new file mode 100644 index 0000000000..85f195600a --- /dev/null +++ b/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.esm.js @@ -0,0 +1,143 @@ +/* @odoo-module */ +/* global vis */ + +import {loadCSS, loadJS} from "@web/core/assets"; +import {registry} from "@web/core/registry"; +import {standardFieldProps} from "@web/views/fields/standard_field_props"; +import {useService} from "@web/core/utils/hooks"; + +const {Component, onWillStart, useEffect, useRef} = owl; + +export class JobDirectGraph extends Component { + setup() { + this.orm = useService("orm"); + this.action = useService("action"); + this.rootRef = useRef("root_vis"); + this.network = null; + onWillStart(async () => { + await loadJS("/queue_job/static/lib/vis/vis-network.min.js"); + loadCSS("/queue_job/static/lib/vis/vis-network.min.css"); + }); + useEffect(() => { + this.renderNetwork(); + this._fitNetwork(); + return () => { + if (this.network) { + this.$el.empty(); + } + return this.rootRef.el; + }; + }); + } + + get $el() { + return $(this.rootRef.el); + } + + get resId() { + return this.props.record.data.id; + } + + get context() { + return this.props.record.getFieldContext(this.props.name); + } + + get model() { + return this.props.record.resModel; + } + + htmlTitle(html) { + const container = document.createElement("div"); + container.innerHTML = html; + return container; + } + + renderNetwork() { + if (this.network) { + this.$el.empty(); + } + let nodes = this.props.value.nodes || []; + if (!nodes.length) { + return; + } + nodes = nodes.map((node) => { + node.title = this.htmlTitle(node.title || ""); + return node; + }); + + const edges = []; + _.each(this.props.value.edges || [], function (edge) { + const edgeFrom = edge[0]; + const edgeTo = edge[1]; + edges.push({ + from: edgeFrom, + to: edgeTo, + arrows: "to", + }); + }); + + const data = { + nodes: new vis.DataSet(nodes), + edges: new vis.DataSet(edges), + }; + const options = { + // Fix the seed to have always the same result for the same graph + layout: {randomSeed: 1}, + }; + // Arbitrary threshold, generation becomes very slow at some + // point, and disabling the stabilization helps to have a fast result. + // Actually, it stabilizes, but is displayed while stabilizing, rather + // than showing a blank canvas. + if (nodes.length > 100) { + options.physics = {stabilization: false}; + } + const network = new vis.Network(this.$el[0], data, options); + network.selectNodes([this.resId]); + var self = this; + network.on("dragging", function () { + // By default, dragging changes the selected node + // to the dragged one, we want to keep the current + // job selected + network.selectNodes([self.resId]); + }); + network.on("click", function (params) { + if (params.nodes.length > 0) { + var resId = params.nodes[0]; + if (resId !== self.resId) { + self.openDependencyJob(resId); + } + } else { + // Clicked outside of the nodes, we want to + // keep the current job selected + network.selectNodes([self.resId]); + } + }); + this.network = network; + } + + async openDependencyJob(resId) { + const action = await this.orm.call( + this.model, + "get_formview_action", + [[resId]], + { + context: this.context, + } + ); + await this.action.doAction(action); + } + + _fitNetwork() { + if (this.network) { + this.network.fit(this.network.body.nodeIndices); + } + } +} + +JobDirectGraph.props = { + ...standardFieldProps, +}; + +JobDirectGraph.template = "queue.JobDirectGraph"; + +registry.category("fields").add("job_directed_graph", JobDirectGraph); diff --git a/queue_job/static/src/scss/queue_job_fields.scss b/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.scss similarity index 59% rename from queue_job/static/src/scss/queue_job_fields.scss rename to queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.scss index 150469a384..acdf88442b 100644 --- a/queue_job/static/src/scss/queue_job_fields.scss +++ b/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.scss @@ -2,4 +2,9 @@ width: 600px; height: 400px; border: 1px solid lightgray; + + div.root_vis { + height: 100%; + width: 100%; + } } diff --git a/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.xml b/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.xml new file mode 100644 index 0000000000..9334854feb --- /dev/null +++ b/queue_job/static/src/views/fields/job_direct_graph/job_direct_graph.xml @@ -0,0 +1,8 @@ + + + + +

+ + + diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py index 0c461db230..1bc976939e 100644 --- a/queue_job/tests/common.py +++ b/queue_job/tests/common.py @@ -95,6 +95,7 @@ def button_that_uses_delayable_chain(self): "odoo.addons.queue_job.delay.Job", name="Job Class", auto_spec=True, + unsafe=True, ) as job_cls_mock: with JobsTrap(job_cls_mock) as trap: yield trap diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index d58739a82e..1afab4e178 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -60,7 +60,7 @@

- + diff --git a/test_queue_job/models/test_models.py b/test_queue_job/models/test_models.py index 0d3f9d6b33..f810dba862 100644 --- a/test_queue_job/models/test_models.py +++ b/test_queue_job/models/test_models.py @@ -2,6 +2,7 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) from odoo import api, fields, models + from odoo.addons.queue_job.delay import chain from odoo.addons.queue_job.exception import RetryableJobError from odoo.addons.queue_job.job import identity_exact diff --git a/test_queue_job/tests/test_dependencies.py b/test_queue_job/tests/test_dependencies.py index 11084b681e..4246fdbeba 100644 --- a/test_queue_job/tests/test_dependencies.py +++ b/test_queue_job/tests/test_dependencies.py @@ -116,7 +116,7 @@ def test_depends_enqueue_waiting_single(self): job_root.perform() job_root.set_done() job_root.store() - self.env["base"].flush() + self.env.flush_all() job_root.enqueue_waiting() diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 094a56165b..1585f992f0 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -198,7 +198,7 @@ def test_store_extra_data(self): self.assertEqual(stored.additional_info, "JUST_TESTING") test_job.set_failed(exc_info="failed test", exc_name="FailedTest") test_job.store() - stored.invalidate_cache() + stored.invalidate_recordset() self.assertEqual(stored.additional_info, "JUST_TESTING_BUT_FAILED") def test_read(self): From 1cd4942f06623f587d3a12723e0a4041b4c049b8 Mon Sep 17 00:00:00 2001 From: "Laurent Mignon (ACSONE)" Date: Tue, 22 Nov 2022 14:20:43 +0100 Subject: [PATCH 88/88] [FIX] queue_job: fix invalid po files --- queue_job/i18n/de.po | 36 ------------------------------------ queue_job/i18n/es.po | 20 -------------------- queue_job/i18n/zh_CN.po | 35 ----------------------------------- 3 files changed, 91 deletions(-) diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index 0f065bce8d..d419a26716 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -942,39 +942,3 @@ msgstr "Assistent zur erneuten Einreihung einer Job-Auswahl" msgid "Worker Pid" msgstr "" -#~ msgid "Channel Method Name" -#~ msgstr "Kanal-Methodenname" - -#~ msgid "Followers (Channels)" -#~ msgstr "Abonnenten (Kanäle)" - -#~ msgid "Number of unread messages" -#~ msgstr "Das ist die Anzahl von ungelesenen Nachrichten" - -#~ msgid "Unread Messages" -#~ msgstr "Ungelesene Nachrichten" - -#~ msgid "Unread Messages Counter" -#~ msgstr "Zähler für ungelesene Nachrichten" - -#~ msgid "Override Channel" -#~ msgstr "Kanal überschreiben" - -#~ msgid "Website Messages" -#~ msgstr "Website Nachrichten" - -#~ msgid "Website communication history" -#~ msgstr "Historie der Website-Kommunikation" - -#~ msgid "If checked new messages require your attention." -#~ msgstr "" -#~ "Wenn es gesetzt ist, erfordern neue Nachrichten Ihre Aufmerksamkeit." - -#~ msgid "Overdue" -#~ msgstr "Überfällig" - -#~ msgid "Planned" -#~ msgstr "Geplant" - -#~ msgid "Today" -#~ msgstr "Heute" diff --git a/queue_job/i18n/es.po b/queue_job/i18n/es.po index 2ae2060da1..7b163ca642 100644 --- a/queue_job/i18n/es.po +++ b/queue_job/i18n/es.po @@ -957,23 +957,3 @@ msgstr "Asistente para volver a poner en cola una selección de trabajos" msgid "Worker Pid" msgstr "Pid del trabajador" - -#~ msgid "Number of unread messages" -#~ msgstr "Número de mensajes no leidos" - -#~ msgid "Unread Messages" -#~ msgstr "Mensajes sin leer" - -#~ msgid "Unread Messages Counter" -#~ msgstr "Nº de mensajes sin leer" - -#~ msgid "Channel Method Name" -#~ msgstr "Nombre del método del canal" - -#~ msgid "Override Channel" -#~ msgstr "Sobreescribir canal" - -#, python-format -#~ msgid "Queue jobs must created by calling 'with_delay()'." -#~ msgstr "Los trabajos de la cola se deben crear llamando a `with_delay()`" - diff --git a/queue_job/i18n/zh_CN.po b/queue_job/i18n/zh_CN.po index ca7fd2bb98..b0f590c883 100644 --- a/queue_job/i18n/zh_CN.po +++ b/queue_job/i18n/zh_CN.po @@ -938,38 +938,3 @@ msgstr "重新排队向导所选的作业" msgid "Worker Pid" msgstr "" -#~ msgid "Channel Method Name" -#~ msgstr "频道方法名称" - -#~ msgid "Followers (Channels)" -#~ msgstr "关注者(频道)" - -#~ msgid "Number of unread messages" -#~ msgstr "未读消息数量" - -#~ msgid "Unread Messages" -#~ msgstr "未读消息" - -#~ msgid "Unread Messages Counter" -#~ msgstr "未读消息计数器" - -#~ msgid "Override Channel" -#~ msgstr "覆盖频道" - -#~ msgid "Website Messages" -#~ msgstr "网站消息" - -#~ msgid "Website communication history" -#~ msgstr "网站交流历史" - -#~ msgid "If checked new messages require your attention." -#~ msgstr "查看是否有需要留意的新消息。" - -#~ msgid "Overdue" -#~ msgstr "逾期" - -#~ msgid "Planned" -#~ msgstr "计划" - -#~ msgid "Today" -#~ msgstr "今天"