This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new e9a72a4 Add SalesforceApexRestOperator (#18819)
e9a72a4 is described below
commit e9a72a4e95e6d23bae010ad92499cd7b06d50037
Author: Mario Taddeucci <[email protected]>
AuthorDate: Fri Oct 8 01:50:34 2021 -0300
Add SalesforceApexRestOperator (#18819)
---
.../example_dags/example_salesforce_apex_rest.py | 35 ++++++++++++
airflow/providers/salesforce/hooks/salesforce.py | 2 -
.../salesforce/operators/salesforce_apex_rest.py | 66 ++++++++++++++++++++++
airflow/providers/salesforce/provider.yaml | 3 +
docs/apache-airflow-providers-salesforce/index.rst | 1 +
.../operators/index.rst | 26 +++++++++
.../operators/salesforce_apex_rest.rst | 39 +++++++++++++
.../providers/salesforce/hooks/test_salesforce.py | 4 +-
.../operators/test_salesforce_apex_rest.py | 49 ++++++++++++++++
9 files changed, 220 insertions(+), 5 deletions(-)
diff --git
a/airflow/providers/salesforce/example_dags/example_salesforce_apex_rest.py
b/airflow/providers/salesforce/example_dags/example_salesforce_apex_rest.py
new file mode 100644
index 0000000..c8e5850
--- /dev/null
+++ b/airflow/providers/salesforce/example_dags/example_salesforce_apex_rest.py
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from datetime import datetime
+
+from airflow import DAG
+from airflow.providers.salesforce.operators.salesforce_apex_rest import
SalesforceApexRestOperator
+
+with DAG(
+ dag_id="salesforce_apex_rest_operator_dag",
+ schedule_interval=None,
+ start_date=datetime(2021, 1, 1),
+ catchup=False,
+) as dag:
+
+ # [START howto_salesforce_apex_rest_operator]
+ payload = {"activity": [{"user": "12345", "action": "update page", "time":
"2014-04-21T13:00:15Z"}]}
+
+ apex_operator = SalesforceApexRestOperator(
+ task_id="apex_task", method='POST', endpoint='User/Activity',
payload=payload
+ )
+ # [END howto_salesforce_apex_rest_operator]
diff --git a/airflow/providers/salesforce/hooks/salesforce.py
b/airflow/providers/salesforce/hooks/salesforce.py
index 6145b1e..36426e8 100644
--- a/airflow/providers/salesforce/hooks/salesforce.py
+++ b/airflow/providers/salesforce/hooks/salesforce.py
@@ -201,8 +201,6 @@ class SalesforceHook(BaseHook):
:return: the names of the fields.
:rtype: list(str)
"""
- self.get_conn()
-
obj_description = self.describe_object(obj)
return [field['name'] for field in obj_description['fields']]
diff --git a/airflow/providers/salesforce/operators/salesforce_apex_rest.py
b/airflow/providers/salesforce/operators/salesforce_apex_rest.py
new file mode 100644
index 0000000..2c55fa5
--- /dev/null
+++ b/airflow/providers/salesforce/operators/salesforce_apex_rest.py
@@ -0,0 +1,66 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from airflow.models import BaseOperator
+from airflow.providers.salesforce.hooks.salesforce import SalesforceHook
+
+
+class SalesforceApexRestOperator(BaseOperator):
+ """
+ Execute a APEX Rest API action
+
+ .. seealso::
+ For more information on how to use this operator, take a look at the
guide:
+ :ref:`howto/operator:SalesforceApexRestOperator`
+
+ :param endpoint: The REST endpoint for the request.
+ :type endpoint: str
+ :param method: HTTP method for the request (default GET)
+ :type method: str
+ :param payload: A dict of parameters to send in a POST / PUT request
+ :type payload: str
+ :param salesforce_conn_id: The :ref:`Salesforce Connection id
<howto/connection:SalesforceHook>`.
+ :type salesforce_conn_id: str
+ """
+
+ def __init__(
+ self,
+ *,
+ endpoint: str,
+ method: str = 'GET',
+ payload: dict = None,
+ salesforce_conn_id: str = 'salesforce_default',
+ **kwargs,
+ ) -> None:
+ super().__init__(**kwargs)
+ self.endpoint = endpoint
+ self.method = method
+ self.payload = payload
+ self.salesforce_conn_id = salesforce_conn_id
+
+ def execute(self, context: dict) -> dict:
+ """
+ Makes an HTTP request to an APEX REST endpoint and pushes results to
xcom.
+ :param context: The task context during execution.
+ :type context: dict
+ :return: Apex response
+ :rtype: dict
+ """
+ sf_hook = SalesforceHook(salesforce_conn_id=self.salesforce_conn_id)
+ conn = sf_hook.get_conn()
+ result = conn.apexecute(action=self.endpoint, method=self.method,
data=self.payload)
+ if self.do_xcom_push:
+ return result
diff --git a/airflow/providers/salesforce/provider.yaml
b/airflow/providers/salesforce/provider.yaml
index 58fd5aa..8997760 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/salesforce/provider.yaml
@@ -35,12 +35,15 @@ additional-dependencies:
integrations:
- integration-name: Salesforce
external-doc-url: https://www.salesforce.com/
+ how-to-guide:
+ -
/docs/apache-airflow-providers-salesforce/operators/salesforce_apex_rest.rst
logo: /integration-logos/salesforce/Salesforce.png
tags: [service]
operators:
- integration-name: Salesforce
python-modules:
+ - airflow.providers.salesforce.operators.salesforce_apex_rest
- airflow.providers.salesforce.operators.tableau_refresh_workbook
sensors:
diff --git a/docs/apache-airflow-providers-salesforce/index.rst
b/docs/apache-airflow-providers-salesforce/index.rst
index 55d47e2..c931f8d 100644
--- a/docs/apache-airflow-providers-salesforce/index.rst
+++ b/docs/apache-airflow-providers-salesforce/index.rst
@@ -27,6 +27,7 @@ Content
:caption: Guides
Connection types <connections/salesforce>
+ Operators <operators/index>
.. toctree::
:maxdepth: 1
diff --git a/docs/apache-airflow-providers-salesforce/operators/index.rst
b/docs/apache-airflow-providers-salesforce/operators/index.rst
new file mode 100644
index 0000000..a769cc2
--- /dev/null
+++ b/docs/apache-airflow-providers-salesforce/operators/index.rst
@@ -0,0 +1,26 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ .. http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+
+
+Salesforce Operators
+====================
+
+.. toctree::
+ :maxdepth: 1
+
+ salesforce_apex_rest
diff --git
a/docs/apache-airflow-providers-salesforce/operators/salesforce_apex_rest.rst
b/docs/apache-airflow-providers-salesforce/operators/salesforce_apex_rest.rst
new file mode 100644
index 0000000..a699f5a
--- /dev/null
+++
b/docs/apache-airflow-providers-salesforce/operators/salesforce_apex_rest.rst
@@ -0,0 +1,39 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ .. http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+.. _howto/operator:SalesforceApexRestOperator:
+
+SalesforceApexRestOperator
+==========================
+
+Use the
:class:`~airflow.providers.salesforce.operators.salesforce_apex_rest.SalesforceApexRestOperator`
to execute Apex Rest.
+
+
+Using the Operator
+^^^^^^^^^^^^^^^^^^
+You can also use this library to call custom Apex methods:
+
+This would call the endpoint
``https://<instance>.salesforce.com/services/apexrest/User/Activity`` with
``payload`` as
+the body content encoded with ``json.dumps``
+
+.. exampleinclude::
/../../airflow/providers/salesforce/example_dags/example_salesforce_apex_rest.py
+ :language: python
+ :start-after: [START howto_salesforce_apex_rest_operator]
+ :end-before: [END howto_salesforce_apex_rest_operator]
+
+You can read more about Apex on the
+`Force.com Apex Code Developer's Guide
<https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dev_guide.htm>`__.
diff --git a/tests/providers/salesforce/hooks/test_salesforce.py
b/tests/providers/salesforce/hooks/test_salesforce.py
index 3970a33..766fa89 100644
--- a/tests/providers/salesforce/hooks/test_salesforce.py
+++ b/tests/providers/salesforce/hooks/test_salesforce.py
@@ -289,17 +289,15 @@ class TestSalesforceHook(unittest.TestCase):
mock_salesforce.return_value.__getattr__(obj).describe.assert_called_once_with()
assert obj_description ==
mock_salesforce.return_value.__getattr__(obj).describe.return_value
-
@patch("airflow.providers.salesforce.hooks.salesforce.SalesforceHook.get_conn")
@patch(
"airflow.providers.salesforce.hooks.salesforce.SalesforceHook.describe_object",
return_value={"fields": [{"name": "field_1"}, {"name": "field_2"}]},
)
- def test_get_available_fields(self, mock_describe_object, mock_get_conn):
+ def test_get_available_fields(self, mock_describe_object):
obj = "obj_name"
available_fields = self.salesforce_hook.get_available_fields(obj)
- mock_get_conn.assert_called_once_with()
mock_describe_object.assert_called_once_with(obj)
assert available_fields == ["field_1", "field_2"]
diff --git a/tests/providers/salesforce/operators/test_salesforce_apex_rest.py
b/tests/providers/salesforce/operators/test_salesforce_apex_rest.py
new file mode 100644
index 0000000..0431a6c
--- /dev/null
+++ b/tests/providers/salesforce/operators/test_salesforce_apex_rest.py
@@ -0,0 +1,49 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import unittest
+from unittest.mock import Mock, patch
+
+from airflow.providers.salesforce.operators.salesforce_apex_rest import
SalesforceApexRestOperator
+
+
+class TestSalesforceApexRestOperator(unittest.TestCase):
+ """
+ Test class for SalesforceApexRestOperator
+ """
+
+
@patch('airflow.providers.salesforce.operators.salesforce_apex_rest.SalesforceHook.get_conn')
+ def test_execute_salesforce_apex_rest(self, mock_get_conn):
+ """
+ Test execute apex rest
+ """
+
+ endpoint = 'User/Activity'
+ method = 'POST'
+ payload = {"activity": [{"user": "12345", "action": "update page",
"time": "2014-04-21T13:00:15Z"}]}
+
+ mock_get_conn.return_value.apexecute = Mock()
+
+ operator = SalesforceApexRestOperator(
+ task_id='task', endpoint=endpoint, method=method, payload=payload
+ )
+
+ operator.execute(context={})
+
+ mock_get_conn.return_value.apexecute.assert_called_once_with(
+ action=endpoint, method=method, data=payload
+ )