codeant-ai-for-open-source[bot] commented on code in PR #37516: URL: https://github.com/apache/superset/pull/37516#discussion_r3448757656
########## tests/unit_tests/common/test_query_context_processor_timing.py: ########## @@ -0,0 +1,470 @@ +# 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 unittest.mock import MagicMock, patch + +import pandas as pd +import pytest + +from superset.commands.chart.data.get_data_command import ChartDataCommand +from superset.commands.chart.exceptions import ChartDataQueryFailedError +from superset.common.chart_data import ChartDataResultType +from superset.common.chart_data_timing import ( + finalize_timing_payload, + RESULT_PROCESSING_START_KEY, + TIMING_KEY, + TIMING_START_KEY, +) +from superset.common.db_query_status import QueryStatus +from superset.common.query_actions import _get_full +from superset.common.query_context_processor import QueryContextProcessor + + [email protected] +def mock_query_obj(): Review Comment: **Suggestion:** Add complete type hints to this new fixture function, including an explicit return type for the mock object it returns. [custom_rule] **Severity Level:** Minor ⚠️ <details> <summary><b>Why it matters? 🤔 </b></summary> This new fixture function has no return type annotation, so it is not fully typed. The rule requires newly added Python functions to have type hints for return values. </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=be90300a4f44456497d64ecc64fcc5d7&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=be90300a4f44456497d64ecc64fcc5d7&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:** tests/unit_tests/common/test_query_context_processor_timing.py **Line:** 38:38 **Comment:** *Custom Rule: Add complete type hints to this new fixture function, including an explicit return type for the mock object it returns. 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%2F37516&comment_hash=990effef41706a3639d3c2410f69b5f0dac55a719f951976dc26adbacd283b05&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F37516&comment_hash=990effef41706a3639d3c2410f69b5f0dac55a719f951976dc26adbacd283b05&reaction=dislike'>👎</a> ########## tests/unit_tests/common/test_query_context_processor_timing.py: ########## @@ -0,0 +1,470 @@ +# 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 unittest.mock import MagicMock, patch + +import pandas as pd +import pytest + +from superset.commands.chart.data.get_data_command import ChartDataCommand +from superset.commands.chart.exceptions import ChartDataQueryFailedError +from superset.common.chart_data import ChartDataResultType +from superset.common.chart_data_timing import ( + finalize_timing_payload, + RESULT_PROCESSING_START_KEY, + TIMING_KEY, + TIMING_START_KEY, +) +from superset.common.db_query_status import QueryStatus +from superset.common.query_actions import _get_full +from superset.common.query_context_processor import QueryContextProcessor + + [email protected] +def mock_query_obj(): + query_obj = MagicMock() + query_obj.columns = ["col1"] + query_obj.column_names = ["col1"] + query_obj.metrics = [] + query_obj.metric_names = [] + query_obj.from_dttm = None + query_obj.to_dttm = None + query_obj.annotation_layers = [] + return query_obj + + [email protected] +def processor_with_cache(): Review Comment: **Suggestion:** Add an explicit return type annotation to this new fixture so the function signature is fully typed. [custom_rule] **Severity Level:** Minor ⚠️ <details> <summary><b>Why it matters? 🤔 </b></summary> This newly added fixture function also lacks a return type annotation. That matches the custom rule requiring new Python code to be fully typed. </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=04f46890d12f4b758feb58fb873b3a54&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=04f46890d12f4b758feb58fb873b3a54&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:** tests/unit_tests/common/test_query_context_processor_timing.py **Line:** 51:51 **Comment:** *Custom Rule: Add an explicit return type annotation to this new fixture so the function signature is fully typed. 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%2F37516&comment_hash=04450bd29458b7c0e2bfc57d77c70d50982328ab8bd7dfb1f228e0b064ff36e6&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F37516&comment_hash=04450bd29458b7c0e2bfc57d77c70d50982328ab8bd7dfb1f228e0b064ff36e6&reaction=dislike'>👎</a> ########## tests/unit_tests/common/test_query_context_processor_timing.py: ########## @@ -0,0 +1,470 @@ +# 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 unittest.mock import MagicMock, patch + +import pandas as pd +import pytest + +from superset.commands.chart.data.get_data_command import ChartDataCommand +from superset.commands.chart.exceptions import ChartDataQueryFailedError +from superset.common.chart_data import ChartDataResultType +from superset.common.chart_data_timing import ( + finalize_timing_payload, + RESULT_PROCESSING_START_KEY, + TIMING_KEY, + TIMING_START_KEY, +) +from superset.common.db_query_status import QueryStatus +from superset.common.query_actions import _get_full +from superset.common.query_context_processor import QueryContextProcessor + + [email protected] +def mock_query_obj(): + query_obj = MagicMock() + query_obj.columns = ["col1"] + query_obj.column_names = ["col1"] + query_obj.metrics = [] + query_obj.metric_names = [] + query_obj.from_dttm = None + query_obj.to_dttm = None + query_obj.annotation_layers = [] + return query_obj + + [email protected] +def processor_with_cache(): + """Create a processor with a mocked cache that returns a loaded result.""" + mock_qc = MagicMock() + mock_qc.force = False + + processor = QueryContextProcessor.__new__(QueryContextProcessor) + processor._query_context = mock_qc + processor._qc_datasource = MagicMock() + processor._qc_datasource.uid = "test_uid" + processor._qc_datasource.column_names = ["col1"] + return processor + + +def finalize_payload( + payload: dict[str, Any], + include_timing: bool = True, + slow_query_threshold_ms: int | None = None, +) -> None: + with patch("superset.common.chart_data_timing.current_app") as mock_app: + mock_app.config = { + "STATS_LOGGER": MagicMock(), + "CHART_DATA_INCLUDE_TIMING": include_timing, + "CHART_DATA_SLOW_QUERY_THRESHOLD_MS": slow_query_threshold_ms, + } + finalize_timing_payload(payload) + + +def internal_timing() -> dict[str, Any]: + return { + TIMING_START_KEY: 1.0, + RESULT_PROCESSING_START_KEY: 2.0, + "validate_ms": 1.0, + "cache_lookup_ms": 2.0, + "db_execution_ms": None, + } + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +def test_timing_present_in_payload( + mock_cache_cls, processor_with_cache, mock_query_obj +): + """Timing dict is included in finalized query result.""" + cache = MagicMock() + cache.is_loaded = True + cache.is_cached = True + cache.df = pd.DataFrame({"col1": [1, 2]}) + cache.cache_dttm = None + cache.queried_dttm = None + cache.applied_template_filters = [] + cache.applied_filter_columns = [] + cache.rejected_filter_columns = [] + cache.annotation_data = {} + cache.error_message = None + cache.query = "" + cache.status = "success" + cache.stacktrace = None + cache.sql_rowcount = 2 + mock_cache_cls.get.return_value = cache + + with patch.object(processor_with_cache, "query_cache_key", return_value="key"): + with patch.object(processor_with_cache, "get_cache_timeout", return_value=300): + result = processor_with_cache.get_df_payload(mock_query_obj) + + finalize_payload(result) + assert "timing" in result + assert TIMING_KEY not in result + timing = result["timing"] + assert "validate_ms" in timing + assert "cache_lookup_ms" in timing + assert "result_processing_ms" in timing + assert "total_ms" in timing + assert "is_cached" in timing + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +def test_timing_omitted_when_config_disabled( + mock_cache_cls, processor_with_cache, mock_query_obj +): + """Timing dict is excluded from response when CHART_DATA_INCLUDE_TIMING is False.""" + cache = MagicMock() + cache.is_loaded = True + cache.is_cached = True + cache.df = pd.DataFrame({"col1": [1]}) + cache.cache_dttm = None + cache.queried_dttm = None + cache.applied_template_filters = [] + cache.applied_filter_columns = [] + cache.rejected_filter_columns = [] + cache.annotation_data = {} + cache.error_message = None + cache.query = "" + cache.status = "success" + cache.stacktrace = None + cache.sql_rowcount = 1 + mock_cache_cls.get.return_value = cache + + with patch.object(processor_with_cache, "query_cache_key", return_value="key"): + with patch.object(processor_with_cache, "get_cache_timeout", return_value=300): + result = processor_with_cache.get_df_payload(mock_query_obj) + + finalize_payload(result, include_timing=False) + assert "timing" not in result + assert TIMING_KEY not in result + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +def test_timing_values_are_non_negative( + mock_cache_cls, processor_with_cache, mock_query_obj +): + """All timing values are non-negative numbers.""" + cache = MagicMock() + cache.is_loaded = True + cache.is_cached = True + cache.df = pd.DataFrame({"col1": [1]}) + cache.cache_dttm = None + cache.queried_dttm = None + cache.applied_template_filters = [] + cache.applied_filter_columns = [] + cache.rejected_filter_columns = [] + cache.annotation_data = {} + cache.error_message = None + cache.query = "" + cache.status = "success" + cache.stacktrace = None + cache.sql_rowcount = 1 + mock_cache_cls.get.return_value = cache + + with patch.object(processor_with_cache, "query_cache_key", return_value="key"): + with patch.object(processor_with_cache, "get_cache_timeout", return_value=300): + result = processor_with_cache.get_df_payload(mock_query_obj) + + finalize_payload(result) + timing = result["timing"] + for key, value in timing.items(): + if isinstance(value, (int, float)) and not isinstance(value, bool): + assert value >= 0, f"timing[{key!r}] should be >= 0, got {value}" + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +def test_timing_no_db_execution_on_cache_hit( + mock_cache_cls, processor_with_cache, mock_query_obj +): + """db_execution_ms is None when the result is served from cache.""" + cache = MagicMock() + cache.is_loaded = True + cache.is_cached = True + cache.df = pd.DataFrame({"col1": [1]}) + cache.cache_dttm = None + cache.queried_dttm = None + cache.applied_template_filters = [] + cache.applied_filter_columns = [] + cache.rejected_filter_columns = [] + cache.annotation_data = {} + cache.error_message = None + cache.query = "" + cache.status = "success" + cache.stacktrace = None + cache.sql_rowcount = 1 + mock_cache_cls.get.return_value = cache + + with patch.object(processor_with_cache, "query_cache_key", return_value="key"): + with patch.object(processor_with_cache, "get_cache_timeout", return_value=300): + result = processor_with_cache.get_df_payload(mock_query_obj) + + finalize_payload(result) + assert result["timing"]["db_execution_ms"] is None + assert result["timing"]["is_cached"] is True + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +def test_timing_has_db_execution_on_cache_miss( + mock_cache_cls, processor_with_cache, mock_query_obj +): + """db_execution_ms is present when the query is executed against the database.""" + cache = MagicMock() + cache.is_loaded = False + cache.is_cached = None + cache.df = pd.DataFrame({"col1": [1]}) + cache.cache_dttm = None + cache.queried_dttm = None + cache.applied_template_filters = [] + cache.applied_filter_columns = [] + cache.rejected_filter_columns = [] + cache.annotation_data = {} + cache.error_message = None + cache.query = "" + cache.status = "success" + cache.stacktrace = None + cache.sql_rowcount = 1 + mock_cache_cls.get.return_value = cache + + processor_with_cache._qc_datasource.column_names = ["col1"] + processor_with_cache.get_query_result = MagicMock() + processor_with_cache.get_annotation_data = MagicMock(return_value={}) + + with patch.object(processor_with_cache, "query_cache_key", return_value="key"): + with patch.object(processor_with_cache, "get_cache_timeout", return_value=300): + result = processor_with_cache.get_df_payload(mock_query_obj) + + finalize_payload(result) + assert "db_execution_ms" in result["timing"] + assert result["timing"]["db_execution_ms"] >= 0 + assert result["is_cached"] is None + assert result["timing"]["is_cached"] is False + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +@patch("superset.common.chart_data_timing.logger") +def test_slow_query_logging( + mock_logger, mock_cache_cls, processor_with_cache, mock_query_obj +): + """WARNING log is emitted when total_ms exceeds the configured threshold.""" + cache = MagicMock() + cache.is_loaded = True + cache.is_cached = True + cache.df = pd.DataFrame({"col1": [1]}) + cache.cache_dttm = None + cache.queried_dttm = None + cache.applied_template_filters = [] + cache.applied_filter_columns = [] + cache.rejected_filter_columns = [] + cache.annotation_data = {} + cache.error_message = None + cache.query = "" + cache.status = "success" + cache.stacktrace = None + cache.sql_rowcount = 1 + mock_cache_cls.get.return_value = cache + + with patch.object(processor_with_cache, "query_cache_key", return_value="key"): + with patch.object(processor_with_cache, "get_cache_timeout", return_value=300): + result = processor_with_cache.get_df_payload(mock_query_obj) + + # Set threshold to 0 so any query triggers slow logging. + finalize_payload(result, slow_query_threshold_ms=0) + + mock_logger.warning.assert_called_once() + call_args = mock_logger.warning.call_args[0] + assert "Slow chart query" in call_args[0] + # On cache hit, db_execution should be "cached" not a number + assert "cached" in call_args + + +def results_payload() -> dict[str, Any]: Review Comment: **Suggestion:** Add a short docstring to this new helper function to explain the payload it constructs. [custom_rule] **Severity Level:** Minor ⚠️ <details> <summary><b>Why it matters? 🤔 </b></summary> This new helper function has no docstring, and the rule explicitly requires newly added Python functions and classes to include docstrings. </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=9f8ba8cd7af44f7b968074cc61464777&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=9f8ba8cd7af44f7b968074cc61464777&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:** tests/unit_tests/common/test_query_context_processor_timing.py **Line:** 306:306 **Comment:** *Custom Rule: Add a short docstring to this new helper function to explain the payload it constructs. 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%2F37516&comment_hash=58300771d43168a61a2d2963ac2055757a184e5e0a9f10c52b1fad1dd1f8de63&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F37516&comment_hash=58300771d43168a61a2d2963ac2055757a184e5e0a9f10c52b1fad1dd1f8de63&reaction=dislike'>👎</a> ########## tests/unit_tests/common/test_query_context_processor_timing.py: ########## @@ -0,0 +1,470 @@ +# 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 unittest.mock import MagicMock, patch + +import pandas as pd +import pytest + +from superset.commands.chart.data.get_data_command import ChartDataCommand +from superset.commands.chart.exceptions import ChartDataQueryFailedError +from superset.common.chart_data import ChartDataResultType +from superset.common.chart_data_timing import ( + finalize_timing_payload, + RESULT_PROCESSING_START_KEY, + TIMING_KEY, + TIMING_START_KEY, +) +from superset.common.db_query_status import QueryStatus +from superset.common.query_actions import _get_full +from superset.common.query_context_processor import QueryContextProcessor + + [email protected] +def mock_query_obj(): + query_obj = MagicMock() + query_obj.columns = ["col1"] + query_obj.column_names = ["col1"] + query_obj.metrics = [] + query_obj.metric_names = [] + query_obj.from_dttm = None + query_obj.to_dttm = None + query_obj.annotation_layers = [] + return query_obj + + [email protected] +def processor_with_cache(): + """Create a processor with a mocked cache that returns a loaded result.""" + mock_qc = MagicMock() + mock_qc.force = False + + processor = QueryContextProcessor.__new__(QueryContextProcessor) + processor._query_context = mock_qc + processor._qc_datasource = MagicMock() + processor._qc_datasource.uid = "test_uid" + processor._qc_datasource.column_names = ["col1"] + return processor + + +def finalize_payload( + payload: dict[str, Any], + include_timing: bool = True, + slow_query_threshold_ms: int | None = None, +) -> None: + with patch("superset.common.chart_data_timing.current_app") as mock_app: + mock_app.config = { + "STATS_LOGGER": MagicMock(), + "CHART_DATA_INCLUDE_TIMING": include_timing, + "CHART_DATA_SLOW_QUERY_THRESHOLD_MS": slow_query_threshold_ms, + } + finalize_timing_payload(payload) + + +def internal_timing() -> dict[str, Any]: + return { + TIMING_START_KEY: 1.0, + RESULT_PROCESSING_START_KEY: 2.0, + "validate_ms": 1.0, + "cache_lookup_ms": 2.0, + "db_execution_ms": None, + } + + +@patch( + "superset.common.query_context_processor.QueryCacheManager", +) +def test_timing_present_in_payload( + mock_cache_cls, processor_with_cache, mock_query_obj +): Review Comment: **Suggestion:** Add type annotations for each test parameter and declare the return type as `None` to satisfy full typing requirements for newly added functions. [custom_rule] **Severity Level:** Minor ⚠️ <details> <summary><b>Why it matters? 🤔 </b></summary> This new test function omits type annotations for all parameters and does not declare a return type. That is a real violation of the typing rule for newly added Python functions. </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=3d4b2edab3cf467b803f01164372184e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=3d4b2edab3cf467b803f01164372184e&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:** tests/unit_tests/common/test_query_context_processor_timing.py **Line:** 91:93 **Comment:** *Custom Rule: Add type annotations for each test parameter and declare the return type as `None` to satisfy full typing requirements for newly added functions. 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%2F37516&comment_hash=341d38dbb109c7065b03400770f8b26d213f52ce936a515c48d128014dce393b&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F37516&comment_hash=341d38dbb109c7065b03400770f8b26d213f52ce936a515c48d128014dce393b&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]
