Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom interface type #11341

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/integrator/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This chapter describes advanced configuration settings.

build
runtime
interface
ngeo
backend
caching
Expand Down
79 changes: 79 additions & 0 deletions doc/integrator/interface.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.. _integrator_interface:

Introduction
------------

This chapter describes how to integrate a new ``custom`` interface in a c2cgeoportal application.

``custom`` means that the interface can be based on other frontend than ngeo event if at the end of this
document we will speak about how to integrate a ngeo as custom interface with a simple build chain.

Review
------

In the c2cgeoportal application we have by default 3 interfaces: desktop, mobile and iframe.

c2cgeoportal provide different things around the interface but they are not hard linked:

- The interface in the admin interface is a way to define the layers visible in the interface.
- The ``interfaces_config`` in the ``vars.yaml`` is a way to define the interface configuration.
- The ``interface`` in the ``vars.yaml`` is a way to configure the interfaces route in c2cgeoportal: ``/``,
``/theme/<theme>``, ``/<interface>`` and ``/<interface>/theme/<theme_name>``.

Configuration
-------------

Here we will describe how to add a new ``custom`` interface in a c2cgeoportal application,
for that we should add a new entry in the ``interface_config`` with the ``type`` set to ``custom``.

.. code:: yaml

vars:
interfaces_config:
custom:
type: custom
name: my_interface

We can also add an optional ``file`` attribute in the config to specify the file that should be used for the interface,
with relative (from ``/etc/static-frontend``) or absolute file name.

Interface integration
---------------------

To publish the interfaces que should provide the interfaces files (HTML, CSS, JavaScript, images, ...) in
the ``/etc/static-frontend/`` directory.

The interface files should be in a directory named with the interface name suffixed by ``.html``.

Note that this folder is also available on the ``/static-frontend/`` endpoint with cache headers without
any cash bustering, then the files (other than the interfaces HTML files) should contains an hash.

If you need cache bustering you should put your files in the ``geoportal/geomapfish_geoportal/static/``
directory (``/etc/geomapfish/static`` in the container).

The interface HTML file is considered as mako template and he can use the following variables:
- ``request``: the Pyramid request object.
- ``dynamicUrl``: the URL to get the interface configuration.
- ``interface``: the interface name.
- ``staticFrontend``: the URL to the static frontend directory.
- ``staticCashBuster``: the URL to the static cash buster directory.

For that you should create a Docker image that provide a ``/etc/static-frontend/`` volume (``VOLUME /etc/static-frontend``).

And include it in the ``docker-compose.yaml`` file with something like:

.. code:: yaml

services:
my_interface:
image: my_interface
user: www-data

geoportal:
volumes_from:
- my_interface:ro

Ngeo integration
----------------

TODO
65 changes: 59 additions & 6 deletions geoportal/c2cgeoportal_geoportal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
TotalPythonObjectMemoryCollector,
)
from c2cgeoportal_geoportal.lib.xsd import XSD
from c2cgeoportal_geoportal.views.entry import Entry, canvas_view
from c2cgeoportal_geoportal.views.entry import Entry, canvas_view, custom_view

if TYPE_CHECKING:
from c2cgeoportal_commons.models import static # pylint: disable=ungrouped-imports,useless-suppression
Expand Down Expand Up @@ -99,6 +99,7 @@ def __call__(self, value: Any, system: dict[str, str]) -> bytes:

INTERFACE_TYPE_NGEO = "ngeo"
INTERFACE_TYPE_CANVAS = "canvas"
INTERFACE_TYPE_CUSTOM = "custom"


