Hello Hackers,
Need some pointers while creating new node on ACI Tree.
In GreenPlum there is a concept of External Tables that are more or less
similar to Foreign Wrappers, but different. So we started implementing a
new node for it.

We copied the Foreign Wrappers node and started it playing with it. The
current status:
We can see the External Tables node, when we open it we see the External
Tables.
The problem that we are facing now is when we select a table and then press
the tabs 'SQL' or Properties nothing happens. What do we need to do in
order for this to work?

Maybe the choice of foreign Wrappers was not the best to start off, but we
would appreciate some help to make this work.


Attached you can find the code that I currently have.

Thanks
Joao
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/external_tables/__init__.py
new file mode 100644
index 00000000..002f07d9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/__init__.py
@@ -0,0 +1,163 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements External Tables Node"""
+from functools import wraps
+from gettext import gettext
+
+from flask import render_template
+
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.collection import CollectionNodeModule
+from pgadmin.browser.server_groups.servers import databases
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.utils.ajax import make_json_response
+from pgadmin.utils.driver import get_driver
+from pgadmin.utils.compile_template_name import compile_template_name
+
+
+class ExternalTablesModule(CollectionNodeModule):
+    """
+    class ExternalTablesModule(CollectionNodeModule)
+
+        A module class for External Tables node derived from
+        CollectionNodeModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the External Tables module
+        and it's base module.
+
+    * get_nodes(gid, sid, did)
+      - Method is used to generate the browser collection node.
+
+    * script_load()
+      - Load the module script for External Tables, when any of
+        the database node is initialized.
+    """
+
+    NODE_TYPE = 'external_tables'
+    COLLECTION_LABEL = gettext("External Tables")
+
+    def __init__(self, *args, **kwargs):
+        """
+        Method is used to initialize the External tables module and
+        it's base module.
+
+        Args:
+            *args:
+            **kwargs:
+        """
+
+        super(ExternalTablesModule, self).__init__(*args, **kwargs)
+        self.max_ver = 0
+
+    def get_nodes(self, gid, sid, did):
+        yield self.generate_browser_collection_node(did)
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for External tables,
+        when any of the database node is initialized.
+
+        Returns: node type of the database module.
+        """
+        return databases.DatabaseModule.NODE_TYPE
+
+    @property
+    def module_use_template_javascript(self):
+        """
+        Returns whether Jinja2 template is used for generating the javascript
+        module.
+        """
+        return False
+
+
+blueprint = ExternalTablesModule(__name__)
+
+
+class ExternalTablesView(PGChildNodeView):
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'server_group_id'},
+        {'type': 'int', 'id': 'server_id'},
+        {'type': 'int', 'id': 'database_id'}
+    ]
+
+    ids = [
+        {'type': 'int', 'id': 'fid'}
+    ]
+
+    operations = dict({
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'children': [{'get': 'children'}]
+    })
+
+    def check_precondition(function_wrapped):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+
+        @wraps(function_wrapped)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(
+                kwargs['server_id']
+            )
+            self.connection = self.manager.connection(
+                did=kwargs['database_id']
+            )
+
+            return function_wrapped(*args, **kwargs)
+
+        return wrap
+
+    @check_precondition
+    def nodes(self, server_group_id, server_id, database_id):
+        """
+        This function will used to create all the child node within that
+        collection.
+        Here it will create all the foreign data wrapper node.
+
+        Args:
+            server_group_id: Server Group ID
+            server_id: Server ID
+            database_id: Database ID
+        """
+        template_name = compile_template_name(
+            'sql/',
+            'list.sql',
+            self.manager.server_type,
+            self.manager.sversion
+        )
+        template = render_template(template_name)
+        status, external_tables = self.connection.execute_2darray(template)
+        icon_css_class = 'icon-external_tables'
+        result = []
+        for external_table in external_tables['rows']:
+            result.append(self.blueprint.generate_browser_node(
+                external_table['oid'],
+                database_id,
+                external_table['name'],
+                inode=False,
+                icon=icon_css_class
+            ))
+        return make_json_response(
+            data=result,
+            status=200
+        )
+
+
+ExternalTablesView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/reverse_engineer_ddl.py b/web/pgadmin/browser/server_groups/servers/databases/external_tables/reverse_engineer_ddl.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/static/img/coll-external_tables.svg b/web/pgadmin/browser/server_groups/servers/databases/external_tables/static/img/coll-external_tables.svg
new file mode 100644
index 00000000..bcce32d6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/static/img/coll-external_tables.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 16 16"><defs><style>.cls-1{fill:#34495e;stroke:#ac9230;stroke-linecap:round;stroke-linejoin:round;}.cls-2{fill:#f2f2f2;}.cls-3{fill:#ac9230;}</style></defs><title>coll-foreign_data_wrapper</title><g id="_2" data-name="2"><line class="cls-1" x1="11.5" y1="4.25" x2="13.5" y2="2.25"/><line class="cls-1" x1="11.5" y1="2.25" x2="13.5" y2="2.25"/><line class="cls-1" x1="13.5" y1="4.25" x2="13.5" y2="2.25"/></g><g id="_3" data-name="3"><path class="cls-2" d="M12.82,6.25a.87.87,0,0,0,.18-.5c0-1.1-2.46-2-5.5-2s-5.5.9-5.5,2a.87.87,0,0,0,.18.5.78.78,0,0,0,0,1,.78.78,0,0,0,0,1,.78.78,0,0,0,0,1,.78.78,0,0,0,0,1,.78.78,0,0,0,0,1,.87.87,0,0,0-.18.5c0,1.1,2.46,2,5.5,2s5.5-.9,5.5-2a.87.87,0,0,0-.18-.5.78.78,0,0,0,0-1,.78.78,0,0,0,0-1,.78.78,0,0,0,0-1,.78.78,0,0,0,0-1,.78.78,0,0,0,0-1Z"/><ellipse class="cls-3" cx="7.5" cy="5.75" rx="5.5" ry="2"/><path class="cls-3" d="M7.5,12.75c-2.56,0-4.71-.64-5.32-1.5a.87.87,0,0,0-.18.5c0,1.1,2.46,2,5.5,2s5.5-.9,5.5-2a.87.87,0,0,0-.18-.5C12.21,12.11,10.07,12.75,7.5,12.75Z"/><path class="cls-3" d="M7.5,8.75c-2.56,0-4.71-.64-5.32-1.5a.87.87,0,0,0-.18.5c0,1.1,2.46,2,5.5,2s5.5-.9,5.5-2a.87.87,0,0,0-.18-.5C12.21,8.11,10.07,8.75,7.5,8.75Z"/><path class="cls-3" d="M7.5,10.75c-2.56,0-4.71-.64-5.32-1.5a.87.87,0,0,0-.18.5c0,1.1,2.46,2,5.5,2s5.5-.9,5.5-2a.87.87,0,0,0-.18-.5C12.21,10.11,10.07,10.75,7.5,10.75Z"/></g></svg>
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/static/img/external_tables.svg b/web/pgadmin/browser/server_groups/servers/databases/external_tables/static/img/external_tables.svg
new file mode 100644
index 00000000..90228c96
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/static/img/external_tables.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 16 16"><defs><style>.cls-1{fill:#f2f2f2;}.cls-2{fill:#2195e7;}.cls-3{fill:none;stroke:#c1cbd5;stroke-linejoin:round;}.cls-3,.cls-4{stroke-width:0.75px;}.cls-4{fill:#def4fd;stroke:#2195e7;stroke-miterlimit:1;}</style></defs><title>coll-table</title><g id="_2" data-name="2"><rect class="cls-1" x="2.42" y="2.66" width="8.88" height="8.25" rx="0.52" ry="0.52"/><path class="cls-2" d="M10.77,3a.15.15,0,0,1,.15.15v7.2a.15.15,0,0,1-.15.15H2.94a.15.15,0,0,1-.15-.15V3.19A.15.15,0,0,1,2.94,3h7.83m0-.75H2.94a.9.9,0,0,0-.9.9v7.2a.9.9,0,0,0,.9.9h7.83a.9.9,0,0,0,.9-.9V3.19a.9.9,0,0,0-.9-.9Z"/><path class="cls-1" d="M4.9,4.63h8.16a.53.53,0,0,1,.53.53v7.66a.52.52,0,0,1-.52.52H4.9a.52.52,0,0,1-.52-.52V5.16A.52.52,0,0,1,4.9,4.63Z"/><path class="cls-2" d="M13.06,5a.15.15,0,0,1,.15.15v7.66a.15.15,0,0,1-.15.15H4.9a.15.15,0,0,1-.15-.15V5.16A.15.15,0,0,1,4.9,5h8.16m0-.75H4.9a.9.9,0,0,0-.9.9v7.66a.9.9,0,0,0,.9.9h8.16a.9.9,0,0,0,.9-.9V5.16a.9.9,0,0,0-.9-.9Z"/><line class="cls-3" x1="4.76" y1="10.42" x2="13.19" y2="10.42"/><line class="cls-3" x1="8.9" y1="8.08" x2="8.89" y2="12.95"/><line class="cls-4" x1="8.9" y1="5.01" x2="8.89" y2="7.56"/><line class="cls-4" x1="13.19" y1="7.73" x2="4.76" y2="7.73"/></g></svg>
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/templates/sql/gpdb_5.0_plus/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/external_tables/templates/sql/gpdb_5.0_plus/get_columns.sql
new file mode 100644
index 00000000..19fd0953
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/templates/sql/gpdb_5.0_plus/get_columns.sql
@@ -0,0 +1,12 @@
+SELECT
+    a.attname AS name, format_type(a.atttypid, NULL) AS cltype,
+    quote_ident(n.nspname)||'.'||quote_ident(c.relname) as inheritedfrom,
+    c.oid as inheritedid
+FROM
+    pg_class c
+JOIN
+    pg_namespace n ON c.relnamespace=n.oid
+JOIN
+    pg_attribute a ON a.attrelid = c.oid AND NOT a.attisdropped AND a.attnum > 0
+WHERE
+  c.oid = {{tid}}::OID
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/templates/sql/gpdb_5.0_plus/list.sql b/web/pgadmin/browser/server_groups/servers/databases/external_tables/templates/sql/gpdb_5.0_plus/list.sql
new file mode 100644
index 00000000..7490583f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/templates/sql/gpdb_5.0_plus/list.sql
@@ -0,0 +1,6 @@
+SELECT pg_class.oid, relname as name
+FROM pg_class
+LEFT JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace::oid
+WHERE relkind = 'r'
+ AND  relstorage = 'x'
+ AND pg_namespace.nspname not like 'gp_toolkit';
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_external_tables_module.py b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_external_tables_module.py
new file mode 100644
index 00000000..f8c47ab3
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_external_tables_module.py
@@ -0,0 +1,99 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import sys
+
+import six
+
+from pgadmin.browser.server_groups.servers\
+    .databases.external_tables import ExternalTablesModule
+from pgadmin.utils.route import BaseTestGenerator
+
+if sys.version_info < (3, 3):
+    from mock import MagicMock, Mock
+else:
+    from unittest.mock import MagicMock, Mock
+
+
+class TestExternalTablesModule(BaseTestGenerator):
+    scenarios = [
+        ('#BackendSupported When access the on a Postgresql Database, '
+         'it returns false',
+         dict(
+             test_type='backend-support',
+             manager=dict(
+                 server_type='pg',
+                 sversion=90100
+             ),
+             expected_result=False,
+         )),
+        ('#BackendSupported When access the on a GreenPlum Database, '
+         'it returns true',
+         dict(
+             test_type='backend-support',
+             manager=dict(
+                 server_type='gpdb',
+                 sversion=82303
+             ),
+             expected_result=True
+         )),
+        ('#get_nodes when trying to retrieve the node, '
+         'it should return true',
+         dict(
+             test_type='get-nodes',
+             function_parameters=dict(
+                 gid=10,
+                 sid=11,
+                 did=12,
+             ),
+             expected_generate_browser_collection_node_called_with=12
+         )),
+        ('#get_module_use_template_javascript when checking if need to '
+         'generate javascript from template, '
+         'it should return false',
+         dict(
+             test_type='template-javascript',
+             expected_result=False
+         ))
+    ]
+
+    def runTest(self):
+        if self.test_type == 'backend-support':
+            self.__test_backend_support()
+        elif self.test_type == 'get-nodes':
+            self.__test_get_nodes()
+        elif self.test_type == 'template-javascript':
+            self.__test_template_javascript()
+
+    def __test_backend_support(self):
+        manager = MagicMock()
+        manager.sversion = self.manager['sversion']
+        manager.server_type = self.manager['server_type']
+        module = ExternalTablesModule('something')
+        self.assertEquals(
+            self.expected_result,
+            module.BackendSupported(manager)
+        )
+
+    def __test_get_nodes(self):
+        module = ExternalTablesModule('something')
+        module.generate_browser_collection_node = Mock()
+
+        result = module.get_nodes(**self.function_parameters)
+        six.next(result)
+
+        module.generate_browser_collection_node.assert_called_with(
+            self.expected_generate_browser_collection_node_called_with
+        )
+
+    def __test_template_javascript(self):
+        module = ExternalTablesModule('something')
+        self.assertEquals(
+            self.expected_result,
+            module.module_use_template_javascript)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_external_tables_view.py b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_external_tables_view.py
new file mode 100644
index 00000000..f0272bd2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_external_tables_view.py
@@ -0,0 +1,159 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import sys
+
+from pgadmin.browser.server_groups.servers.databases.external_tables import \
+    ExternalTablesView
+from pgadmin.utils.route import BaseTestGenerator
+
+if sys.version_info < (3, 3):
+    from mock import MagicMock, patch
+else:
+    from unittest.mock import MagicMock, patch
+
+
+class TestExternalTablesView(BaseTestGenerator):
+    scenarios = [
+        ('#check_precondition When executing any http call, '
+         'it saves stores the connection and the manager in the class object',
+         dict(
+             test_type='check-precondition',
+             function_parameters=dict(
+                 server_group_id=0,
+                 server_id=1,
+                 database_id=2,
+             ),
+             manager=MagicMock(),
+             connection=MagicMock(execute_2darray=MagicMock()),
+             execute_2darray_return_value=(True, dict(rows=[])),
+             expected_manager_connection_to_be_called_with=dict(
+                 did=2
+             ),
+         )),
+        ('#nodes When retrieving the nodes '
+         'and the database does not have external tables, '
+         'it return no child nodes '
+         'and status 200',
+         dict(
+             test_type='nodes',
+             function_parameters=dict(
+                 server_group_id=0,
+                 server_id=1,
+                 database_id=2,
+             ),
+             manager=MagicMock(server_type='gpdb', sversion=80323),
+             connection=MagicMock(execute_2darray=MagicMock()),
+             execute_2darray_return_value=(True, dict(rows=[])),
+
+             expect_render_template_called_with='sql/#gpdb#80323#/list.sql',
+             expected_make_json_response_called_with=dict(
+                 data=[],
+                 status=200
+             ),
+         )),
+        ('#nodes When retrieving the nodes '
+         'and the database has 2 external tables'
+         'it return 2 child nodes '
+         'and status 200',
+         dict(
+             test_type='nodes',
+             function_parameters=dict(
+                 server_group_id=0,
+                 server_id=1,
+                 database_id=2,
+             ),
+
+             manager=MagicMock(server_type='gpdb', sversion=80323),
+             connection=MagicMock(execute_2darray=MagicMock()),
+             execute_2darray_return_value=(True, dict(
+                 rows=[
+                     dict(
+                         oid='oid1',
+                         name='table_one'
+                     ),
+                     dict(
+                         oid='oid2',
+                         name='table_two'
+                     ),
+                 ]
+             )),
+
+             expect_render_template_called_with='sql/#gpdb#80323#/list.sql',
+             expected_make_json_response_called_with=dict(
+                 data=[
+                     {
+                         'id': "external_tables/oid1",
+                         'label': 'table_one',
+                         'icon': 'icon-external_tables',
+                         'inode': False,
+                         '_type': 'external_tables',
+                         '_id': 'oid1',
+                         '_pid': 2,
+                         'module': 'pgadmin.node.external_tables'
+                     },
+                     {
+                         'id': "external_tables/oid2",
+                         'label': 'table_two',
+                         'icon': 'icon-external_tables',
+                         'inode': False,
+                         '_type': 'external_tables',
+                         '_id': 'oid2',
+                         '_pid': 2,
+                         'module': 'pgadmin.node.external_tables'
+                     }
+                 ],
+                 status=200
+             ),
+         )),
+    ]
+
+    @patch('pgadmin.browser.server_groups.servers.databases.external_tables'
+           '.get_driver')
+    def runTest(self, get_driver_mock):
+        self.__before_all(get_driver_mock)
+
+        if self.test_type == 'check-precondition':
+            self.__test_backend_support()
+        elif self.test_type == 'nodes':
+            self.__test_nodes()
+
+    @patch('pgadmin.browser.server_groups.servers.databases.external_tables'
+           '.render_template')
+    def __test_backend_support(self, _):
+        self.manager.connection = MagicMock(return_value=self.connection)
+        external_tables_view = ExternalTablesView(cmd='')
+        external_tables_view.nodes(**self.function_parameters)
+        self.manager.connection.assert_called_with(
+            **self.expected_manager_connection_to_be_called_with
+        )
+        self.assertEquals(self.manager, external_tables_view.manager)
+        self.assertEquals(self.connection, external_tables_view.connection)
+
+    @patch('pgadmin.browser.server_groups.servers.databases.external_tables'
+           '.render_template')
+    @patch('pgadmin.browser.server_groups.servers.databases.external_tables'
+           '.make_json_response')
+    def __test_nodes(self, make_json_response_mock, render_template_mock):
+        external_tables_view = ExternalTablesView(cmd='')
+        external_tables_view.nodes(**self.function_parameters)
+        make_json_response_mock.assert_called_with(
+            **self.expected_make_json_response_called_with
+        )
+        render_template_mock.assert_called_with(
+            self.expect_render_template_called_with
+        )
+
+    def __before_all(self, get_driver_mock):
+        self.connection.execute_2darray.return_value = \
+            self.execute_2darray_return_value
+        self.manager.connection = MagicMock(return_value=self.connection)
+        get_driver_mock.return_value = MagicMock(
+            connection_manager=MagicMock(return_value=self.manager)
+        )
diff --git a/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_reverse_engineer_ddl.py b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_reverse_engineer_ddl.py
new file mode 100644
index 00000000..49ebdfa4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/external_tables/tests/test_reverse_engineer_ddl.py
@@ -0,0 +1,62 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2018, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import sys
+
+from pgadmin.browser.server_groups.servers.databases.external_tables import \
+    ExternalTablesView
+from pgadmin.utils.route import BaseTestGenerator
+
+if sys.version_info < (3, 3):
+    from mock import MagicMock, patch
+else:
+    from unittest.mock import MagicMock, patch
+
+
+class TestReverseEngineerDDL(BaseTestGenerator):
+    scenarios = [
+        ('#format When a , '
+         'it return no child nodes '
+         'and status 200',
+         dict(
+             test_type='nodes',
+             function_parameters=dict(
+                 server_group_id=0,
+                 server_id=1,
+                 database_id=2,
+             ),
+             manager=MagicMock(server_type='gpdb', sversion=80323),
+             connection=MagicMock(execute_2darray=MagicMock()),
+             execute_2darray_return_value=(True, dict(rows=[])),
+
+             expect_render_template_called_with='sql/#gpdb#80323#/list.sql',
+             expected_make_json_response_called_with=dict(
+                 data=[],
+                 status=200
+             ),
+         )),
+
+    ]
+
+    def runTest(self):
+
+        if self.test_type == 'check-precondition':
+            self.__test_backend_support()
+
+    @patch('pgadmin.browser.server_groups.servers.databases.external_tables'
+           '.render_template')
+    def __test_backend_support(self, _):
+        self.manager.connection = MagicMock(return_value=self.connection)
+        external_tables_view = ExternalTablesView(cmd='')
+        external_tables_view.nodes(**self.function_parameters)
+        self.manager.connection.assert_called_with(
+            **self.expected_manager_connection_to_be_called_with
+        )
+        self.assertEquals(self.manager, external_tables_view.manager)
+        self.assertEquals(self.connection, external_tables_view.connection)
diff --git a/web/pgadmin/static/bundle/browser.js b/web/pgadmin/static/bundle/browser.js
index d39ce22b..3fcc69d8 100644
--- a/web/pgadmin/static/bundle/browser.js
+++ b/web/pgadmin/static/bundle/browser.js
@@ -1,5 +1,6 @@
 define('bundled_browser',[
   'pgadmin.browser',
+  'sources/browser/server_groups/servers/databases/external_tables/index',
 ], function(pgBrowser) {
   pgBrowser.init();
 });
