Skip to content

[ongoing discussion] Draft for cart operations and other GraphQL improvements #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Oct 19, 2018

Conversation

paliarush
Copy link
Contributor

@paliarush paliarush commented Jul 31, 2018

For the context see recording of previous GraphQL community meeting.

GraphQL project wiki: https://github.com/magento/graphql-ce/wiki

quantity: Float!
configurable_options:[ConfigurableOptionInput!]!
customizable_options:[CustomizableOptionInput!]
}
Copy link

Choose a reason for hiding this comment

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

Can't configurable_options be replaced by a single simple_sku: Sku! field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we go with simple_sku, then the client on its end will have to map selected options combination to the SKU:

  • Get a map during product request (option1, option2, optionN => simple SKU, ...)
  • Calculate current simple SKU based on selected options
  • Send simple SKU along with add product to cart request

Looks like more work for client app developers. I am probably missing something, please clarify why simple_sku is preferable.

Thanks for the review!

Copy link

Choose a reason for hiding this comment

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

I think there already is some form of simple_sku mapping in the frontend, especially if a frontend developer wishes to fetch images from the simple product etc. Or are there other strategies in that regard?

Copy link
Contributor

Choose a reason for hiding this comment

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

@paales Can you help by providing the motivation for using simple_sku in lieu of sending along options?

AFAICT, it seems like, in any situation you'd be adding an item to a cart, you'd always have a list of the options (since the shopper needs to select them). So it feels like needing to track a simple_sku on the client-side might be work that is unnecessary. Granted, it's very little extra work, but I'm curious what the motivation would be for that > options list.

Copy link

Choose a reason for hiding this comment

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

In the past I've always found it much easier to supply a sku rather than a list of options that map to a simple product sku. I agree with @paales that usually the sku is already known on the frontend.

@@ -0,0 +1,49 @@
**Overview**

As a Magento developer, I need to manipulate the shopping cart via GraphQL so that I can build basic ecommerce experiences for shoppers on the front-end using only GraphQL.
Copy link
Contributor

Choose a reason for hiding this comment

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

for shoppers on the front-end using only GraphQL.

This is a big nitpick, so...sorry!

I think it's super important that we avoid language that couples the concept of GraphQL in Magento to the front-end specifically.

I know it's a small thing, but we want to make sure everyone has the mindset that this is an API for any external consumer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please suggest how to stress, that GraphQL in Magento is intended for store front scenarios only, not for admin ones.

Copy link
Contributor

@DrewML DrewML Aug 3, 2018

Choose a reason for hiding this comment

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

How about:

As a Magento developer, I need to manipulate the shopping cart via GraphQL so that I can programmatically create orders on behalf of a shopper.

Thoughts?

- Can create "order in one call" mutation in the future if needed
- Hashed IDs for cart items
- Single input object
- Async nature of the flow must be supported on the client side (via AJAX calls)
Copy link
Contributor

@DrewML DrewML Jul 31, 2018

Choose a reason for hiding this comment

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

Might want to expand on this point more, since all requests from the browser are async (barring sync XMLHTTPRequest, which is deprecated).

Maybe add some additional language around this clarifying that it creates a "job" of sorts, which helps imply that the results of the mutation will be some object representing a task that will be finished some time in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Asynchronous server side implementation is not approved yet. The schema may change, if it will be approved.


**Open questions:**

- Do we want to implement server-side asynchronous mutations by default?
Copy link
Contributor

Choose a reason for hiding this comment

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

By default for cart mutations, or for all mutations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@akaplya suggested for all mutations. Still under discussion.

Copy link

Choose a reason for hiding this comment

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

For a server less architecture as we have in CIF this might not work OOTB.

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 decision is to have Synchronous implementation by default. Asynchronous implementation may be added later on the framework level as it was for REST.

- Can create "order in one call" mutation in the future if needed
- Hashed IDs for cart items
- Single input object
- Async nature of the flow must be supported on the client side (via AJAX calls)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's important to include why there is a desire for these mutations to be async. When we chatted briefly yesterday, I got the impression that it's because this could be a long-running operation, and we don't want to keep the request open. Is that accurate?

It's just worth making the justification clear, because a concept of a "job" that needs to be polled (or a subscription) pushes some complexity to the consumer of the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not necessarily long-running, may be just a lot of concurrent requests during peak hours. Decision on the async server side is not made yet.

qty: Float!
product: CartItemProduct!
image: CartItemImage!
prices: CartItemPrices!
Copy link

Choose a reason for hiding this comment

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

How/where are the calculated cartItem prices like: prices with(out) taxes? Or the applied discounts?

name: String!
type: String!
# Does it make sense to provide several basic fields (with potentially better performance) in addition to the ful product object?
details: ProductInterface!
Copy link

Choose a reason for hiding this comment

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

This should depend on the client so I am in favour of adding it to the cartItem. I guess that most clients will just query for some fields and not for the entire ProductInterface (not sure if it matters for performance).