def add_interface_config(config: pyramid.config.Configurator, interface_config: dict[str, Any]) -> None:
Expand Down Expand Up @@ -139,6 +140,15 @@ def add_interface(
interface_config=interface_config,
**kwargs,
)
elif interface_type == INTERFACE_TYPE_CUSTOM:
assert interface_config is not None
add_interface_custom(
config,
route_name=interface_name,
route=route,
interface_config=interface_config,
**kwargs,
)
else:
_LOG.error(
"Unknown interface type '%s', should be '%s' or '%s'.",
Expand Down Expand Up @@ -209,6 +219,39 @@ def add_interface_canvas(
)


def add_interface_custom(
config: pyramid.config.Configurator,
route_name: str,
route: str,
interface_config: dict[str, Any],
permission: Optional[str] = None,
) -> None:
"""Add the ngeo interfaces views and routes."""

renderer = os.path.join("/etc/static-frontend", interface_config.get("file", f"{route_name}.html"))
config.add_route(route_name, route, request_method="GET")
# Permalink theme: recover the theme for generating custom viewer.js URL
config.add_route(
f"{route_name}theme",
f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
request_method="GET",
)
view = partial(custom_view, interface_config=interface_config)
view.__module__ = custom_view.__module__
config.add_view(
view,
route_name=route_name,
renderer=renderer,
permission=permission,
)
config.add_view(
view,
route_name=f"{route_name}theme",
renderer=renderer,
permission=permission,
)


def add_admin_interface(config: pyramid.config.Configurator) -> None:
"""Add the administration interface views and routes."""

Expand Down Expand Up @@ -607,12 +650,17 @@ def add_static_route(name: str, attr: str, path: str, renderer: str) -> None:
config.add_route(name, path, request_method="GET")
config.add_view(Entry, attr=attr, route_name=name, renderer=renderer)

add_static_route("favicon", "favicon", "/favicon.ico", "/etc/geomapfish/static/images/favicon.ico")
add_static_route("robot.txt", "robot_txt", "/robot.txt", "/etc/geomapfish/static/robot.txt")
static_files = config.get_settings().get("static_files", {})
for name, attr, path in [
("favicon.ico", "favicon", "/favicon.ico"),
("robot.txt", "robot_txt", "/robot.txt"),
("api.js.map", "apijsmap", "/api.js.map"),
("api.css", "apicss", "/api.css"),
("apihelp.html", "apihelp", "/apihelp/index.html"),
]:
if static_files.get(name):
add_static_route(name, attr, path, static_files[name])
config.add_route("apijs", "/api.js", request_method="GET")
add_static_route("apijsmap", "apijsmap", "/api.js.map", "/etc/static-ngeo/api.js.map")
add_static_route("apicss", "apicss", "/api.css", "/etc/static-ngeo/api.css")
add_static_route("apihelp", "apihelp", "/apihelp/index.html", "/etc/geomapfish/static/apihelp/index.html")
c2cgeoportal_geoportal.views.add_redirect(config, "apihelp_redirect", "/apihelp.html", "apihelp")

config.add_route("themes", "/themes", request_method="GET", pregenerator=C2CPregenerator(role=True))
Expand Down Expand Up @@ -747,6 +795,11 @@ def add_static_route(name: str, attr: str, path: str, renderer: str) -> None:
path="/etc/static-ngeo",
cache_max_age=int(config.get_settings()["default_max_age"]),
)
config.add_static_view(
name="static-frontend",
path="/etc/static-frontend",
cache_max_age=int(config.get_settings()["default_max_age"]),
)

# Add the c2cgeoportal static view with cache buster
config.add_static_view(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ENV CONFIG_VARS sqlalchemy.url sqlalchemy.pool_recycle sqlalchemy.pool_size sqla
dbsessions urllogin host_forward_host headers_whitelist headers_blacklist \
smtp c2c.base_path welcome_email \
lingua_extractor interfaces_config interfaces devserver_url api authentication intranet metrics pdfreport \
vector_tiles i18next main_ogc_server
vector_tiles i18next main_ogc_server static_files

COPY . /tmp/config/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ vars:
escapeValue: false
backend: {}

static_files:
favicon.ico: /etc/geomapfish/static/images/favicon.ico
robot.txt: /etc/geomapfish/static/robot.txt
api.js: /etc/static-ngeo/api.js
api.js.map: /etc/static-ngeo/api.js.map
api.css: /etc/static-ngeo/api.css
apihelp.html: /etc/geomapfish/static/apihelp/index.html

interfaces_config:
default:
constants:
Expand Down
24 changes: 21 additions & 3 deletions geoportal/c2cgeoportal_geoportal/views/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ def get_ngeo_index_vars(self) -> dict[str, Any]:

@staticmethod
@_CACHE_REGION.cache_on_arguments()
def get_apijs(api_name: Optional[str]) -> str:
with open("/etc/static-ngeo/api.js", encoding="utf-8") as api_file:
def get_apijs(api_filename: str, api_name: Optional[str]) -> str:
with open(api_filename, encoding="utf-8") as api_file:
api = api_file.read().split("\n")
sourcemap = api.pop(-1)
if api_name:
Expand All @@ -77,7 +77,10 @@ def get_apijs(api_name: Optional[str]) -> str:

@view_config(route_name="apijs") # type: ignore
def apijs(self) -> pyramid.response.Response:
self.request.response.text = self.get_apijs(self.request.registry.settings["api"].get("name"))
self.request.response.text = self.get_apijs(
self.request.registry.settings["static_files"]["api.js"],
self.request.registry.settings["api"].get("name"),
)
set_common_headers(self.request, "api", Cache.PUBLIC, content_type="application/javascript")
return self.request.response

Expand Down Expand Up @@ -138,3 +141,18 @@ def canvas_view(request: pyramid.request.Request, interface_config: dict[str, An
),
"spinner": spinner,
}


def custom_view(request: pyramid.request.Request, interface_config: dict[str, Any]) -> dict[str, Any]:
"""Get view used as entry point of a canvas interface."""

set_common_headers(request, "index", Cache.PUBLIC_NO, content_type="text/html")

dynamic_url = request.route_url("dynamic")
return {
"request": request,
"dynamicUrl": dynamic_url,
"interface": interface_config["name"],
"staticFrontend": request.static_url("/etc/static-frontend"),
"staticCashBuster": request.static_url("/etc/geomapfish/static"),
}
Loading