You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2021/03/03 09:33:01 UTC

[airflow] 37/41: Add Tableau provider separate from Salesforce Provider (#14030)

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v2-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit d3359a03a986c3674b9058e4e800c41190120e62
Author: Jyoti Dhiman <36...@users.noreply.github.com>
AuthorDate: Thu Feb 25 17:52:54 2021 +0530

    Add Tableau provider separate from Salesforce Provider (#14030)
    
    Closes #13614
    
    (cherry picked from commit 45e72ca83049a7db526b1f0fbd94c75f5f92cc75)
---
 CONTRIBUTING.rst                                   |   1 +
 airflow/providers/dependencies.json                |   3 +
 airflow/providers/salesforce/CHANGELOG.rst         |  16 ++++
 airflow/providers/salesforce/hooks/tableau.py      | 104 ++-------------------
 .../operators/tableau_refresh_workbook.py          |  88 ++---------------
 airflow/providers/salesforce/provider.yaml         |   6 +-
 .../salesforce/sensors/tableau_job_status.py       |  68 +++-----------
 .../{salesforce => tableau}/CHANGELOG.rst          |   0
 .../provider.yaml => tableau/__init__.py}          |  34 +------
 .../example_dags/__init__.py}                      |  33 -------
 .../example_tableau_refresh_workbook.py            |   4 +-
 .../provider.yaml => tableau/hooks/__init__.py}    |  34 +------
 .../{salesforce => tableau}/hooks/tableau.py       |   0
 .../operators/__init__.py}                         |  33 -------
 .../operators/tableau_refresh_workbook.py          |   4 +-
 .../{salesforce => tableau}/provider.yaml          |  26 +++---
 .../provider.yaml => tableau/sensors/__init__.py}  |  33 -------
 .../sensors/tableau_job_status.py                  |   2 +-
 .../apache-airflow-providers-tableau/index.rst     |  29 +++++-
 docs/integration-logos/tableau/tableau.png         | Bin 0 -> 4142 bytes
 docs/spelling_wordlist.txt                         |   1 +
 .../run_install_and_test_provider_packages.sh      |   2 +-
 setup.py                                           |   4 +-
 tests/core/test_providers_manager.py               |   1 +
 .../providers/tableau/hooks/__init__.py            |  34 +------
 .../{salesforce => tableau}/hooks/test_tableau.py  |  32 +++++--
 .../providers/tableau/operators/__init__.py        |  33 -------
 .../operators/test_tableau_refresh_workbook.py     |  26 +++++-
 .../providers/tableau/sensors/__init__.py          |  33 -------
 .../sensors/test_tableau_job_status.py             |  16 +++-
 30 files changed, 162 insertions(+), 538 deletions(-)

diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 0a6f381..857d3bb 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -654,6 +654,7 @@ microsoft.mssql            odbc
 mysql                      amazon,presto,vertica
 opsgenie                   http
 postgres                   amazon
+salesforce                 tableau
 sftp                       ssh
 slack                      http
 snowflake                  slack
diff --git a/airflow/providers/dependencies.json b/airflow/providers/dependencies.json
index 836020c..b01e96c 100644
--- a/airflow/providers/dependencies.json
+++ b/airflow/providers/dependencies.json
@@ -67,6 +67,9 @@
   "postgres": [
     "amazon"
   ],
+  "salesforce": [
+    "tableau"
+  ],
   "sftp": [
     "ssh"
   ],
diff --git a/airflow/providers/salesforce/CHANGELOG.rst b/airflow/providers/salesforce/CHANGELOG.rst
index cef7dda..b4eb0ed 100644
--- a/airflow/providers/salesforce/CHANGELOG.rst
+++ b/airflow/providers/salesforce/CHANGELOG.rst
@@ -19,6 +19,22 @@
 Changelog
 ---------
 
+1.0.2
+.....
+
+Tableau provider moved to separate 'tableau' provider
+
+Things done:
+
+    - Tableau classes imports classes from 'tableau' provider with deprecation warning
+
+
+1.0.1
+.....
+
+Updated documentation and readme files.
+
+
 1.0.0
 .....
 
diff --git a/airflow/providers/salesforce/hooks/tableau.py b/airflow/providers/salesforce/hooks/tableau.py
index 51c2f98..cf5f7f3 100644
--- a/airflow/providers/salesforce/hooks/tableau.py
+++ b/airflow/providers/salesforce/hooks/tableau.py
@@ -14,102 +14,14 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from enum import Enum
-from typing import Any, Optional
 
-from tableauserverclient import Pager, PersonalAccessTokenAuth, Server, TableauAuth
-from tableauserverclient.server import Auth
+import warnings
 