type CartItemImage {
alt_text: String!
thumbnail: ProductMediaGalleryEntriesContent!
Copy link

Choose a reason for hiding this comment

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

Probably out of scope the current PR scope but would be really nice to have the absolute url here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think URL is already part of ProductInterface, what if we remove cart item image and just leave reference to ProductInterface?

type Cart {
id: String
items: [CartItemInterface]
}
Copy link

Choose a reason for hiding this comment

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

Missing the cart totals, currency, updated dates and all the other information. I know that this is not in the scope but my suggestion is to add all these data to cart definition ASAP to make things clear. Btw how can a client be sure that the cart he is currently updating is the last one? The use case here is when the same cart is updated by the end user and also by a back office.

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 cart is updated by ID and there is only one active cart per customer at a time. If there are conflicting writes to the same cart (from store front and from the admin), then the last one wins. This should not happen in normal use cases.


**Open questions:**

- Do we want to implement server-side asynchronous mutations by default?
Copy link

Choose a reason for hiding this comment

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

For a server less architecture as we have in CIF this might not work OOTB.

code: String!
}

input CancelCouponForCartInput {
Copy link

@misha-kotov misha-kotov Aug 3, 2018

Choose a reason for hiding this comment

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

suggest "remove coupon from cart", as in "RemoveCouponFromCartInput", "RemoveCouponFromCartOutput"

}

