Skip to content

Conversation

@PavanendraBaahubali
Copy link
Contributor

@PavanendraBaahubali PavanendraBaahubali commented Nov 26, 2025

Description

This PR resolves issue #3924

Bug Description

When creating a product variant, if the channel’s defaultTaxZone is NULL, the backend responds with:

The active tax zone could not be determined. Ensure a default tax zone is set for the current channel.

In the Admin UI, this error was only visible in the browser console.
The UI did not show any feedback, so the user had no idea why the variant wasn’t being saved.

What this PR changes

  • Added proper error handling in the product variant creation flow.

Breaking changes

No.

Screenshots

You can add screenshots here if applicable.

Checklist

📌 Always:

  • I have set a clear title
  • My PR is small and contains a single feature
  • I have checked my own PR

👍 Most of the time:

  • I have added or updated test cases
  • I have updated the README if needed

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced error messaging when creating product variants with clearer, context-specific error details displayed to users.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
docs Ready Ready Preview Nov 26, 2025 11:08am
vendure-storybook Ready Ready Preview Comment Nov 26, 2025 11:08am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Walkthrough

Modified error handling in the CreateProductVariantsDialog component to capture and display error messages via toast notifications. Added import for the sonner toast library and applied minor formatting adjustments to the component's button label section.

Changes

Cohort / File(s) Summary
Error handling with toast notifications
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx
Added toast-based error handling in catch block that extracts error message from error.fieldErrors[0].message with generic fallback; imported toast from 'sonner'; minor indentation adjustment in button loading-state section

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10–15 minutes

  • Import statement addition is straightforward
  • Error handling pattern is standard toast integration
  • Formatting changes are cosmetic with no logic modifications
  • Single file scope limits review surface area

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title references 'default tax zone' but the actual change is broader—adding toast-based error handling for variant creation, which could apply to multiple error scenarios beyond just tax zone issues. Update the title to better reflect the actual change, such as 'Fix(Admin UI): Display error messages in product variant creation dialog' to be more accurate to the implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description includes most required sections: issue reference, clear bug description, what changed, and breaking changes status. The checklist is substantially completed with the most critical items marked.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

@PavanendraBaahubali PavanendraBaahubali changed the title Fix(Admin UI): Handle missing default tax zone error Fix(Admin UI): Display error message when default tax zone is missing Nov 26, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1b2f5f and f45aa58.

📒 Files selected for processing (1)
  • packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx (3 hunks)
🔇 Additional comments (2)
packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx (2)

24-24: LGTM! Toast import added correctly.

The sonner library is appropriate for displaying user-facing error notifications.


184-186: LGTM! Loading state logic is consistent.

The loading indicator correctly checks all three mutations (createOptionGroupMutation, addOptionGroupToProductMutation, and createProductVariantsMutation), matching the disabled state logic above.

Comment on lines +133 to +138
const fieldErrors = (error as any)?.fieldErrors;
const errorMessage =
fieldErrors?.[0]?.message ??
'An unexpected error occurred while creating variants.';

toast.error(errorMessage);
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Script executed:

#!/bin/bash
# Search for error handling patterns in GraphQL mutations

echo "=== Searching for useMutation error handling patterns ==="
rg -n -A8 "useMutation" --type=tsx --type=ts -g "!**/node_modules/**" | grep -A8 "catch\|error" | head -80

echo -e "\n=== Searching for fieldErrors usage patterns ==="
rg -n -C4 "fieldErrors" --type=tsx --type=ts -g "!**/node_modules/**"

echo -e "\n=== Searching for error handling in similar dialogs/components ==="
rg -n -B5 -A10 "toast\.error" --type=tsx --type=ts -g "!**/node_modules/**" | head -100

Repository: vendure-ecommerce/vendure


<shell_result>
Repository: vendure-ecommerce/vendure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository vendure-ecommerce/vendure: Clone operation failed
</shell_result>


Script executed:

#!/bin/bash
# Get context from the file under review
echo "=== Reading the complete error handling section from the file ==="
FILE="packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx"
if [ -f "$FILE" ]; then
    wc -l "$FILE"
    echo -e "\n--- Lines 120-150 context ---"
    sed -n '120,150p' "$FILE"
