diff --git a/requirements.txt b/requirements.txt index a223b0b0..aea7b36e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ # generated from manifests external_dependencies bravado_core jsonschema<4 +markdown swagger_spec_validator diff --git a/sync/__manifest__.py b/sync/__manifest__.py index 68d2c25f..1ae5e001 100644 --- a/sync/__manifest__.py +++ b/sync/__manifest__.py @@ -1,21 +1,20 @@ -# Copyright 2020-2021 Ivan Yelizariev +# Copyright 2020-2021,2024 Ivan Yelizariev # Copyright 2020-2021 Denis Mudarisov # Copyright 2021 Ilya Ilchenko # License MIT (https://opensource.org/licenses/MIT). { - "name": """Sync Studio""", - "summary": """Synchronize something with anything: SystemX↔Odoo, Odoo1↔Odoo2, SystemX↔SystemY. ETL/ESB tool similar to OCA/connector, but more flexible""", - "category": "Extra Tools", - "images": ["images/sync-studio.jpg"], - "version": "16.0.6.2.0", + "name": "Sync 🪬 Studio", + "summary": """Join the Amazing 😍 Community ⤵️""", + "category": "VooDoo ✨ Magic", + "version": "16.0.7.0.0", "application": True, - "author": "IT Projects Labs, Ivan Yelizariev", - "support": "help@itpp.dev", + "author": "Ivan Kropotkin", + "support": "info@odoomagic.com", "website": "https://sync_studio.t.me/", "license": "Other OSI approved licence", # MIT "depends": ["base_automation", "mail", "queue_job"], - "external_dependencies": {"python": [], "bin": []}, + "external_dependencies": {"python": ["markdown"], "bin": []}, "data": [ "security/sync_groups.xml", "security/ir.model.access.csv", @@ -32,13 +31,19 @@ "wizard/sync_make_module_views.xml", "data/queue_job_function_data.xml", ], + "assets": { + "web.assets_backend": [ + "sync/static/src/scss/src.scss", + ], + }, "demo": [ - "data/sync_project_context_demo.xml", - "data/sync_project_telegram_demo.xml", - "data/sync_project_odoo2odoo_demo.xml", - "data/sync_project_trello_github_demo.xml", - "data/sync_project_unittest_demo.xml", - "data/sync_project_context_demo.xml", + # Obsolete + # "data/sync_project_context_demo.xml", + # "data/sync_project_telegram_demo.xml", + # "data/sync_project_odoo2odoo_demo.xml", + # "data/sync_project_trello_github_demo.xml", + # "data/sync_project_unittest_demo.xml", + # "data/sync_project_context_demo.xml", ], "qweb": [], "post_load": None, diff --git a/sync/doc/MAGIC.rst b/sync/doc/MAGIC.rst new file mode 100644 index 00000000..fce385c8 --- /dev/null +++ b/sync/doc/MAGIC.rst @@ -0,0 +1,90 @@ +This document describes `MAGIC.*` tools available on Project Code evaluation (core code, library code, task code) + + +Base +==== + +* ``MAGIC.env``: Odoo Environment +* ``MAGIC.log(message, level=MAGIC.LOG_INFO)``: logging function to record debug information + + log levels: + + * ``MAGIC.LOG_DEBUG`` + * ``MAGIC.LOG_INFO`` + * ``MAGIC.LOG_WARNING`` + * ``MAGIC.LOG_ERROR`` + +* ``MAGIC.log_transmission(recipient_str, data_str)``: report on data transfer to external recipients + +Links +===== + +* ``MAGIC.get_link(...)`` +* ``MAGIC.set_link(...)`` + +These methods are documented separetely in ``__. + +Sync Helpers +============ + +* ``MAGIC.sync_odoo2x(...)`` +* ``MAGIC.sync_x2odoo(...)`` +* ``MAGIC.sync_external(...)`` + +These methods are documented separetely in ``__. + +Event +===== + +* ``MAGIC.trigger_name``: available in tasks' code only +* ``MAGIC.user``: user related to the event, e.g. who clicked a button + +Asynchronous work +================= + +* ``MAGIC.add_job(func_name, **options)(*func_args, **func_kwargs)``: call a function asynchronously; options are similar to ``with_delay`` method of ``queue_job`` module: + + * ``priority``: Priority of the job, 0 being the higher priority. Default is 10. + * ``eta``: Estimated Time of Arrival of the job. It will not be executed before this date/time. + * ``max_retries``: maximum number of retries before giving up and set the job + state to 'failed'. A value of 0 means infinite retries. Default is 5. + * ``description`` human description of the job. If None, description is + computed from the function doc or name + * ``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 added. + + +Attachments +=========== + +* ``attachment._public_url()``: generates access url. Can be used to pass attachments to an external system as url, instead of direct uploading the content. + +Libs +==== + +* ``MAGIC.json`` +* ``MAGIC.time`` +* ``MAGIC.datetime`` +* ``MAGIC.dateutil`` +* ``MAGIC.timezone`` +* ``MAGIC.b64encode`` +* ``MAGIC.b64decode`` + +Tools +===== + +* ``MAGIC.url2base64`` +* ``MAGIC.url2bin`` +* ``MAGIC.get_lang(env, lang_code=False)``: returns `res.lang` record +* ``MAGIC.html2plaintext`` +* ``MAGIC.type2str``: get type of the given object +* ``MAGIC.DEFAULT_SERVER_DATETIME_FORMAT`` +* ``MAGIC.AttrDict``: like a dictionary, but with accessing to attributes via dot. + +Exceptions +========== + +* ``MAGIC.UserError`` +* ``MAGIC.ValidationError`` +* ``MAGIC.RetryableJobError``: raise to restart job from beginning; e.g. in case of temporary errors like broken connection +* ``MAGIC.OSError`` diff --git a/sync/doc/changelog.rst b/sync/doc/changelog.rst index df5ebd01..1515164c 100644 --- a/sync/doc/changelog.rst +++ b/sync/doc/changelog.rst @@ -1,3 +1,8 @@ +`7.0.0` +------- + +- **New:** Dramatic improvement on Sync Studio API + `6.2.0` ------- diff --git a/sync/doc/index.rst b/sync/doc/index.rst index 8e415746..032b683a 100644 --- a/sync/doc/index.rst +++ b/sync/doc/index.rst @@ -41,389 +41,7 @@ User Access Levels * ``Sync Studio: User``: read-only access * ``Sync Studio: Developer``: restricted write access -* ``Sync Studio: Administrator``: same as Developer, but with access to **Secrets** - -Project -======= - -* Open menu ``[[ Sync Studio ]] >> Projects`` -* Create a project - - * **Name**, e.g. *Legacy migration* - - * In the ``Parameters`` tab - - * **Params** - - * **Key** - * **Value** - * **Texts**: Translatable parameters - * **Secrets**: Parameters with restricted access: key values are visible for Administrators only - - * In the ``Evaluation Context`` tab - - * **Evaluation context**: predefined additional variables and methods - * **Common_code**: code that is executed before running any - project's task. Can be used for initialization or for helpers. Any variables - and functions that don't start with underscore symbol will be available in - task's code. - - * In the ``Available Tasks`` tab - - * **Name**, e.g. *Sync products* - * **Code**: code with at least one of the following functions - - * ``handle_cron()`` - * ``handle_db(records)`` - - * ``records``: all records on which this task is triggered - - * ``handle_webhook(httprequest)`` - - * ``httprequest``: contains information about request, e.g. - - * `httprequest.data `__: request data - * `httprequest.files `__: uploaded files - * `httprequest.remote_addr `__: ip address of the caller. - * see `Werkzeug doc - `__ - for more information. - * optionally can return data as a response to the webhook request; any data transferred in this way are logged via ``log_transmission`` function: - - * for *json* webhook: - * ``return json_data`` - * for *x-www-form-urlencoded* webhook: - * ``return data_str`` - * ``return data_str, status`` - * ``return data_str, status, headers`` - - * ``status`` is a response code, e.g. ``200``, ``403``, etc. - * ``headers`` is a list of key-value tuples, e.g. ``[('Content-Type', 'text/html')]`` - * ``handle_button()`` - - * **Cron Triggers**, **DB Triggers**, **Webhook Triggers**, **Manual - Triggers**: when to execute the Code. See below for further information - -Job Triggers -============ - -Cron ----- - -* **Trigger Name**, e.g. ``NIGHTLY_SYNC`` -* **Execute Every**: every 2 hours, every 1 week, etc. -* **Next Execution Date** -* **Scheduler User** - -DB --- - -* **Trigger Name**, e.g. ``PRODUCT_PRICE_CHANGE`` -* **Model** -* **Trigger Condition** - - * On Creation - * On Update - * On Creation & Update - * On Deletion - * Based on Timed Condition - - * Allows to trigger task before, after on in time of Date/Time fields, e.g. - 1 day after Sale Order is closed - -* **Apply on**: records filter -* **Before Update Domain**: additional records filter for *On Update* event -* **Watched fields**: fields list for *On Update* event - -Webhook -------- - -* **Trigger Name**, e.g. ``ON_EXTERNAL_UPDATE`` -* **Webhook Type**: *application/x-www-form-urlencoded* or *application/json* - -* **Webhook URL**: readonly. - -Button ------- - -* **Trigger Name**, e.g. ``SYNC_ALL_PRODUCTS`` - -Code -==== - -Available variables and functions: ----------------------------------- - -Base -~~~~ - -* ``env``: Odoo Environment -* ``log(message, level=LOG_INFO)``: logging function to record debug information - - log levels: - - * ``LOG_DEBUG`` - * ``LOG_INFO`` - * ``LOG_WARNING`` - * ``LOG_ERROR`` - * - -* ``log_transmission(recipient_str, data_str)``: report on data transfer to external recipients - -Links -~~~~~ - -* ``.set_link(relation_name, external, sync_date=None, allow_many2many=False) -> link``: makes link between Odoo and external resource - - * ``allow_many2many``: when False raises an error if there is a link for the - ``record`` and ``relation_name`` or if there is a link for ``relation_name`` - and ``external``; - -* ``.search_links(relation_name) -> links`` -* ``get_link(relation_name, external_ref, model=None) -> link`` - -Odoo Link usage: - -* ``link.odoo``: normal Odoo record - - * ``link.odoo._name``: model name, e.g. ``res.partner`` - * ``link.odoo.id``: odoo record id - * ``link.odoo.``: some field of the record, e.g. ``link.odoo.email``: partner email - -* ``link.external``: external reference, e.g. external id of a partner -* ``link.sync_date``: last saved date-time information -* ``links.odoo``: normal Odoo RecordSet -* ``links.external``: list of all external references -* ``links.sync_date``: minimal data-time among links -* ``links.update_links(sync_date=None)``: set new sync_date value; if value is not passed, then ``now()`` is used -* ``links.unlink()``: delete links -* ``for link in links:``: iterate over links -* ``if links``: check that link set is not empty -* ``len(links)``: number of links in the set -* sets operations: - - * ``links1 == links2``: sets are equal - * ``links1 - links2``: links that are in first set, but not in another - * ``links1 | links2``: union - * ``links1 & links2``: intersection - * ``links1 ^ links2``: equal to ``(links1 | links2) - (links1 & links2)`` - - - -You can also link external data with external data on syncing two different system (e.g. github and trello). - -* ``set_link(relation_name, {"github": github_issue_num, "trello": trello_card_num}, sync_date=None, allow_many2many=False, model=None) -> elink`` - * ``refs`` is a dictionary with system name and references pairs, e.g. - - .. code-block:: python - - { - "github": github_issue_num, - "trello": trello_card_num, - } - -* ``search_links(relation_name, refs) -> elinks``: - * ``refs`` may contain list of references as values, e.g. - - .. code-block:: python - - { - "github": [github_issue_num], - "trello": [trello_card_num], - } - - * use None values to don't filter by reference value of that system, e.g. - - .. code-block:: python - - { - "github": None, - "trello": [trello_card_num], - } - - * if references for both systems are passed, then elink is added to result - only when its references are presented in both references lists -* ``get_link(relation_name, refs, model=None) -> elink`` - - * At least one of the reference should be not Falsy - * ``get_link`` raise error, if there are few odoo records linked to the - references. Set work with multiple relations (*one2many*, *many2one*, - *many2many*) use ``set_link(..., allow_many2many=False)`` and - ``search_links`` - -In place of ``github`` and ``trello`` you can use other labels depending on what you sync. - -External Link is similar to Odoo link with the following differences: - -* ``elink.get()``, e.g. ``elink.get("github")``: reference value for system; it's a replacement for ``link.odoo`` and ``link.external`` in Odoo link - -Sync Helpers -~~~~~~~~~~~~ - -For one2one syncronization you can use following helpers. - -* ``sync_odoo2x(src_list, sync_info, create=False, update=False)`` - - * ``sync_info["x"]["create"](odoo_record) -> external_ref``: create external record and return reference - * ``sync_info["x"]["update"](external_ref, odoo_record) -> external_ref``: update external record - * ``sync_info["x"]["get_ref"](x)``: get reference for an item in src_list - -* ``sync_x2odoo(src_list, sync_info, create=False, update=False)`` - - * ``sync_info["odoo"]["create"](x) -> odoo_record``: create odoo record from external data - * ``sync_info["odoo"]["update"](odoo_record, x) -> odoo_record``: update odoo record according to providing external data - -Common args: - -* ``sync_info["relation"]``: same as ``relation_name`` in ``set_link``, ``get_link`` -* ``src_list``: iterator of ``x`` or ``odoo_record`` values -* ``create``: boolean value for "create record if it doesn't exist" -* ``update``: boolean value for "update record if it exists" - -To use helpers, create ``sync_info`` with all information, e.g. - -.. code-block:: python - - EMPLOYEE_SYNC = { - "relation": "my_system_and_odoo_employee_rel", - "x": { - "get_ref": employee2ref, - "create": employee_create, - "update": employee_update, - }, - "odoo": { - "create": employee_create_odoo, - "update": employee_update_odoo, - } - } - -Then you can reuse in all syncronizations, e.g. - -.. code-block:: python - - # on initial fetching records from external system - sync_x2odoo(all_employees_x, EMPLOYEE_SYNC, create=True) - - # to push all updates to external system - sync_odoo2x(all_employees_odoo, EMPLOYEE_SYNC, update=True) - - # on updating a single record push all updates to external system - sync_odoo2x([employee_odoo], EMPLOYEE_SYNC, update=True) - - -There is a similar helper for syncronization between two external systems: - -* ``sync_external(src_list, relation, src_info, dst_info, create=False, update=False)`` - - * ``src_info["get_ref"](src_data)``: get reference for an item in src_list - * ``src_info["system"]``: e.g. ``"github"`` - * ``src_info["update"](dst_ref, src_data)`` - * ``src_info["create"](src_data) -> dst_ref`` - * ``dst["system"]``: e.g. ``"trello"`` - -Project Values -~~~~~~~~~~~~~~ - -* ``params.``: project params -* ``webhooks.``: contains webhook url; only in tasks' code - -Event -~~~~~ - -* ``trigger_name``: available in tasks' code only -* ``user``: user related to the event, e.g. who clicked a button - -Asynchronous work -~~~~~~~~~~~~~~~~~ - -* ``add_job(func_name, **options)(*func_args, **func_kwargs)``: call a function asynchronously; options are similar to ``with_delay`` method of ``queue_job`` module: - - * ``priority``: Priority of the job, 0 being the higher priority. Default is 10. - * ``eta``: Estimated Time of Arrival of the job. It will not be executed before this date/time. - * ``max_retries``: maximum number of retries before giving up and set the job - state to 'failed'. A value of 0 means infinite retries. Default is 5. - * ``description`` human description of the job. If None, description is - computed from the function doc or name - * ``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 added. - - -Attachments -~~~~~~~~~~~ - -* ``attachment._public_url()``: generates access url. Can be used to pass attachments to an external system as url, instead of direct uploading the content. - -Libs -~~~~ - -* ``json`` -* ``time`` -* ``datetime`` -* ``dateutil`` -* ``timezone`` -* ``b64encode`` -* ``b64decode`` - -Tools -~~~~~ - -* ``url2base64`` -* ``url2bin`` -* ``get_lang(env, lang_code=False)``: returns `res.lang` record -* ``html2plaintext`` -* ``type2str``: get type of the given object -* ``DEFAULT_SERVER_DATETIME_FORMAT`` - -Exceptions -~~~~~~~~~~ - -* ``UserError`` -* ``ValidationError`` -* ``RetryableJobError``: raise to restart job from beginning; e.g. in case of temporary errors like broken connection -* ``OSError`` - -Evaluation context ------------------- - -Evaluation provides additional variables and methods for a project. For example, for telegram integration is could be method to send message to a telegram user. -To add a new context, create ``sync.project.context`` record and add method ``_eval_context_NAME``. Example: - -.. code-block:: xml - - - - - my_project - My Sync Project - - - - -.. code-block:: python - - import requests - from odoo import api, fields, models - - class SyncProject(models.Model): - - _inherit = "sync.project.context" - - @api.model - def _eval_context_my_project(self, secrets, eval_context): - """Additional function to make http request - - * httpPost(url, **kwargs) - """ - log_transmission = eval_context["log_transmission"] - log = eval_context["log"] - def httpPOST(url, **kwargs): - log_transmission(url, json.dumps(kwargs)) - r = requests.request("POST", url, **kwargs) - log("Response: %s" % r.text) - return r.text - return { - "httpPost": httpPost - } +* ``Sync Studio: Administrator``: same as Developer, but with access to the **Secrets** and to the Core Code Running Job =========== @@ -477,307 +95,3 @@ hand corner. You can filter and group logs by following fields: * Job Start Time * Log Level * Status (Success / Fail) - -Demo Project: Odoo <-> Telegram -=============================== - -In this project we create new partners and attach messages sent to telegram bot. -Odoo Messages prefixed with ``/telegram`` are sent back to telegram. - -To try it, you need to install this module in demo mode. Also, your odoo -instance must be accessible over internet to receive telegram webhooks. Due to -telegram requirements, your web server must use `https` connection. - -How it works ------------- - -*Webhook Trigger* waits for an update from telegram. Once it happened, the action depends on message text: - -* for ``/start`` message (it's sent on first bot usage), we reply with welcome - message (can be configured in project parameter TELEGRAM_WELCOME_MESSAGE) and - create a partner with **Internal Reference** equal to *@telegram* - -* for any other message we attach a message copy to the partner with corresponding **Internal Reference** - -*DB trigger* waits for a message attached to a telegram partner (telegram partners are filtered by **Internal Reference** field). If the message has ``/telegram`` prefix, task's code is run: - -* a message copy (after removing the prefix) is sent to corresponding telegram user -* attach report message to the partner record - -Configuration -------------- - -In Telegram: - -* send message ``/new`` to @BotFather and follow further instructions to create bot and get the bot token - -In Odoo: - -* `Activate Developer Mode `__ -* Open menu ``[[ Settings ]] >> Technical >> Parameters >> System Parameters`` -* Check that parameter ``web.base.url`` is properly set and it's accessible over - internet (it should not localhost). Also, telegram accepts https addresses only (i.e. not http) -* Open menu ``[[ Sync Studio ]] >> Sync Projects`` -* Select *Demo Telegram Integration* project -* Go to ``Parameters`` tab -* Set **Secrets**: - - * TELEGRAM_BOT_TOKEN - -* Unarchive the project -* Open *Manual Triggers* Tab -* Click button ``[Run Now]`` near to *Setup* task - -Usage ------ - -In Telegram: - -* send some message to the created bot - -In Odoo: - -* Open Contacts/Customers menu -* RESULT: there is new partner with name *Telegram:* (the prefix can be configured in project parameter PARTNER_NAME_PREFIX) -* Open the partner and attach a log/message with prefix ``/telegram``, e.g. ``/telegram Hello! How can I help you?`` -* Wait few seconds to get confirmation -* RESULT: you will see new attached message from Odoo Bot with confirmation that message is sent to telegram - -In telegram: - -* RESULT: the message is delivered via bot - -You can continue chatting in this way - -Demo Project: Odoo2odoo -======================= - -In this project we push partners to external Odoo 13.0 and sync back avatar changes. - -To try it, you need to install this module in demo mode. - -How it works ------------- - -*DB trigger* waits for partner creation. When it happens, task's code is run: - -* creates a copy of partner on external Odoo - - * XMLRPC is used as API - -* gets back id of the partner copy on external Odoo -* attaches the id to the partner of our Odoo via ``set_link`` method - -To sync changes on external Odoo we use *Cron trigger*. It runs every 15 minutes. You can also run it manually. The code works as following: - -* call ``search_links`` function to get ids to sync and the oldest sync date -* request to the external Odoo for the partners, but filtered by sync time to don't load partner without new updates -* for each of the fetched partner compare its update time with sync date saved in the link - - * if a partner is updated since last sync, then update partner and sync date - -Configuration -------------- - -* Open menu ``[[ Sync Studio ]] >> Sync Projects`` -* Select *Demo Odoo2odoo integration* project -* Go to ``Parameters`` tab -* Set **Params**: - * URL, e.g. ``https://odoo.example`` - * DB, e.g. ``odoo`` -* Set **Secrets**: - - * USERNAME, e.g. ``admin`` - * PASSWORD, e.g. ``admin`` -* Unarchive the project - -Usage ------ - -**Syncing new partner.** - -* Open Contacts/Customers menu -* Create new partner -* Go back to the project -* Click ``Logs`` button and check that there are no errors - -* Open the external Odoo - - * RESULT: the partner copy is on the external Odoo - * Update avatar image on it - -* Go back to the *Demo Odoo2odoo Integration* project in our Odoo -* Click ``Available Tasks`` tab -* Click ``[Edit]`` -* Go to ``Sync Remote Partners Updates`` task -* Click on ``Available Triggers`` tab and go inside ``CHECK_EXTERNAL_ODOO`` trigger -* Configure cron -* Make trigger Active on the upper right corner -* Click ``[Save]`` - -* Then you can trigger synchronization in some of the following ways: - - 1. Click ``[Run Manually]`` inside the trigger - - 2. Simply wait up to cron job will start on a schedule :) - -* Now open the partner in our Odoo -* RESULT: avatar is synced from external Odoo -* You can try to change avatar on external Odoo again and should get the same results - -**Uploading all existing partners.** - -* Open menu ``[[ Sync Studio ]] >> Sync Projects`` -* Select *Demo Odoo2odoo Integration* project -* Choose Sync Task *Sync Local Partners To Remote Odoo* -* Click button ``[Run Now]`` -* Open the external Odoo - - * RESULT: copies of all our partners are in the external Odoo; they have *Sync Studio:* prefix (can be configured in project parameter UPLOAD_ALL_PARTNER_PREFIX) - -Demo project: GitHub <-> Trello -=============================== - -In this project we create copies of github issues/pull requests and their -messages in trello cards. It's one side synchronization: new cards and message in -trello are not published in github. Trello and Github labels are -synchronized in both directions. - -To try it, you need to install this module in demo mode. Also, your odoo -instance must be accessible over internet to receive github and trello webhooks. - -How it works ------------- - - -*Github Webhook Trigger* waits from GitHub for issue creation and new messages: - -* if there is no trello card linked to the issue, then create trello card and link it with the issue -* if new message is posted in github issue, then post message copy in trello card - -*Github Webhook Trigger* waits from GitHub for label attaching/detaching (*Trello Webhook Trigger* works in the same way) - -* if label is attached in GitHub issue , then check for github label and trello - label links and create trello label if there is no such link yet -* if label is attached in github issue, then attach corresponding label in trello card -* if label is detached in github issue, then detach corresponding label in trello card - -*Github Webhook Trigger* waits from GitHub for label updating/deleting (*Trello Webhook Trigger* works in the same way): - -* if label is changed and there is trello label linked to it, then update the label -* if label is changed and there is trello label linked to it, then delete the label - -There is still possibility that labels are mismatch, e.g. due to github api -temporary unavailability or misfunction (e.g. api request to add label responded -with success, but label was not attached) or if odoo was stopped when github -tried to notify about updates. In some cases, we can just retry the handler -(e.g. there was an error on api request to github/trello, then the system tries -few times to repeat label attaching/detaching). As a solution for cases when -retrying didn't help (e.g. api is still not working) or cannot help (e.g. odoo -didn't get webhook notification), we run a *Cron Trigger* at night to check for -labels mismatch and synchronize them. In ``LABELS_MERGE_STRATEGY`` you can -choose which strategy to use: - -* ``USE_TRELLO`` -- ignore github labels and override them with trello labels -* ``USE_GITHUB`` -- ignore trello labels and override them with push github labels -* ``UNION`` -- add missed labels from both side -* ``INTERSECTION`` -- remove labels that are not attached on both side - -Configuration -------------- - -* Open menu ``[[ Sync Studio ]] >> Sync Projects`` -* Select *Demo Github-Trello Integration* project -* In ``Parameters`` tab set **Secrets** (check Description and Documentation links near the parameters table about how to get the secret parameters): - - * ``GITHUB_REPO`` - * ``GITHUB_TOKEN`` - * ``TRELLO_BOARD_ID`` - * ``TRELLO_KEY`` - * ``TRELLO_TOKEN`` - -* In *Available Tasks* tab: - - * Click ``[Edit]`` - * Open *Labels Conflict resolving* task - * In *Available Triggers* tab: - - * Open *CONFLICT_RESOLVING* Cron - * Change **Next Execution Date** in webhook to the night time - * Set **Number of Calls**, a negative value means no limit (e.g. `-1`) - * Make it active by checking the box in front of the corresponding field - * Click ``[Save]`` -* Save all the changes you made in the integration -* Make integration Active by clicking ``Action >> Unarchive`` -* In project's *Manual Triggers* tab: - - * Click ``[Run Now]`` buttons in trigger *SETUP_GITHUB* - * Click ``[Run Now]`` buttons in triggers *SETUP_TRELLO*. Note, that `it doesn't work `_ without one of the following workarounds: - - * delete `line `__ that raise exception in case of type mismatching (search for ``Function declared as capable of handling request of type`` in standard Odoo code). In most cases, this workaround doesn't need to be reverted - * open file ``sync/controllers/webhook.py`` and temporarily change ``type="json"`` to ``type="http"``. Revert the changes after successfully setting up trello - * Add a temporal handler in your proxy/web server. Example for nginx: - - .. code-block:: nginx - - location /website/action-json/ { - return 200 "{}"; - } - -Usage ------ - -**Syncing new Github issue** - -* Open Github -* Create issue -* Open trello -* RESULT: you see a copy of the Github issue -* Go back to the Github issue -* Post a message -* Now go back to the trello card -* RESULT: you see a copy of the message -* You can also add/remove github issue labels or trello card labels (note that the name of the label must be added - in Trello so that there are no errors in the GitHub). - - * RESULT: once you change them on one side, after short time, you will see the changes on another side - -**Labels syncing** - -* Open Github or Trello -* Rename or delete some label -* RESULT: the same happened in both systems - -**Conflict resolving** - -* Create a github issue and check that it's syncing to trello -* Stop Odoo -* Make *different* changes of labels both in github issue and trello card -* Start Odoo -* Open menu ``[[ Sync Studio ]] >> Projects`` -* Select *Demo Trello-Github integration* project -* Click ``[Edit]`` and open *Labels Conflict Resolving* task in *Available Tasks* tab -* Make ``CONFLICT_RESOLVING`` Cron Trigger run in one of the following ways - - 1. Choose Cron Trigger and click ``[Run Manually]`` - - 2. Change **Next Execution Date** to a past time and wait up to 1 minute - -* RESULT: the github issue and corresponding trello card the same set of labels. The merging is done according to selected strategy in ``LABELS_MERGE_STRATEGY`` parameter. - - -**Syncing all existing Github issues.** - -* Open menu ``[[ Sync Studio ]] >> Projects`` -* Select *Demo Tello-Github Integration* project -* Click button ``[Run Now]`` near to ``PUSH_ALL_ISSUES`` manual trigger -* It will start asynchronous jobs. You can check progress via button *Jobs* -* After some time open Trello - - * RESULT: copies of all *open* github issues are in trello; they have *GITHUB:* prefix (can be configured in project parameter ISSUE_FROM_GITHUB_PREFIX) - -Custom Integration -================== - -If you made a custom integration via UI and want to package it into a module, -open the Sync Project and click ``[Actions] -> Export to XML`` button. diff --git a/sync/doc/links.rst b/sync/doc/links.rst new file mode 100644 index 00000000..56e0f12c --- /dev/null +++ b/sync/doc/links.rst @@ -0,0 +1,83 @@ +This document describes Sync Studio tools that help linking records between resources (internal and external). + +* ``.set_link(relation_name, external, sync_date=None, allow_many2many=False) -> link``: makes link between Odoo and external resource + + * ``allow_many2many``: when False raises an error if there is a link for the + ``record`` and ``relation_name`` or if there is a link for ``relation_name`` + and ``external``; + +* ``.search_links(relation_name) -> links`` +* ``MAGIC.get_link(relation_name, external_ref, model=None) -> link`` + +Odoo Link usage: + +* ``link.odoo``: normal Odoo record + + * ``link.odoo._name``: model name, e.g. ``res.partner`` + * ``link.odoo.id``: odoo record id + * ``link.odoo.``: some field of the record, e.g. ``link.odoo.email``: partner email + +* ``link.external``: external reference, e.g. external id of a partner +* ``link.sync_date``: last saved date-time information +* ``links.odoo``: normal Odoo RecordSet +* ``links.external``: list of all external references +* ``links.sync_date``: minimal data-time among links +* ``links.update_links(sync_date=None)``: set new sync_date value; if value is not passed, then ``now()`` is used +* ``links.unlink()``: delete links +* ``for link in links:``: iterate over links +* ``if links``: check that link set is not empty +* ``len(links)``: number of links in the set +* sets operations: + + * ``links1 == links2``: sets are equal + * ``links1 - links2``: links that are in first set, but not in another + * ``links1 | links2``: union + * ``links1 & links2``: intersection + * ``links1 ^ links2``: equal to ``(links1 | links2) - (links1 & links2)`` + +You can also link external data with external data on syncing two different system (e.g. github and trello). + +* ``MAGIC.set_link(relation_name, {"github": github_issue_num, "trello": trello_card_num}, sync_date=None, allow_many2many=False, model=None) -> elink`` + * ``refs`` is a dictionary with system name and references pairs, e.g. + + .. code-block:: python + + { + "github": github_issue_num, + "trello": trello_card_num, + } + +* ``search_links(relation_name, refs) -> elinks``: + * ``refs`` may contain list of references as values, e.g. + + .. code-block:: python + + { + "github": [github_issue_num], + "trello": [trello_card_num], + } + + * use None values to don't filter by reference value of that system, e.g. + + .. code-block:: python + + { + "github": None, + "trello": [trello_card_num], + } + + * if references for both systems are passed, then elink is added to result + only when its references are presented in both references lists +* ``get_link(relation_name, refs, model=None) -> elink`` + + * At least one of the reference should be not Falsy + * ``get_link`` raise error, if there are few odoo records linked to the + references. Set work with multiple relations (*one2many*, *many2one*, + *many2many*) use ``set_link(..., allow_many2many=False)`` and + ``search_links`` + +In place of ``github`` and ``trello`` you can use other labels depending on what you sync. + +External Link is similar to Odoo link with the following differences: + +* ``elink.get()``, e.g. ``elink.get("github")``: reference value for system; it's a replacement for ``link.odoo`` and ``link.external`` in Odoo link diff --git a/sync/doc/old.rst b/sync/doc/old.rst new file mode 100644 index 00000000..2f96724c --- /dev/null +++ b/sync/doc/old.rst @@ -0,0 +1,232 @@ +Demo Project: Odoo2odoo (obsolete) +======================= + +In this project we push partners to external Odoo 13.0 and sync back avatar changes. + +To try it, you need to install this module in demo mode. + +How it works +------------ + +*DB trigger* waits for partner creation. When it happens, task's code is run: + +* creates a copy of partner on external Odoo + + * XMLRPC is used as API + +* gets back id of the partner copy on external Odoo +* attaches the id to the partner of our Odoo via ``set_link`` method + +To sync changes on external Odoo we use *Cron trigger*. It runs every 15 minutes. You can also run it manually. The code works as following: + +* call ``search_links`` function to get ids to sync and the oldest sync date +* request to the external Odoo for the partners, but filtered by sync time to don't load partner without new updates +* for each of the fetched partner compare its update time with sync date saved in the link + + * if a partner is updated since last sync, then update partner and sync date + +Configuration +------------- + +* Open menu ``[[ Sync Studio ]] >> Sync Projects`` +* Select *Demo Odoo2odoo integration* project +* Go to ``Parameters`` tab +* Set **Params**: + * URL, e.g. ``https://odoo.example`` + * DB, e.g. ``odoo`` +* Set **Secrets**: + + * USERNAME, e.g. ``admin`` + * PASSWORD, e.g. ``admin`` +* Unarchive the project + +Usage +----- + +**Syncing new partner.** + +* Open Contacts/Customers menu +* Create new partner +* Go back to the project +* Click ``Logs`` button and check that there are no errors + +* Open the external Odoo + + * RESULT: the partner copy is on the external Odoo + * Update avatar image on it + +* Go back to the *Demo Odoo2odoo Integration* project in our Odoo +* Click ``Available Tasks`` tab +* Click ``[Edit]`` +* Go to ``Sync Remote Partners Updates`` task +* Click on ``Available Triggers`` tab and go inside ``CHECK_EXTERNAL_ODOO`` trigger +* Configure cron +* Make trigger Active on the upper right corner +* Click ``[Save]`` + +* Then you can trigger synchronization in some of the following ways: + + 1. Click ``[Run Manually]`` inside the trigger + + 2. Simply wait up to cron job will start on a schedule :) + +* Now open the partner in our Odoo +* RESULT: avatar is synced from external Odoo +* You can try to change avatar on external Odoo again and should get the same results + +**Uploading all existing partners.** + +* Open menu ``[[ Sync Studio ]] >> Sync Projects`` +* Select *Demo Odoo2odoo Integration* project +* Choose Sync Task *Sync Local Partners To Remote Odoo* +* Click button ``[Run Now]`` +* Open the external Odoo + + * RESULT: copies of all our partners are in the external Odoo; they have *Sync Studio:* prefix (can be configured in project parameter UPLOAD_ALL_PARTNER_PREFIX) + +Demo project: GitHub <-> Trello (obsolete) +=============================== + +In this project we create copies of github issues/pull requests and their +messages in trello cards. It's one side synchronization: new cards and message in +trello are not published in github. Trello and Github labels are +synchronized in both directions. + +To try it, you need to install this module in demo mode. Also, your odoo +instance must be accessible over internet to receive github and trello webhooks. + +How it works +------------ + + +*Github Webhook Trigger* waits from GitHub for issue creation and new messages: + +* if there is no trello card linked to the issue, then create trello card and link it with the issue +* if new message is posted in github issue, then post message copy in trello card + +*Github Webhook Trigger* waits from GitHub for label attaching/detaching (*Trello Webhook Trigger* works in the same way) + +* if label is attached in GitHub issue , then check for github label and trello + label links and create trello label if there is no such link yet +* if label is attached in github issue, then attach corresponding label in trello card +* if label is detached in github issue, then detach corresponding label in trello card + +*Github Webhook Trigger* waits from GitHub for label updating/deleting (*Trello Webhook Trigger* works in the same way): + +* if label is changed and there is trello label linked to it, then update the label +* if label is changed and there is trello label linked to it, then delete the label + +There is still possibility that labels are mismatch, e.g. due to github api +temporary unavailability or misfunction (e.g. api request to add label responded +with success, but label was not attached) or if odoo was stopped when github +tried to notify about updates. In some cases, we can just retry the handler +(e.g. there was an error on api request to github/trello, then the system tries +few times to repeat label attaching/detaching). As a solution for cases when +retrying didn't help (e.g. api is still not working) or cannot help (e.g. odoo +didn't get webhook notification), we run a *Cron Trigger* at night to check for +labels mismatch and synchronize them. In ``LABELS_MERGE_STRATEGY`` you can +choose which strategy to use: + +* ``USE_TRELLO`` -- ignore github labels and override them with trello labels +* ``USE_GITHUB`` -- ignore trello labels and override them with push github labels +* ``UNION`` -- add missed labels from both side +* ``INTERSECTION`` -- remove labels that are not attached on both side + +Configuration +------------- + +* Open menu ``[[ Sync Studio ]] >> Sync Projects`` +* Select *Demo Github-Trello Integration* project +* In ``Parameters`` tab set **Secrets** (check Description and Documentation links near the parameters table about how to get the secret parameters): + + * ``GITHUB_REPO`` + * ``GITHUB_TOKEN`` + * ``TRELLO_BOARD_ID`` + * ``TRELLO_KEY`` + * ``TRELLO_TOKEN`` + +* In *Available Tasks* tab: + + * Click ``[Edit]`` + * Open *Labels Conflict resolving* task + * In *Available Triggers* tab: + + * Open *CONFLICT_RESOLVING* Cron + * Change **Next Execution Date** in webhook to the night time + * Set **Number of Calls**, a negative value means no limit (e.g. `-1`) + * Make it active by checking the box in front of the corresponding field + * Click ``[Save]`` +* Save all the changes you made in the integration +* Make integration Active by clicking ``Action >> Unarchive`` +* In project's *Manual Triggers* tab: + + * Click ``[Run Now]`` buttons in trigger *SETUP_GITHUB* + * Click ``[Run Now]`` buttons in triggers *SETUP_TRELLO*. Note, that `it doesn't work `_ without one of the following workarounds: + + * delete `line `__ that raise exception in case of type mismatching (search for ``Function declared as capable of handling request of type`` in standard Odoo code). In most cases, this workaround doesn't need to be reverted + * open file ``sync/controllers/webhook.py`` and temporarily change ``type="json"`` to ``type="http"``. Revert the changes after successfully setting up trello + * Add a temporal handler in your proxy/web server. Example for nginx: + + .. code-block:: nginx + + location /website/action-json/ { + return 200 "{}"; + } + +Usage +----- + +**Syncing new Github issue** + +* Open Github +* Create issue +* Open trello +* RESULT: you see a copy of the Github issue +* Go back to the Github issue +* Post a message +* Now go back to the trello card +* RESULT: you see a copy of the message +* You can also add/remove github issue labels or trello card labels (note that the name of the label must be added + in Trello so that there are no errors in the GitHub). + + * RESULT: once you change them on one side, after short time, you will see the changes on another side + +**Labels syncing** + +* Open Github or Trello +* Rename or delete some label +* RESULT: the same happened in both systems + +**Conflict resolving** + +* Create a github issue and check that it's syncing to trello +* Stop Odoo +* Make *different* changes of labels both in github issue and trello card +* Start Odoo +* Open menu ``[[ Sync Studio ]] >> Projects`` +* Select *Demo Trello-Github integration* project +* Click ``[Edit]`` and open *Labels Conflict Resolving* task in *Available Tasks* tab +* Make ``CONFLICT_RESOLVING`` Cron Trigger run in one of the following ways + + 1. Choose Cron Trigger and click ``[Run Manually]`` + + 2. Change **Next Execution Date** to a past time and wait up to 1 minute + +* RESULT: the github issue and corresponding trello card the same set of labels. The merging is done according to selected strategy in ``LABELS_MERGE_STRATEGY`` parameter. + + +**Syncing all existing Github issues.** + +* Open menu ``[[ Sync Studio ]] >> Projects`` +* Select *Demo Tello-Github Integration* project +* Click button ``[Run Now]`` near to ``PUSH_ALL_ISSUES`` manual trigger +* It will start asynchronous jobs. You can check progress via button *Jobs* +* After some time open Trello + + * RESULT: copies of all *open* github issues are in trello; they have *GITHUB:* prefix (can be configured in project parameter ISSUE_FROM_GITHUB_PREFIX) + +Custom Integration (obsolete) +================== + +If you made a custom integration via UI and want to package it into a module, +open the Sync Project and click ``[Actions] -> Export to XML`` button. diff --git a/sync/doc/sync.rst b/sync/doc/sync.rst new file mode 100644 index 00000000..9afbe83a --- /dev/null +++ b/sync/doc/sync.rst @@ -0,0 +1,63 @@ +This document describes Sync Studio tools that help implementing basic syncronization between systems (internal and external). + + +For one2one syncronization you can use following helpers. + +* ``MAGIC.sync_odoo2x(src_list, sync_info, create=False, update=False)`` + + * ``sync_info["x"]["create"](odoo_record) -> external_ref``: create external record and return reference + * ``sync_info["x"]["update"](external_ref, odoo_record) -> external_ref``: update external record + * ``sync_info["x"]["get_ref"](x)``: get reference for an item in src_list + +* ``MAGIC.sync_x2odoo(src_list, sync_info, create=False, update=False)`` + + * ``sync_info["odoo"]["create"](x) -> odoo_record``: create odoo record from external data + * ``sync_info["odoo"]["update"](odoo_record, x) -> odoo_record``: update odoo record according to providing external data + +Common args: + +* ``sync_info["relation"]``: same as ``relation_name`` in ``set_link``, ``get_link`` +* ``src_list``: iterator of ``x`` or ``odoo_record`` values +* ``create``: boolean value for "create record if it doesn't exist" +* ``update``: boolean value for "update record if it exists" + +To use helpers, create ``sync_info`` with all information, e.g. + +.. code-block:: python + + EMPLOYEE_SYNC = { + "relation": "my_system_and_odoo_employee_rel", + "x": { + "get_ref": employee2ref, + "create": employee_create, + "update": employee_update, + }, + "odoo": { + "create": employee_create_odoo, + "update": employee_update_odoo, + } + } + +Then you can reuse in all syncronizations, e.g. + +.. code-block:: python + + # on initial fetching records from external system + sync_x2odoo(all_employees_x, EMPLOYEE_SYNC, create=True) + + # to push all updates to external system + sync_odoo2x(all_employees_odoo, EMPLOYEE_SYNC, update=True) + + # on updating a single record push all updates to external system + sync_odoo2x([employee_odoo], EMPLOYEE_SYNC, update=True) + + +There is a similar helper for syncronization between two external systems: + +* ``MAGIC.sync_external(src_list, relation, src_info, dst_info, create=False, update=False)`` + + * ``src_info["get_ref"](src_data)``: get reference for an item in src_list + * ``src_info["system"]``: e.g. ``"github"`` + * ``src_info["update"](dst_ref, src_data)`` + * ``src_info["create"](src_data) -> dst_ref`` + * ``dst["system"]``: e.g. ``"trello"`` diff --git a/sync/images/sync-studio.jpg b/sync/images/sync-studio.jpg deleted file mode 100644 index 99197a65..00000000 Binary files a/sync/images/sync-studio.jpg and /dev/null differ diff --git a/sync/models/sync_project.py b/sync/models/sync_project.py index c0bdf421..ac4ba027 100644 --- a/sync/models/sync_project.py +++ b/sync/models/sync_project.py @@ -5,15 +5,16 @@ import base64 import logging +from datetime import datetime from pytz import timezone from odoo import api, fields, models -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, frozendict, html2plaintext from odoo.tools.misc import get_lang from odoo.tools.safe_eval import ( - datetime, + datetime as safe_datetime, dateutil, json, safe_eval, @@ -24,7 +25,7 @@ from odoo.addons.queue_job.exception import RetryableJobError -from ..tools import url2base64, url2bin +from ..tools import compile_markdown_to_html, fetch_gist_data, url2base64, url2bin from .ir_logging import LOG_CRITICAL, LOG_DEBUG, LOG_ERROR, LOG_INFO, LOG_WARNING _logger = logging.getLogger(__name__) @@ -47,25 +48,30 @@ class SyncProject(models.Model): "Name", help="e.g. Legacy Migration or eCommerce Synchronization", required=True ) active = fields.Boolean(default=False) - # Deprecated, please use eval_context_ids - # TODO: delete in v17 release - eval_context = fields.Selection([], string="Evaluation context") - eval_context_ids = fields.Many2many( - "sync.project.context", string="Evaluation contexts" + + source_url = fields.Char( + "Source", + help="Paste link to gist page, e.g. https://gist.github.com/yelizariev/e0585a0817c4d87b65b8a3d945da7ca2", ) - eval_context_description = fields.Text(compute="_compute_eval_context_description") + source_updated_at = fields.Datetime("Version", readonly=True) + description = fields.Html(readonly=True) + + core_code = fields.Text(string="Core Code", readonly=True) + common_code = fields.Text("Common Code") - common_code = fields.Text( - "Common Code", - help=""" - A place for helpers and constants. + param_ids = fields.One2many( + "sync.project.param", "project_id", copy=True, string="Parameters" + ) + param_description = fields.Html(readonly=True) - You can add here a function or variable, that don't start with underscore and then reuse it in task's code. - """, + text_param_ids = fields.One2many( + "sync.project.text", "project_id", copy=True, string="Templates" ) - param_ids = fields.One2many("sync.project.param", "project_id", copy=True) - text_param_ids = fields.One2many("sync.project.text", "project_id", copy=True) + text_param_description = fields.Html(readonly=True) + secret_ids = fields.One2many("sync.project.secret", "project_id", copy=True) + secret_description = fields.Html(readonly=True) + task_ids = fields.One2many("sync.task", "project_id", copy=True) task_count = fields.Integer(compute="_compute_task_count") trigger_cron_count = fields.Integer( @@ -143,6 +149,14 @@ def _check_python_code(self): if msg: raise ValidationError(msg) + def write(self, vals): + if "core_code" in vals and not self.env.user.has_group( + "sync.sync_group_manager" + ): + raise AccessError(_("Only Administrator can update the Core Code.")) + + return super().write(vals) + def _get_log_function(self, job, function): self.ensure_one() @@ -280,7 +294,7 @@ def record2image(record, fname=None): "url2bin": url2bin, "html2plaintext": html2plaintext, "time": time, - "datetime": datetime, + "datetime": safe_datetime, "dateutil": dateutil, "timezone": timezone, "b64encode": base64.b64encode, @@ -427,6 +441,53 @@ def link_src_dst(src_data, dst_ref): "sync_external": sync_external, } + def magic_upgrade(self): + self.ensure_one() + if not self.source_url: + raise UserError(_("Please provide url to the gist page")) + + gist_content = fetch_gist_data(self.source_url) + gist_files = {} + for file_name, file_info in gist_content["files"].items(): + gist_files[file_name] = file_info["content"] + + vals = {} + + if not self.name: + vals["name"] = gist_content.get("description", "Sync 🪬 Studio") + + vals["source_updated_at"] = datetime.strptime( + gist_content.get("updated_at"), "%Y-%m-%dT%H:%M:%SZ" + ) + + # [Documentation] + vals["description"] = ( + compile_markdown_to_html(gist_files.get("README.md")) + if gist_files.get("README.md") + else "

