codeant-ai-for-open-source[bot] commented on code in PR #40443: URL: https://github.com/apache/superset/pull/40443#discussion_r3326085968
########## superset/commands/extension/settings/update.py: ########## @@ -0,0 +1,66 @@ +# 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 +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import logging +from functools import partial +from typing import Any + +from superset.commands.base import BaseCommand +from superset.commands.extension.settings.exceptions import ( + ExtensionSettingsUpdateFailedError, +) +from superset.daos.extension import ExtensionSettingsDAO +from superset.utils.decorators import on_error, transaction + +logger = logging.getLogger(__name__) + + +class UpdateExtensionSettingsCommand(BaseCommand): + """Apply a partial update to global extension admin settings. + + The payload is a dict that may contain: + * active_chatbot_id: str | None — empty string is normalised to None. + * enabled: dict[str, bool] — per-extension toggle map. Non-bool values + are silently skipped. + + Keys not present in the payload are left untouched. + """ + + def __init__(self, body: dict[str, Any]): + self._body = body or {} + + @transaction( + on_error=partial(on_error, reraise=ExtensionSettingsUpdateFailedError), + ) + def run(self) -> dict[str, Any]: + self.validate() + + if "active_chatbot_id" in self._body: + value = self._body["active_chatbot_id"] + active_chatbot_id = str(value) if isinstance(value, str) and value else None + ExtensionSettingsDAO.upsert_active_chatbot_id(active_chatbot_id) + + enabled = self._body.get("enabled") + if isinstance(enabled, dict): + for extension_id, value in enabled.items(): + if not isinstance(value, bool): + continue + ExtensionSettingsDAO.upsert_enabled_flag(extension_id, value) Review Comment: **Suggestion:** `extension_id` keys are written directly without validating column constraints, but the backing model limits `extension_id` to 250 chars. Oversized keys will fail at the database layer and surface as a generic update failure. Validate key length and type before calling DAO upsert so bad input returns a deterministic client error. [api mismatch] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ Oversized extension IDs cause 500 during settings updates. - ⚠️ Admin extension toggle requests fail unexpectedly for bad keys. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. The `ExtensionEnabled` model in `superset/extensions/models.py:32-37` defines `extension_id = Column(String(250), primary_key=True)`, constraining extension identifiers to 250 characters at the database level. 2. The PUT `/api/v1/extensions/settings` endpoint implemented in `ExtensionsRestApi.put_settings` (`superset/extensions/api.py:189-218`) reads the JSON body into `body = request.get_json(silent=True) or {}` and passes it to `UpdateExtensionSettingsCommand(body).run()`. 3. As an admin, send a PUT `/api/v1/extensions/settings` request with an `enabled` payload whose key exceeds 250 characters, for example `{"enabled": {"x"*300: true}}` (a 300-character extension ID string); `UpdateExtensionSettingsCommand.run` at `superset/commands/extension/settings/update.py:56-61` iterates over `enabled.items()`, sees the boolean value, and calls `ExtensionSettingsDAO.upsert_enabled_flag(extension_id, value)` with this oversized key. 4. Inside `ExtensionSettingsDAO.upsert_enabled_flag` in `superset/daos/extension.py:122-124`, `_upsert_extension_enabled_row` at `superset/daos/extension.py:62-92` builds a dialect-specific insert/upsert into `ExtensionEnabled.extension_id`; when the underlying database enforces the 250-character limit, the insert with a 300-character `extension_id` raises a database error (e.g., a `DataError`/`SQLAlchemyError`), which is converted by the `@transaction` decorator into an `ExtensionSettingsUpdateFailedError` and surfaces through the API as a 500-level failure instead of a deterministic client validation error about the bad key. ``` </details> [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=3d9d01984ed443c8b898e0cbbc1cda26&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=3d9d01984ed443c8b898e0cbbc1cda26&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** superset/commands/extension/settings/update.py **Line:** 58:61 **Comment:** *Api Mismatch: `extension_id` keys are written directly without validating column constraints, but the backing model limits `extension_id` to 250 chars. Oversized keys will fail at the database layer and surface as a generic update failure. Validate key length and type before calling DAO upsert so bad input returns a deterministic client error. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=fb3220b2a7687c3e574cf71db1c77e9e0c32d11eb085828beee0ca737d970e97&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=fb3220b2a7687c3e574cf71db1c77e9e0c32d11eb085828beee0ca737d970e97&reaction=dislike'>👎</a> ########## superset/daos/extension.py: ########## @@ -0,0 +1,124 @@ +# 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 +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from typing import Any + +from sqlalchemy.dialects.mysql import insert as mysql_insert +from sqlalchemy.dialects.postgresql import insert as pg_insert +from sqlalchemy.dialects.sqlite import insert as sqlite_insert + +from superset import db +from superset.extensions.models import ExtensionEnabled, ExtensionSettings + +SETTINGS_ROW_ID = 1 + + +def _upsert_extension_settings_row(active_chatbot_id: str | None) -> None: + """Dialect-aware single-statement upsert for the singleton settings row.""" + dialect = db.session.get_bind().dialect.name + if dialect == "postgresql": + stmt = ( + pg_insert(ExtensionSettings) + .values(id=SETTINGS_ROW_ID, active_chatbot_id=active_chatbot_id) + .on_conflict_do_update( + index_elements=["id"], + set_={"active_chatbot_id": active_chatbot_id}, + ) + ) + elif dialect == "sqlite": + stmt = ( + sqlite_insert(ExtensionSettings) + .values(id=SETTINGS_ROW_ID, active_chatbot_id=active_chatbot_id) + .on_conflict_do_update( + index_elements=["id"], + set_={"active_chatbot_id": active_chatbot_id}, + ) + ) + elif dialect in ("mysql", "mariadb"): + stmt = mysql_insert(ExtensionSettings).values( + id=SETTINGS_ROW_ID, active_chatbot_id=active_chatbot_id + ) + stmt = stmt.on_duplicate_key_update(active_chatbot_id=active_chatbot_id) + else: + raise NotImplementedError( + f"ExtensionSettings upsert not implemented for dialect '{dialect}'." + ) Review Comment: **Suggestion:** The DAO hard-fails with `NotImplementedError` for any SQLAlchemy dialect outside the three explicitly handled ones, which turns settings reads/writes into 500s on other supported metadata backends. Add a portable fallback upsert path (or explicit compatibility guard earlier in startup) instead of raising at runtime. [incomplete implementation] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ Unsupported metadata dialects crash extension settings writes and reads. - ⚠️ Deployments on non-core databases cannot use extension settings. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. The helper `_upsert_extension_settings_row` in `superset/daos/extension.py:29-59` determines the SQLAlchemy dialect via `dialect = db.session.get_bind().dialect.name` and only implements upsert logic for `"postgresql"`, `"sqlite"`, and `"mysql"/"mariadb"`, raising `NotImplementedError` in the `else` branch at lines 55-58 for any other dialect. 2. Configure Superset's metadata database URI to use an unsupported SQLAlchemy dialect (for example `mssql+pyodbc://...`), so that `db.session.get_bind().dialect.name` evaluates to `"mssql"` when `_upsert_extension_settings_row` and `_upsert_extension_enabled_row` execute. 3. As an admin, call the PUT `/api/v1/extensions/settings` endpoint implemented by `ExtensionsRestApi.put_settings` in `superset/extensions/api.py:189-218`; after passing the admin check and reading the JSON body, it invokes `UpdateExtensionSettingsCommand(body).run()`. 4. When `UpdateExtensionSettingsCommand.run` attempts to persist changes via `ExtensionSettingsDAO.upsert_active_chatbot_id` or `ExtensionSettingsDAO.upsert_enabled_flag` (`superset/daos/extension.py:118-124`), the helpers `_upsert_extension_settings_row` / `_upsert_extension_enabled_row` see `dialect == "mssql"` and hit the `else` branch raising `NotImplementedError`, which is not a `SQLAlchemyError`; the `@transaction` decorator re-raises this exception, and the `@safe` API wrapper turns the call into an HTTP 500 error, making extension settings writes unusable on any metadata backend whose dialect name is not exactly `"postgresql"`, `"sqlite"`, `"mysql"`, or `"mariadb"`. ``` </details> [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=057f92cced0e439a9336077212628d4e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=057f92cced0e439a9336077212628d4e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** superset/daos/extension.py **Line:** 55:58 **Comment:** *Incomplete Implementation: The DAO hard-fails with `NotImplementedError` for any SQLAlchemy dialect outside the three explicitly handled ones, which turns settings reads/writes into 500s on other supported metadata backends. Add a portable fallback upsert path (or explicit compatibility guard earlier in startup) instead of raising at runtime. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=a9fd7d52108c6e3089fb5695b54581c722b6f4c69d6e62e512cafbd2903ac0ed&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=a9fd7d52108c6e3089fb5695b54581c722b6f4c69d6e62e512cafbd2903ac0ed&reaction=dislike'>👎</a> ########## superset/commands/extension/settings/update.py: ########## @@ -0,0 +1,66 @@ +# 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 +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import logging +from functools import partial +from typing import Any + +from superset.commands.base import BaseCommand +from superset.commands.extension.settings.exceptions import ( + ExtensionSettingsUpdateFailedError, +) +from superset.daos.extension import ExtensionSettingsDAO +from superset.utils.decorators import on_error, transaction + +logger = logging.getLogger(__name__) + + +class UpdateExtensionSettingsCommand(BaseCommand): + """Apply a partial update to global extension admin settings. + + The payload is a dict that may contain: + * active_chatbot_id: str | None — empty string is normalised to None. + * enabled: dict[str, bool] — per-extension toggle map. Non-bool values + are silently skipped. + + Keys not present in the payload are left untouched. + """ + + def __init__(self, body: dict[str, Any]): + self._body = body or {} + + @transaction( + on_error=partial(on_error, reraise=ExtensionSettingsUpdateFailedError), + ) + def run(self) -> dict[str, Any]: + self.validate() + + if "active_chatbot_id" in self._body: + value = self._body["active_chatbot_id"] + active_chatbot_id = str(value) if isinstance(value, str) and value else None + ExtensionSettingsDAO.upsert_active_chatbot_id(active_chatbot_id) Review Comment: **Suggestion:** Invalid `active_chatbot_id` types are coerced to `None`, which unintentionally clears the stored setting when callers send malformed values (for example numbers) instead of rejecting them. This creates silent data loss; only `None` or non-empty strings should be accepted, and other types should fail validation. [logic error] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ Invalid chatbot IDs silently clear stored active_chatbot_id setting. - ⚠️ Admin UI shows chatbot unset despite previous valid configuration. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. The PUT settings endpoint `/api/v1/extensions/settings` calls `UpdateExtensionSettingsCommand.run` from `ExtensionsRestApi.put_settings` at `superset/extensions/api.py:189-218`, passing `body = request.get_json(silent=True) or {}` into the command. 2. First, set a valid chatbot ID by calling PUT `/api/v1/extensions/settings` as an admin with JSON `{"active_chatbot_id": "acme.chatbot"}`; `UpdateExtensionSettingsCommand.run` at `superset/commands/extension/settings/update.py:51-54` writes `"acme.chatbot"` into `ExtensionSettings.active_chatbot_id` via `ExtensionSettingsDAO.upsert_active_chatbot_id`. 3. Next, as the same admin, send another PUT `/api/v1/extensions/settings` with an invalid type for the same field, for example `{"active_chatbot_id": 123}` (integer instead of string/null), so `self._body["active_chatbot_id"]` at line 52 is `123`. 4. In `UpdateExtensionSettingsCommand.run`, the line `active_chatbot_id = str(value) if isinstance(value, str) and value else None` at `superset/commands/extension/settings/update.py:53` evaluates to `None` because `value` is not a `str`, and `ExtensionSettingsDAO.upsert_active_chatbot_id(active_chatbot_id)` at line 54 writes `NULL` into the `ExtensionSettings.active_chatbot_id` column (`superset/extensions/models.py:29`), silently clearing the previously stored chatbot ID; a subsequent GET `/api/v1/extensions/settings` via `ExtensionsRestApi.get_settings` (`superset/extensions/api.py:175-185`) shows `"active_chatbot_id": null` even though the client sent malformed data. ``` </details> [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=9a2200faa99144ccb4748db12b5d1676&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=9a2200faa99144ccb4748db12b5d1676&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** superset/commands/extension/settings/update.py **Line:** 53:54 **Comment:** *Logic Error: Invalid `active_chatbot_id` types are coerced to `None`, which unintentionally clears the stored setting when callers send malformed values (for example numbers) instead of rejecting them. This creates silent data loss; only `None` or non-empty strings should be accepted, and other types should fail validation. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=07f65fce726c942b220b38763d9cd5eeae8766f317a5116d7de0cd4906057608&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=07f65fce726c942b220b38763d9cd5eeae8766f317a5116d7de0cd4906057608&reaction=dislike'>👎</a> ########## superset/commands/extension/settings/update.py: ########## @@ -0,0 +1,66 @@ +# 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 +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import logging +from functools import partial +from typing import Any + +from superset.commands.base import BaseCommand +from superset.commands.extension.settings.exceptions import ( + ExtensionSettingsUpdateFailedError, +) +from superset.daos.extension import ExtensionSettingsDAO +from superset.utils.decorators import on_error, transaction + +logger = logging.getLogger(__name__) + + +class UpdateExtensionSettingsCommand(BaseCommand): + """Apply a partial update to global extension admin settings. + + The payload is a dict that may contain: + * active_chatbot_id: str | None — empty string is normalised to None. + * enabled: dict[str, bool] — per-extension toggle map. Non-bool values + are silently skipped. + + Keys not present in the payload are left untouched. + """ + + def __init__(self, body: dict[str, Any]): + self._body = body or {} + + @transaction( + on_error=partial(on_error, reraise=ExtensionSettingsUpdateFailedError), + ) + def run(self) -> dict[str, Any]: + self.validate() + + if "active_chatbot_id" in self._body: + value = self._body["active_chatbot_id"] + active_chatbot_id = str(value) if isinstance(value, str) and value else None + ExtensionSettingsDAO.upsert_active_chatbot_id(active_chatbot_id) + + enabled = self._body.get("enabled") Review Comment: **Suggestion:** The command assumes `self._body` is always a mapping, but API payloads can be valid JSON arrays/scalars. When that happens, calling `.get(...)` on a non-dict raises runtime errors and returns a 500 instead of a client validation error. Add a strict type check in `validate()` (or constructor) to reject non-object payloads with 400/422 before `run()` logic executes. [type error] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ PUT /api/v1/extensions/settings returns 500 for invalid bodies. - ⚠️ Admins receive generic server error instead client validation. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. The PUT settings endpoint `/api/v1/extensions/settings` is implemented by `ExtensionsRestApi.put_settings` in `superset/extensions/api.py:189-218`, which reads the request body with `body = request.get_json(silent=True) or {}` and passes it to `UpdateExtensionSettingsCommand(body).run()`. 2. Authenticate as an admin (so `security_manager.is_admin()` at `superset/extensions/api.py:214` returns True) and send an HTTP PUT to `/api/v1/extensions/settings` with `Content-Type: application/json` and a top-level JSON array body, for example `["invalid"]`. 3. Flask's `request.get_json(silent=True)` returns the Python list `["invalid"]`, which is truthy, so `body` becomes a list; `UpdateExtensionSettingsCommand.__init__` at `superset/commands/extension/settings/update.py:42-43` sets `self._body = body or {}`, so `self._body` is this list, not a dict. 4. When `UpdateExtensionSettingsCommand.run` executes at `superset/commands/extension/settings/update.py:48-63`, it reaches `enabled = self._body.get("enabled")` at line 56; since `self._body` is a list, this raises `AttributeError: 'list' object has no attribute 'get'`, which propagates through the `@transaction` decorator (not a `SQLAlchemyError`) and is converted by the `@safe` API decorator into an HTTP 500 response instead of a 4xx validation error for an invalid request body. ``` </details> [Fix in Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=300be47ff94b45b180cbbcb10bff0677&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) | [Fix in VSCode Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=300be47ff94b45b180cbbcb10bff0677&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** superset/commands/extension/settings/update.py **Line:** 56:56 **Comment:** *Type Error: The command assumes `self._body` is always a mapping, but API payloads can be valid JSON arrays/scalars. When that happens, calling `.get(...)` on a non-dict raises runtime errors and returns a 500 instead of a client validation error. Add a strict type check in `validate()` (or constructor) to reject non-object payloads with 400/422 before `run()` logic executes. Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix ``` </details> <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=2a0592772be75fa146c6c3074cdba3e52bc1c4a6c67b85c3a27ce961b7a65120&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40443&comment_hash=2a0592772be75fa146c6c3074cdba3e52bc1c4a6c67b85c3a27ce961b7a65120&reaction=dislike'>👎</a> -- 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]