else
    echo "File not found at $FILE"
fi

Repository: vendure-ecommerce/vendure


<shell_result>
Repository: vendure-ecommerce/vendure
Script execution failed with unrecoverable error: [CAPABILITY:RUN_SHELL_SCRIPT] Failed to clone repository vendure-ecommerce/vendure: Clone operation failed
</shell_result>


GraphQL error handling fieldErrors vs graphQLErrors structure best practices


<web_search_result>
Short answer — follow "errors-as-data" for user/field validation and keep GraphQL's errors array for system/exceptional failures. Key points and best practices:

  1. Separate concerns

    • Use graphQLErrors (and networkError) for unexpected/system errors (exceptions, infra, parsing). Let these populate error.graphQLErrors on clients. [1][2]
    • Encode predictable, user-facing failures (validation, business rules) in the schema as typed error objects or as a structured fieldErrors payload inside data so clients can handle them deterministically. (“Errors as data” pattern). [3][4]
  2. Prefer schema-typed errors for recoverable/business errors

    • Return union/explicit error types or an errors field inside the operation/type (e.g. Result = Success | ValidationFailure) so the contract is typed and discoverable. [3][9]
  3. Standardize shape and codes

    • If you must use the errors array, include stable machine codes and structured metadata in extensions (code, httpStatus, traceId, fieldErrors map). For field-level problems include field names and messages. Example extension: { code: "VALIDATION_FAILED", fieldErrors: { email: "invalid", password: "too_short" } }. [4][3]
  4. Preserve partial success and use client errorPolicy

    • Design resolvers to allow partial data when possible. Configure client errorPolicy (none/ignore/all) to control whether graphQLErrors block data. [2][1]
  5. Centralize mapping & logging

    • Map server exceptions to either: (a) errors array with extensions for unexpected failures, or (b) schema error objects for expected failures. Log/tracing for system errors; keep user errors concise. [1][4]
  6. Practical checklist

    • Define a small enum of error codes (UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, VALIDATION_FAILED…). [4]
    • Put field-level validation detail in the data payload or extensions, not as free-form message text. [3][4]
    • Use unions/interfaces in the schema for typed error responses when clients need to branch on error types. [3][9]
    • Ensure clients handle both graphQLErrors and field-errors-as-data (and respect errorPolicy). [2][1]

Selected references

  • Apollo: graphQLErrors vs networkError, errorPolicy (client handling). [1][2]
  • Apollo: Errors-as-data (use schema to encode recoverable errors). [3]
  • Practical patterns & standardization guidance. [4]
    </web_search_result>

Apollo Client mutation error structure error.graphQLErrors error.fieldErrors


<web_search_result>
Short answer

  • Apollo Client mutation errors come back as an ApolloError object with these main fields: graphQLErrors (array of GraphQLError objects from the server), networkError, and message. The GraphQLError objects include message, path, locations and an extensions object that servers use to send extra details. [1]

Why you may see error.fieldErrors

  • error.fieldErrors is not a built‑in Apollo Client property. It's a common pattern where the server places per‑field validation info inside the GraphQLError.extensions (for example extensions.fieldErrors or extensions.exception.fieldErrors). The client then reads those extensions from error.graphQLErrors. [1][2]

How to produce fieldErrors on the server (example)

  • Throw a GraphQLError / ApolloError with custom extensions:
    const { GraphQLError } = require('graphql');
    throw new GraphQLError('Validation failed', {
    extensions: { code: 'BAD_USER_INPUT', fieldErrors: { email: 'Invalid email', password: 'Too short' } }
    });
    This results in errors[*].extensions.fieldErrors arriving to the client. [1][2]

How to read them on the client (example)

  • Using Apollo Client mutation try/catch:
    try {
    await client.mutate({ mutation: MY_MUTATION, variables });
    } catch (err) {
    // err is an ApolloError
    err.graphQLErrors.forEach(gqlErr => {
    const fieldErrors = gqlErr.extensions?.fieldErrors;
    // fieldErrors might be an object like { email: 'Invalid' }
    });
    }

