Skip to content

Refactor of widgets dependencies system #11627

@allyoucanmap

Description

@allyoucanmap

Description

This task aims to refactor the dependencies system to:

  • clarify and generalize available interactions
  • review the dependencies logic
  • allow introduction of new widgets in the connections system, in particular the dynamic filter widget

Current state of the implementation

The connections system was introduced in #2783 as support for the map bounding box filter and over time we included additional type of interactions.

Summary of available connection interactions:

  • Map widget can:
    • filter by extent other widget
    • pass layers/groups information to other widgets (legend)
  • Table widget can:
    • apply filter on columns to other widget (quickFilters)
    • zoom to extent using filter on columns bounds in map widget (quickFilters)
    • zoom to a single feature in map widget
  • Legend widget can:
    • update visibility, expanded and opacity properties of layers in map widget
    • apply interactive legend filter when available

Looking to the list above we can divide connections in following types:

  • filter action
  • map events action (specific implementation: from table to map)
  • properties update action (specific implementation: from legend to map)

At the current state some connections are available only when the source layer name match with the target layer name or (in case of map) one of the available layers with the same name. We should review how connection can be provided based in some cases we want to connections between different data sources but with similar attributes.

The current system uses a property inside the widget that describe the mapping of properties of another widget, e.g:

{
    id: 'w-map-01',
    widgetType: 'map',
    selectedMapId: 'm-01',
    maps: [
        { mapId: 'm-01' }
    ],
    dependenciesMap: {
        "filter": "widgets[w-table-01].filter",
        "quickFilters": "widgets[w-table-01].quickFilters",
        "layer": "widgets[w-table-01].layer",
        "options": "widgets[w-table-01].options",
        "dependenciesMap": "widgets[w-table-01].dependenciesMap",
        "mapSync": "widgets[w-table-01].mapSync"
    }
},
{
    id: 'w-table-01',
    widgetType: 'table'
}

The sample above is a map connected to a table and the dependenciesMap is an object where for each property we have a map to other widgets, in this case we can say a table because the id in the path is using w-table-01. With this approach a dependenciesMap could get properties from different widget without declaring a hierarchical structure. The widget components (enhandedWidgets) are then implemented with a set of HOC where each of them will prepare props combining them with the one coming from the dependencies:

export const MapWidget = compose(
    dependenciesToWidget,
    dependenciesToLayers,
    dependenciesToMapProp('center'),
    dependenciesToMapProp('zoom'),
    dependenciesToExtent,
    mapWidget
)(BaseMapWidget);

Note: dependenciesToWidget performs the transformation from the path to the actual value

Dependencies supported at the moment:

Refactor proposal

We could approach with a graph declaration structure:

"widgets": [
    {
        "id": "w-map-01",
        "widgetType": "map",
        "selectedMapId": "m-01",
        "maps": [ { "id": "m-01" } ]
    },
    {
        "id": "w-table-01",
        "widgetType": "table",
        "filter": {}
    },
],
"interactions": [
    { 
         "id": "interaction-01"
         "trigger": {
             "eventType": "CHANGE",
             "source": "widgets["w-map-01"].maps["w-map-01"].extent"

         },
         "transformers": [{type: "delay", 500}, {
                "type": "extentToFilter"
         }],
         "effect": {
               "target": "widgets["w-table-01"].filter", 
               "type": "UPSERT",
               "filterId": "interaction-01"
         }
    }
]

In this example of the interactions, when the extent of the map changes.

All the connected widgets as object with following properties:

In term of connection interactions we could have:

  • direct connection:
  • side effect connection, e.g. a widget update a map view that update the filter viewport and has effect on children

Each widget should define the connection interface that will be used to represent in UI possible connections:

- MapWidget
    interface: {
        "sendProps": ["BBOX", "FILTER"],
        "receiveProps": ["BBOX", "FILTER"]
    },
- TableWidget
    interface: {
        "receiveProps": ["FILTER"],
        "sendProps": ["BBOX", "FILTER"]
    },
- ChartWidget

We could define following interface type based on the current support:

  • FILTER -> we should uniform all type of filter in a single object instead to have different naming
  • BBOX -> trigger zoom to extent
  • ZOOM -> trigger zoom (not sure it's current supported)
  • LAYERS -> used by legend to list the layers
  • GROUPS -> used by legend to list the groups

Refactor requirements

  • Review current dependencies system configuration
  • Implement method to convert current configuration with the improved one
  • Refactor how widget component reconstruct their relation to other widgets (current dependenciesTo... HOC)
  • Generalize usage of the current available interaction actions, avoiding specific implementation to a widget type
  • Implementation should take into account dashboard and widgets on a map. (While refactoring we should think to map with widgets as a dashboard with a single map)

What kind of improvement you want to add? (check one with "x", remove the others)

  • Refactoring (no functional changes, no api changes)

Other useful information

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions