From 8018d30d9da03f4e7fce6b287be0b177fc0a7c54 Mon Sep 17 00:00:00 2001 From: Antoine Laviron Date: Thu, 30 Jul 2026 18:11:14 +0200 Subject: [PATCH] fix(tableau-next-semantic-model-generate): correct broken CLI examples, unqualified syntax, aggregation choices, error docs --- .../SKILL.md | 10 ++++-- .../references/api-reference.md | 31 +++++++++++++------ .../references/metric-design.md | 20 ++++++------ .../scripts/_shared/calc_field_templates.py | 8 ++--- .../scripts/create_calc_field.py | 2 +- 5 files changed, 44 insertions(+), 27 deletions(-) diff --git a/skills/tableau-next-semantic-model-generate/SKILL.md b/skills/tableau-next-semantic-model-generate/SKILL.md index ef65957..72649e2 100644 --- a/skills/tableau-next-semantic-model-generate/SKILL.md +++ b/skills/tableau-next-semantic-model-generate/SKILL.md @@ -330,8 +330,12 @@ Format: `fieldApiName:tableApiName` (repeat `--additional-dimension` for multipl **Creation success is not data success.** Empty results (empty source, filter excludes everything, expression resolves to nothing) → dashboard shows "No results to show." Check with `--verify-only`: ```bash -python scripts/create_metric.py --sdm --name --verify-only -python scripts/create_calc_field.py --sdm --type measurement --name --verify-only +# --verify-only ignores --label/--calculated-field/--time-* but argparse still requires them; pass any values. +python scripts/create_metric.py --sdm --name \ + --label "verify" --calculated-field \ + --time-field --time-table \ + --verify-only +python scripts/create_calc_field.py --sdm --type measurement --name --label "verify" --verify-only ``` Non-zero exit + **NOT shippable** = don't proceed to dashboards. Full protocol (why not to re-POST, `query_data.py --count` for raw objects, empty-source guidance) in **[references/concepts.md](references/concepts.md)** and [references/empty-source-handling.md](references/empty-source-handling.md). @@ -353,7 +357,7 @@ python scripts/update_sdm.py {{SDM_NAME}} \ python scripts/update_sdm.py {{SDM_NAME}} --agent-enabled --dry-run # Confirm via discovery (stored state, not CLI success message) -python scripts/discover_sdm.py {{SDM_NAME}} --json +python scripts/discover_sdm.py --sdm {{SDM_NAME}} --json ``` The update is a PATCH (partial body — server merges), idempotent. `--categories` takes controlled "Semantic Category" values only — the server rejects unknown values with `Invalid Semantic Category`. diff --git a/skills/tableau-next-semantic-model-generate/references/api-reference.md b/skills/tableau-next-semantic-model-generate/references/api-reference.md index 48554b5..553df93 100644 --- a/skills/tableau-next-semantic-model-generate/references/api-reference.md +++ b/skills/tableau-next-semantic-model-generate/references/api-reference.md @@ -418,21 +418,34 @@ Downstream builders that consume this contract should enforce this pre-POST and ## Error Responses -All endpoints return structured error responses: +Salesforce API error responses come in two shapes depending on the endpoint. + +**Array form (most common):** a top-level JSON array of one or more error objects. + +```json +[ + { + "errorCode": "INVALID_API_INPUT", + "message": "Field 'Amount' is not found in the semantic model.", + "fields": ["dataObjectFields"] + } +] +``` + +**Single-object form:** a bare JSON object (used by some endpoints and by the platform for auth/session errors). ```json { - "error": { - "code": "INVALID_FIELD", - "message": "Field 'Amount' not found in semantic model 'Sales_Cloud12_backward'", - "details": { - "fieldName": "Amount", - "availableFields": ["Total_Amount", "Close_Date", "Stage"] - } - } + "errorCode": "INVALID_SESSION_ID", + "message": "Session expired or invalid.", + "fields": [] } ``` +A `localizedMessage` key may appear in place of (or alongside) `message` in platform-level errors. + +The `fields` array is present when the error is scoped to specific request fields; it is omitted or empty otherwise. + **Common Error Codes:** - `INVALID_TOKEN`: Authentication token is invalid or expired - `INSUFFICIENT_PERMISSIONS`: User lacks required permissions diff --git a/skills/tableau-next-semantic-model-generate/references/metric-design.md b/skills/tableau-next-semantic-model-generate/references/metric-design.md index 0a6e4be..ce81e41 100644 --- a/skills/tableau-next-semantic-model-generate/references/metric-design.md +++ b/skills/tableau-next-semantic-model-generate/references/metric-design.md @@ -26,7 +26,7 @@ python scripts/create_calc_field.py \ --type measurement \ --name Total_Revenue_clc \ --label "Total Revenue" \ - --expression "SUM([Amount])" \ + --expression "SUM([Opportunity_TAB_Sales_Cloud].[Amount])" \ --aggregation Sum # Step 2: Create metric @@ -141,7 +141,7 @@ python scripts/create_calc_field.py \ --type measurement \ --name Win_Rate_clc \ --label "Win Rate" \ - --expression "SUM([Won_Count]) / SUM([Total_Count])" \ + --expression "SUM([Opportunity_TAB_Sales_Cloud].[Won_Count]) / SUM([Opportunity_TAB_Sales_Cloud].[Total_Count])" \ --aggregation UserAgg # Step 2: Create metric @@ -169,7 +169,7 @@ python scripts/create_calc_field.py \ --type measurement \ --name Weighted_Pipeline_clc \ --label "Weighted Pipeline Value" \ - --expression "SUM([Amount] * [Probability])" \ + --expression "SUM([Opportunity_TAB_Sales_Cloud].[Amount] * [Opportunity_TAB_Sales_Cloud].[Probability])" \ --aggregation Sum # Step 2: Create metric @@ -197,7 +197,7 @@ python scripts/create_calc_field.py \ --type measurement \ --name Avg_Sales_Cycle_clc \ --label "Average Sales Cycle (Days)" \ - --expression "AVG(DATEDIFF('day', [Created_Date], [Close_Date]))" \ + --expression "AVG(DATEDIFF('day', [Opportunity_TAB_Sales_Cloud].[Created_Date], [Opportunity_TAB_Sales_Cloud].[Close_Date]))" \ --aggregation Avg # Step 2: Create metric @@ -375,12 +375,12 @@ After creating a metric, test it by: | Pattern | Calc Field Expression | Aggregation | Use Case | |---------|----------------------|-------------|----------| -| Simple sum | `SUM([Field])` | Sum | Total revenue, total count | -| Average | `AVG([Field])` | Avg | Average deal size, average duration | -| Ratio | `SUM([A]) / SUM([B])` | UserAgg | Win rate, conversion rate | -| Weighted | `SUM([A] * [B])` | Sum | Weighted pipeline, risk-adjusted | -| Time calc | `AVG(DATEDIFF('day', [Start], [End]))` | Avg | Sales cycle, time to close | -| Conditional sum | `SUM(IF condition THEN [Field] ELSE 0 END)` | Sum | Won revenue, qualified leads | +| Simple sum | `SUM([Table].[Field])` | Sum | Total revenue, total count | +| Average | `AVG([Table].[Field])` | Avg | Average deal size, average duration | +| Ratio | `SUM([Table].[A]) / SUM([Table].[B])` | UserAgg | Win rate, conversion rate | +| Weighted | `SUM([Table].[A] * [Table].[B])` | Sum | Weighted pipeline, risk-adjusted | +| Time calc | `AVG(DATEDIFF('day', [Table].[Start], [Table].[End]))` | Avg | Sales cycle, time to close | +| Conditional sum | `SUM(IF condition THEN [Table].[Field] ELSE 0 END)` | Sum | Won revenue, qualified leads | ## Metric Lifecycle diff --git a/skills/tableau-next-semantic-model-generate/scripts/_shared/calc_field_templates.py b/skills/tableau-next-semantic-model-generate/scripts/_shared/calc_field_templates.py index c2189ea..cb9b496 100644 --- a/skills/tableau-next-semantic-model-generate/scripts/_shared/calc_field_templates.py +++ b/skills/tableau-next-semantic-model-generate/scripts/_shared/calc_field_templates.py @@ -119,7 +119,7 @@ def build_calculated_measurement( api_name: API name (must end with _clc) label: Display label expression: Tableau formula expression - aggregation_type: Aggregation type (Sum, Avg, Count, Min, Max, UserAgg, Median) + aggregation_type: Aggregation type (Sum, Avg, Count, CountDistinct, Min, Max, UserAgg) data_type: Data type (Number, Text, Boolean, DateTime) decimal_place: Decimal places for Number fields description: Field description @@ -155,11 +155,11 @@ def build_calculated_measurement( final_agg_type = aggregation_type level = "Row" # Validate explicit aggregation types are allowed - if final_agg_type not in ("Sum", "Avg", "Count", "Min", "Max", "Median", "UserAgg"): + if final_agg_type not in ("Sum", "Avg", "Count", "CountDistinct", "Min", "Max", "UserAgg"): import warnings warnings.warn( f"Explicit aggregation type '{final_agg_type}' may not be supported. " - f"Common types: Sum, Avg, Count, Min, Max, Median" + f"Common types: Sum, Avg, Count, CountDistinct, Min, Max" ) payload = { @@ -262,7 +262,7 @@ def validate_calc_field( # Check aggregation type if field_type == "measurement" and aggregation_type: - valid_agg = ["Sum", "Avg", "Count", "Min", "Max", "UserAgg", "Median"] + valid_agg = ["Sum", "Avg", "Count", "CountDistinct", "Min", "Max", "UserAgg"] if aggregation_type not in valid_agg: errors.append(f"aggregationType must be one of: {', '.join(valid_agg)}") diff --git a/skills/tableau-next-semantic-model-generate/scripts/create_calc_field.py b/skills/tableau-next-semantic-model-generate/scripts/create_calc_field.py index 19c52ee..9b181e0 100755 --- a/skills/tableau-next-semantic-model-generate/scripts/create_calc_field.py +++ b/skills/tableau-next-semantic-model-generate/scripts/create_calc_field.py @@ -139,7 +139,7 @@ def main() -> int: # Measurement-specific parser.add_argument("--aggregation", default="UserAgg", - choices=["Sum", "Avg", "Count", "Min", "Max", "UserAgg", "Median"], + choices=["Sum", "Avg", "Count", "CountDistinct", "Min", "Max", "UserAgg"], help="Aggregation type (for measurements, default: UserAgg)") parser.add_argument("--data-type", choices=["Number", "Text", "Boolean", "DateTime"], help="Data type (default: Number for measurements, inferred for dimensions)")