diff --git a/web/pgadmin/static/js/browser/server_groups/servers/databases/external_tables/external_tables.js b/web/pgadmin/static/js/browser/server_groups/servers/databases/external_tables/external_tables.js
new file mode 100644
index 00000000..d0655c52
--- /dev/null
+++ b/web/pgadmin/static/js/browser/server_groups/servers/databases/external_tables/external_tables.js
@@ -0,0 +1,35 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+export function initialize(pgBrowser, gettext) {
+  if (!pgBrowser.Nodes['coll-external_tables']) {
+    pgBrowser.Nodes['coll-external_tables'] =
+      pgBrowser.Collection.extend({
+        node: 'external_tables',
+        label: gettext('External Tables'),
+        type: 'coll-external_tables',
+        columns: ['name', 'fdwowner', 'description'],
+      });
+  }
+
+  if (!pgBrowser.Nodes['external_table']) {
+    pgBrowser.Nodes['external_table'] = pgBrowser.Node.extend({
+      parent_type: 'database',
+      type: 'external_table',
+      label: gettext('External Table'),
+      hasSQL: true,
+      hasDepends: true,
+      canDrop: true,
+      canDropCascade: true,
+    });
+  }
+
+  return pgBrowser;
+}
+
diff --git a/web/pgadmin/static/js/browser/server_groups/servers/databases/external_tables/index.js b/web/pgadmin/static/js/browser/server_groups/servers/databases/external_tables/index.js
new file mode 100644
index 00000000..54cd14bc
--- /dev/null
+++ b/web/pgadmin/static/js/browser/server_groups/servers/databases/external_tables/index.js
@@ -0,0 +1,18 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import pgBrowser from 'top/browser/static/js/browser';
+import gettext from 'sources/gettext';
+import {initialize} from './external_tables';
+
+let pgBrowserOut = initialize(pgBrowser, gettext);
+
+module.exports = {
+  pgBrowser: pgBrowserOut,
+};
diff --git a/web/regression/javascript/browser/server_groups/servers/databases/external_tables/external_tables_spec.js b/web/regression/javascript/browser/server_groups/servers/databases/external_tables/external_tables_spec.js
new file mode 100644
index 00000000..4236438a
--- /dev/null
+++ b/web/regression/javascript/browser/server_groups/servers/databases/external_tables/external_tables_spec.js
@@ -0,0 +1,55 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {initialize} from 'sources/browser/server_groups/servers/databases/external_tables/external_tables';
+
+describe('when external tables is loaded', () => {
+  let pgBrowser;
+  let gettext;
+  let result;
+  beforeEach(() => {
+    pgBrowser = {
+      Nodes: {},
+    };
+    pgBrowser.Collection = jasmine.createSpyObj('Collection', ['extend']);
+    pgBrowser.Node = jasmine.createSpyObj('Node', ['extend']);
+    pgBrowser.Collection.extend.and.returnValue('extended object');
+    pgBrowser.Node.extend.and.returnValue('extended node object');
+    gettext = jasmine.createSpy('gettext').and.callFake((text) => text);
+  });
+
+  describe('when external tables is already defined', () => {
+    beforeEach(() => {
+      pgBrowser.Nodes['coll-external_tables'] = {};
+      result = initialize(pgBrowser, gettext);
+    });
+
+    it('does not reinitialize it', () => {
+      expect(pgBrowser.Collection.extend).not.toHaveBeenCalled();
+    });
+
+    it('returns the not updated version of pgBrowser', () => {
+      expect(result).toBe(pgBrowser);
+    });
+  });
+
+  describe('when external tables is not defined', () => {
+    beforeEach(() => {
+      result = initialize(pgBrowser, gettext);
+    });
+
+    it('initializes "coll-external_tables"', () => {
+      expect(pgBrowser.Collection.extend).toHaveBeenCalled();
+    });
+
+    it('returns the updated version of pgBrowser', () => {
+      expect(result.Nodes['coll-external_tables']).not.toBeUndefined();
+    });
+  });
+});
diff --git a/web/webpack.config.js b/web/webpack.config.js
index 84f8466e..4dbf1357 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -146,12 +146,21 @@ module.exports = {
           presets: ['es2015', 'react'],
         },
       },