-from airflow.hooks.base import BaseHook
+# pylint: disable=unused-import
+from airflow.providers.tableau.hooks.tableau import TableauHook, TableauJobFinishCode  # noqa
 
-
-class TableauJobFinishCode(Enum):
-    """
-    The finish code indicates the status of the job.
-
-    .. seealso:: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#query_job
-
-    """
-
-    PENDING = -1
-    SUCCESS = 0
-    ERROR = 1
-    CANCELED = 2
-
-
-class TableauHook(BaseHook):
-    """
-    Connects to the Tableau Server Instance and allows to communicate with it.
-
-    .. seealso:: https://tableau.github.io/server-client-python/docs/
-
-    :param site_id: The id of the site where the workbook belongs to.
-        It will connect to the default site if you don't provide an id.
-    :type site_id: Optional[str]
-    :param tableau_conn_id: The Tableau Connection id containing the credentials
-        to authenticate to the Tableau Server.
-    :type tableau_conn_id: str
-    """
-
-    conn_name_attr = 'tableau_conn_id'
-    default_conn_name = 'tableau_default'
-    conn_type = 'tableau'
-    hook_name = 'Tableau'
-
-    def __init__(self, site_id: Optional[str] = None, tableau_conn_id: str = default_conn_name) -> None:
-        super().__init__()
-        self.tableau_conn_id = tableau_conn_id
-        self.conn = self.get_connection(self.tableau_conn_id)
-        self.site_id = site_id or self.conn.extra_dejson.get('site_id', '')
-        self.server = Server(self.conn.host, use_server_version=True)
-        self.tableau_conn = None
-
-    def __enter__(self):
-        if not self.tableau_conn:
-            self.tableau_conn = self.get_conn()
-        return self
-
-    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
-        self.server.auth.sign_out()
-
-    def get_conn(self) -> Auth.contextmgr:
-        """
-        Signs in to the Tableau Server and automatically signs out if used as ContextManager.
-
-        :return: an authorized Tableau Server Context Manager object.
-        :rtype: tableauserverclient.server.Auth.contextmgr
-        """
-        if self.conn.login and self.conn.password:
-            return self._auth_via_password()
-        if 'token_name' in self.conn.extra_dejson and 'personal_access_token' in self.conn.extra_dejson:
-            return self._auth_via_token()
-        raise NotImplementedError('No Authentication method found for given Credentials!')
-
-    def _auth_via_password(self) -> Auth.contextmgr:
-        tableau_auth = TableauAuth(
-            username=self.conn.login, password=self.conn.password, site_id=self.site_id
-        )
-        return self.server.auth.sign_in(tableau_auth)
-
-    def _auth_via_token(self) -> Auth.contextmgr:
-        tableau_auth = PersonalAccessTokenAuth(
-            token_name=self.conn.extra_dejson['token_name'],
-            personal_access_token=self.conn.extra_dejson['personal_access_token'],
-            site_id=self.site_id,
-        )
-        return self.server.auth.sign_in_with_personal_access_token(tableau_auth)
-
-    def get_all(self, resource_name: str) -> Pager:
-        """
-        Get all items of the given resource.
-
-        .. seealso:: https://tableau.github.io/server-client-python/docs/page-through-results
-
-        :param resource_name: The name of the resource to paginate.
-            For example: jobs or workbooks
-        :type resource_name: str
-        :return: all items by returning a Pager.
-        :rtype: tableauserverclient.Pager
-        """
-        resource = getattr(self.server, resource_name)
-        return Pager(resource.get)
+warnings.warn(
+    "This module is deprecated. Please use `airflow.providers.tableau.hooks.tableau`.",
+    DeprecationWarning,
+    stacklevel=2,
+)
diff --git a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py b/airflow/providers/salesforce/operators/tableau_refresh_workbook.py
index 7d4ffdc..309af33 100644
--- a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py
+++ b/airflow/providers/salesforce/operators/tableau_refresh_workbook.py
@@ -14,84 +14,16 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from typing import Optional
 
-from tableauserverclient import WorkbookItem
+import warnings
 
-from airflow.exceptions import AirflowException
-from airflow.models import BaseOperator
-from airflow.providers.salesforce.hooks.tableau import TableauHook
-from airflow.utils.decorators import apply_defaults
+# pylint: disable=unused-import
+from airflow.providers.tableau.operators.tableau_refresh_workbook import (  # noqa
+    TableauRefreshWorkbookOperator,
+)
 
-
-class TableauRefreshWorkbookOperator(BaseOperator):
-    """
-    Refreshes a Tableau Workbook/Extract
-
-    .. seealso:: https://tableau.github.io/server-client-python/docs/api-ref#workbooks
-
-    :param workbook_name: The name of the workbook to refresh.
-    :type workbook_name: str
-    :param site_id: The id of the site where the workbook belongs to.
-    :type site_id: Optional[str]
-    :param blocking: By default the extract refresh will be blocking means it will wait until it has finished.
-    :type blocking: bool
-    :param tableau_conn_id: The Tableau Connection id containing the credentials
-        to authenticate to the Tableau Server.
-    :type tableau_conn_id: str
-    """
-
-    @apply_defaults
-    def __init__(
-        self,
-        *,
-        workbook_name: str,
-        site_id: Optional[str] = None,
-        blocking: bool = True,
-        tableau_conn_id: str = 'tableau_default',
-        **kwargs,
-    ) -> None:
-        super().__init__(**kwargs)
-        self.workbook_name = workbook_name
-        self.site_id = site_id
-        self.blocking = blocking
-        self.tableau_conn_id = tableau_conn_id
-
-    def execute(self, context: dict) -> str:
-        """
-        Executes the Tableau Extract Refresh and pushes the job id to xcom.
-
-        :param context: The task context during execution.
-        :type context: dict
-        :return: the id of the job that executes the extract refresh
-        :rtype: str
-        """
-        with TableauHook(self.site_id, self.tableau_conn_id) as tableau_hook:
-            workbook = self._get_workbook_by_name(tableau_hook)
-
-            job_id = self._refresh_workbook(tableau_hook, workbook.id)
-            if self.blocking:
-                from airflow.providers.salesforce.sensors.tableau_job_status import TableauJobStatusSensor
-
-                TableauJobStatusSensor(
-                    job_id=job_id,
-                    site_id=self.site_id,
-                    tableau_conn_id=self.tableau_conn_id,
-                    task_id='wait_until_succeeded',
-                    dag=None,
-                ).execute(context={})
-                self.log.info('Workbook %s has been successfully refreshed.', self.workbook_name)
-            return job_id
-
-    def _get_workbook_by_name(self, tableau_hook: TableauHook) -> WorkbookItem:
-        for workbook in tableau_hook.get_all(resource_name='workbooks'):
-            if workbook.name == self.workbook_name:
-                self.log.info('Found matching workbook with id %s', workbook.id)
-                return workbook
-
-        raise AirflowException(f'Workbook {self.workbook_name} not found!')
-
-    def _refresh_workbook(self, tableau_hook: TableauHook, workbook_id: str) -> str:
-        job = tableau_hook.server.workbooks.refresh(workbook_id)
-        self.log.info('Refreshing Workbook %s...', self.workbook_name)
-        return job.id
+warnings.warn(
+    "This module is deprecated. Please use `airflow.providers.tableau.operators.tableau_refresh_workbook`.",
+    DeprecationWarning,
+    stacklevel=2,
+)
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/salesforce/provider.yaml
index fe739ff..c0992d8 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/salesforce/provider.yaml
@@ -22,6 +22,8 @@ description: |
     `Salesforce <https://www.salesforce.com/>`__
 
 versions:
+  - 1.0.2
+  - 1.0.1
   - 1.0.0
 
 integrations:
@@ -40,10 +42,12 @@ sensors:
       - airflow.providers.salesforce.sensors.tableau_job_status
 
 hooks:
+  - integration-name: Tableau
+    python-modules:
+      - airflow.providers.salesforce.hooks.tableau
   - integration-name: Salesforce
     python-modules:
       - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
 
 hook-class-names:
   - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/sensors/tableau_job_status.py b/airflow/providers/salesforce/sensors/tableau_job_status.py
index 4939203..076159e 100644
--- a/airflow/providers/salesforce/sensors/tableau_job_status.py
+++ b/airflow/providers/salesforce/sensors/tableau_job_status.py
@@ -14,63 +14,17 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from typing import Optional
 
-from airflow.exceptions import AirflowException
-from airflow.providers.salesforce.hooks.tableau import TableauHook, TableauJobFinishCode
-from airflow.sensors.base import BaseSensorOperator
-from airflow.utils.decorators import apply_defaults
+import warnings
 
+# pylint: disable=unused-import
+from airflow.providers.tableau.sensors.tableau_job_status import (  # noqa
+    TableauJobFailedException,
+    TableauJobStatusSensor,
+)
 