type CartAddress {
pices: CartAddressTotals
Copy link

Choose a reason for hiding this comment

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

Is this the CartAddressPrices? Also, note the small typo, the field name should be prices

Copy link

Choose a reason for hiding this comment

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

Why plural?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think plural makes sense because there are multiple price types stored in this field.
Do you strongly believe it should be singular in this case?

Copy link

Choose a reason for hiding this comment

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

Not really, but there's this small inconsistency where you have a plural to designate lists but here is a single object. Why don't name it totals?

@@ -0,0 +1,57 @@
interface CartItemInterface {
prices: CartItemTotals
Copy link

Choose a reason for hiding this comment

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

Where is the CartItemTotals defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I called the type CartItemTotals first and then renamed it to CartItemPrices, forgot to change here.

}

type Cart {
prices: CartTotals
Copy link

Choose a reason for hiding this comment

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

Where is the CartTotals defined?

@paliarush paliarush changed the title Initial draft for add items to cart GraphQL mutations Initial draft for cart mutations and other GraphQL improvements Aug 15, 2018
suffix: String
gender: GenderEnum
date_of_birth: String
vat_number: String # Do we need it at all on storefront? Do we need more details
Copy link

Choose a reason for hiding this comment

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

The presence of a vat_number can influence the rendering in many ways, so I'd say yes.

- Improved extensibility for cart item mutations
- Added cart item removal and update schemas
postcode: String
country_code: String!
telephone: String!
fax: String

Choose a reason for hiding this comment

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

do we really need fax anymore? I would get rid of it if we can do it without breaking things

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}

input AddShippingAddressToCartInput {
address_id: Int # Can be provided in one-page checkout and is required for multi-shipping checkout

Choose a reason for hiding this comment

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

Have we accounted for multishipping scenarios here? I don't yet see how we tie cart items with shipping addresses, in case there are multiple

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

addShippingAddressToCart(input: AddShippingAddressToCartInput): AddShippingAddressToCartOutput
}

input AddBillingAddressToCartInput {

Choose a reason for hiding this comment

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

Suggestion: rename to setBillingAddressOnCart (to show that there can only be one)

input AddShippingAddressToCartInput {
address_id: Int # Can be provided in one-page checkout and is required for multi-shipping checkout
address: CartAddressInput
shipping_method_code: String # Should be on address level for multi-shipping checkout

Choose a reason for hiding this comment

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

Needs to be a separate mutation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}

input CartAddressInput {
firstname: String!

Choose a reason for hiding this comment

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

How are we going to add customer address attributes?

}

type CartAddress {
firstname: String!

Choose a reason for hiding this comment

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

how are we going to set information about customer address attributes

BILLING
}

type CheckoutShippingMethod {

Choose a reason for hiding this comment

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

I think we also need to add information about the error message, it can be displayed if in the system configuration we choose carriers/dhl/showmethod = 1

Choose a reason for hiding this comment

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

case code/label become optional if error message is displayed (setting "show method if not applicable")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


type CheckoutShippingMethod {
code: String!
label: String!

Choose a reason for hiding this comment

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

Also, online shipping methods can have different rates, how are we going to display this information?

type CheckoutPaymentMethod {
code: String!
label: String!
# Do we need more info from quote_payment DB table?

Choose a reason for hiding this comment

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

need to add "amount" or "balance" for things like store credit, gift card, reward pts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

type CheckoutPaymentMethod {
code: String!
label: String!
# Do we need more info from quote_payment DB table?

Choose a reason for hiding this comment

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

Add sort order

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

type CheckoutPaymentMethod {
code: String!
label: String!
# Do we need more info from quote_payment DB table?

Choose a reason for hiding this comment

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

handle payment method-specific field such as "instructions", "payable to", "send check to", etc

type CheckoutPaymentMethod {
code: String!
label: String!
# Do we need more info from quote_payment DB table?

Choose a reason for hiding this comment

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

consider "terms and conditions" and "billing agreements"

type CheckoutPaymentMethod {
code: String!
label: String!
# Do we need more info from quote_payment DB table?

Choose a reason for hiding this comment

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

consider gift messaging, gift wrapping, gift receipt

@@ -0,0 +1,58 @@
type Query {
Copy link

@irenelagno irenelagno Sep 19, 2018

Choose a reason for hiding this comment

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

please add information

  • about shipping address if we add an item from gift registry;
  • how can we set gift messages, gift wrapping, printed cart information;
  • how can we set checkout agreements
    <extension_attributes for="Magento\Quote\Api\Data\PaymentInterface"> <!-- This is needed to provide type hint to serializer --> <attribute code="agreement_ids" type="string[]" /> </extension_attributes>
  • Also, if graphQL be compatible with B2B edition

is_virtual: Boolean!
}

type CheckoutCustomer {
Copy link
Member

Choose a reason for hiding this comment

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

It would be great to use polymorphism here.

interface CheckoutCustomer {
  # the mandatory fields
}

type GuestCheckoutCustomer implements CheckoutCustomer {
  # …
  middle_name: String
}
type LoggedInCheckoutCustomer implements CheckoutCustomer {
  # …
  middle_name: String!
}

React components could then require different fragments depending on the type

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea in general. I don't understand though, why middle_name should be required for the registered customer. The only difference I see right now is that we can eliminate is_guest field in this way.

Please clarify which exact fields should be different between guest and customer implementations.

Copy link
Member

@real34 real34 Sep 21, 2018

Choose a reason for hiding this comment

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

I agree that the middle_name is not a good example. I meant user information mandatory upon registration, such as email for instance.

The idea of having such a structure, is to open rooms for extension in the shop. For instance in a B2B context, a LoggedInCheckoutCustomer may have a sales_representative: SalesPerson! attached which would not be available for guests.
Developers would then only extend the LoggedInCheckoutCustomer type, making implementation more explicit.

middle_name: String
suffix: String
gender: GenderEnum
date_of_birth: String
Copy link
Member

Choose a reason for hiding this comment

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

Are there any plans for a scalar Date type in the core?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not have plans for that, but I know that this topic was discussed in scope of Webonyx project: webonyx/graphql-php#228

Copy link
Member

Choose a reason for hiding this comment

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

Ok, thank you.

code: String!
label: String!
balance: Money
sort_order: Int
Copy link
Member

Choose a reason for hiding this comment

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

Is there a use case for exposing this value (vs having sort params in queries returning this type)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We thought of a use case when the client app needs to honor sort order of available payment methods during rendering.

Copy link
Member

Choose a reason for hiding this comment

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

I think this pattern is better to be delegated to the server logic. By default available_payment_methods would return sorted values.

If a use case where methods should be sorted differently, it could lead to a different signature with an optional param:

available_payment_methods(sortDesc: Boolean)

}

input UpdateBundleProductCartItemInput {
details: UpdateCartItemDetailsInput!
Copy link

@naydav naydav Sep 25, 2018

Choose a reason for hiding this comment

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

Maybe it will be better to use data instead of details

data: CartItemInput!


type SelectedCustomizableOptionValue {
id: Int
label: String!
Copy link

Choose a reason for hiding this comment

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

What is label for SelectedCustomizableOptionValue ?
Is the value entered / selected by the user?

type SelectedCustomizableOption {
id: Int!
label: String!
type: String!
Copy link

@naydav naydav Sep 26, 2018

Choose a reason for hiding this comment

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

Do we need to add Required flag value?

type SelectedCustomizableOptionValue {
id: Int
label: String!
price: CartItemSelectedOptionValuePrice!
Copy link

@naydav naydav Sep 26, 2018

Choose a reason for hiding this comment

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

Do we need price info?

Maybe id and value will be enough for the response about selected options

{
   "id": 14,
   "value": "test",                    
 }

}

input UpdateSimpleProductCartItemInput {
details: UpdateCartItemDetailsInput!
Copy link

Choose a reason for hiding this comment

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

Why do we need one more level like UpdateCartItemDetailsInput

Now request is

cartItems: [
        {
          data: {
            sku: "product_dynamic_24494", 
            qty: 1
          }, 
          customizable_options: [
            {id: 5, value: "test"},
           ]
        }
      ]

Can we use next?

cartItems: [
        {
          sku: "product_dynamic_24494", 
          qty: 1,
          customizable_options: [
            {id: 5, value: "test"},
           ]
        }
      ]
``

@buskamuza buskamuza merged commit 646fc6f into magento:master Oct 19, 2018
@buskamuza
Copy link
Contributor

Merged the initial document.
Please process any remaining questions in smaller documents that modify the original one.

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.

10 participants