From ec129c4432f5d4e8e418bbb8ff34d5f17f4533d6 Mon Sep 17 00:00:00 2001
From: RomanBachaloSigmaSoftware
Date: Fri, 15 Mar 2024 14:21:00 +0200
Subject: [PATCH 01/22] update example
---
.../examples/eg011_embedded_sending.py | 191 ++++++++++++++++--
1 file changed, 177 insertions(+), 14 deletions(-)
diff --git a/app/eSignature/examples/eg011_embedded_sending.py b/app/eSignature/examples/eg011_embedded_sending.py
index ea5ef59..9e438eb 100644
--- a/app/eSignature/examples/eg011_embedded_sending.py
+++ b/app/eSignature/examples/eg011_embedded_sending.py
@@ -1,7 +1,10 @@
-from docusign_esign import EnvelopesApi, ReturnUrlRequest
+import base64
+from os import path
+
+from docusign_esign import EnvelopesApi, ReturnUrlRequest, EnvelopesApi, EnvelopeDefinition, \
+ Document, Signer, CarbonCopy, SignHere, Tabs, Recipients
from flask import url_for, session, request
-from .eg002_signing_via_email import Eg002SigningViaEmailController
from ...consts import pattern, demo_docs_path
from ...docusign import create_api_client
@@ -24,7 +27,7 @@ def get_args():
"signer_name": signer_name,
"cc_email": cc_email,
"cc_name": cc_name,
- "status": "sent",
+ "status": "created",
}
args = {
"starting_view": starting_view,
@@ -36,36 +39,196 @@ def get_args():
}
return args
- @staticmethod
- def worker(args,doc_docx_path,doc_pdf_path):
+ @classmethod
+ def worker(cls, args, doc_docx_path, doc_pdf_path):
"""
This function does the work of creating the envelope in
draft mode and returning a URL for the sender"s view
"""
# Step 2. Create the envelope with "created" (draft) status
- args["envelope_args"]["status"] = "created"
- # Using worker from example 002
- results = Eg002SigningViaEmailController.worker(args, doc_docx_path, doc_pdf_path)
- envelope_id = results["envelope_id"]
+ envelope = cls.create_envelope(args, doc_docx_path, doc_pdf_path)
+ envelope_id = envelope.envelope_id
# Step 3. Create the sender view
+ sender_view_url = cls.create_sender_view(args, envelope_id)
+
+ return {"envelope_id": envelope_id, "redirect_url": sender_view_url}
+
+ @classmethod
+ #ds-snippet-start:eSign11Step3
+ def create_sender_view(cls, args, envelope_id):
view_request = ReturnUrlRequest(return_url=args["ds_return_url"])
# Exceptions will be caught by the calling function
- #ds-snippet-start:eSign11Step3
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
envelope_api = EnvelopesApi(api_client)
- results = envelope_api.create_sender_view(
+ sender_view = envelope_api.create_sender_view(
account_id=args["account_id"],
envelope_id=envelope_id,
return_url_request=view_request
)
# Switch to Recipient and Documents view if requested by the user
- url = results.url
+ url = sender_view.url
if args["starting_view"] == "recipient":
url = url.replace("send=1", "send=0")
- #ds-snippet-end:eSign13Step3
- return {"envelope_id": envelope_id, "redirect_url": url}
+ return url
+ #ds-snippet-end:eSign11Step3
+
+ @classmethod
+ #ds-snippet-start:eSign11Step2
+ def create_envelope(cls, args, doc_docx_path, doc_pdf_path):
+ envelope_args = args["envelope_args"]
+ # Create the envelope request object
+ envelope_definition = cls.make_envelope(envelope_args, doc_docx_path, doc_pdf_path)
+ api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
+ # Call Envelopes::create API method
+ # Exceptions will be caught by the calling function
+ envelopes_api = EnvelopesApi(api_client)
+ return envelopes_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition)
+
+ @classmethod
+ def make_envelope(cls, args, doc_docx_path, doc_pdf_path):
+ """
+ Creates envelope
+ Document 1: An HTML document.
+ Document 2: A Word .docx document.
+ Document 3: A PDF document.
+ DocuSign will convert all of the documents to the PDF format.
+ The recipients" field tags are placed using anchor strings.
+ """
+
+ # document 1 (html) has sign here anchor tag **signature_1**
+ # document 2 (docx) has sign here anchor tag /sn1/
+ # document 3 (pdf) has sign here anchor tag /sn1/
+ #
+ # The envelope has two recipients.
+ # recipient 1 - signer
+ # recipient 2 - cc
+ # The envelope will be sent first to the signer.
+ # After it is signed, a copy is sent to the cc person.
+
+ # create the envelope definition
+ env = EnvelopeDefinition(
+ email_subject="Please sign this document set"
+ )
+ doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")).decode("ascii")
+ # read files 2 and 3 from a local directory
+ # The reads could raise an exception if the file is not available!
+ with open(path.join(demo_docs_path, doc_docx_path), "rb") as file:
+ doc2_docx_bytes = file.read()
+ doc2_b64 = base64.b64encode(doc2_docx_bytes).decode("ascii")
+ with open(path.join(demo_docs_path, doc_pdf_path), "rb") as file:
+ doc3_pdf_bytes = file.read()
+ doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode("ascii")
+
+ # Create the document models
+ document1 = Document( # create the DocuSign document object
+ document_base64=doc1_b64,
+ name="Order acknowledgement", # can be different from actual file name
+ file_extension="html", # many different document types are accepted
+ document_id="1" # a label used to reference the doc
+ )
+ document2 = Document( # create the DocuSign document object
+ document_base64=doc2_b64,
+ name="Battle Plan", # can be different from actual file name
+ file_extension="docx", # many different document types are accepted
+ document_id="2" # a label used to reference the doc
+ )
+ document3 = Document( # create the DocuSign document object
+ document_base64=doc3_b64,
+ name="Lorem Ipsum", # can be different from actual file name
+ file_extension="pdf", # many different document types are accepted
+ document_id="3" # a label used to reference the doc
+ )
+ # The order in the docs array determines the order in the envelope
+ env.documents = [document1, document2, document3]
+
+ # Create the signer recipient model
+ signer1 = Signer(
+ email=args["signer_email"],
+ name=args["signer_name"],
+ recipient_id="1",
+ routing_order="1"
+ )
+ # routingOrder (lower means earlier) determines the order of deliveries
+ # to the recipients. Parallel routing order is supported by using the
+ # same integer as the order for two or more recipients.
+
+ # create a cc recipient to receive a copy of the documents
+ cc1 = CarbonCopy(
+ email=args["cc_email"],
+ name=args["cc_name"],
+ recipient_id="2",
+ routing_order="2"
+ )
+
+ # Create signHere fields (also known as tabs) on the documents,
+ # We"re using anchor (autoPlace) positioning
+ #
+ # The DocuSign platform searches throughout your envelope"s
+ # documents for matching anchor strings. So the
+ # signHere2 tab will be used in both document 2 and 3 since they
+ # use the same anchor string for their "signer 1" tabs.
+ sign_here1 = SignHere(
+ anchor_string="**signature_1**",
+ anchor_units="pixels",
+ anchor_y_offset="10",
+ anchor_x_offset="20"
+ )
+
+ sign_here2 = SignHere(
+ anchor_string="/sn1/",
+ anchor_units="pixels",
+ anchor_y_offset="10",
+ anchor_x_offset="20"
+ )
+
+ # Add the tabs model (including the sign_here tabs) to the signer
+ # The Tabs object wants arrays of the different field/tab types
+ signer1.tabs = Tabs(sign_here_tabs=[sign_here1, sign_here2])
+
+ # Add the recipients to the envelope object
+ recipients = Recipients(signers=[signer1], carbon_copies=[cc1])
+ env.recipients = recipients
+
+ # Request that the envelope be sent by setting |status| to "sent".
+ # To request that the envelope be created as a draft, set to "created"
+ env.status = args["status"]
+
+ return env
+
+ @classmethod
+ def create_document1(cls, args):
+ """ Creates document 1 -- an html document"""
+
+ return f"""
+
+
+
+
+
+
+ World Wide Corp
+ Order Processing Division
+ Ordered by {args["signer_name"]}
+ Email: {args["signer_email"]}
+ Copy to: {args["cc_name"]}, {args["cc_email"]}
+
+ Candy bonbon pastry jujubes lollipop wafer biscuit biscuit. Topping brownie sesame snaps sweet roll pie.
+ Croissant danish biscuit soufflé caramels jujubes jelly. Dragée danish caramels lemon drops dragée.
+ Gummi bears cupcake biscuit tiramisu sugar plum pastry. Dragée gummies applicake pudding liquorice.
+ Donut jujubes oat cake jelly-o.
+ Dessert bear claw chocolate cake gummies lollipop sugar plum ice cream gummies cheesecake.
+
+
+ Agreed: **signature_1**/
+
+
+ """
+ #ds-snippet-end:eSign11Step2
From 60804cd8c0ed8ad53c83399d1feac1a98690f774 Mon Sep 17 00:00:00 2001
From: RomanBachaloSigmaSoftware
Date: Fri, 15 Mar 2024 14:25:06 +0200
Subject: [PATCH 02/22] fix linter error
---
app/webforms/examples/eg001_create_instance.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/webforms/examples/eg001_create_instance.py b/app/webforms/examples/eg001_create_instance.py
index 6d72432..49f4a2a 100644
--- a/app/webforms/examples/eg001_create_instance.py
+++ b/app/webforms/examples/eg001_create_instance.py
@@ -2,7 +2,7 @@
from docusign_webforms import ApiClient, FormInstanceManagementApi, FormManagementApi, CreateInstanceRequestBody
from docusign_esign import Document, Signer, SignHere, Tabs, Recipients, TemplatesApi, Checkbox, DateSigned, \
- Text, EnvelopeTemplate
+ Text, EnvelopeTemplate
from ...docusign import create_api_client
From 85deeee9adc278ab40ed40ffe4ee48ee9b62f9ba Mon Sep 17 00:00:00 2001
From: RomanBachaloSigmaSoftware
<77355790+RomanBachaloSigmaSoftware@users.noreply.github.com>
Date: Fri, 22 Mar 2024 22:52:40 +0200
Subject: [PATCH 03/22] Added maestro code examples (#142)
* added examples
* updates and fixes
* remove redundant scope
* update example 1
* adding codeDepot markers
---------
Co-authored-by: Paige Rossi
---
.gitignore | 3 +
app/__init__.py | 5 +
app/consts.py | 3 +
app/docusign/ds_client.py | 8 +
app/docusign/utils.py | 12 +-
app/ds_config_sample.py | 1 +
app/maestro/__init__.py | 3 +
.../examples/eg001_trigger_workflow.py | 75 +++
app/maestro/examples/eg002_cancel_workflow.py | 45 ++
.../examples/eg003_get_workflow_status.py | 35 ++
app/maestro/utils.py | 584 ++++++++++++++++++
app/maestro/views/__init__.py | 3 +
app/maestro/views/eg001_trigger_workflow.py | 184 ++++++
app/maestro/views/eg002_cancel_workflow.py | 103 +++
.../views/eg003_get_workflow_status.py | 76 +++
app/static/assets/search.js | 3 +
app/templates/error.html | 2 +-
app/templates/home.html | 2 +-
.../maestro/eg001_publish_workflow.html | 11 +
.../maestro/eg001_trigger_workflow.html | 59 ++
.../maestro/eg002_cancel_workflow.html | 38 ++
.../maestro/eg003_get_workflow_status.html | 38 ++
22 files changed, 1290 insertions(+), 3 deletions(-)
create mode 100644 app/maestro/__init__.py
create mode 100644 app/maestro/examples/eg001_trigger_workflow.py
create mode 100644 app/maestro/examples/eg002_cancel_workflow.py
create mode 100644 app/maestro/examples/eg003_get_workflow_status.py
create mode 100644 app/maestro/utils.py
create mode 100644 app/maestro/views/__init__.py
create mode 100644 app/maestro/views/eg001_trigger_workflow.py
create mode 100644 app/maestro/views/eg002_cancel_workflow.py
create mode 100644 app/maestro/views/eg003_get_workflow_status.py
create mode 100644 app/templates/maestro/eg001_publish_workflow.html
create mode 100644 app/templates/maestro/eg001_trigger_workflow.html
create mode 100644 app/templates/maestro/eg002_cancel_workflow.html
create mode 100644 app/templates/maestro/eg003_get_workflow_status.html
diff --git a/.gitignore b/.gitignore
index 73a0dc0..101b3e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,3 +123,6 @@ private.key
# Current flask session
flask_session/
+
+# Workflow ID file
+WORKFLOW_ID.txt
\ No newline at end of file
diff --git a/app/__init__.py b/app/__init__.py
index ac472b8..39f35b4 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -12,6 +12,7 @@
from .monitor import views as monitor_views
from .admin import views as admin_views
from .connect import views as connect_views
+from .maestro import views as maestro_views
from .webforms import views as webforms_views
from .views import core
@@ -114,6 +115,10 @@
app.register_blueprint(connect_views.cneg001)
+app.register_blueprint(maestro_views.mseg001)
+app.register_blueprint(maestro_views.mseg002)
+app.register_blueprint(maestro_views.mseg003)
+
app.register_blueprint(webforms_views.weg001)
if "DYNO" in os.environ: # On Heroku?
diff --git a/app/consts.py b/app/consts.py
index 532015f..530ce45 100644
--- a/app/consts.py
+++ b/app/consts.py
@@ -29,6 +29,8 @@
# Base uri for callback function
base_uri_suffix = "/restapi"
+# Workflow name
+workflow_name = "Example workflow - send invite to signer"
# Default languages for brand
languages = {
@@ -114,5 +116,6 @@
"ROOMS": "Rooms",
"ADMIN": "Admin",
"CONNECT": "Connect",
+ "MAESTRO": "Maestro",
"WEBFORMS": "WebForms"
}
diff --git a/app/docusign/ds_client.py b/app/docusign/ds_client.py
index 384194e..348b4b4 100644
--- a/app/docusign/ds_client.py
+++ b/app/docusign/ds_client.py
@@ -33,6 +33,10 @@
"asset_group_account_read", "asset_group_account_clone_write", "asset_group_account_clone_read"
]
+MAESTRO_SCOPES = [
+ "signature", "aow_manage"
+]
+
WEBFORMS_SCOPES = [
"signature", "webforms_read", "webforms_instance_read", "webforms_instance_write"
]
@@ -61,6 +65,8 @@ def _auth_code_grant(cls, api):
use_scopes.extend(CLICK_SCOPES)
elif api == "Admin":
use_scopes.extend(ADMIN_SCOPES)
+ elif api == "Maestro":
+ use_scopes.extend(MAESTRO_SCOPES)
elif api == "WebForms":
use_scopes.extend(WEBFORMS_SCOPES)
else:
@@ -99,6 +105,8 @@ def _jwt_auth(cls, api):
use_scopes.extend(CLICK_SCOPES)
elif api == "Admin":
use_scopes.extend(ADMIN_SCOPES)
+ elif api == "Maestro":
+ use_scopes.extend(MAESTRO_SCOPES)
elif api == "WebForms":
use_scopes.extend(WEBFORMS_SCOPES)
else:
diff --git a/app/docusign/utils.py b/app/docusign/utils.py
index 730751f..eefc0c0 100644
--- a/app/docusign/utils.py
+++ b/app/docusign/utils.py
@@ -1,7 +1,7 @@
from datetime import timedelta, datetime
from functools import wraps
import requests
-import urllib
+from urllib.parse import urlparse, parse_qs
import json
import re
@@ -148,6 +148,16 @@ def get_user_info(access_token, base_path, oauth_host_name):
api_client.set_oauth_host_name(oauth_host_name)
return api_client.get_user_info(access_token)
+def get_parameter_value_from_url(url, param_name):
+ parsed_url = urlparse(url)
+ query_params = parse_qs(parsed_url.query)
+
+ # Access the parameter value (returns a list)
+ param_value_list = query_params.get(param_name, [])
+
+ # If the parameter exists, return the first value; otherwise, return None
+ return param_value_list[0] if param_value_list else None
+
def replace_template_id(file_path, template_id):
with open(file_path, 'r') as file:
content = file.read()
diff --git a/app/ds_config_sample.py b/app/ds_config_sample.py
index 2d04866..b18d165 100644
--- a/app/ds_config_sample.py
+++ b/app/ds_config_sample.py
@@ -16,6 +16,7 @@
"rooms_api_client_host": "https://demo.rooms.docusign.com/restapi",
"monitor_api_client_host": "https://lens-d.docusign.net",
"admin_api_client_host": "https://api-d.docusign.net/management",
+ "maestro_api_client_host": "https://demo.services.docusign.net/",
"webforms_api_client_host": "https://apps-d.docusign.com/api/webforms/v1.1",
"allow_silent_authentication": True, # a user can be silently authenticated if they have an
# active login session on another tab of the same browser
diff --git a/app/maestro/__init__.py b/app/maestro/__init__.py
new file mode 100644
index 0000000..1697d73
--- /dev/null
+++ b/app/maestro/__init__.py
@@ -0,0 +1,3 @@
+from .views import mseg001
+from .views import mseg002
+from .views import mseg003
diff --git a/app/maestro/examples/eg001_trigger_workflow.py b/app/maestro/examples/eg001_trigger_workflow.py
new file mode 100644
index 0000000..7a99cac
--- /dev/null
+++ b/app/maestro/examples/eg001_trigger_workflow.py
@@ -0,0 +1,75 @@
+from docusign_maestro import WorkflowManagementApi, WorkflowTriggerApi, TriggerPayload
+from flask import session, request
+
+from app.docusign.utils import get_parameter_value_from_url
+from app.ds_config import DS_CONFIG
+from app.maestro.utils import create_maestro_api_client
+from app.consts import pattern
+
+
+class Eg001TriggerWorkflowController:
+ @staticmethod
+ def get_args():
+ """Get request and session arguments"""
+ return {
+ "account_id": session["ds_account_id"],
+ "base_path": DS_CONFIG["maestro_api_client_host"],
+ "access_token": session["ds_access_token"],
+ "workflow_id": session["workflow_id"],
+ "instance_name": pattern.sub("", request.form.get("instance_name")),
+ "signer_email": pattern.sub("", request.form.get("signer_email")),
+ "signer_name": pattern.sub("", request.form.get("signer_name")),
+ "cc_email": pattern.sub("", request.form.get("cc_email")),
+ "cc_name": pattern.sub("", request.form.get("cc_name")),
+ }
+
+ @staticmethod
+ def get_workflow_definitions(args):
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ workflow_management_api = WorkflowManagementApi(api_client)
+ workflow_definitions = workflow_management_api.get_workflow_definitions(args["account_id"], status="active")
+
+ return workflow_definitions
+
+ @staticmethod
+ def get_workflow_definition(args):
+ #ds-snippet-start:Maestro1Step2
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ #ds-snippet-end:Maestro1Step2
+
+ #ds-snippet-start:Maestro1Step3
+ workflow_management_api = WorkflowManagementApi(api_client)
+ workflow_definition = workflow_management_api.get_workflow_definition(args["account_id"], args["workflow_id"])
+ #ds-snippet-end:Maestro1Step3
+
+ return workflow_definition
+
+ @staticmethod
+ def trigger_workflow(workflow, args):
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+
+ #ds-snippet-start:Maestro1Step4
+ trigger_payload = TriggerPayload(
+ instance_name=args["instance_name"],
+ participant={},
+ payload={
+ "signerEmail": args["signer_email"],
+ "signerName": args["signer_name"],
+ "ccEmail": args["cc_email"],
+ "ccName": args["cc_name"]
+ },
+ metadata={}
+ )
+ mtid = get_parameter_value_from_url(workflow.trigger_url, "mtid")
+ mtsec = get_parameter_value_from_url(workflow.trigger_url, "mtsec")
+ #ds-snippet-end:Maestro1Step4
+
+ #ds-snippet-start:Maestro1Step5
+ workflow_trigger_api = WorkflowTriggerApi(api_client)
+ trigger_response = workflow_trigger_api.trigger_workflow(
+ args["account_id"],
+ trigger_payload,
+ mtid=mtid, mtsec=mtsec
+ )
+ #ds-snippet-end:Maestro1Step5
+ return trigger_response
diff --git a/app/maestro/examples/eg002_cancel_workflow.py b/app/maestro/examples/eg002_cancel_workflow.py
new file mode 100644
index 0000000..4425ef1
--- /dev/null
+++ b/app/maestro/examples/eg002_cancel_workflow.py
@@ -0,0 +1,45 @@
+from docusign_maestro import WorkflowInstanceManagementApi
+from flask import session
+
+from app.ds_config import DS_CONFIG
+from app.maestro.utils import create_maestro_api_client
+
+
+class Eg002CancelWorkflowController:
+ @staticmethod
+ def get_args():
+ """Get request and session arguments"""
+ return {
+ "account_id": session["ds_account_id"],
+ "base_path": DS_CONFIG["maestro_api_client_host"],
+ "access_token": session["ds_access_token"],
+ "workflow_id": session["workflow_id"],
+ "instance_id": session["instance_id"]
+ }
+
+ @staticmethod
+ def get_instance_state(args):
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ workflow_instance_management_api = WorkflowInstanceManagementApi(api_client)
+ instance = workflow_instance_management_api.get_workflow_instance(
+ args["account_id"],
+ args["workflow_id"],
+ args["instance_id"]
+ )
+
+ return instance.instance_state
+
+ @staticmethod
+ def cancel_workflow_instance(args):
+ #ds-snippet-start:Maestro2Step2
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ #ds-snippet-end:Maestro2Step2
+
+ #ds-snippet-start:Maestro2Step3
+ workflow_instance_management_api = WorkflowInstanceManagementApi(api_client)
+ cancel_result = workflow_instance_management_api.cancel_workflow_instance(
+ args["account_id"],
+ args["instance_id"]
+ )
+ #ds-snippet-end:Maestro2Step3
+ return cancel_result
diff --git a/app/maestro/examples/eg003_get_workflow_status.py b/app/maestro/examples/eg003_get_workflow_status.py
new file mode 100644
index 0000000..4777f0a
--- /dev/null
+++ b/app/maestro/examples/eg003_get_workflow_status.py
@@ -0,0 +1,35 @@
+from docusign_maestro import WorkflowInstanceManagementApi
+from flask import session
+
+from app.ds_config import DS_CONFIG
+from app.maestro.utils import create_maestro_api_client
+
+
+class Eg003GetWorkflowStatusController:
+ @staticmethod
+ def get_args():
+ """Get request and session arguments"""
+ return {
+ "account_id": session["ds_account_id"],
+ "base_path": DS_CONFIG["maestro_api_client_host"],
+ "access_token": session["ds_access_token"],
+ "workflow_id": session["workflow_id"],
+ "instance_id": session["instance_id"]
+ }
+
+ @staticmethod
+ def get_workflow_instance(args):
+ #ds-snippet-start:Maestro3Step2
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ #ds-snippet-end:Maestro3Step2
+
+ #ds-snippet-start:Maestro3Step3
+ workflow_instance_management_api = WorkflowInstanceManagementApi(api_client)
+ instance = workflow_instance_management_api.get_workflow_instance(
+ args["account_id"],
+ args["workflow_id"],
+ args["instance_id"]
+ )
+ #ds-snippet-end:Maestro3Step3
+
+ return instance
diff --git a/app/maestro/utils.py b/app/maestro/utils.py
new file mode 100644
index 0000000..a44125d
--- /dev/null
+++ b/app/maestro/utils.py
@@ -0,0 +1,584 @@
+import uuid
+from docusign_maestro import ApiClient, WorkflowManagementApi, WorkflowDefinition, DeployRequest, \
+ DSWorkflowTrigger, DSWorkflowVariableFromVariable, DeployStatus
+
+import json
+
+
+def create_maestro_api_client(base_path, access_token):
+ api_client = ApiClient()
+ api_client.host = base_path
+ api_client.set_default_header(header_name="Authorization", header_value=f"Bearer {access_token}")
+
+ return api_client
+
+
+def create_workflow(args):
+ signer_id = str(uuid.uuid4())
+ cc_id = str(uuid.uuid4())
+ trigger_id = "wfTrigger"
+
+ participants = {
+ signer_id: {
+ "participantRole": "Signer"
+ },
+ cc_id: {
+ "participantRole": "CC"
+ }
+ }
+
+ dac_id_field = f"dacId_{trigger_id}"
+ id_field = f"id_{trigger_id}"
+ signer_name_field = f"signerName_{trigger_id}"
+ signer_email_field = f"signerEmail_{trigger_id}"
+ cc_name_field = f"ccName_{trigger_id}"
+ cc_email_field = f"ccEmail_{trigger_id}"
+
+ trigger = DSWorkflowTrigger(
+ name="Get_URL",
+ type="Http",
+ http_type="Get",
+ id=trigger_id,
+ input={
+ 'metadata': {
+ 'customAttributes': {}
+ },
+ 'payload': {
+ dac_id_field: {
+ 'source': 'step',
+ 'propertyName': 'dacId',
+ 'stepId': trigger_id
+ },
+ id_field: {
+ 'source': 'step',
+ 'propertyName': 'id',
+ 'stepId': trigger_id
+ },
+ signer_name_field: {
+ 'source': 'step',
+ 'propertyName': 'signerName',
+ 'stepId': trigger_id
+ },
+ signer_email_field: {
+ 'source': 'step',
+ 'propertyName': 'signerEmail',
+ 'stepId': trigger_id
+ },
+ cc_name_field: {
+ 'source': 'step',
+ 'propertyName': 'ccName',
+ 'stepId': trigger_id
+ },
+ cc_email_field: {
+ 'source': 'step',
+ 'propertyName': 'ccEmail',
+ 'stepId': trigger_id
+ }
+ },
+ 'participants': {}
+ },
+ output={
+ dac_id_field: {
+ 'source': 'step',
+ 'propertyName': 'dacId',
+ 'stepId': trigger_id
+ }
+ }
+ )
+
+ variables = {
+ dac_id_field: DSWorkflowVariableFromVariable(source='step', property_name='dacId', step_id=trigger_id),
+ id_field: DSWorkflowVariableFromVariable(source='step', property_name='id', step_id=trigger_id),
+ signer_name_field: DSWorkflowVariableFromVariable(source='step', property_name='signerName',
+ step_id=trigger_id),
+ signer_email_field: DSWorkflowVariableFromVariable(source='step', property_name='signerEmail',
+ step_id=trigger_id),
+ cc_name_field: DSWorkflowVariableFromVariable(source='step', property_name='ccName', step_id=trigger_id),
+ cc_email_field: DSWorkflowVariableFromVariable(source='step', property_name='ccEmail', step_id=trigger_id),
+ 'envelopeId_step2': DSWorkflowVariableFromVariable(source='step', property_name='envelopeId', step_id='step2',
+ type='String'),
+ 'combinedDocumentsBase64_step2': DSWorkflowVariableFromVariable(source='step',
+ property_name='combinedDocumentsBase64',
+ step_id='step2', type='File'),
+ 'fields.signer.text.value_step2': DSWorkflowVariableFromVariable(source='step',
+ property_name='fields.signer.text.value',
+ step_id='step2', type='String')
+ }
+
+ step1 = {
+ 'id': 'step1',
+ 'name': 'Set Up Invite',
+ 'moduleName': 'Notification-SendEmail',
+ 'configurationProgress': 'Completed',
+ 'type': 'DS-EmailNotification',
+ 'config': {
+ 'templateType': 'WorkflowParticipantNotification',
+ 'templateVersion': 1,
+ 'language': 'en',
+ 'sender_name': 'DocuSign Orchestration',
+ 'sender_alias': 'Orchestration',
+ 'participantId': signer_id
+ },
+ 'input': {
+ 'recipients': [
+ {
+ 'name': {
+ 'source': 'step',
+ 'propertyName': 'signerName',
+ 'stepId': trigger_id
+ },
+ 'email': {
+ 'source': 'step',
+ 'propertyName': 'signerEmail',
+ 'stepId': trigger_id
+ }
+ }
+ ],
+ 'mergeValues': {
+ 'CustomMessage': 'Follow this link to access and complete the workflow.',
+ 'ParticipantFullName': {
+ 'source': 'step',
+ 'propertyName': 'signerName',
+ 'stepId': trigger_id
+ }
+ }
+ },
+ 'output': {}
+ }
+
+ step2 = {
+ "id": 'step2',
+ "name": 'Get Signatures',
+ "moduleName": 'ESign',
+ "configurationProgress": 'Completed',
+ "type": 'DS-Sign',
+ "config": {
+ "participantId": signer_id,
+ },
+ "input": {
+ "isEmbeddedSign": True,
+ "documents": [
+ {
+ "type": 'FromDSTemplate',
+ "eSignTemplateId": args["template_id"],
+ },
+ ],
+ "emailSubject": 'Please sign this document',
+ "emailBlurb": '',
+ "recipients": {
+ "signers": [
+ {
+ "defaultRecipient": 'false',
+ "tabs": {
+ "signHereTabs": [
+ {
+ "stampType": 'signature',
+ "name": 'SignHere',
+ "tabLabel": 'Sign Here',
+ "scaleValue": '1',
+ "optional": 'false',
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '191',
+ "yPosition": '148',
+ "tabId": '1',
+ "tabType": 'signhere',
+ },
+ ],
+ 'textTabs': [
+ {
+ "requireAll": 'false',
+ "value": '',
+ "required": 'false',
+ "locked": 'false',
+ "concealValueOnDocument": 'false',
+ "disableAutoSize": 'false',
+ "tabLabel": 'text',
+ "font": 'helvetica',
+ "fontSize": 'size14',
+ "localePolicy": {},
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '153',
+ "yPosition": '230',
+ "width": '84',
+ "height": '23',
+ "tabId": '2',
+ "tabType": 'text',
+ },
+ ],
+ "checkboxTabs": [
+ {
+ "name": '',
+ "tabLabel": 'ckAuthorization',
+ "selected": 'false',
+ "selectedOriginal": 'false',
+ "requireInitialOnSharedChange": 'false',
+ "required": 'true',
+ "locked": 'false',
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '75',
+ "yPosition": '417',
+ "width": '0',
+ "height": '0',
+ "tabId": '3',
+ "tabType": 'checkbox',
+ },
+ {
+ "name": '',
+ "tabLabel": 'ckAuthentication',
+ "selected": 'false',
+ "selectedOriginal": 'false',
+ "requireInitialOnSharedChange": 'false',
+ "required": 'true',
+ "locked": 'false',
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '75',
+ "yPosition": '447',
+ "width": '0',
+ "height": '0',
+ "tabId": '4',
+ "tabType": 'checkbox',
+ },
+ {
+ "name": '',
+ "tabLabel": 'ckAgreement',
+ "selected": 'false',
+ "selectedOriginal": 'false',
+ "requireInitialOnSharedChange": 'false',
+ "required": 'true',
+ "locked": 'false',
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '75',
+ "yPosition": '478',
+ "width": '0',
+ "height": '0',
+ "tabId": '5',
+ "tabType": 'checkbox',
+ },
+ {
+ "name": '',
+ "tabLabel": 'ckAcknowledgement',
+ "selected": 'false',
+ "selectedOriginal": 'false',
+ "requireInitialOnSharedChange": 'false',
+ "required": 'true',
+ "locked": 'false',
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '75',
+ "yPosition": '508',
+ "width": '0',
+ "height": '0',
+ "tabId": '6',
+ "tabType": 'checkbox',
+ },
+ ],
+ "radioGroupTabs": [
+ {
+ "documentId": '1',
+ "recipientId": '1',
+ "groupName": 'radio1',
+ "radios": [
+ {
+ "pageNumber": '1',
+ "xPosition": '142',
+ "yPosition": '384',
+ "value": 'white',
+ "selected": 'false',
+ "tabId": '7',
+ "required": 'false',
+ "locked": 'false',
+ "bold": 'false',
+ "italic": 'false',
+ "underline": 'false',
+ "fontColor": 'black',
+ "fontSize": 'size7',
+ },
+ {
+ "pageNumber": '1',
+ "xPosition": '74',
+ "yPosition": '384',
+ "value": 'red',
+ "selected": 'false',
+ "tabId": '8',
+ "required": 'false',
+ "locked": 'false',
+ "bold": 'false',
+ "italic": 'false',
+ "underline": 'false',
+ "fontColor": 'black',
+ "fontSize": 'size7',
+ },
+ {
+ "pageNumber": '1',
+ "xPosition": '220',
+ "yPosition": '384',
+ "value": 'blue',
+ "selected": 'false',
+ "tabId": '9',
+ "required": 'false',
+ "locked": 'false',
+ "bold": 'false',
+ "italic": 'false',
+ "underline": 'false',
+ "fontColor": 'black',
+ "fontSize": 'size7',
+ },
+ ],
+ "shared": 'false',
+ "requireInitialOnSharedChange": 'false',
+ "requireAll": 'false',
+ "tabType": 'radiogroup',
+ "value": '',
+ "originalValue": '',
+ },
+ ],
+ "listTabs": [
+ {
+ "listItems": [
+ {
+ "text": 'Red',
+ "value": 'red',
+ "selected": 'false',
+ },
+ {
+ "text": 'Orange',
+ "value": 'orange',
+ "selected": 'false',
+ },
+ {
+ "text": 'Yellow',
+ "value": 'yellow',
+ "selected": 'false',
+ },
+ {
+ "text": 'Green',
+ "value": 'green',
+ "selected": 'false',
+ },
+ {
+ "text": 'Blue',
+ "value": 'blue',
+ "selected": 'false',
+ },
+ {
+ "text": 'Indigo',
+ "value": 'indigo',
+ "selected": 'false',
+ },
+ {
+ "text": 'Violet',
+ "value": 'violet',
+ "selected": 'false',
+ },
+ ],
+ "value": '',
+ "originalValue": '',
+ "required": 'false',
+ "locked": 'false',
+ "requireAll": 'false',
+ "tabLabel": 'list',
+ "font": 'helvetica',
+ "fontSize": 'size14',
+ "localePolicy": {},
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '142',
+ "yPosition": '291',
+ "width": '78',
+ "height": '0',
+ "tabId": '10',
+ "tabType": 'list',
+ },
+ ],
+ "numericalTabs": [
+ {
+ "validationType": 'currency',
+ "value": '',
+ "required": 'false',
+ "locked": 'false',
+ "concealValueOnDocument": 'false',
+ "disableAutoSize": 'false',
+ "tabLabel": 'numericalCurrency',
+ "font": 'helvetica',
+ "fontSize": 'size14',
+ "localePolicy": {
+ "cultureName": 'en-US',
+ "currencyPositiveFormat":
+ 'csym_1_comma_234_comma_567_period_89',
+ "currencyNegativeFormat":
+ 'opar_csym_1_comma_234_comma_567_period_89_cpar',
+ "currencyCode": 'usd',
+ },
+ "documentId": '1',
+ "recipientId": '1',
+ "pageNumber": '1',
+ "xPosition": '163',
+ "yPosition": '260',
+ "width": '84',
+ "height": '0',
+ "tabId": '11',
+ "tabType": 'numerical',
+ },
+ ],
+ },
+ "signInEachLocation": 'false',
+ "agentCanEditEmail": 'false',
+ "agentCanEditName": 'false',
+ "requireUploadSignature": 'false',
+ "name": {
+ "source": 'step',
+ "propertyName": 'signerName',
+ "stepId": trigger_id,
+ },
+ "email": {
+ "source": 'step',
+ "propertyName": 'signerEmail',
+ "stepId": trigger_id,
+ },
+ "recipientId": '1',
+ "recipientIdGuid": '00000000-0000-0000-0000-000000000000',
+ "accessCode": '',
+ "requireIdLookup": 'false',
+ "routingOrder": '1',
+ "note": '',
+ "roleName": 'signer',
+ "completedCount": '0',
+ "deliveryMethod": 'email',
+ "templateLocked": 'false',
+ "templateRequired": 'false',
+ "inheritEmailNotificationConfiguration": 'false',
+ "recipientType": 'signer',
+ },
+ ],
+ "carbonCopies": [
+ {
+ "agentCanEditEmail": 'false',
+ "agentCanEditName": 'false',
+ "name": {
+ "source": 'step',
+ "propertyName": 'ccName',
+ "stepId": trigger_id,
+ },
+ "email": {
+ "source": 'step',
+ "propertyName": 'ccEmail',
+ "stepId": trigger_id,
+ },
+ "recipientId": '2',
+ "recipientIdGuid": '00000000-0000-0000-0000-000000000000',
+ "accessCode": '',
+ "requireIdLookup": 'false',
+ "routingOrder": '2',
+ "note": '',
+ "roleName": 'cc',
+ "completedCount": '0',
+ "deliveryMethod": 'email',
+ "templateLocked": 'false',
+ "templateRequired": 'false',
+ "inheritEmailNotificationConfiguration": 'false',
+ "recipientType": 'carboncopy',
+ },
+ ],
+ "certifiedDeliveries": [],
+ },
+ },
+ "output": {
+ "envelopeId_step2": {
+ "source": 'step',
+ "propertyName": 'envelopeId',
+ "stepId": 'step2',
+ "type": 'String',
+ },
+ "combinedDocumentsBase64_step2": {
+ "source": 'step',
+ "propertyName": 'combinedDocumentsBase64',
+ "stepId": 'step2',
+ "type": 'File',
+ },
+ 'fields.signer.text.value_step2': {
+ "source": 'step',
+ "propertyName": 'fields.signer.text.value',
+ "stepId": 'step2',
+ "type": 'String',
+ },
+ },
+ }
+
+ step3 = {
+ "id": 'step3',
+ "name": 'Show a Confirmation Screen',
+ "moduleName": 'ShowConfirmationScreen',
+ "configurationProgress": 'Completed',
+ "type": 'DS-ShowScreenStep',
+ "config": {
+ "participantId": signer_id
+ },
+ "input": {
+ "httpType": "Post",
+ "payload": {
+ "participantId": signer_id,
+ "confirmationMessage": {
+ "title": 'Tasks complete',
+ "description": 'You have completed all your workflow tasks.'
+ }
+ }
+ },
+ "output": {}
+ }
+
+ workflow_definition = WorkflowDefinition(
+ workflow_name="Example workflow - send invite to signer",
+ workflow_description="",
+ document_version="1.0.0",
+ schema_version="1.0.0",
+ account_id=args["account_id"],
+ participants=participants,
+ trigger=trigger,
+ variables=variables,
+ steps=[step1, step2, step3]
+ )
+
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ workflow_management_api = WorkflowManagementApi(api_client)
+ # body = {"workflowDefinition": workflow_definition.__dict__}
+ workflow = workflow_management_api.create_workflow_definition(
+ args["account_id"],
+ {"workflowDefinition": workflow_definition}
+ )
+
+ return workflow.workflow_definition_id
+
+
+def publish_workflow(args, workflow_id):
+ api_client = create_maestro_api_client(args["base_path"], args["access_token"])
+ workflow_management_api = WorkflowManagementApi(api_client)
+
+ try:
+ deploy_request = DeployRequest(
+ deployment_status=DeployStatus.PUBLISH
+ )
+ workflow_management_api.publish_or_un_publish_workflow_definition(
+ args["account_id"],
+ workflow_id,
+ deploy_request
+ )
+ except Exception as err:
+ if hasattr(err, 'response') and hasattr(err.response, 'data'):
+ response_data = json.loads(err.response.data)
+ if 'message' in response_data:
+ is_consent_required = response_data['message'] == 'Consent required'
+ if is_consent_required:
+ return response_data["consentUrl"]
+ raise err
diff --git a/app/maestro/views/__init__.py b/app/maestro/views/__init__.py
new file mode 100644
index 0000000..93520d8
--- /dev/null
+++ b/app/maestro/views/__init__.py
@@ -0,0 +1,3 @@
+from .eg001_trigger_workflow import mseg001
+from .eg002_cancel_workflow import mseg002
+from .eg003_get_workflow_status import mseg003
diff --git a/app/maestro/views/eg001_trigger_workflow.py b/app/maestro/views/eg001_trigger_workflow.py
new file mode 100644
index 0000000..8df42bd
--- /dev/null
+++ b/app/maestro/views/eg001_trigger_workflow.py
@@ -0,0 +1,184 @@
+"""Example 001: How to trigger a Maestro workflow"""
+
+import json
+
+from docusign_maestro.client.api_exception import ApiException
+from flask import render_template, Blueprint, session
+
+from ..examples.eg001_trigger_workflow import Eg001TriggerWorkflowController
+from ...docusign import authenticate, ensure_manifest, get_example_by_number
+from ...ds_config import DS_CONFIG
+from ...error_handlers import process_error
+from ...consts import API_TYPE
+from ..utils import create_workflow, publish_workflow
+
+example_number = 1
+api = API_TYPE["MAESTRO"]
+eg = f"mseg00{example_number}" # reference (and url) for this example
+mseg001 = Blueprint(eg, __name__)
+
+
+@mseg001.route(f"/{eg}", methods=["POST"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def trigger_workflow():
+ """
+ 1. Get required arguments
+ 2. Call the worker method
+ 3. Show results
+ """
+ example = get_example_by_number(session["manifest"], example_number, api)
+
+ # 1. Get required arguments
+ args = Eg001TriggerWorkflowController.get_args()
+ try:
+ # 1. Call the worker method
+ print("args:\n\n")
+ print(args)
+ workflow = Eg001TriggerWorkflowController.get_workflow_definition(args)
+ results = Eg001TriggerWorkflowController.trigger_workflow(workflow, args)
+ session["instance_id"] = results.instance_id
+ except ApiException as err:
+ if hasattr(err, "status"):
+ if err.status == 403:
+ return render_template(
+ "error.html",
+ err=err,
+ error_code=err.status,
+ error_message=session["manifest"]["SupportingTexts"]["ContactSupportToEnableFeature"]
+ .format("Maestro")
+ )
+
+ return process_error(err)
+ # 3. Show results
+ return render_template(
+ "example_done.html",
+ title=example["ExampleName"],
+ message=example["ResultsPageText"],
+ json=json.dumps(json.dumps(results.to_dict()))
+ )
+
+
+@mseg001.route(f"/{eg}", methods=["GET"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def get_view():
+ """responds with the form for the example"""
+ example = get_example_by_number(session["manifest"], example_number, api)
+ additional_page_data = next((p for p in example["AdditionalPage"] if p["Name"] == "publish_workflow"), None)
+
+ args = {
+ "account_id": session["ds_account_id"],
+ "base_path": DS_CONFIG["maestro_api_client_host"],
+ "access_token": session["ds_access_token"],
+ "template_id": session.get("template_id", None)
+ }
+ try:
+ workflows = Eg001TriggerWorkflowController.get_workflow_definitions(args)
+
+ if workflows.count > 0:
+ sorted_workflows = sorted(
+ workflows.value,
+ key=lambda w: w.last_updated_date,
+ reverse=True
+ )
+
+ if sorted_workflows:
+ session["workflow_id"] = sorted_workflows[0].id
+
+ if "workflow_id" not in session:
+ if "template_id" not in session:
+ return render_template(
+ "maestro/eg001_trigger_workflow.html",
+ title=example["ExampleName"],
+ example=example,
+ template_ok=False,
+ source_file="eg001_trigger_workflow.py",
+ source_url=DS_CONFIG["github_example_url"] + "eg001_trigger_workflow.py",
+ documentation=DS_CONFIG["documentation"] + eg,
+ show_doc=DS_CONFIG["documentation"],
+ )
+
+ # if there is no workflow, then create one
+ session["workflow_id"] = create_workflow(args)
+ consent_url = publish_workflow(args, session["workflow_id"])
+
+ if consent_url:
+ return render_template(
+ "maestro/eg001_publish_workflow.html",
+ title=example["ExampleName"],
+ message=additional_page_data["ResultsPageText"],
+ consent_url=consent_url
+ )
+
+ except ApiException as err:
+ if hasattr(err, "status"):
+ if err.status == 403:
+ return render_template(
+ "error.html",
+ err=err,
+ error_code=err.status,
+ error_message=session["manifest"]["SupportingTexts"]["ContactSupportToEnableFeature"]
+ .format("Maestro")
+ )
+
+ return process_error(err)
+
+ return render_template(
+ "maestro/eg001_trigger_workflow.html",
+ title=example["ExampleName"],
+ example=example,
+ template_ok=True,
+ source_file="eg001_trigger_workflow.py",
+ source_url=DS_CONFIG["github_example_url"] + "eg001_trigger_workflow.py",
+ documentation=DS_CONFIG["documentation"] + eg,
+ show_doc=DS_CONFIG["documentation"],
+ )
+
+@mseg001.route(f"/{eg}publish", methods=["POST"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def publish_workflow_view():
+ """responds with the form for the example"""
+ example = get_example_by_number(session["manifest"], example_number, api)
+ additional_page_data = next((p for p in example["AdditionalPage"] if p["Name"] == "publish_workflow"), None)
+
+ args = {
+ "account_id": session["ds_account_id"],
+ "base_path": DS_CONFIG["maestro_api_client_host"],
+ "access_token": session["ds_access_token"]
+ }
+ try:
+ consent_url = publish_workflow(args, session["workflow_id"])
+
+ if consent_url:
+ return render_template(
+ "maestro/eg001_publish_workflow.html",
+ title=example["ExampleName"],
+ message=additional_page_data["ResultsPageText"],
+ consent_url=consent_url
+ )
+
+ except ApiException as err:
+ if hasattr(err, "status"):
+ if err.status == 403:
+ return render_template(
+ "error.html",
+ err=err,
+ error_code=err.status,
+ error_message=session["manifest"]["SupportingTexts"]["ContactSupportToEnableFeature"]
+ .format("Maestro")
+ )
+
+ return process_error(err)
+
+ return render_template(
+ "maestro/eg001_trigger_workflow.html",
+ title=example["ExampleName"],
+ example=example,
+ template_ok=True,
+ source_file="eg001_trigger_workflow.py",
+ source_url=DS_CONFIG["github_example_url"] + "eg001_trigger_workflow.py",
+ documentation=DS_CONFIG["documentation"] + eg,
+ show_doc=DS_CONFIG["documentation"],
+ )
diff --git a/app/maestro/views/eg002_cancel_workflow.py b/app/maestro/views/eg002_cancel_workflow.py
new file mode 100644
index 0000000..e5a86fa
--- /dev/null
+++ b/app/maestro/views/eg002_cancel_workflow.py
@@ -0,0 +1,103 @@
+"""Example 002: How to cancel a Maestro workflow instance"""
+
+import json
+
+from docusign_maestro.client.api_exception import ApiException
+from flask import render_template, Blueprint, session
+
+from ..examples.eg002_cancel_workflow import Eg002CancelWorkflowController
+from ...docusign import authenticate, ensure_manifest, get_example_by_number
+from ...ds_config import DS_CONFIG
+from ...error_handlers import process_error
+from ...consts import API_TYPE
+
+example_number = 2
+api = API_TYPE["MAESTRO"]
+eg = f"mseg00{example_number}" # reference (and url) for this example
+mseg002 = Blueprint(eg, __name__)
+
+
+@mseg002.route(f"/{eg}", methods=["POST"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def cancel_workflow():
+ """
+ 1. Get required arguments
+ 2. Call the worker method
+ 3. Show results
+ """
+ example = get_example_by_number(session["manifest"], example_number, api)
+
+ # 1. Get required arguments
+ args = Eg002CancelWorkflowController.get_args()
+ try:
+ # 1. Call the worker method
+ results = Eg002CancelWorkflowController.cancel_workflow_instance(args)
+ except ApiException as err:
+ if hasattr(err, "status"):
+ if err.status == 403:
+ return render_template(
+ "error.html",
+ err=err,
+ error_code=err.status,
+ error_message=session["manifest"]["SupportingTexts"]["ContactSupportToEnableFeature"]
+ .format("Maestro")
+ )
+
+ return process_error(err)
+ # 3. Show results
+ return render_template(
+ "example_done.html",
+ title=example["ExampleName"],
+ message=example["ResultsPageText"].format(session["instance_id"]),
+ json=json.dumps(json.dumps(results.to_dict()))
+ )
+
+
+@mseg002.route(f"/{eg}", methods=["GET"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def get_view():
+ """responds with the form for the example"""
+ example = get_example_by_number(session["manifest"], example_number, api)
+
+ instance_ok = False
+ workflow_id = session.get("workflow_id", None)
+ instance_id = session.get("instance_id", None)
+ if workflow_id and instance_id:
+ args = {
+ "account_id": session["ds_account_id"],
+ "base_path": DS_CONFIG["maestro_api_client_host"],
+ "access_token": session["ds_access_token"],
+ "workflow_id": workflow_id,
+ "instance_id": instance_id
+ }
+
+ try:
+ state = Eg002CancelWorkflowController.get_instance_state(args)
+ instance_ok = state.lower() == "in progress"
+ except ApiException as err:
+ if hasattr(err, "status"):
+ if err.status == 403:
+ return render_template(
+ "error.html",
+ err=err,
+ error_code=err.status,
+ error_message=session["manifest"]["SupportingTexts"]["ContactSupportToEnableFeature"]
+ .format("Maestro")
+ )
+
+ return process_error(err)
+
+ return render_template(
+ "maestro/eg002_cancel_workflow.html",
+ title=example["ExampleName"],
+ example=example,
+ instance_ok=instance_ok,
+ workflow_id=workflow_id,
+ instance_id=instance_id,
+ source_file="eg002_cancel_workflow.py",
+ source_url=DS_CONFIG["github_example_url"] + "eg002_cancel_workflow.py",
+ documentation=DS_CONFIG["documentation"] + eg,
+ show_doc=DS_CONFIG["documentation"],
+ )
diff --git a/app/maestro/views/eg003_get_workflow_status.py b/app/maestro/views/eg003_get_workflow_status.py
new file mode 100644
index 0000000..6a5cc37
--- /dev/null
+++ b/app/maestro/views/eg003_get_workflow_status.py
@@ -0,0 +1,76 @@
+"""Example 003: How to get the status of a Maestro workflow instance"""
+
+import json
+
+from docusign_maestro.client.api_exception import ApiException
+from flask import render_template, Blueprint, session
+
+from ..examples.eg003_get_workflow_status import Eg003GetWorkflowStatusController
+from ...docusign import authenticate, ensure_manifest, get_example_by_number
+from ...ds_config import DS_CONFIG
+from ...error_handlers import process_error
+from ...consts import API_TYPE
+
+example_number = 3
+api = API_TYPE["MAESTRO"]
+eg = f"mseg00{example_number}" # reference (and url) for this example
+mseg003 = Blueprint(eg, __name__)
+
+
+@mseg003.route(f"/{eg}", methods=["POST"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def get_workflow_status():
+ """
+ 1. Get required arguments
+ 2. Call the worker method
+ 3. Show results
+ """
+ example = get_example_by_number(session["manifest"], example_number, api)
+
+ # 1. Get required arguments
+ args = Eg003GetWorkflowStatusController.get_args()
+ try:
+ # 1. Call the worker method
+ results = Eg003GetWorkflowStatusController.get_workflow_instance(args)
+ except ApiException as err:
+ if hasattr(err, "status"):
+ if err.status == 403:
+ return render_template(
+ "error.html",
+ err=err,
+ error_code=err.status,
+ error_message=session["manifest"]["SupportingTexts"]["ContactSupportToEnableFeature"]
+ .format("Maestro")
+ )
+
+ return process_error(err)
+ # 3. Show results
+ return render_template(
+ "example_done.html",
+ title=example["ExampleName"],
+ message=example["ResultsPageText"].format(results.instance_state),
+ json=json.dumps(json.dumps(results.to_dict(), default=str))
+ )
+
+
+@mseg003.route(f"/{eg}", methods=["GET"])
+@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
+@authenticate(eg=eg, api=api)
+def get_view():
+ """responds with the form for the example"""
+ example = get_example_by_number(session["manifest"], example_number, api)
+
+ workflow_id = session.get("workflow_id", None)
+ instance_id = session.get("instance_id", None)
+ return render_template(
+ "maestro/eg003_get_workflow_status.html",
+ title=example["ExampleName"],
+ example=example,
+ workflow_id=workflow_id,
+ instance_id=instance_id,
+ source_file="eg003_get_workflow_status.py",
+ source_url=DS_CONFIG["github_example_url"] + "eg003_get_workflow_status.py",
+ documentation=DS_CONFIG["documentation"] + eg,
+ show_doc=DS_CONFIG["documentation"],
+ )
diff --git a/app/static/assets/search.js b/app/static/assets/search.js
index 92e2e2c..8c564c7 100644
--- a/app/static/assets/search.js
+++ b/app/static/assets/search.js
@@ -6,6 +6,7 @@ const DS_SEARCH = (function () {
ROOMS: "rooms",
ADMIN: "admin",
CONNECT: "connect",
+ MAESTRO: "maestro",
WEBFORMS: "webforms"
}
@@ -143,6 +144,8 @@ const DS_SEARCH = (function () {
return "eg";
case API_TYPES.CONNECT:
return "cneg";
+ case API_TYPES.MAESTRO:
+ return "mseg";
case API_TYPES.WEBFORMS:
return "weg";
}
diff --git a/app/templates/error.html b/app/templates/error.html
index 35b9a86..1a8819d 100644
--- a/app/templates/error.html
+++ b/app/templates/error.html
@@ -7,7 +7,7 @@ Problem: an error occurred
Error information:
{% if error_code %}
- {{ error_code }}: {{ error_message }}
+ {{ error_code }}: {{ error_message | safe }}
{% else %}
{{ err }}
{% endif %}
diff --git a/app/templates/home.html b/app/templates/home.html
index 72e9b04..701c56f 100644
--- a/app/templates/home.html
+++ b/app/templates/home.html
@@ -45,7 +45,7 @@ {{ group["Name"] }}
{% for example in group["Examples"] -%}
{% if not example["SkipForLanguages"] or "python" not in example["SkipForLanguages"] %}
{% if example.CFREnabled != "CFROnly" %}
- {% set api_prefix = "a" if api["Name"] == "Admin" else "c" if api["Name"] == "Click" else "r" if api["Name"] == "Rooms" else "m" if api["Name"] == "Monitor" else "cn" if api["Name"] == "Connect" else "w" if api["Name"] == "WebForms" else "" %}
+ {% set api_prefix = "a" if api["Name"] == "Admin" else "c" if api["Name"] == "Click" else "r" if api["Name"] == "Rooms" else "m" if api["Name"] == "Monitor" else "cn" if api["Name"] == "Connect" else "ms" if api["Name"] == "Maestro" else "w" if api["Name"] == "WebForms" else "" %}
diff --git a/app/templates/maestro/eg001_publish_workflow.html b/app/templates/maestro/eg001_publish_workflow.html
new file mode 100644
index 0000000..b17ae2d
--- /dev/null
+++ b/app/templates/maestro/eg001_publish_workflow.html
@@ -0,0 +1,11 @@
+ {% extends "base.html" %} {% block content %}
+
+{{ title }}
+
{{ message.format(consent_url) | safe }}
+
+
+
+{% endblock %}
diff --git a/app/templates/maestro/eg001_trigger_workflow.html b/app/templates/maestro/eg001_trigger_workflow.html
new file mode 100644
index 0000000..3660b61
--- /dev/null
+++ b/app/templates/maestro/eg001_trigger_workflow.html
@@ -0,0 +1,59 @@
+ {% extends "base.html" %} {% block content %}
+
+{% include 'example_info.html' %}
+
+{% set form_index = 0 %}
+{% set instance_name_index = 0 %}
+{% set signer_email_index = 1 %}
+{% set signer_name_index = 2 %}
+{% set cc_email_index = 3 %}
+{% set cc_name_index = 4 %}
+{% set redirect_to8_index = 0 %}
+
+{% if template_ok %}
+
+{% else %}
+ {{ example['RedirectsToOtherCodeExamples'][redirect_to8_index]['RedirectText'].format('href="eg008"') | safe }}
+
+
+{% endif %}
+
+{% endblock %}
diff --git a/app/templates/maestro/eg002_cancel_workflow.html b/app/templates/maestro/eg002_cancel_workflow.html
new file mode 100644
index 0000000..a0545d7
--- /dev/null
+++ b/app/templates/maestro/eg002_cancel_workflow.html
@@ -0,0 +1,38 @@
+ {% extends "base.html" %} {% block content %}
+
+{% include 'example_info.html' %}
+
+{% set form_index = 0 %}
+{% set workflow_id_index = 0 %}
+{% set instance_id_index = 1 %}
+{% set redirect_to8_index = 0 %}
+
+{% if instance_ok %}
+
+{% else %}
+ {{ example['RedirectsToOtherCodeExamples'][redirect_to8_index]['RedirectText'].format('href="mseg001"') | safe }}
+
+
+{% endif %}
+
+{% endblock %}
diff --git a/app/templates/maestro/eg003_get_workflow_status.html b/app/templates/maestro/eg003_get_workflow_status.html
new file mode 100644
index 0000000..50b4abd
--- /dev/null
+++ b/app/templates/maestro/eg003_get_workflow_status.html
@@ -0,0 +1,38 @@
+ {% extends "base.html" %} {% block content %}
+
+{% include 'example_info.html' %}
+
+{% set form_index = 0 %}
+{% set workflow_id_index = 0 %}
+{% set instance_id_index = 1 %}
+{% set redirect_to8_index = 0 %}
+
+{% if workflow_id and instance_id %}
+
+{% else %}
+ {{ example['RedirectsToOtherCodeExamples'][redirect_to8_index]['RedirectText'].format('href="mseg001"') | safe }}
+
+
+{% endif %}
+
+{% endblock %}
From 7d5c2fd1565b925bd322d793a31951225758c271 Mon Sep 17 00:00:00 2001
From: RomanBachaloSigmaSoftware
<77355790+RomanBachaloSigmaSoftware@users.noreply.github.com>
Date: Fri, 22 Mar 2024 23:05:29 +0200
Subject: [PATCH 04/22] add enforce_signer_visibility (#146)
---
.../examples/eg040_document_visibility.py | 1 +
.../eSignature/eg040_document_visibility.html | 28 ++++++++++---------
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/app/eSignature/examples/eg040_document_visibility.py b/app/eSignature/examples/eg040_document_visibility.py
index 293467e..f940518 100644
--- a/app/eSignature/examples/eg040_document_visibility.py
+++ b/app/eSignature/examples/eg040_document_visibility.py
@@ -82,6 +82,7 @@ def make_envelope(cls, args, doc_docx_path, doc_pdf_path):
"""
env = EnvelopeDefinition(
email_subject = "Please sign this document set",
+ enforce_signer_visibility = "true",
)
doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")).decode("ascii")
diff --git a/app/templates/eSignature/eg040_document_visibility.html b/app/templates/eSignature/eg040_document_visibility.html
index 61f03bc..176d203 100644
--- a/app/templates/eSignature/eg040_document_visibility.html
+++ b/app/templates/eSignature/eg040_document_visibility.html
@@ -3,10 +3,12 @@
{% include 'example_info.html' %}
{% set recipient_form_index = 0 %}
-{% set signer_email_index = 0 %}
-{% set signer_name_index = 1 %}
-{% set cc_email_index = 2 %}
-{% set cc_name_index = 3 %}
+{% set signer1_email_index = 0 %}
+{% set signer1_name_index = 1 %}
+{% set signer2_email_index = 2 %}
+{% set signer2_name_index = 3 %}
+{% set cc_email_index = 4 %}
+{% set cc_name_index = 5 %}