sancho11 opened a new issue, #42788:
URL: https://github.com/apache/superset/issues/42788

   ### Bug description
   
   ## Bug description
   
   A deck.gl "Multiple Layers" (`deck_multi`) chart renders only the base map, 
with none of its configured layers, even though every one of those layers 
renders correctly as a standalone chart.
   
   There are two independent root causes behind that single symptom. I'm filing 
them together because they live in the same code path: the way `deck_multi` 
loads its sub-layers; and a single PR can address both. Happy to split them if 
maintainers prefer.
   
   The shared context: `deck_multi` is the only deck.gl chart still declared 
with `useLegacyApi: true` 
([`Multi/index.ts`](https://github.com/apache/superset/blob/master/superset-frontend/plugins/preset-chart-deckgl/src/Multi/index.ts)).
 Every other layer type (`deck_scatter`, `deck_polygon`, `deck_path`, …) goes 
through `/api/v1/chart/data`. That is exactly why the layers work standalone 
and fail here: only `deck_multi` still exercises the legacy `explore_json` + 
`viz.py` path.
   
   Both causes below are therefore concrete symptoms of the migration tracked 
in #41047 ("Make the Deck.GL plugin non-legacy"), whose first item is migrating 
the plugins from the legacy API to V1. Completing that would remove both by 
construction. This report is about what breaks in the meantime, since the 
failure is silent and hard to diagnose.
   
   ---
   
   ### Cause 1: sub-layer requests are incompatible with `GLOBAL_ASYNC_QUERIES`
   
   To draw its sub-layers, `Multi.tsx` fetches each one from 
`/superset/explore_json/` with a bare `SupersetClient.get`:
   
   
https://github.com/apache/superset/blob/master/superset-frontend/plugins/preset-chart-deckgl/src/Multi/Multi.tsx#L342-L344
   
   ```ts
   SupersetClient.get({ endpoint: url })
     .then(({ json }) => {
       const layer = createLayerFromData(subsliceCopy, json);
   ```
   
   That request bypasses the chart pipeline, so it has no access to the 
async-query machinery in `src/middleware/asyncEvent.ts`, a Redux middleware, 
not reachable from a plugin.
   
   Meanwhile `explore_json` short-circuits into the async path:
   
   
https://github.com/apache/superset/blob/master/superset/views/core.py#L361-L392
   
   ```python
   if (
       is_feature_enabled("GLOBAL_ASYNC_QUERIES")
       and response_type == ChartDataResultFormat.JSON
   ):
       # First, look for the chart query results in the cache.
       with contextlib.suppress(CacheLoadError):
           viz_obj = get_viz(..., force_cached=True, force=force)
           payload = viz_obj.get_payload()
           if payload is not None:
               return self.send_data_payload_response(viz_obj, payload)
       # Otherwise, kick off a background job to run the chart query.
       ...
       return json_success(json.dumps(job_metadata), status=202)
   ```
   
   So each sub-layer request returns either **200 with the payload**, when the 
result was already cached, or **202 with `{"channel_id": ..., "job_id": ..., 
"status": "pending", "result_url": null}`** and no `data` at all.
   
   `Multi.tsx` cannot consume the 202 handoff. It passes the job-metadata body 
straight to the layer generator, which fails on the missing key, and the 
`.catch` in `loadSingleLayer` swallows it to the console. The layer simply 
never appears.
   
   This makes the bug look random: a layer renders if and only if its result 
happens to be warm in the data cache. Typically one or two layers show up on a 
second visit to the chart and everything else vanishes. On a dashboard, where 
the layer queries carry dashboard filters and are usually cold, nothing renders 
at all.
   
   Worth noting that routing these queries through the async path buys nothing: 
building the parent chart's payload already runs every sub-layer query 
synchronously, since `DeckGLMultiLayer.get_data()` calls 
`viz_instance.get_payload()` for each slice. The work is already done by the 
time the browser issues these requests.
   
   ---
   
   ### Cause 2: a non-string `line_column` raises, and takes the whole chart 
with it
   
   `DeckPathViz.get_properties` assumes the driver returned a JSON string:
   
   https://github.com/apache/superset/blob/master/superset/viz.py#L2369
   
   ```python
   line_type = self.form_data["line_type"]
   deser = self.deser_map[line_type]
   line_column = self.form_data["line_column"]
   path = deser(data[line_column])
   ```
   
   With `line_type: "json"`, `deser` is `json.loads`. psycopg2 returns a 
`jsonb` column already parsed into a `list`/`dict`, and a `bytea` column as a 
`memoryview`. `simplejson.loads` rejects anything that is not `str` with a 
single, misleading message:
   
   ```python
   if _PY3 and not isinstance(s, str):
       raise TypeError("Input string must be text, not bytes")
   ```
   
   The message says "bytes", but the value is usually a `list`; which sent me 
looking for an encoding problem that did not exist. `None` (a NULL row) and an 
unrecognized `line_type` such as `zipcode` (absent from `deser_map`, so 
`KeyError`) fail here too.
   
   The client-side transform for the same layer already handles every one of 
these shapes, including GeoJSON `Polygon` / `MultiPolygon` / `Feature`:
   
   
https://github.com/apache/superset/blob/master/superset-frontend/plugins/preset-chart-deckgl/src/layers/Polygon/transformProps.ts
   
   ```ts
   const parsed =
     typeof rawPolygonData === 'string'
       ? JSON.parse(rawPolygonData)
       : rawPolygonData;
   ```
   
   On top of that, `DeckGLMultiLayer.get_data()` builds every sub-slice payload 
with no guard:
   
   https://github.com/apache/superset/blob/master/superset/viz.py#L1758
   
   ```python
   viz_instance = viz_class(datasource=slc.datasource, form_data=form_data)
   payload = viz_instance.get_payload()
   ```
   
   So the exception propagates out of the parent chart's request. The client 
gets a 500 and no `slices` at all, and the chart collapses to a bare base map; 
including the layers that were perfectly fine. One misconfigured or 
unusually-typed layer takes out everything.
   
   ---
   
   ### Reproduction steps
   
   **For cause 1:**
   
   1. Enable `GLOBAL_ASYNC_QUERIES` (with the Celery worker and cache backend 
configured).
   2. Create two or more deck.gl layer charts (e.g. a Scatter and an Arc) and 
confirm each renders on its own.
   3. Create a deck.gl "Multiple Layers" chart including both.
   4. Open it in Explore, or add it to a dashboard and load the dashboard.
   5. Open DevTools → Network and filter on `explore_json`.
   
   **For cause 2:**
   
   1. On PostgreSQL, create a table with a `jsonb` column holding polygon 
coordinates, e.g. `[[lon, lat], [lon, lat], ...]`.
   2. Create a deck.gl Polygon chart on it, with `line_column` set to that 
column and `line_type` = `json`. It renders correctly.
   3. Create a deck.gl "Multiple Layers" chart including that Polygon layer.
   4. Open the multi-layer chart.
   
   ### Expected results
   
   The multi-layer chart renders all its configured sub-layers, matching what 
each standalone layer chart shows.
   
   ### Actual results
   
   Only the base map renders.
   
   **Cause 1:** the `explore_json` requests for the missing sub-layers return 
`202` with job metadata and no `data`. Per missing layer, the browser console 
shows:
   
   ```
   Error loading layer for slice <id>: TypeError: can't access property 
"features", data is undefined
   ```
   
   Superset's logs show the cache probe failing for each sub-layer that did not 
render:
   
   ```
   WARNING:superset.viz:force_cached (viz.py): value not found for cache key 
cc248d487cd0963e9ca42faad689b33841110781d6f7dc1534a62915fa99cf25
   WARNING:superset.viz:force_cached (viz.py): value not found for cache key 
7617950abf012ed1478ec3580e2d9ca37d6b3da8b97cc13b4bb6d38e1c8971ce
   ```
   
   and, for the one that did:
   
   ```
   INFO:superset.viz:Serving from cache
   ```
   
   **Cause 2:** HTTP 500 and `Data error: Input string must be text, not 
bytes`, with the stacktrace below.
   
   ### Screenshots/recordings
   
   ## Cause 1: sub-layer requests are incompatible with GLOBAL_ASYNC_QUERIES
   <img width="979" height="519" alt="Image" 
src="https://github.com/user-attachments/assets/caf60b82-de49-452d-b995-c4fdac883026";
 />
   At this second image we should see both the arc layer and a heatmap layer 
but it did not render.
   <img width="964" height="665" alt="Image" 
src="https://github.com/user-attachments/assets/2fc63a5b-96cf-4635-8e9d-923960eefd18";
 />
   
   ## Cause 2: a non-string line_column raises, and takes the whole chart with 
it
   <img width="1162" height="519" alt="Image" 
src="https://github.com/user-attachments/assets/28360bc5-787d-4369-8a9a-a95f08a805b8";
 />
   
   
   ### Superset version
   
   master / latest-dev
   
   ### Python version
   
   3.11
   
   ### Node version
   
   18 or greater
   
   ### Browser
   
   Firefox
   
   ### Additional context
   
   **Feature flags:** `GLOBAL_ASYNC_QUERIES` enabled. Disabling it makes cause 
1 disappear entirely, which confirms that diagnosis.
   
   **Environment:** Docker, gunicorn sync workers, Redis cache + Celery, 
PostgreSQL/TimescaleDB analytics database via psycopg2.
   
   Stacktrace for cause 2:
   
   ```
   ERROR:superset.views.error_handling:Input string must be text, not bytes
   Traceback (most recent call last):
     File "/app/superset/views/error_handling.py", line 125, in wraps
       return f(self, *args, **kwargs)
     File "/app/superset/views/core.py", line 374, in explore_json
       payload = viz_obj.get_payload()
     File "/app/superset/viz.py", line 1793, in get_payload
       payload = super().get_payload(query_obj)
     File "/app/superset/viz.py", line 522, in get_payload
       payload["data"] = self.get_data(df)
     File "/app/superset/viz.py", line 1758, in get_data
       payload = viz_instance.get_payload()
     File "/app/superset/viz.py", line 522, in get_payload
       payload["data"] = self.get_data(df)
     File "/app/superset/viz.py", line 2388, in get_data
       return super().get_data(df)
     File "/app/superset/viz.py", line 2103, in get_data
       feature = self.get_properties(data)
     File "/app/superset/viz.py", line 2446, in get_properties
       super().get_properties(data)
     File "/app/superset/viz.py", line 2376, in get_properties
       path = deser(data[line_column])
     File "/app/superset/utils/json.py", line 256, in loads
       return simplejson.loads(
     File "/app/.venv/lib/python3.11/site-packages/simplejson/decoder.py", line 
418, in raw_decode
       raise TypeError("Input string must be text, not bytes")
   TypeError: Input string must be text, not bytes
   ```
   
   The two `viz.py:522` frames show the nesting: the outer one is the 
`deck_multi` payload, the inner one the polygon sub-slice whose failure 
propagates all the way out.
   
   ### Suggested fixes
   
   **For cause 1**, roughly in order of how well each addresses the underlying 
design:
   
   1. **Serve the sub-layer data from the parent payload.** 
`DeckGLMultiLayer.get_data()` already builds every sub-slice payload; it merges 
the features by `viz_type` rather than keying them by `slice_id`. Emitting them 
per slice would let `Multi.tsx` drop the N extra requests entirely, and the 
parent chart request already handles `GLOBAL_ASYNC_QUERIES` correctly because 
it goes through the normal chart pipeline. The catch is that `Multi.tsx` 
applies its own `layerFilterScope` logic to `adhoc_filters` in Explore mode, 
which `_apply_layer_filtering` on the backend does not replicate identically, 
so the filter semantics would need reconciling first.
   2. **Have `Multi.tsx` mark its sub-layer requests and let `explore_json` 
answer them synchronously.** Small and low-risk, and it adds no query load for 
the reason described above; the parent request has already populated the cache 
these requests read. This is what I'm running locally.
   3. **Teach `Multi.tsx` the async protocol.** This would mean duplicating the 
`asyncEvent` middleware inside a plugin, including sharing the global event 
stream, which seems clearly wrong.
   
   **For cause 2:**
   
   4. Normalize the `line_column` value in `DeckPathViz.get_properties` the way 
the client-side transform already does; accept `str`, 
`bytes`/`bytearray`/`memoryview`, an already-parsed `list`, and GeoJSON 
geometry/Feature mappings, and return an empty path for NULL instead of 
raising. Falling back to an empty path for an unrecognized `line_type` would 
also remove the `KeyError` on `zipcode`.
   5. Wrap the per-slice `get_payload()` in `DeckGLMultiLayer.get_data()` so a 
sub-slice that cannot be rendered is logged and skipped, and the remaining 
layers still render.
   
   **Independently of both**, `Multi.tsx` should not treat a payload without 
`data` as a valid layer payload. `BaseViz.get_payload` only sets 
`payload["data"]` when the query succeeded, so a failed sub-slice also answers 
200 with the reason in `payload["errors"]` and no `data` key. Checking for that 
and reporting `errors[0].message` turns an opaque `TypeError` into the actual 
cause; during this investigation it was exactly what masked cause 2 behind a 
misleading frontend error.
   
   I have all of the above running locally against the reproductions described 
here, and I'm happy to open a PR for whichever direction maintainers prefer.
   
   ### Related issues
   
   I searched the tracker and did not find either cause reported. Adjacent but 
distinct:
   
   - #41047 (open): "Make the Deck.GL plugin non-legacy". The umbrella 
migration; both causes here stem from `deck_multi` still being legacy, so 
finishing it would resolve them. If that work is imminent, this report may be 
most useful as a set of test cases.
   - #32642 (open): `deck_multi` does not refresh after the underlying dataset 
changes. Different failure mode (stale data rather than no data).
   - #36779 (closed): deck.gl Polygon hover error in 6.0.0. Client-side tooltip 
path, unrelated to the server-side `line_column` deserialization described here.
   
   ### Checklist
   
   - [x] I have searched Superset docs and Slack and didn't find a solution to 
my problem.
   - [x] I have searched the GitHub issue tracker and didn't find a similar bug 
report.
   - [x] I have checked Superset's logs for errors and if I found a relevant 
Python stacktrace, I included it here as text in the "additional context" 
section.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to