+    }, {
+      test: /external_table.*\.js/,
+      use: {
+        loader: 'babel-loader',
+        options: {
+          presets: ['es2015'],
+        },
+      },
     }, {
       // Transforms the code in a way that it works in the webpack environment.
       // It uses imports-loader internally to load dependency. Its
       // configuration is specified in webpack.shim.js
       // Ref: https://www.npmjs.com/package/shim-loader
       test: /\.js/,
+      exclude: [/external_table/],
       loader: 'shim-loader',
       query: webpackShimConfig,
       include: path.join(__dirname, '/pgadmin/browser'),
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index b0cf5fd6..25c2b519 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -120,6 +120,7 @@ var webpackShimConfig = {
   // Map module id to file path used in 'define(['baseurl', 'misc']). It is
   // used by webpack while creating bundle
   resolveAlias: {
+    'top': path.join(__dirname, './pgadmin'),
     'bundled_codemirror': path.join(__dirname, './pgadmin/static/bundle/codemirror'),
     'bundled_browser': path.join(__dirname, './pgadmin/static/bundle/browser'),
     'sources': path.join(__dirname, './pgadmin/static/js'),
@@ -212,6 +213,8 @@ var webpackShimConfig = {
     'pgadmin.node.catalog_object': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/catalog_objects/static/js/catalog_object'),
     'pgadmin.dashboard': path.join(__dirname, './pgadmin/dashboard/static/js/dashboard'),
     'pgadmin.node.foreign_data_wrapper': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/static/js/foreign_data_wrapper'),
+    'pgadmin.node.external_table': path.join(__dirname, './pgadmin/static/js/browser/server_groups/servers/databases/external_tables/index'),
+    'pgadmin.node.external_tables': path.join(__dirname, './pgadmin/static/js/browser/server_groups/servers/databases/external_tables/index'),
     'pgadmin.node.foreign_key': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key'),
     'pgadmin.browser.server.variable': path.join(__dirname, './pgadmin/browser/server_groups/servers/static/js/variable'),
     'pgadmin.tools.grant_wizard': path.join(__dirname, './pgadmin/tools/grant_wizard/static/js/grant_wizard'),

Reply via email to