-class TableauJobFailedException(AirflowException):
-    """An exception that indicates that a Job failed to complete."""
-
-
-class TableauJobStatusSensor(BaseSensorOperator):
-    """
-    Watches the status of a Tableau Server Job.
-
-    .. seealso:: https://tableau.github.io/server-client-python/docs/api-ref#jobs
-
-    :param job_id: The job to watch.
-    :type job_id: str
-    :param site_id: The id of the site where the workbook belongs to.
-    :type site_id: Optional[str]
-    :param tableau_conn_id: The Tableau Connection id containing the credentials
-        to authenticate to the Tableau Server.
-    :type tableau_conn_id: str
-    """
-
-    template_fields = ('job_id',)
-
-    @apply_defaults
-    def __init__(
-        self,
-        *,
-        job_id: str,
-        site_id: Optional[str] = None,
-        tableau_conn_id: str = 'tableau_default',
-        **kwargs,
-    ) -> None:
-        super().__init__(**kwargs)
-        self.tableau_conn_id = tableau_conn_id
-        self.job_id = job_id
-        self.site_id = site_id
-
-    def poke(self, context: dict) -> bool:
-        """
-        Pokes until the job has successfully finished.
-
-        :param context: The task context during execution.
-        :type context: dict
-        :return: True if it succeeded and False if not.
-        :rtype: bool
-        """
-        with TableauHook(self.site_id, self.tableau_conn_id) as tableau_hook:
-            finish_code = TableauJobFinishCode(
-                int(tableau_hook.server.jobs.get_by_id(self.job_id).finish_code)
-            )
-            self.log.info('Current finishCode is %s (%s)', finish_code.name, finish_code.value)
-            if finish_code in [TableauJobFinishCode.ERROR, TableauJobFinishCode.CANCELED]:
-                raise TableauJobFailedException('The Tableau Refresh Workbook Job failed!')
-            return finish_code == TableauJobFinishCode.SUCCESS
+warnings.warn(
+    "This module is deprecated. Please use `airflow.providers.tableau.sensors.tableau_job_status`.",
+    DeprecationWarning,
+    stacklevel=2,
+)
diff --git a/airflow/providers/salesforce/CHANGELOG.rst b/airflow/providers/tableau/CHANGELOG.rst
similarity index 100%
copy from airflow/providers/salesforce/CHANGELOG.rst
copy to airflow/providers/tableau/CHANGELOG.rst
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to airflow/providers/tableau/__init__.py
index fe739ff..217e5db 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/tableau/__init__.py
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,36 +15,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/example_dags/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to airflow/providers/tableau/example_dags/__init__.py
index fe739ff..13a8339 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/tableau/example_dags/__init__.py
@@ -14,36 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/example_dags/example_tableau_refresh_workbook.py b/airflow/providers/tableau/example_dags/example_tableau_refresh_workbook.py
similarity index 92%
rename from airflow/providers/salesforce/example_dags/example_tableau_refresh_workbook.py
rename to airflow/providers/tableau/example_dags/example_tableau_refresh_workbook.py
index 32b347c..da1cc8b 100644
--- a/airflow/providers/salesforce/example_dags/example_tableau_refresh_workbook.py
+++ b/airflow/providers/tableau/example_dags/example_tableau_refresh_workbook.py
@@ -23,8 +23,8 @@ when the operation actually finishes. That's why we have another task that check
 from datetime import timedelta
 
 from airflow import DAG