Please add README.md file to place some documentation here

" + ) + + # [PARAMS] and [SECRETS] + for field_name, file_name in ( + ("param_description", ".markdown"), + ("text_param_description", "settings.templates.markdown"), + ("secret_description", "settings.secrets.markdown"), + ): + if gist_files.get(file_name): + vals[field_name] = compile_markdown_to_html(gist_files[file_name]) + + # [CORE] and [LIB] + for field_name, file_name in ( + ("core_code", "core.py"), + ("common_code", "library.py"), + ): + if gist_files.get(file_name): + vals[field_name] = gist_files[file_name] + + # TODO: tasks + + self.update(vals) + class SyncProjectParamMixin(models.AbstractModel): @@ -442,9 +503,11 @@ class SyncProjectParamMixin(models.AbstractModel): help="A virtual field that, during writing, stores the value in the value field, but only if it is empty. \ It's used during module upgrade to prevent overwriting parameter values. ", ) + project_id = fields.Many2one("sync.project", ondelete="cascade") + + # Deprecated fields to be deleted in v17+ description = fields.Char("Description", translate=True) url = fields.Char("Documentation") - project_id = fields.Many2one("sync.project", ondelete="cascade") _sql_constraints = [("key_uniq", "unique (project_id, key)", "Key must be unique.")] diff --git a/sync/models/sync_project_demo.py b/sync/models/sync_project_demo.py index 68104c5c..32c853ba 100644 --- a/sync/models/sync_project_demo.py +++ b/sync/models/sync_project_demo.py @@ -1,3 +1,5 @@ +# WARNING: This file is obsolete and will be deleted soon + # Copyright 2020 Ivan Yelizariev # Copyright 2020 Denis Mudarisov # License MIT (https://opensource.org/licenses/MIT). diff --git a/sync/models/sync_task.py b/sync/models/sync_task.py index e190cb74..0f4762fd 100644 --- a/sync/models/sync_task.py +++ b/sync/models/sync_task.py @@ -26,6 +26,7 @@ class SyncTask(models.Model): code = fields.Text("Code") code_check = fields.Text("Syntax check", store=False, readonly=True) active = fields.Boolean(default=True) + show_magic_button = fields.Boolean(default=False) cron_ids = fields.One2many("sync.trigger.cron", "sync_task_id", copy=True) automation_ids = fields.One2many( "sync.trigger.automation", "sync_task_id", copy=True @@ -101,6 +102,10 @@ def _compute_active_triggers(self): r.active_webhook_ids = r.with_context(active_test=True).webhook_ids r.active_button_ids = r.with_context(active_test=True).button_ids + def magic_button(self): + # TODO + pass + def start( self, trigger, args=None, with_delay=False, force=False, raise_on_error=True ): diff --git a/sync/static/description/icon-menu.png b/sync/static/description/icon-menu.png new file mode 100644 index 00000000..112fcb82 Binary files /dev/null and b/sync/static/description/icon-menu.png differ diff --git a/sync/static/description/icon.png b/sync/static/description/icon.png index 554001c6..8704bf89 100644 Binary files a/sync/static/description/icon.png and b/sync/static/description/icon.png differ diff --git a/sync/static/src/scss/src.scss b/sync/static/src/scss/src.scss new file mode 100644 index 00000000..351496cc --- /dev/null +++ b/sync/static/src/scss/src.scss @@ -0,0 +1,6 @@ +.html_field_container { + padding: 0 2em; +} +.html_field_container img { + max-width: 100%; +} diff --git a/sync/tools.py b/sync/tools.py index 71f4cef2..a292b3f1 100644 --- a/sync/tools.py +++ b/sync/tools.py @@ -1,9 +1,16 @@ -# Copyright 2021 Ivan Yelizariev +# Copyright 2021,2024 Ivan Yelizariev # License MIT (https://opensource.org/licenses/MIT). import base64 import functools +import json +import re +import markdown import requests +import urllib3 + +from odoo.exceptions import UserError +from odoo.tools.translate import _ from .models.ir_logging import LOG_ERROR @@ -60,3 +67,52 @@ def url2base64(url): if not bin: return None return base64.b64encode(content) + + +def compile_markdown_to_html(markdown_content): + markdown_content = remove_front_matter(markdown_content) + + html = markdown.markdown(markdown_content) + + return html + + +def remove_front_matter(markdown_content): + # Find the front matter and remove it from the content + front_matter_match = re.match(r"---\n.*?\n---\n", markdown_content, re.DOTALL) + if front_matter_match: + return markdown_content[front_matter_match.end() :] + else: + return markdown_content + + +def fetch_gist_data(gist_page): + # https://gist.github.com/yelizariev/e0585a0817c4d87b65b8a3d945da7ca2 + # [0] [1] [2] [3] [4] + path_parts = gist_page.split("/") + try: + gist_code = path_parts[4] + except IndexError as err: + raise UserError(_("Not a valid gist url %s"), gist_page) from err + + # Construct the URL for the Gist API endpoint + url = f"https://api.github.com/gists/{gist_code}" + + # TODO: support GITHUB_TOKEN + headers = { + # "Authorization": f"token {GITHUB_TOKEN}", + "Accept": "application/vnd.github.v3+json" + } + + # Create a connection pool manager + http = urllib3.PoolManager() + + # Make the GET request to fetch the Gist information + response = http.request("GET", url, headers=headers) + if response.status != 200: + raise Exception(f"Failed to fetch Gist data. Status code: {response.status}") + + # Get the Gist content from the response + gist_content = json.loads(response.data.decode("utf-8")) + + return gist_content diff --git a/sync/views/sync_menus.xml b/sync/views/sync_menus.xml index 175b63f8..8d954aa0 100644 --- a/sync/views/sync_menus.xml +++ b/sync/views/sync_menus.xml @@ -4,7 +4,7 @@ diff --git a/sync/views/sync_project_views.xml b/sync/views/sync_project_views.xml index 46c6dfb8..da8fef84 100644 --- a/sync/views/sync_project_views.xml +++ b/sync/views/sync_project_views.xml @@ -123,53 +123,43 @@ - -

-
- Hint: to add manual triggers navigate to corresponding - Task via "Available Tasks" tab -

- - - - + + + + + + +