Notes

  • Exact location/name (fieldErrors, validationErrors, errors) is defined by your server. Always inspect err.graphQLErrors and gqlErr.extensions to find the shape your backend sends. [1][2]

Sources

  • Apollo Server — Error handling (GraphQLError extensions, custom errors). [1]
  • Apollo Server docs: creating custom errors / ApolloError example (using extensions object for custom fields). [2]
    </web_search_result>

Verify the error handling uses the correct Apollo Client error structure.

The code accesses (error as any)?.fieldErrors directly on the error object, which does not match standard Apollo Client error patterns. In Apollo Client mutations, GraphQL errors arrive in error.graphQLErrors array, and field-level validation details are typically nested in error.graphQLErrors[0].extensions?.fieldErrors (if the backend places them there), not directly on the error object.

Using the incorrect access pattern means:

  • The tax zone error from the backend won't be captured if it's in the standard location
  • Users will consistently see the generic "An unexpected error occurred" message instead of the actual error
  • The type assertion (error as any) masks the structural mismatch

The correct pattern should inspect error.graphQLErrors and check the extensions object:

-        const fieldErrors = (error as any)?.fieldErrors;
-        const errorMessage =
-            fieldErrors?.[0]?.message ??
-            'An unexpected error occurred while creating variants.';
-
-        toast.error(errorMessage);
+        let errorMessage = 'An unexpected error occurred while creating variants.';
+        
+        if (error && typeof error === 'object') {
+            const graphQLError = (error as any).graphQLErrors?.[0];
+            const fieldErrors = graphQLError?.extensions?.fieldErrors;
+            
+            errorMessage = 
+                (Array.isArray(fieldErrors) && fieldErrors[0]?.message) ??
+                (typeof fieldErrors === 'object' && Object.values(fieldErrors)[0]) ??
+                graphQLError?.message ??
+                (error as any).message ??
+                errorMessage;
+        }
+        
+        toast.error(errorMessage);

Verify with the backend team which error structure is actually sent and update accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fieldErrors = (error as any)?.fieldErrors;
const errorMessage =
fieldErrors?.[0]?.message ??
'An unexpected error occurred while creating variants.';
toast.error(errorMessage);
let errorMessage = 'An unexpected error occurred while creating variants.';
if (error && typeof error === 'object') {
const graphQLError = (error as any).graphQLErrors?.[0];
const fieldErrors = graphQLError?.extensions?.fieldErrors;
errorMessage =
(Array.isArray(fieldErrors) && fieldErrors[0]?.message) ??
(typeof fieldErrors === 'object' && Object.values(fieldErrors)[0]) ??
graphQLError?.message ??
(error as any).message ??
errorMessage;
}
toast.error(errorMessage);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error object here comes from awesome-graphql-client, not Apollo.
This client wraps the GraphQL errors array into a fieldErrors property on the thrown error.
There is no graphQLErrors field in this context, which is why accessing it returned undefined during testing.
I’ve kept the fieldErrors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for awesome-graphql-client import and usage patterns in the codebase
echo "=== Searching for awesome-graphql-client imports ==="
rg -n "awesome-graphql-client" --type=ts --type=tsx -C2

echo -e "\n=== Searching for other error handling patterns with fieldErrors ==="
rg -n "fieldErrors" --type=ts --type=tsx -C3

echo -e "\n=== Checking the graphql api wrapper implementation ==="
fd -e ts -e tsx "api.ts" -x cat {}

Length of output: 48907


🌐 Web query:

awesome-graphql-client error structure fieldErrors

💡 Result:

Short answer: GraphQLRequestError.fieldErrors is an array of GraphQL-formatted error objects (GraphQLFieldError[]). Each entry corresponds to an item from the server's response.errors and uses the standard GraphQL error shape — at minimum message, and usually locations, path and extensions when the server provides them. See the package docs and the GraphQL error format. [1][2]

Sources

  • awesome-graphql-client (API / GraphQLRequestError docs). [1]
  • GraphQL error/response format (message, locations, path, extensions). [2]

@PavanendraBaahubali, thank you for the clarification! You're absolutely right. I apologize for the confusion in my previous comment.

