codeant-ai-for-open-source[bot] commented on code in PR #40399:
URL: https://github.com/apache/superset/pull/40399#discussion_r3422505265
##########
superset/mcp_service/dashboard/tool/generate_dashboard.py:
##########
@@ -276,27 +288,32 @@ def generate_dashboard( # noqa: C901
from superset.models.dashboard import Dashboard
with
event_logger.log_context(action="mcp.generate_dashboard.db_write"):
- json_metadata = json.dumps(
- {
- "filter_scopes": {},
- "expanded_slices": {},
- "refresh_frequency": 0,
- "timed_refresh_immune_slices": [],
- "color_scheme": None,
- "label_colors": {},
- "shared_label_colors": {},
- "color_scheme_domain": [],
- "cross_filters_enabled": False,
- "native_filter_configuration": [],
- "global_chart_configuration": {
- "scope": {
- "rootPath": ["ROOT_ID"],
- "excluded": [],
- }
- },
- "chart_configuration": {},
- }
- )
+ # Build the default json_metadata that every new dashboard gets.
+ # When the caller supplied json_metadata_overrides, merge those
+ # in shallowly so the LLM can override label_colors / color_scheme
+ # / cross_filters_enabled without re-specifying the whole shape.
+ json_metadata_dict: Dict[str, Any] = {
+ "filter_scopes": {},
+ "expanded_slices": {},
+ "refresh_frequency": 0,
+ "timed_refresh_immune_slices": [],
+ "color_scheme": None,
+ "label_colors": {},
+ "shared_label_colors": {},
Review Comment:
**Suggestion:** `shared_label_colors` is initialized as an object, but
Superset dashboard metadata expects it to be a list/array. Persisting the wrong
type creates schema drift and can trigger downstream normalization/reset
behavior in dashboard metadata handling. Initialize it with a list (`[]`) to
match the expected metadata contract. [type error]
<details>
<summary><b>Severity Level:</b> Major ⚠️</summary>
```mdx
- ⚠️ MCP-created dashboards persist shared_label_colors as dict, not list.
- ⚠️ Schema drift from DashboardDAO and tests expecting list type.
- ⚠️ Future color-configuration updates may see inconsistent metadata.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. The `generate_dashboard` tool builds default dashboard metadata in
`json_metadata_dict`
at `superset/mcp_service/dashboard/tool/generate_dashboard.py:291-313`,
initializing
`"label_colors": {}` and `"shared_label_colors": {}` before dumping to JSON
(`json_metadata = json.dumps(json_metadata_dict)` at
`generate_dashboard.py:316`).
2. A caller invokes `generate_dashboard` (entry at
`generate_dashboard.py:195-212`)
without providing `json_metadata_overrides` (so the defaults are used). The
resulting
dashboard row is committed with `dashboard.json_metadata` containing
`"shared_label_colors": {}` (`generate_dashboard.py:319-323`).
3. Elsewhere in the codebase, dashboard JSON metadata consistently treats
`shared_label_colors` as a list/array, not a dict: `DashboardDAO` writes
`md["shared_label_colors"] = data.get("shared_label_colors", [])` at
`superset/daos/dashboard.py:16-18`; test fixtures and APIs expect
`shared_label_colors` to
be `[]` or a list (e.g. `tests/integration_tests/dashboards/api_tests.py:86`
uses
`"shared_label_colors": []` in json_metadata,
`tests/unit_tests/fixtures/assets_configs.py:179,257` and
`tests/unit_tests/security/manager_test.py:944` all use `[]`).
4. The MCP schema documentation for
`json_metadata_overrides.shared_label_colors` at
`superset/mcp_service/dashboard/schemas.py:649-654` describes it as
"shared_label_colors
(list of label names for cross-chart color consistency)", reinforcing the
list contract.
Thus, every dashboard created via `generate_dashboard` with no explicit
override persists
`shared_label_colors` as `{}` instead of the list type expected by DAO logic
and tests,
creating schema drift between MCP-created dashboards and dashboards managed
through the
core REST/API/DAO paths.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=03596e74ff614946ae36c471400daac8&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=03596e74ff614946ae36c471400daac8&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/mcp_service/dashboard/tool/generate_dashboard.py
**Line:** 301:302
**Comment:**
*Type Error: `shared_label_colors` is initialized as an object, but
Superset dashboard metadata expects it to be a list/array. Persisting the wrong
type creates schema drift and can trigger downstream normalization/reset
behavior in dashboard metadata handling. Initialize it with a list (`[]`) to
match the expected metadata contract.
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%2F40399&comment_hash=650ae455b84965cc7441b66c19e9da3df827d642a122fa68660acd453c8654f1&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40399&comment_hash=650ae455b84965cc7441b66c19e9da3df827d642a122fa68660acd453c8654f1&reaction=dislike'>👎</a>
##########
superset/mcp_service/dashboard/tool/generate_dashboard.py:
##########
@@ -253,9 +260,14 @@ def generate_dashboard( # noqa: C901
),
)
- # Create dashboard layout with chart objects
+ # Create dashboard layout with chart objects.
+ # If the caller provided an explicit position_json, use it verbatim;
+ # otherwise auto-generate a packed-grid layout from the chart ids.
with event_logger.log_context(action="mcp.generate_dashboard.layout"):
- layout = _create_dashboard_layout(chart_objects)
+ if request.position_json:
+ layout = request.position_json
+ else:
+ layout = _create_dashboard_layout(chart_objects)
Review Comment:
**Suggestion:** The explicit-layout branch uses a truthiness check, so
passing an empty dict for `position_json` is treated as "not provided" and
silently replaced by auto-layout. This breaks the documented "when set, use
caller layout verbatim" contract. Check `is not None` instead, so explicitly
provided empty/partial dicts are handled as caller intent. [incorrect condition
logic]
<details>
<summary><b>Severity Level:</b> Major ⚠️</summary>
```mdx
- ⚠️ MCP generate_dashboard ignores explicitly provided empty layout dict.
- ⚠️ Behavior contradicts GenerateDashboardRequest.position_json
documentation.
- ⚠️ Callers cannot intentionally create dashboards with empty layouts.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. The MCP entrypoint `generate_dashboard` is defined at
`superset/mcp_service/dashboard/tool/generate_dashboard.py:195-212` and
takes a
`GenerateDashboardRequest` (schema at
`superset/mcp_service/dashboard/schemas.py:605-676`)
with optional `position_json: Dict[str, Any] | None`, documented as: "When
set, replaces
the auto-generated layout entirely." (`schemas.py:634-642`).
2. A caller (LLM via MCP) invokes `generate_dashboard` with a payload that
includes an
explicit but empty layout, e.g. `position_json={}` along with valid
`chart_ids`, so that
the field is *present* but falsey. This request is parsed into
`GenerateDashboardRequest.position_json` as an empty dict (per Pydantic
typing).
3. Inside `generate_dashboard`, the layout selection block at
`generate_dashboard.py:263-270` executes: `if request.position_json: layout =
request.position_json else: layout =
_create_dashboard_layout(chart_objects)`. Because
`request.position_json` is `{}`, the truthiness check fails and
`_create_dashboard_layout(chart_objects)` is called, silently discarding the
caller-provided layout.
4. The resulting dashboard is saved with the auto-generated grid layout
(`dashboard.position_json` at `generate_dashboard.py:321-322`) even though
`position_json`
was supplied. This behavior contradicts the documented contract in
`GenerateDashboardRequest.position_json` ("When set, replaces the
auto-generated layout
entirely" at `schemas.py:637-641`), so any caller relying on "presence"
semantics for
`position_json` cannot obtain an intentionally empty or minimal layout.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=742981291e714323ac24f9818589381c&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=742981291e714323ac24f9818589381c&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/mcp_service/dashboard/tool/generate_dashboard.py
**Line:** 267:270
**Comment:**
*Incorrect Condition Logic: The explicit-layout branch uses a
truthiness check, so passing an empty dict for `position_json` is treated as
"not provided" and silently replaced by auto-layout. This breaks the documented
"when set, use caller layout verbatim" contract. Check `is not None` instead,
so explicitly provided empty/partial dicts are handled as caller intent.
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%2F40399&comment_hash=cb0237f8ce20d1408e9093fba8921fc8c0c4c24473efe57f83cdd3e1464cda59&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40399&comment_hash=cb0237f8ce20d1408e9093fba8921fc8c0c4c24473efe57f83cdd3e1464cda59&reaction=dislike'>👎</a>
##########
superset/mcp_service/dashboard/tool/generate_dashboard.py:
##########
@@ -363,6 +423,15 @@ def generate_dashboard( # noqa: C901
error="Failed to create dashboard due to a database
error.",
)
+ # ``dashboard.id`` is fixed at create-commit time; the post-commit
+ # re-fetch below either returns the same row or fails — in either
+ # outcome the URL doesn't change. Bind it once so the three
+ # downstream consumers (partial response, DashboardInfo.url,
+ # response.dashboard_url) share a single source.
+ dashboard_url = (
+ f"{get_superset_base_url()}/superset/dashboard/{dashboard.id}/"
+ )
Review Comment:
**Suggestion:** The returned dashboard URL is always built with the numeric
ID, even when a slug was successfully set on creation. This makes the response
inconsistent with dashboard URL semantics used elsewhere (slug preferred when
present) and with this PR's slug behavior. Build the URL with `dashboard.slug
or dashboard.id` so callers get the canonical route. [api mismatch]
<details>
<summary><b>Severity Level:</b> Major ⚠️</summary>
```mdx
- ⚠️ generate_dashboard returns ID URL even when slug is set.
- ⚠️ Inconsistent with slug-or-id URL pattern in schemas.serializer.
- ⚠️ Callers may use non-canonical URLs for new slugged dashboards.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. The request schema `GenerateDashboardRequest` in
`superset/mcp_service/dashboard/schemas.py:605-633` defines an optional
`slug` with the
documented behavior: "When set, the dashboard is reachable at
/superset/dashboard/<slug>/
instead of /superset/dashboard/<id>/." (`schemas.py:625-632`).
2. A caller invokes the MCP `generate_dashboard` tool
(`superset/mcp_service/dashboard/tool/generate_dashboard.py:195-212`) with a
payload that
includes `slug="q4-exec-review"` and valid `chart_ids`. Inside the tool, the
slug is
written to the model via `dashboard.slug = request.slug` at
`generate_dashboard.py:328-329`.
3. After committing the new dashboard, the tool constructs `dashboard_url` at
`generate_dashboard.py:431-433` as
`f"{get_superset_base_url()}/superset/dashboard/{dashboard.id}/"`,
unconditionally using
the numeric ID and ignoring any slug that was just set.
4. Other serializers in the MCP layer build canonical dashboard URLs using
`slug or id`:
`serialize_dashboard_object` in
`superset/mcp_service/dashboard/schemas.py:1408-1416`
computes `dashboard_url =
f"{get_superset_base_url()}/superset/dashboard/{slug or
dashboard_id}/"`. As a result, for a slugged dashboard, `generate_dashboard`
returns an
ID-based URL while `get_dashboard_info` (via
`dashboard_serializer`/`serialize_dashboard_object`) returns a slug-based
URL, leading to
inconsistent URL semantics despite the documented "slug preferred" behavior.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=7962efc49f37495d9806c865dd8dc355&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=7962efc49f37495d9806c865dd8dc355&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/mcp_service/dashboard/tool/generate_dashboard.py
**Line:** 431:433
**Comment:**
*Api Mismatch: The returned dashboard URL is always built with the
numeric ID, even when a slug was successfully set on creation. This makes the
response inconsistent with dashboard URL semantics used elsewhere (slug
preferred when present) and with this PR's slug behavior. Build the URL with
`dashboard.slug or dashboard.id` so callers get the canonical route.
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%2F40399&comment_hash=0ad321fffc7fb70c6a0a01f35674497897d60c57fe9e837816b8b46feebe6df1&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40399&comment_hash=0ad321fffc7fb70c6a0a01f35674497897d60c57fe9e837816b8b46feebe6df1&reaction=dislike'>👎</a>
##########
tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py:
##########
@@ -461,6 +461,117 @@ async def test_generate_dashboard_creation_failure(
# rollback called by tool + event_logger error handling
assert mock_db_session.rollback.call_count >= 1
+ @patch("superset.models.dashboard.Dashboard")
+ @patch("superset.db.session")
+ @pytest.mark.asyncio
+ async def test_generate_dashboard_duplicate_slug_returns_actionable_error(
+ self,
+ mock_db_session,
+ mock_dashboard_cls,
+ mcp_server,
+ ) -> None:
+ """When the supplied slug collides with an existing dashboard,
+ ``commit()`` raises ``IntegrityError``. The tool catches it,
+ recognises the slug-uniqueness violation, and returns a
+ structured error naming the offending slug so the LLM can
+ propose a different one — instead of the generic
+ "internal error" message used for other DB failures."""
+ from sqlalchemy.exc import IntegrityError
+
+ mock_query = Mock()
+ mock_filter = Mock()
+ mock_query.filter.return_value = mock_filter
+ mock_query.filter_by.return_value = mock_filter
+ mock_filter.order_by.return_value = mock_filter
+ mock_filter.all.return_value = [_mock_chart(id=1)]
+ mock_filter.first.return_value = Mock(
+ id=1,
+ username="admin",
+ first_name="Admin",
+ last_name="User",
+ email="[email protected]",
+ active=True,
+ )
+ mock_db_session.query.return_value = mock_query
+ # Postgres-shaped IntegrityError naming the slug constraint.
+ mock_db_session.commit.side_effect = IntegrityError(
+ statement="INSERT INTO dashboards ...",
+ params={},
+ orig=Exception(
+ 'duplicate key value violates unique constraint '
+ '"dashboards_slug_key"\n'
+ 'DETAIL: Key (slug)=(my-slug) already exists.'
+ ),
+ )
+ mock_dashboard_cls.return_value = _mock_dashboard(id=100)
+
+ request = {
+ "chart_ids": [1],
+ "dashboard_title": "Q4 Review",
+ "slug": "my-slug",
+ }
+
+ async with Client(mcp_server) as client:
+ result = await client.call_tool(
+ "generate_dashboard", {"request": request}
+ )
+
+ err = result.structured_content["error"]
+ assert err is not None
+ assert "my-slug" in err
+ assert "already in use" in err
+ assert "different slug" in err or "Choose a different" in err
+ assert result.structured_content["dashboard"] is None
+ assert mock_db_session.rollback.call_count >= 1
+
+ @patch("superset.models.dashboard.Dashboard")
+ @patch("superset.db.session")
+ @pytest.mark.asyncio
+ async def test_generate_dashboard_integrity_error_unrelated_to_slug(
+ self,
+ mock_db_session,
+ mock_dashboard_cls,
+ mcp_server,
+ ) -> None:
+ """An IntegrityError that is NOT about the slug (e.g. an FK
+ violation on chart_id) gets the generic constraint message
+ rather than the slug-specific one — slug detection must not
+ match every IntegrityError indiscriminately."""
+ from sqlalchemy.exc import IntegrityError
+
+ mock_query = Mock()
+ mock_filter = Mock()
+ mock_query.filter.return_value = mock_filter
+ mock_query.filter_by.return_value = mock_filter
+ mock_filter.order_by.return_value = mock_filter
+ mock_filter.all.return_value = [_mock_chart(id=1)]
+ mock_filter.first.return_value = Mock(
+ id=1, username="admin", first_name="Admin",
+ last_name="User", email="[email protected]", active=True,
+ )
+ mock_db_session.query.return_value = mock_query
+ mock_db_session.commit.side_effect = IntegrityError(
+ statement="INSERT INTO dashboard_slices ...",
+ params={},
+ orig=Exception(
+ 'violates foreign key constraint
"dashboard_slices_slice_id_fkey"'
+ ),
+ )
+ mock_dashboard_cls.return_value = _mock_dashboard(id=101)
+
+ # No slug → the slug-detection branch must not match.
+ request = {"chart_ids": [1], "dashboard_title": "No Slug Dashboard"}
+
Review Comment:
**Suggestion:** This test is intended to prove that non-slug
`IntegrityError`s do not trigger the slug-specific error branch, but the
request omits `slug`, so the branch precondition is never exercised. A
regression where slug-specific messaging is incorrectly returned when a slug is
provided (even for unrelated constraints) would slip through. Include a slug in
the request and keep the mocked FK `IntegrityError` to validate the real
decision path. [incomplete implementation]
<details>
<summary><b>Severity Level:</b> Major ⚠️</summary>
```mdx
- ⚠️ Slug error disambiguation untested when slug is provided.
- ⚠️ Future regressions may mislabel non-slug DB constraint errors.
- ⚠️ MCP `generate_dashboard` reliability reduced for failure scenarios.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Inspect the slug-specific IntegrityError handling in `generate_dashboard`
at
`superset/mcp_service/dashboard/tool/generate_dashboard.py:380-389`, where
`err_text =
str(db_err).lower()` and the slug branch is guarded by `if request.slug and
"slug" in
err_text:`.
2. Inspect `test_generate_dashboard_duplicate_slug_returns_actionable_error`
in
`tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py:18-76`,
which
sets `request["slug"] = "my-slug"` and mocks an `IntegrityError` whose text
contains
`"dashboards_slug_key"` and `"Key (slug)=(my-slug)"`, thereby exercising
only the "slug
present + slug in error text" happy-path branch.
3. Inspect `test_generate_dashboard_integrity_error_unrelated_to_slug` in
the same test
file at
`tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py:81-119`,
where `mock_db_session.commit.side_effect` is an `IntegrityError` with
message `'violates
foreign key constraint "dashboard_slices_slice_id_fkey"'` and the request is
built at line
113 as `{"chart_ids": [1], "dashboard_title": "No Slug Dashboard"}` without
any `slug`
field.
4. Reason about the execution path: for this test, `generate_dashboard()`
sees
`request.slug` as falsy, so the condition `if request.slug and "slug" in
err_text:` at
`generate_dashboard.py:380-389` is not evaluated as True regardless of the
error message;
only the generic constraint error branch at lines 20-26 runs. There is no
test case where
`request.slug` is set **and** the `IntegrityError` text does **not** mention
`"slug"`, so
a regression that returns slug-specific messaging for any IntegrityError
when a slug is
provided (e.g., changing the condition to `if request.slug:`) would not be
caught by the
current tests. Adding a slug to this non-slug `IntegrityError` test would
exercise that
disambiguation path.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=3569348096cb4464a7380c55691b4d8b&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=3569348096cb4464a7380c55691b4d8b&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/mcp_service/dashboard/tool/test_dashboard_generation.py
**Line:** 563:564
**Comment:**
*Incomplete Implementation: This test is intended to prove that
non-slug `IntegrityError`s do not trigger the slug-specific error branch, but
the request omits `slug`, so the branch precondition is never exercised. A
regression where slug-specific messaging is incorrectly returned when a slug is
provided (even for unrelated constraints) would slip through. Include a slug in
the request and keep the mocked FK `IntegrityError` to validate the real
decision path.
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%2F40399&comment_hash=6a6c725c151b1dbe4b2670f8a8b0d671e12e2a9ade54b21acbdf15c2c5d2e91d&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40399&comment_hash=6a6c725c151b1dbe4b2670f8a8b0d671e12e2a9ade54b21acbdf15c2c5d2e91d&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]