-from airflow.providers.salesforce.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator
-from airflow.providers.salesforce.sensors.tableau_job_status import TableauJobStatusSensor
+from airflow.providers.tableau.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator
+from airflow.providers.tableau.sensors.tableau_job_status import TableauJobStatusSensor
 from airflow.utils.dates import days_ago
 
 DEFAULT_ARGS = {
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/hooks/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to airflow/providers/tableau/hooks/__init__.py
index fe739ff..217e5db 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/tableau/hooks/__init__.py
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,36 +15,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/hooks/tableau.py b/airflow/providers/tableau/hooks/tableau.py
similarity index 100%
copy from airflow/providers/salesforce/hooks/tableau.py
copy to airflow/providers/tableau/hooks/tableau.py
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/operators/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to airflow/providers/tableau/operators/__init__.py
index fe739ff..13a8339 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/tableau/operators/__init__.py
@@ -14,36 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py b/airflow/providers/tableau/operators/tableau_refresh_workbook.py
similarity index 95%
copy from airflow/providers/salesforce/operators/tableau_refresh_workbook.py
copy to airflow/providers/tableau/operators/tableau_refresh_workbook.py
index 7d4ffdc..25ca77b 100644
--- a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py
+++ b/airflow/providers/tableau/operators/tableau_refresh_workbook.py
@@ -20,7 +20,7 @@ from tableauserverclient import WorkbookItem
 
 from airflow.exceptions import AirflowException
 from airflow.models import BaseOperator
-from airflow.providers.salesforce.hooks.tableau import TableauHook
+from airflow.providers.tableau.hooks.tableau import TableauHook
 from airflow.utils.decorators import apply_defaults
 
 
@@ -71,7 +71,7 @@ class TableauRefreshWorkbookOperator(BaseOperator):
 
             job_id = self._refresh_workbook(tableau_hook, workbook.id)
             if self.blocking:
-                from airflow.providers.salesforce.sensors.tableau_job_status import TableauJobStatusSensor
+                from airflow.providers.tableau.sensors.tableau_job_status import TableauJobStatusSensor
 
                 TableauJobStatusSensor(
                     job_id=job_id,
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/provider.yaml
similarity index 61%
copy from airflow/providers/salesforce/provider.yaml
copy to airflow/providers/tableau/provider.yaml
index fe739ff..e777947 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/tableau/provider.yaml
@@ -16,34 +16,34 @@
 # under the License.
 
 ---
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
+package-name: apache-airflow-providers-tableau
+name: Tableau
 description: |
-    `Salesforce <https://www.salesforce.com/>`__
+    `Tableau <https://www.tableau.com/>`__
 
 versions:
   - 1.0.0
 
 integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
+  - integration-name: Tableau
+    external-doc-url: https://www.tableau.com/
+    logo: /integration-logos/tableau/tableau.png
     tags: [service]
 
 operators:
-  - integration-name: Salesforce
+  - integration-name: Tableau
     python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
+      - airflow.providers.tableau.operators.tableau_refresh_workbook
 
 sensors:
-  - integration-name: Salesforce
+  - integration-name: Tableau
     python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
+      - airflow.providers.tableau.sensors.tableau_job_status
 
 hooks:
-  - integration-name: Salesforce
+  - integration-name: Tableau
     python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
+      - airflow.providers.tableau.hooks.tableau
 
 hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
+  - airflow.providers.tableau.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/sensors/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to airflow/providers/tableau/sensors/__init__.py
index fe739ff..13a8339 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/airflow/providers/tableau/sensors/__init__.py
@@ -14,36 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/airflow/providers/salesforce/sensors/tableau_job_status.py b/airflow/providers/tableau/sensors/tableau_job_status.py
similarity index 96%
copy from airflow/providers/salesforce/sensors/tableau_job_status.py
copy to airflow/providers/tableau/sensors/tableau_job_status.py
index 4939203..518e2f0 100644
--- a/airflow/providers/salesforce/sensors/tableau_job_status.py
+++ b/airflow/providers/tableau/sensors/tableau_job_status.py
@@ -17,7 +17,7 @@
 from typing import Optional
 
 from airflow.exceptions import AirflowException
-from airflow.providers.salesforce.hooks.tableau import TableauHook, TableauJobFinishCode
+from airflow.providers.tableau.hooks.tableau import TableauHook, TableauJobFinishCode
 from airflow.sensors.base import BaseSensorOperator
 from airflow.utils.decorators import apply_defaults
 
diff --git a/airflow/providers/salesforce/CHANGELOG.rst b/docs/apache-airflow-providers-tableau/index.rst
similarity index 56%
copy from airflow/providers/salesforce/CHANGELOG.rst
copy to docs/apache-airflow-providers-tableau/index.rst
index cef7dda..47ace94 100644
--- a/airflow/providers/salesforce/CHANGELOG.rst
+++ b/docs/apache-airflow-providers-tableau/index.rst
@@ -1,3 +1,4 @@
+
  .. 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
@@ -15,11 +16,29 @@
     specific language governing permissions and limitations
     under the License.
 
+``apache-airflow-providers-tableau``
+=======================================
+
+Content
+-------
+
+.. toctree::
+    :maxdepth: 1
+    :caption: Guides
+
+    Connection types <connections/tableau>
+
+.. toctree::
+    :maxdepth: 1
+    :caption: References
+
+    Python API <_api/airflow/providers/tableau/index>
 
-Changelog
----------
+.. toctree::
+    :maxdepth: 1
+    :caption: Resources
 
-1.0.0
-.....
+    Example DAGs <https://github.com/apache/airflow/tree/master/airflow/providers/tableau/example_dags>
+    PyPI Repository <https://pypi.org/project/apache-airflow-providers-tableau/>
 
-Initial version of the provider.
+.. THE REMINDER OF THE FILE IS AUTOMATICALLY GENERATED. IT WILL BE OVERWRITTEN AT RELEASE TIME!
diff --git a/docs/integration-logos/tableau/tableau.png b/docs/integration-logos/tableau/tableau.png
new file mode 100644
index 0000000..4ec356c
Binary files /dev/null and b/docs/integration-logos/tableau/tableau.png differ
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 71f9e34..0e89285 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -1280,6 +1280,7 @@ sync'ed
 sys
 syspath
 systemd
+tableau
 tableauserverclient
 tablefmt
 tagKey
diff --git a/scripts/in_container/run_install_and_test_provider_packages.sh b/scripts/in_container/run_install_and_test_provider_packages.sh
index 76d41e4..5eb039a 100755
--- a/scripts/in_container/run_install_and_test_provider_packages.sh
+++ b/scripts/in_container/run_install_and_test_provider_packages.sh
@@ -95,7 +95,7 @@ function discover_all_provider_packages() {
     # Columns is to force it wider, so it doesn't wrap at 80 characters
     COLUMNS=180 airflow providers list
 
-    local expected_number_of_providers=63
+    local expected_number_of_providers=64
     local actual_number_of_providers
     actual_providers=$(airflow providers list --output yaml | grep package_name)
     actual_number_of_providers=$(wc -l <<<"$actual_providers")
diff --git a/setup.py b/setup.py
index 2867b36..4ee7a5c 100644
--- a/setup.py
+++ b/setup.py
@@ -444,7 +444,7 @@ statsd = [
     'statsd>=3.3.0, <4.0',
 ]
 tableau = [
-    'tableauserverclient~=0.12',
+    'tableauserverclient',
 ]
 telegram = [
     'python-telegram-bot==13.0',
@@ -576,6 +576,7 @@ PROVIDERS_REQUIREMENTS: Dict[str, List[str]] = {
     'snowflake': snowflake,
     'sqlite': [],
     'ssh': ssh,
+    'tableau': tableau,
     'telegram': telegram,
     'vertica': vertica,
     'yandex': yandex,
@@ -608,7 +609,6 @@ CORE_EXTRAS_REQUIREMENTS: Dict[str, List[str]] = {
     'rabbitmq': rabbitmq,
     'sentry': sentry,
     'statsd': statsd,
-    'tableau': tableau,
     'virtualenv': virtualenv,
 }
 
diff --git a/tests/core/test_providers_manager.py b/tests/core/test_providers_manager.py
index 39ee588..9112d5e 100644
--- a/tests/core/test_providers_manager.py
+++ b/tests/core/test_providers_manager.py
@@ -81,6 +81,7 @@ ALL_PROVIDERS = [
     # 'apache-airflow-providers-snowflake',
     'apache-airflow-providers-sqlite',
     'apache-airflow-providers-ssh',
+    'apache-airflow-providers-tableau',
     'apache-airflow-providers-telegram',
     'apache-airflow-providers-vertica',
     'apache-airflow-providers-yandex',
diff --git a/airflow/providers/salesforce/provider.yaml b/tests/providers/tableau/hooks/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to tests/providers/tableau/hooks/__init__.py
index fe739ff..217e5db 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/tests/providers/tableau/hooks/__init__.py
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,36 +15,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/tests/providers/salesforce/hooks/test_tableau.py b/tests/providers/tableau/hooks/test_tableau.py
similarity index 81%
rename from tests/providers/salesforce/hooks/test_tableau.py
rename to tests/providers/tableau/hooks/test_tableau.py
index 130746d..66ecdf7 100644
--- a/tests/providers/salesforce/hooks/test_tableau.py
+++ b/tests/providers/tableau/hooks/test_tableau.py
@@ -19,12 +19,19 @@ import unittest
 from unittest.mock import patch
 
 from airflow import configuration, models
-from airflow.providers.salesforce.hooks.tableau import TableauHook
+from airflow.providers.tableau.hooks.tableau import TableauHook
 from airflow.utils import db
 
 
 class TestTableauHook(unittest.TestCase):
+    """
+    Test class for TableauHook
+    """
+
     def setUp(self):
+        """
+        setup
+        """
         configuration.conf.load_test_config()
 
         db.merge_conn(
@@ -46,9 +53,12 @@ class TestTableauHook(unittest.TestCase):
             )
         )
 
-    @patch('airflow.providers.salesforce.hooks.tableau.TableauAuth')
-    @patch('airflow.providers.salesforce.hooks.tableau.Server')
+    @patch('airflow.providers.tableau.hooks.tableau.TableauAuth')
+    @patch('airflow.providers.tableau.hooks.tableau.Server')
     def test_get_conn_auth_via_password_and_site_in_connection(self, mock_server, mock_tableau_auth):
+        """
+        Test get conn auth via password
+        """
         with TableauHook(tableau_conn_id='tableau_test_password') as tableau_hook:
             mock_server.assert_called_once_with(tableau_hook.conn.host, use_server_version=True)
             mock_tableau_auth.assert_called_once_with(
@@ -59,9 +69,12 @@ class TestTableauHook(unittest.TestCase):
             mock_server.return_value.auth.sign_in.assert_called_once_with(mock_tableau_auth.return_value)
         mock_server.return_value.auth.sign_out.assert_called_once_with()
 
-    @patch('airflow.providers.salesforce.hooks.tableau.PersonalAccessTokenAuth')
-    @patch('airflow.providers.salesforce.hooks.tableau.Server')
+    @patch('airflow.providers.tableau.hooks.tableau.PersonalAccessTokenAuth')
+    @patch('airflow.providers.tableau.hooks.tableau.Server')
     def test_get_conn_auth_via_token_and_site_in_init(self, mock_server, mock_tableau_auth):
+        """
+        Test get conn auth via token
+        """
         with TableauHook(site_id='test', tableau_conn_id='tableau_test_token') as tableau_hook:
             mock_server.assert_called_once_with(tableau_hook.conn.host, use_server_version=True)
             mock_tableau_auth.assert_called_once_with(
@@ -74,10 +87,13 @@ class TestTableauHook(unittest.TestCase):
             )
         mock_server.return_value.auth.sign_out.assert_called_once_with()
 
-    @patch('airflow.providers.salesforce.hooks.tableau.TableauAuth')
-    @patch('airflow.providers.salesforce.hooks.tableau.Server')
-    @patch('airflow.providers.salesforce.hooks.tableau.Pager', return_value=[1, 2, 3])
+    @patch('airflow.providers.tableau.hooks.tableau.TableauAuth')
+    @patch('airflow.providers.tableau.hooks.tableau.Server')
+    @patch('airflow.providers.tableau.hooks.tableau.Pager', return_value=[1, 2, 3])
     def test_get_all(self, mock_pager, mock_server, mock_tableau_auth):  # pylint: disable=unused-argument
+        """
+        Test get all
+        """
         with TableauHook(tableau_conn_id='tableau_test_password') as tableau_hook:
             jobs = tableau_hook.get_all(resource_name='jobs')
             assert jobs == mock_pager.return_value
diff --git a/airflow/providers/salesforce/provider.yaml b/tests/providers/tableau/operators/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to tests/providers/tableau/operators/__init__.py
index fe739ff..13a8339 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/tests/providers/tableau/operators/__init__.py
@@ -14,36 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/tests/providers/salesforce/operators/test_tableau_refresh_workbook.py b/tests/providers/tableau/operators/test_tableau_refresh_workbook.py
similarity index 80%
rename from tests/providers/salesforce/operators/test_tableau_refresh_workbook.py
rename to tests/providers/tableau/operators/test_tableau_refresh_workbook.py
index 77139c1..72377a5 100644
--- a/tests/providers/salesforce/operators/test_tableau_refresh_workbook.py
+++ b/tests/providers/tableau/operators/test_tableau_refresh_workbook.py
@@ -21,11 +21,18 @@ from unittest.mock import Mock, patch
 import pytest
 
 from airflow.exceptions import AirflowException
-from airflow.providers.salesforce.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator
+from airflow.providers.tableau.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator
 
 
 class TestTableauRefreshWorkbookOperator(unittest.TestCase):
+    """
+    Test class for TableauRefreshWorkbookOperator
+    """
+
     def setUp(self):
+        """
+        setup
+        """
         self.mocked_workbooks = []
         for i in range(3):
             mock_workbook = Mock()
@@ -34,8 +41,11 @@ class TestTableauRefreshWorkbookOperator(unittest.TestCase):
             self.mocked_workbooks.append(mock_workbook)
         self.kwargs = {'site_id': 'test_site', 'task_id': 'task', 'dag': None}
 
-    @patch('airflow.providers.salesforce.operators.tableau_refresh_workbook.TableauHook')
+    @patch('airflow.providers.tableau.operators.tableau_refresh_workbook.TableauHook')
     def test_execute(self, mock_tableau_hook):
+        """
+        Test Execute
+        """
         mock_tableau_hook.get_all = Mock(return_value=self.mocked_workbooks)
         mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook)
         operator = TableauRefreshWorkbookOperator(blocking=False, workbook_name='wb_2', **self.kwargs)
@@ -45,9 +55,12 @@ class TestTableauRefreshWorkbookOperator(unittest.TestCase):
         mock_tableau_hook.server.workbooks.refresh.assert_called_once_with(2)
         assert mock_tableau_hook.server.workbooks.refresh.return_value.id == job_id
 
-    @patch('airflow.providers.salesforce.sensors.tableau_job_status.TableauJobStatusSensor')
-    @patch('airflow.providers.salesforce.operators.tableau_refresh_workbook.TableauHook')
+    @patch('airflow.providers.tableau.sensors.tableau_job_status.TableauJobStatusSensor')
+    @patch('airflow.providers.tableau.operators.tableau_refresh_workbook.TableauHook')
     def test_execute_blocking(self, mock_tableau_hook, mock_tableau_job_status_sensor):
+        """
+        Test execute blocking
+        """
         mock_tableau_hook.get_all = Mock(return_value=self.mocked_workbooks)
         mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook)
         operator = TableauRefreshWorkbookOperator(workbook_name='wb_2', **self.kwargs)
@@ -64,8 +77,11 @@ class TestTableauRefreshWorkbookOperator(unittest.TestCase):
             dag=None,
         )
 
-    @patch('airflow.providers.salesforce.operators.tableau_refresh_workbook.TableauHook')
+    @patch('airflow.providers.tableau.operators.tableau_refresh_workbook.TableauHook')
     def test_execute_missing_workbook(self, mock_tableau_hook):
+        """
+        Test execute missing workbook
+        """
         mock_tableau_hook.get_all = Mock(return_value=self.mocked_workbooks)
         mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook)
         operator = TableauRefreshWorkbookOperator(workbook_name='test', **self.kwargs)
diff --git a/airflow/providers/salesforce/provider.yaml b/tests/providers/tableau/sensors/__init__.py
similarity index 50%
copy from airflow/providers/salesforce/provider.yaml
copy to tests/providers/tableau/sensors/__init__.py
index fe739ff..13a8339 100644
--- a/airflow/providers/salesforce/provider.yaml
+++ b/tests/providers/tableau/sensors/__init__.py
@@ -14,36 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
----
-package-name: apache-airflow-providers-salesforce
-name: Salesforce
-description: |
-    `Salesforce <https://www.salesforce.com/>`__
-
-versions:
-  - 1.0.0
-
-integrations:
-  - integration-name: Salesforce
-    external-doc-url: https://www.salesforce.com/
-    tags: [service]
-
-operators:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.operators.tableau_refresh_workbook
-
-sensors:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.sensors.tableau_job_status
-
-hooks:
-  - integration-name: Salesforce
-    python-modules:
-      - airflow.providers.salesforce.hooks.salesforce
-      - airflow.providers.salesforce.hooks.tableau
-
-hook-class-names:
-  - airflow.providers.salesforce.hooks.tableau.TableauHook
diff --git a/tests/providers/salesforce/sensors/test_tableau_job_status.py b/tests/providers/tableau/sensors/test_tableau_job_status.py
similarity index 84%
rename from tests/providers/salesforce/sensors/test_tableau_job_status.py
rename to tests/providers/tableau/sensors/test_tableau_job_status.py
index 7f01011..ea6eeb2 100644
--- a/tests/providers/salesforce/sensors/test_tableau_job_status.py
+++ b/tests/providers/tableau/sensors/test_tableau_job_status.py
@@ -21,18 +21,25 @@ from unittest.mock import Mock, patch
 import pytest
 from parameterized import parameterized
 
-from airflow.providers.salesforce.sensors.tableau_job_status import (
+from airflow.providers.tableau.sensors.tableau_job_status import (
     TableauJobFailedException,
     TableauJobStatusSensor,
 )
 
 
 class TestTableauJobStatusSensor(unittest.TestCase):
+    """
+    Test Class for JobStatusSensor
+    """
+
     def setUp(self):
         self.kwargs = {'job_id': 'job_2', 'site_id': 'test_site', 'task_id': 'task', 'dag': None}
 
-    @patch('airflow.providers.salesforce.sensors.tableau_job_status.TableauHook')
+    @patch('airflow.providers.tableau.sensors.tableau_job_status.TableauHook')
     def test_poke(self, mock_tableau_hook):
+        """
+        Test poke
+        """
         mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook)
         mock_get = mock_tableau_hook.server.jobs.get_by_id
         mock_get.return_value.finish_code = '0'
@@ -44,8 +51,11 @@ class TestTableauJobStatusSensor(unittest.TestCase):
         mock_get.assert_called_once_with(sensor.job_id)
 
     @parameterized.expand([('1',), ('2',)])
-    @patch('airflow.providers.salesforce.sensors.tableau_job_status.TableauHook')
+    @patch('airflow.providers.tableau.sensors.tableau_job_status.TableauHook')
     def test_poke_failed(self, finish_code, mock_tableau_hook):
+        """
+        Test poke failed
+        """
         mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook)
         mock_get = mock_tableau_hook.server.jobs.get_by_id
         mock_get.return_value.finish_code = finish_code