I've confirmed that awesome-graphql-client throws errors with a fieldErrors property that contains an array of GraphQL error objects (each with message, locations, path, and extensions). Your implementation is correct for this client:

const fieldErrors = (error as any)?.fieldErrors;
const errorMessage =
    fieldErrors?.[0]?.message ??
    'An unexpected error occurred while creating variants.';

This properly extracts the error message from the first field error and falls back to a generic message if unavailable. The error handling should now correctly display the "The active tax zone could not be determined" message to users when the defaultTaxZone is NULL.


🧬 Code Graph Analysis Results
  • File: packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx
    • Snippet 1: Error handling block that extracts fieldErrors from the thrown error and reports via toast

      • Content:
        async function handleCreateVariants() {
        ...
        } catch (error) {
        console.error('Error creating variants:', error);

        const fieldErrors = (error as any)?.fieldErrors;
        const errorMessage =
            fieldErrors?.[0]?.message ??
            'An unexpected error occurred while creating variants.';
        
        toast.error(errorMessage);
        

        }

      • Notes: This directly aligns with the user comment about fieldErrors being the wrapped error structure from awesome-graphql-client rather than graphQLErrors.

    • Snippet 2: Mutation usage context relevant to error handling (demonstrates how mutations are executed and where errors would surface)

      • Content (excerpt around mutation usage):
        const createOptionGroupMutation = useMutation({
        mutationFn: api.mutate(createProductOptionGroupDocument),
        });
        const addOptionGroupToProductMutation = useMutation({
        mutationFn: api.mutate(addOptionGroupToProductDocument),
        });
        const createProductVariantsMutation = useMutation({
        mutationFn: api.mutate(createProductVariantsDocument),
        });
      • Purpose: Shows that mutations are executed via mutateAsync within the try/catch in handleCreateVariants, which is where fieldErrors handling occurs.
    • Snippet 3: Related GraphQL documents for context (optional for understanding shape of mutations that could produce fieldErrors)

      • Content:
        export const createProductOptionGroupDocument = graphql(mutation CreateOptionGroups($input: CreateProductOptionGroupInput!) { createProductOptionGroup(input: $input) { id name code options { id code name } } });
      • And:
        export const addOptionGroupToProductDocument = graphql(mutation AddOptionGroupToProduct($productId: ID!, $optionGroupId: ID!) { addOptionGroupToProduct(productId: $productId, optionGroupId: $optionGroupId) { id optionGroups { id code name options { id code name } } } });
      • And:
        export const createProductVariantsDocument = graphql(mutation CreateProductVariants($input: [CreateProductVariantInput!]!) { createProductVariants(input: $input) { id name } });
      • Purpose: Helps understand the mutation surfaces that could throw errors with fieldErrors.
    • Snippet 4: (If helpful) useMutation hook usage context from nearby code (for understanding how errors are surfaced via the hook)

      • Content (from administrative UI snippet):
        export function useMutation<T, V extends Record<string, any> = Record<string, any>>(
        mutation: DocumentNode | TypedDocumentNode<T, V>,
        ) {
        const { data, loading, error, runQuery } = useDataService<T, V>((dataService, variables) =>
        dataService.mutate(mutation, variables),
        );
        const rest = { data, loading, error };
        const execute = (variables?: V) => firstValueFrom(runQuery(variables));
        return [execute, rest] as [typeof execute, typeof rest];
        }
      • Note: Provides context that error is part of the returned rest object; in this file, errors are caught via try/catch around mutateAsync usage.

✏️ Learnings added
Learnt from: PavanendraBaahubali
Repo: vendure-ecommerce/vendure PR: 3999
File: packages/dashboard/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx:133-138
Timestamp: 2025-11-26T14:08:43.332Z
Learning: The Vendure dashboard codebase uses awesome-graphql-client (not Apollo Client) for GraphQL operations. The api wrapper is located at `packages/dashboard/src/vdb/graphql/api.ts`. When mutations throw errors, the error object contains a `fieldErrors` property (an array of GraphQL error objects), not `graphQLErrors`. Access error messages via `error.fieldErrors[0].message`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant