Separate sync and async app webhooks (#1635)

* Separate sync and async app webhooks

* Remove create webhook page and cleanups

* Update util

* Auto unselect events when anyEvent choice is selected

* Update test snapshots
This commit is contained in:
Dawid Tarasiuk 2021-12-13 15:43:30 +01:00 committed by GitHub
parent e875b02adc
commit 147d8c89cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1722 additions and 12976 deletions

View file

@ -8046,10 +8046,6 @@
"context": "webhooks section name", "context": "webhooks section name",
"string": "Webhooks" "string": "Webhooks"
}, },
"src_dot_webhooks_dot_components_dot_WebhookCreatePage_dot_3493926696": {
"context": "header",
"string": "Create Webhook"
},
"src_dot_webhooks_dot_components_dot_WebhookDeleteDialog_dot_216945727": { "src_dot_webhooks_dot_components_dot_WebhookDeleteDialog_dot_216945727": {
"context": "delete webhook", "context": "delete webhook",
"string": "Are you sure you want to delete this webhook?" "string": "Are you sure you want to delete this webhook?"
@ -8062,238 +8058,78 @@
"context": "dialog header", "context": "dialog header",
"string": "Delete Webhook" "string": "Delete Webhook"
}, },
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1013231747": { "src_dot_webhooks_dot_components_dot_WebhookDetailsPage_dot_header": {
"context": "event",
"string": "Confirm payment"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1087314240": {
"context": "event",
"string": "Checkout updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1366770851": {
"context": "event",
"string": "Order confirmed"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1368317066": {
"context": "event",
"string": "Invoice deleted"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1368438665": {
"context": "event",
"string": "Shipping methods for checkout listed"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1436364351": {
"context": "section header",
"string": "Events"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_144149815": {
"context": "event",
"string": "Product variant deleted"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1564545489": {
"context": "event",
"string": "Draft order deleted"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1594316266": {
"context": "event",
"string": "Fulfillment canceled"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1606361075": {
"context": "event",
"string": "Order updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1616635110": {
"context": "event",
"string": "User notified"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_1624692657": {
"context": "event",
"string": "Refund payment"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2126893364": {
"context": "event",
"string": "Product variant updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2240725235": {
"context": "event",
"string": "Checkout created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2315238863": {
"context": "event",
"string": "Product variant out of stock"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2328272362": {
"context": "event",
"string": "Sale deleted"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2454751033": {
"context": "event",
"string": "All events"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2745028894": {
"context": "event",
"string": "Page deleted"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2862596150": {
"context": "event",
"string": "Invoice sent"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_2899821092": {
"context": "event",
"string": "Product created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3051538277": {
"context": "webhook events",
"string": "Expand or restrict webhooks permissions to register certain events in Saleor system."
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3285325968": {
"context": "event",
"string": "Product variant created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3316426878": {
"context": "event",
"string": "Product updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3345061702": {
"context": "event",
"string": "Order fully paid"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_337693660": {
"context": "event",
"string": "Void payment"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3400883706": {
"context": "event",
"string": "Order created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3418398751": {
"context": "event",
"string": "Draft order updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_36047288": {
"context": "event",
"string": "Translation created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3610454973": {
"context": "event",
"string": "Draft order created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3617444329": {
"context": "event",
"string": "Order cancelled"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3618648517": {
"context": "event",
"string": "Page updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3671033983": {
"context": "event",
"string": "Product deleted"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3742472683": {
"context": "event",
"string": "Capture payment"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3907151399": {
"context": "event",
"string": "Order fulfilled"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3970234993": {
"context": "event",
"string": "Customer created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_4053089191": {
"context": "event",
"string": "List payment gateways"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_4175628606": {
"context": "event",
"string": "Sale created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_4186057882": {
"context": "event",
"string": "Invoice requested"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_4281441551": {
"context": "event",
"string": "Fulfillment created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_4292269645": {
"context": "event",
"string": "Process payment"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_482477254": {
"context": "event",
"string": "Translation updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_523513128": {
"context": "event",
"string": "Sale updated"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_679080833": {
"context": "event",
"string": "Page created"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_69923590": {
"context": "event",
"string": "Product variant back in stock"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_759562905": {
"context": "event",
"string": "Authorize payment"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_787792649": {
"context": "event",
"string": "Customer updated"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_1089569085": {
"context": "webhook",
"string": "Secret Key"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_1690209105": {
"context": "webhook",
"string": "Target URL"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_1826224431": {
"context": "webhook",
"string": "Webhook Name"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_330298209": {
"context": "section header",
"string": "Webhook Information"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_3763861707": {
"context": "webhook target url help text",
"string": "This URL will receive webhook POST requests"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_3809115222": {
"context": "webhook secret key help text",
"string": "secret key is used to create a hash signature with each payload. *optional field"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_4194304040": {
"context": "webhook specific information",
"string": "Webhook specific information"
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_2772025990": {
"context": "webhooks active",
"string": "Webhook is active"
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_313090629": {
"context": "webhook active",
"string": "If you want to disable this webhook please uncheck the box below."
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_596557805": {
"context": "section header",
"string": "Webhook Status"
},
"src_dot_webhooks_dot_components_dot_WebhooksDetailsPage_dot_1595053355": {
"context": "header", "context": "header",
"string": "Unnamed Webhook Details" "string": "Unnamed Webhook Details"
}, },
"src_dot_webhooks_dot_components_dot_WebhooksDetailsPage_dot_408706360": { "src_dot_webhooks_dot_components_dot_WebhookDetailsPage_dot_headerCreate": {
"context": "header",
"string": "Create Webhook"
},
"src_dot_webhooks_dot_components_dot_WebhookDetailsPage_dot_headerNamed": {
"context": "header", "context": "header",
"string": "{webhookName} Details" "string": "{webhookName} Details"
}, },
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_assignPermissionsToAsynchronousEvents": {
"context": "section description",
"string": "Assign permissions to register asynchronous events for this webhook."
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_assignPermissionsToSynchronousEvents": {
"context": "section description",
"string": "Assign permissions to register synchronous events for this webhook."
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_asynchronousEvents": {
"context": "section subheader",
"string": "Asynchronous events"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_events": {
"context": "section header",
"string": "Events"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_registeredEvents": {
"context": "input label",
"string": "Registered events"
},
"src_dot_webhooks_dot_components_dot_WebhookEvents_dot_synchronousEvents": {
"context": "section subheader",
"string": "Synchronous events"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_secretKey": {
"context": "webhook input label",
"string": "Secret Key"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_secretKeyDescription": {
"context": "webhook input help text",
"string": "secret key is used to create a hash signature with each payload. *optional field"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_targetUrl": {
"context": "webhook input label",
"string": "Target URL"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_targetUrlDescription": {
"context": "webhook input help text",
"string": "This URL will receive webhook POST requests"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_webhookInformation": {
"context": "section header",
"string": "Webhook Information"
},
"src_dot_webhooks_dot_components_dot_WebhookInfo_dot_webhookName": {
"context": "webhook input label",
"string": "Webhook Name"
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_webhookActive": {
"context": "webhooks active label",
"string": "Webhook is active"
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_webhookActiveDescription": {
"context": "webhook active description",
"string": "If you want to disable this webhook please uncheck the box below."
},
"src_dot_webhooks_dot_components_dot_WebhookStatus_dot_webhookStatus": {
"context": "section header",
"string": "Webhook Status"
},
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_1153324159": { "src_dot_webhooks_dot_components_dot_WebhooksList_dot_1153324159": {
"string": "No webhooks found" "string": "No webhooks found"
}, },

View file

@ -804,6 +804,12 @@ type BulkStockError {
index: Int index: Int
} }
input CardInput {
code: String!
cvc: String
money: MoneyInput!
}
input CatalogueInput { input CatalogueInput {
products: [ID] products: [ID]
categories: [ID] categories: [ID]
@ -1108,6 +1114,7 @@ type CheckoutError {
message: String message: String
code: CheckoutErrorCode! code: CheckoutErrorCode!
variants: [ID!] variants: [ID!]
lines: [ID!]
addressType: AddressTypeEnum addressType: AddressTypeEnum
} }
@ -1139,6 +1146,14 @@ enum CheckoutErrorCode {
UNAVAILABLE_VARIANT_IN_CHANNEL UNAVAILABLE_VARIANT_IN_CHANNEL
} }
input CheckoutFilterInput {
customer: String
created: DateRangeInput
search: String
metadata: [MetadataFilter]
channels: [ID]
}
type CheckoutLanguageCodeUpdate { type CheckoutLanguageCodeUpdate {
checkout: Checkout checkout: Checkout
checkoutErrors: [CheckoutError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.") checkoutErrors: [CheckoutError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.")
@ -1181,6 +1196,11 @@ type CheckoutLinesAdd {
errors: [CheckoutError!]! errors: [CheckoutError!]!
} }
type CheckoutLinesDelete {
checkout: Checkout
errors: [CheckoutError!]!
}
type CheckoutLinesUpdate { type CheckoutLinesUpdate {
checkout: Checkout checkout: Checkout
checkoutErrors: [CheckoutError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.") checkoutErrors: [CheckoutError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.")
@ -1212,6 +1232,17 @@ type CheckoutShippingMethodUpdate {
errors: [CheckoutError!]! errors: [CheckoutError!]!
} }
enum CheckoutSortField {
CREATION_DATE
CUSTOMER
PAYMENT
}
input CheckoutSortingInput {
direction: OrderDirection!
field: CheckoutSortField!
}
type ChoiceValue { type ChoiceValue {
raw: String raw: String
verbose: String verbose: String
@ -3713,6 +3744,11 @@ type Money {
amount: Float! amount: Float!
} }
input MoneyInput {
currency: String!
amount: PositiveDecimal!
}
type MoneyRange { type MoneyRange {
start: Money start: Money
stop: Money stop: Money
@ -3812,6 +3848,7 @@ type Mutation {
paymentRefund(amount: PositiveDecimal, paymentId: ID!): PaymentRefund paymentRefund(amount: PositiveDecimal, paymentId: ID!): PaymentRefund
paymentVoid(paymentId: ID!): PaymentVoid paymentVoid(paymentId: ID!): PaymentVoid
paymentInitialize(channel: String, gateway: String!, paymentData: JSONString): PaymentInitialize paymentInitialize(channel: String, gateway: String!, paymentData: JSONString): PaymentInitialize
paymentCheckBalance(input: PaymentCheckBalanceInput!): PaymentCheckBalance
pageCreate(input: PageCreateInput!): PageCreate pageCreate(input: PageCreateInput!): PageCreate
pageDelete(id: ID!): PageDelete pageDelete(id: ID!): PageDelete
pageBulkDelete(ids: [ID]!): PageBulkDelete pageBulkDelete(ids: [ID]!): PageBulkDelete
@ -3914,7 +3951,8 @@ type Mutation {
checkoutCustomerAttach(checkoutId: ID, customerId: ID, token: UUID): CheckoutCustomerAttach checkoutCustomerAttach(checkoutId: ID, customerId: ID, token: UUID): CheckoutCustomerAttach
checkoutCustomerDetach(checkoutId: ID, token: UUID): CheckoutCustomerDetach checkoutCustomerDetach(checkoutId: ID, token: UUID): CheckoutCustomerDetach
checkoutEmailUpdate(checkoutId: ID, email: String!, token: UUID): CheckoutEmailUpdate checkoutEmailUpdate(checkoutId: ID, email: String!, token: UUID): CheckoutEmailUpdate
checkoutLineDelete(checkoutId: ID, lineId: ID, token: UUID): CheckoutLineDelete checkoutLineDelete(checkoutId: ID, lineId: ID, token: UUID): CheckoutLineDelete @deprecated(reason: "DEPRECATED: Will be removed in Saleor 4.0. Use `checkoutLinesDelete` instead.")
checkoutLinesDelete(linesIds: [ID]!, token: UUID!): CheckoutLinesDelete
checkoutLinesAdd(checkoutId: ID, lines: [CheckoutLineInput]!, token: UUID): CheckoutLinesAdd checkoutLinesAdd(checkoutId: ID, lines: [CheckoutLineInput]!, token: UUID): CheckoutLinesAdd
checkoutLinesUpdate(checkoutId: ID, lines: [CheckoutLineInput]!, token: UUID): CheckoutLinesUpdate checkoutLinesUpdate(checkoutId: ID, lines: [CheckoutLineInput]!, token: UUID): CheckoutLinesUpdate
checkoutRemovePromoCode(checkoutId: ID, promoCode: String, promoCodeId: ID, token: UUID): CheckoutRemovePromoCode checkoutRemovePromoCode(checkoutId: ID, promoCode: String, promoCodeId: ID, token: UUID): CheckoutRemovePromoCode
@ -4881,6 +4919,19 @@ enum PaymentChargeStatusEnum {
CANCELLED CANCELLED
} }
type PaymentCheckBalance {
data: JSONString
paymentErrors: [PaymentError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.")
errors: [PaymentError!]!
}
input PaymentCheckBalanceInput {
gatewayId: String!
method: String!
channel: String!
card: CardInput!
}
type PaymentCountableConnection { type PaymentCountableConnection {
pageInfo: PageInfo! pageInfo: PageInfo!
edges: [PaymentCountableEdge!]! edges: [PaymentCountableEdge!]!
@ -4912,6 +4963,7 @@ enum PaymentErrorCode {
PAYMENT_ERROR PAYMENT_ERROR
NOT_SUPPORTED_GATEWAY NOT_SUPPORTED_GATEWAY
CHANNEL_INACTIVE CHANNEL_INACTIVE
BALANCE_CHECK_ERROR
} }
input PaymentFilterInput { input PaymentFilterInput {
@ -5902,7 +5954,7 @@ type Query {
exportFiles(filter: ExportFileFilterInput, sortBy: ExportFileSortingInput, before: String, after: String, first: Int, last: Int): ExportFileCountableConnection exportFiles(filter: ExportFileFilterInput, sortBy: ExportFileSortingInput, before: String, after: String, first: Int, last: Int): ExportFileCountableConnection
taxTypes: [TaxType] taxTypes: [TaxType]
checkout(token: UUID): Checkout checkout(token: UUID): Checkout
checkouts(channel: String, before: String, after: String, first: Int, last: Int): CheckoutCountableConnection checkouts(sortBy: CheckoutSortingInput, filter: CheckoutFilterInput, channel: String, before: String, after: String, first: Int, last: Int): CheckoutCountableConnection
checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection
channel(id: ID): Channel channel(id: ID): Channel
channels: [Channel!] channels: [Channel!]
@ -7207,7 +7259,9 @@ type Webhook implements Node {
isActive: Boolean! isActive: Boolean!
secretKey: String secretKey: String
id: ID! id: ID!
events: [WebhookEvent!]! events: [WebhookEvent!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `asyncEvents` or `syncEvents` instead.")
syncEvents: [WebhookEventSync!]!
asyncEvents: [WebhookEventAsync!]!
app: App! app: App!
} }
@ -7221,6 +7275,8 @@ input WebhookCreateInput {
name: String name: String
targetUrl: String targetUrl: String
events: [WebhookEventTypeEnum] events: [WebhookEventTypeEnum]
asyncEvents: [WebhookEventTypeAsync!]
syncEvents: [WebhookEventTypeSync!]
app: ID app: ID
isActive: Boolean isActive: Boolean
secretKey: String secretKey: String
@ -7251,6 +7307,55 @@ type WebhookEvent {
name: String! name: String!
} }
type WebhookEventAsync {
eventType: WebhookEventTypeAsync!
name: String!
}
type WebhookEventSync {
eventType: WebhookEventTypeSync!
name: String!
}
enum WebhookEventTypeAsync {
ANY_EVENTS
ORDER_CREATED
ORDER_CONFIRMED
ORDER_FULLY_PAID
ORDER_UPDATED
ORDER_CANCELLED
ORDER_FULFILLED
DRAFT_ORDER_CREATED
DRAFT_ORDER_UPDATED
DRAFT_ORDER_DELETED
SALE_CREATED
SALE_UPDATED
SALE_DELETED
INVOICE_REQUESTED
INVOICE_DELETED
INVOICE_SENT
CUSTOMER_CREATED
CUSTOMER_UPDATED
PRODUCT_CREATED
PRODUCT_UPDATED
PRODUCT_DELETED
PRODUCT_VARIANT_CREATED
PRODUCT_VARIANT_UPDATED
PRODUCT_VARIANT_DELETED
PRODUCT_VARIANT_OUT_OF_STOCK
PRODUCT_VARIANT_BACK_IN_STOCK
CHECKOUT_CREATED
CHECKOUT_UPDATED
FULFILLMENT_CREATED
FULFILLMENT_CANCELED
NOTIFY_USER
PAGE_CREATED
PAGE_UPDATED
PAGE_DELETED
TRANSLATION_CREATED
TRANSLATION_UPDATED
}
enum WebhookEventTypeEnum { enum WebhookEventTypeEnum {
ANY_EVENTS ANY_EVENTS
ORDER_CREATED ORDER_CREATED
@ -7298,6 +7403,17 @@ enum WebhookEventTypeEnum {
TRANSLATION_UPDATED TRANSLATION_UPDATED
} }
enum WebhookEventTypeSync {
PAYMENT_AUTHORIZE
PAYMENT_CAPTURE
PAYMENT_CONFIRM
PAYMENT_LIST_GATEWAYS
PAYMENT_PROCESS
PAYMENT_REFUND
PAYMENT_VOID
SHIPPING_LIST_METHODS_FOR_CHECKOUT
}
enum WebhookSampleEventTypeEnum { enum WebhookSampleEventTypeEnum {
ORDER_CREATED ORDER_CREATED
ORDER_CONFIRMED ORDER_CONFIRMED
@ -7354,6 +7470,8 @@ input WebhookUpdateInput {
name: String name: String
targetUrl: String targetUrl: String
events: [WebhookEventTypeEnum] events: [WebhookEventTypeEnum]
asyncEvents: [WebhookEventTypeAsync!]
syncEvents: [WebhookEventTypeSync!]
app: ID app: ID
isActive: Boolean isActive: Boolean
secretKey: String secretKey: String

File diff suppressed because it is too large Load diff

View file

@ -1817,6 +1817,45 @@ export enum WebhookErrorCode {
UNIQUE = "UNIQUE", UNIQUE = "UNIQUE",
} }
export enum WebhookEventTypeAsync {
ANY_EVENTS = "ANY_EVENTS",
CHECKOUT_CREATED = "CHECKOUT_CREATED",
CHECKOUT_UPDATED = "CHECKOUT_UPDATED",
CUSTOMER_CREATED = "CUSTOMER_CREATED",
CUSTOMER_UPDATED = "CUSTOMER_UPDATED",
DRAFT_ORDER_CREATED = "DRAFT_ORDER_CREATED",
DRAFT_ORDER_DELETED = "DRAFT_ORDER_DELETED",
DRAFT_ORDER_UPDATED = "DRAFT_ORDER_UPDATED",
FULFILLMENT_CANCELED = "FULFILLMENT_CANCELED",
FULFILLMENT_CREATED = "FULFILLMENT_CREATED",
INVOICE_DELETED = "INVOICE_DELETED",
INVOICE_REQUESTED = "INVOICE_REQUESTED",
INVOICE_SENT = "INVOICE_SENT",
NOTIFY_USER = "NOTIFY_USER",
ORDER_CANCELLED = "ORDER_CANCELLED",
ORDER_CONFIRMED = "ORDER_CONFIRMED",
ORDER_CREATED = "ORDER_CREATED",
ORDER_FULFILLED = "ORDER_FULFILLED",
ORDER_FULLY_PAID = "ORDER_FULLY_PAID",
ORDER_UPDATED = "ORDER_UPDATED",
PAGE_CREATED = "PAGE_CREATED",
PAGE_DELETED = "PAGE_DELETED",
PAGE_UPDATED = "PAGE_UPDATED",
PRODUCT_CREATED = "PRODUCT_CREATED",
PRODUCT_DELETED = "PRODUCT_DELETED",
PRODUCT_UPDATED = "PRODUCT_UPDATED",
PRODUCT_VARIANT_BACK_IN_STOCK = "PRODUCT_VARIANT_BACK_IN_STOCK",
PRODUCT_VARIANT_CREATED = "PRODUCT_VARIANT_CREATED",
PRODUCT_VARIANT_DELETED = "PRODUCT_VARIANT_DELETED",
PRODUCT_VARIANT_OUT_OF_STOCK = "PRODUCT_VARIANT_OUT_OF_STOCK",
PRODUCT_VARIANT_UPDATED = "PRODUCT_VARIANT_UPDATED",
SALE_CREATED = "SALE_CREATED",
SALE_DELETED = "SALE_DELETED",
SALE_UPDATED = "SALE_UPDATED",
TRANSLATION_CREATED = "TRANSLATION_CREATED",
TRANSLATION_UPDATED = "TRANSLATION_UPDATED",
}
export enum WebhookEventTypeEnum { export enum WebhookEventTypeEnum {
ANY_EVENTS = "ANY_EVENTS", ANY_EVENTS = "ANY_EVENTS",
CHECKOUT_CREATED = "CHECKOUT_CREATED", CHECKOUT_CREATED = "CHECKOUT_CREATED",
@ -1864,6 +1903,17 @@ export enum WebhookEventTypeEnum {
TRANSLATION_UPDATED = "TRANSLATION_UPDATED", TRANSLATION_UPDATED = "TRANSLATION_UPDATED",
} }
export enum WebhookEventTypeSync {
PAYMENT_AUTHORIZE = "PAYMENT_AUTHORIZE",
PAYMENT_CAPTURE = "PAYMENT_CAPTURE",
PAYMENT_CONFIRM = "PAYMENT_CONFIRM",
PAYMENT_LIST_GATEWAYS = "PAYMENT_LIST_GATEWAYS",
PAYMENT_PROCESS = "PAYMENT_PROCESS",
PAYMENT_REFUND = "PAYMENT_REFUND",
PAYMENT_VOID = "PAYMENT_VOID",
SHIPPING_LIST_METHODS_FOR_CHECKOUT = "SHIPPING_LIST_METHODS_FOR_CHECKOUT",
}
export enum WeightUnitsEnum { export enum WeightUnitsEnum {
G = "G", G = "G",
KG = "KG", KG = "KG",
@ -2963,6 +3013,8 @@ export interface WebhookCreateInput {
name?: string | null; name?: string | null;
targetUrl?: string | null; targetUrl?: string | null;
events?: (WebhookEventTypeEnum | null)[] | null; events?: (WebhookEventTypeEnum | null)[] | null;
asyncEvents?: WebhookEventTypeAsync[] | null;
syncEvents?: WebhookEventTypeSync[] | null;
app?: string | null; app?: string | null;
isActive?: boolean | null; isActive?: boolean | null;
secretKey?: string | null; secretKey?: string | null;
@ -2972,6 +3024,8 @@ export interface WebhookUpdateInput {
name?: string | null; name?: string | null;
targetUrl?: string | null; targetUrl?: string | null;
events?: (WebhookEventTypeEnum | null)[] | null; events?: (WebhookEventTypeEnum | null)[] | null;
asyncEvents?: WebhookEventTypeAsync[] | null;
syncEvents?: WebhookEventTypeSync[] | null;
app?: string | null; app?: string | null;
isActive?: boolean | null; isActive?: boolean | null;
secretKey?: string | null; secretKey?: string | null;

View file

@ -1,99 +0,0 @@
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer";
import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import Savebar from "@saleor/components/Savebar";
import { WebhookErrorFragment } from "@saleor/fragments/types/WebhookErrorFragment";
import { Backlink } from "@saleor/macaw-ui";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
import WebhookInfo from "@saleor/webhooks/components/WebhookInfo";
import WebhookStatus from "@saleor/webhooks/components/WebhookStatus";
import React from "react";
import { useIntl } from "react-intl";
export interface FormData {
events: WebhookEventTypeEnum[];
isActive: boolean;
name: string;
secretKey: string | null;
targetUrl: string;
allEvents: boolean;
}
export interface WebhookCreatePageProps {
appName: string;
disabled: boolean;
errors: WebhookErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
onSubmit: (data: FormData) => void;
}
const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({
appName = "",
disabled,
errors,
saveButtonBarState,
onBack,
onSubmit
}) => {
const intl = useIntl();
const initialForm: FormData = {
allEvents: false,
events: [],
isActive: false,
name: "",
secretKey: "",
targetUrl: ""
};
return (
<Form initial={initialForm} onSubmit={onSubmit}>
{({ data, hasChanged, submit, change }) => (
<Container>
<Backlink onClick={onBack}>{appName}</Backlink>
<PageHeader
title={intl.formatMessage({
defaultMessage: "Create Webhook",
description: "header"
})}
/>
<Grid>
<div>
<WebhookInfo
data={data}
disabled={disabled}
errors={errors}
onChange={change}
/>
</div>
<div>
<WebhookEvents
data={data}
disabled={disabled}
onChange={change}
/>
<FormSpacer />
<WebhookStatus
data={data.isActive}
disabled={disabled}
onChange={change}
/>
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}
/>
</Container>
)}
</Form>
);
};
WebhookCreatePage.displayName = "WebhookCreatePage";
export default WebhookCreatePage;

View file

@ -1,29 +0,0 @@
import Decorator from "@saleor/storybook/Decorator";
import { WebhookErrorCode } from "@saleor/types/globalTypes";
import { storiesOf } from "@storybook/react";
import React from "react";
import WebhookCreatePage, { WebhookCreatePageProps } from "./WebhookCreatePage";
const props: WebhookCreatePageProps = {
appName: "App",
disabled: false,
errors: [],
onBack: () => undefined,
onSubmit: () => undefined,
saveButtonBarState: "default"
};
storiesOf("Views / Apps / Webhooks / Create webhook", module)
.addDecorator(Decorator)
.add("default", () => <WebhookCreatePage {...props} />)
.add("loading", () => <WebhookCreatePage {...props} disabled={true} />)
.add("form errors", () => (
<WebhookCreatePage
{...props}
errors={["name", "targetUrl", "secretKey", null].map(field => ({
__typename: "WebhookError",
code: WebhookErrorCode.INVALID,
field
}))}
/>
));

View file

@ -1,2 +0,0 @@
export { default } from "./WebhookCreatePage";
export * from "./WebhookCreatePage";

View file

@ -4,11 +4,11 @@ import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import { webhook } from "../../fixtures"; import { webhook } from "../../fixtures";
import WebhooksDetailsPage, { import WebhookDetailsPage, {
WebhooksDetailsPageProps WebhookDetailsPageProps
} from "./WebhooksDetailsPage"; } from "./WebhookDetailsPage";
const props: WebhooksDetailsPageProps = { const props: WebhookDetailsPageProps = {
appName: "app", appName: "app",
disabled: false, disabled: false,
errors: [], errors: [],
@ -19,15 +19,16 @@ const props: WebhooksDetailsPageProps = {
}; };
storiesOf("Views / Apps / Webhooks / Webhook details", module) storiesOf("Views / Apps / Webhooks / Webhook details", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <WebhooksDetailsPage {...props} />) .add("default", () => <WebhookDetailsPage {...props} />)
.add("undefined", () => <WebhookDetailsPage {...props} webhook={undefined} />)
.add("unnamed", () => ( .add("unnamed", () => (
<WebhooksDetailsPage {...props} webhook={{ ...webhook, name: null }} /> <WebhookDetailsPage {...props} webhook={{ ...webhook, name: null }} />
)) ))
.add("loading", () => ( .add("loading", () => (
<WebhooksDetailsPage {...props} webhook={undefined} disabled={true} /> <WebhookDetailsPage {...props} webhook={undefined} disabled={true} />
)) ))
.add("form errors", () => ( .add("form errors", () => (
<WebhooksDetailsPage <WebhookDetailsPage
{...props} {...props}
errors={["name", "targetUrl", "secretKey", null].map(field => ({ errors={["name", "targetUrl", "secretKey", null].map(field => ({
__typename: "WebhookError", __typename: "WebhookError",

View file

@ -0,0 +1,134 @@
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer";
import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import Savebar from "@saleor/components/Savebar";
import { WebhookErrorFragment } from "@saleor/fragments/types/WebhookErrorFragment";
import { Backlink } from "@saleor/macaw-ui";
import {
WebhookEventTypeAsync,
WebhookEventTypeSync
} from "@saleor/types/globalTypes";
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
import WebhookInfo from "@saleor/webhooks/components/WebhookInfo";
import WebhookStatus from "@saleor/webhooks/components/WebhookStatus";
import {
createAsyncEventsSelectHandler,
createSyncEventsSelectHandler
} from "@saleor/webhooks/handlers";
import { WebhookDetails_webhook } from "@saleor/webhooks/types/WebhookDetails";
import {
mapAsyncEventsToChoices,
mapSyncEventsToChoices
} from "@saleor/webhooks/utils";
import React from "react";
import { useIntl } from "react-intl";
import { getHeaderTitle } from "./messages";
export interface FormData {
syncEvents: WebhookEventTypeSync[];
asyncEvents: WebhookEventTypeAsync[];
isActive: boolean;
name: string;
secretKey: string | null;
targetUrl: string;
}
export interface WebhookDetailsPageProps {
appName: string;
disabled: boolean;
errors: WebhookErrorFragment[];
webhook?: WebhookDetails_webhook;
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
onSubmit: (data: FormData) => void;
}
const WebhookDetailsPage: React.FC<WebhookDetailsPageProps> = ({
appName,
disabled,
errors,
webhook,
saveButtonBarState,
onBack,
onSubmit
}) => {
const intl = useIntl();
const initialForm: FormData = {
syncEvents: webhook?.syncEvents?.map(event => event.eventType) || [],
asyncEvents: webhook?.asyncEvents?.map(event => event.eventType) || [],
isActive: !!webhook?.isActive,
name: webhook?.name || "",
secretKey: webhook?.secretKey || "",
targetUrl: webhook?.targetUrl || ""
};
return (
<Form initial={initialForm} onSubmit={onSubmit}>
{({ data, hasChanged, submit, change }) => {
const syncEventsChoices = disabled
? []
: mapSyncEventsToChoices(Object.values(WebhookEventTypeSync));
const asyncEventsChoices = disabled
? []
: mapAsyncEventsToChoices(
Object.values(WebhookEventTypeAsync),
data.asyncEvents
);
const handleSyncEventsSelect = createSyncEventsSelectHandler(
change,
data.syncEvents
);
const handleAsyncEventsSelect = createAsyncEventsSelectHandler(
change,
data.asyncEvents
);
return (
<Container>
<Backlink onClick={onBack}>{appName}</Backlink>
<PageHeader title={getHeaderTitle(intl, webhook)} />
<Grid>
<div>
<WebhookInfo
data={data}
disabled={disabled}
errors={errors}
onChange={change}
/>
</div>
<div>
<WebhookEvents
data={data}
syncEventsChoices={syncEventsChoices}
asyncEventsChoices={asyncEventsChoices}
onSyncEventChange={handleSyncEventsSelect}
onAsyncEventChange={handleAsyncEventsSelect}
/>
<FormSpacer />
<WebhookStatus
data={data.isActive}
disabled={disabled}
onChange={change}
/>
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}
/>
</Container>
);
}}
</Form>
);
};
WebhookDetailsPage.displayName = "WebhookDetailsPage";
export default WebhookDetailsPage;

View file

@ -0,0 +1,2 @@
export { default } from "./WebhookDetailsPage";
export * from "./WebhookDetailsPage";

View file

@ -0,0 +1,35 @@
import { getStringOrPlaceholder } from "@saleor/misc";
import { WebhookDetails_webhook } from "@saleor/webhooks/types/WebhookDetails";
import { isUnnamed } from "@saleor/webhooks/utils";
import { IntlShape } from "react-intl";
import { defineMessages } from "react-intl";
export const messages = defineMessages({
header: {
defaultMessage: "Unnamed Webhook Details",
description: "header"
},
headerNamed: {
defaultMessage: "{webhookName} Details",
description: "header"
},
headerCreate: {
defaultMessage: "Create Webhook",
description: "header"
}
});
export const getHeaderTitle = (
intl: IntlShape,
webhook?: WebhookDetails_webhook
) => {
if (!webhook) {
return intl.formatMessage(messages.headerCreate);
}
if (isUnnamed(webhook)) {
return intl.formatMessage(messages.header);
}
return intl.formatMessage(messages.headerNamed, {
webhookName: getStringOrPlaceholder(webhook?.name)
});
};

View file

@ -1,294 +1,95 @@
import { Card, CardContent, Typography } from "@material-ui/core"; import { Card, CardContent, Typography } from "@material-ui/core";
import VerticalSpacer from "@saleor/apps/components/VerticalSpacer";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import MultiAutocompleteSelectField, {
MultiAutocompleteChoiceType
} from "@saleor/components/MultiAutocompleteSelectField";
import { ChangeEvent } from "@saleor/hooks/useForm"; import { ChangeEvent } from "@saleor/hooks/useForm";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes"; import {
import { toggle } from "@saleor/utils/lists"; WebhookEventTypeAsync,
WebhookEventTypeSync
} from "@saleor/types/globalTypes";
import {
mapAsyncEventsToChoices,
mapSyncEventsToChoices
} from "@saleor/webhooks/utils";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { messages } from "./messages";
interface WebhookEventsProps { interface WebhookEventsProps {
data: { data: {
allEvents: boolean; syncEvents: WebhookEventTypeSync[];
events: string[]; asyncEvents: WebhookEventTypeAsync[];
}; };
disabled: boolean; syncEventsChoices: MultiAutocompleteChoiceType[];
onChange: (event: ChangeEvent, cb?: () => void) => void; asyncEventsChoices: MultiAutocompleteChoiceType[];
onSyncEventChange: (event: ChangeEvent) => void;
onAsyncEventChange: (event: ChangeEvent) => void;
} }
const WebhookEvents: React.FC<WebhookEventsProps> = ({ const WebhookEvents: React.FC<WebhookEventsProps> = ({
data, data,
disabled, syncEventsChoices,
onChange asyncEventsChoices,
onSyncEventChange,
onAsyncEventChange
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const eventsEnum = Object.values(WebhookEventTypeEnum);
const translatedEvents: Record<WebhookEventTypeEnum, string> = {
[WebhookEventTypeEnum.ANY_EVENTS]: intl.formatMessage({
defaultMessage: "All events",
description: "event"
}),
[WebhookEventTypeEnum.CHECKOUT_CREATED]: intl.formatMessage({
defaultMessage: "Checkout created",
description: "event"
}),
[WebhookEventTypeEnum.CHECKOUT_UPDATED]: intl.formatMessage({
defaultMessage: "Checkout updated",
description: "event"
}),
[WebhookEventTypeEnum.CUSTOMER_CREATED]: intl.formatMessage({
defaultMessage: "Customer created",
description: "event"
}),
[WebhookEventTypeEnum.CUSTOMER_UPDATED]: intl.formatMessage({
defaultMessage: "Customer updated",
description: "event"
}),
[WebhookEventTypeEnum.CHECKOUT_CREATED]: intl.formatMessage({
defaultMessage: "Checkout created",
description: "event"
}),
[WebhookEventTypeEnum.CHECKOUT_UPDATED]: intl.formatMessage({
defaultMessage: "Checkout updated",
description: "event"
}),
[WebhookEventTypeEnum.ORDER_CANCELLED]: intl.formatMessage({
defaultMessage: "Order cancelled",
description: "event"
}),
[WebhookEventTypeEnum.ORDER_CREATED]: intl.formatMessage({
defaultMessage: "Order created",
description: "event"
}),
[WebhookEventTypeEnum.ORDER_CONFIRMED]: intl.formatMessage({
defaultMessage: "Order confirmed",
description: "event"
}),
[WebhookEventTypeEnum.ORDER_FULFILLED]: intl.formatMessage({
defaultMessage: "Order fulfilled",
description: "event"
}),
[WebhookEventTypeEnum.ORDER_FULLY_PAID]: intl.formatMessage({
defaultMessage: "Order fully paid",
description: "event"
}),
[WebhookEventTypeEnum.ORDER_UPDATED]: intl.formatMessage({
defaultMessage: "Order updated",
description: "event"
}),
[WebhookEventTypeEnum.DRAFT_ORDER_CREATED]: intl.formatMessage({
defaultMessage: "Draft order created",
description: "event"
}),
[WebhookEventTypeEnum.DRAFT_ORDER_DELETED]: intl.formatMessage({
defaultMessage: "Draft order deleted",
description: "event"
}),
[WebhookEventTypeEnum.DRAFT_ORDER_UPDATED]: intl.formatMessage({
defaultMessage: "Draft order updated",
description: "event"
}),
[WebhookEventTypeEnum.SALE_CREATED]: intl.formatMessage({
defaultMessage: "Sale created",
description: "event"
}),
[WebhookEventTypeEnum.SALE_UPDATED]: intl.formatMessage({
defaultMessage: "Sale updated",
description: "event"
}),
[WebhookEventTypeEnum.SALE_DELETED]: intl.formatMessage({
defaultMessage: "Sale deleted",
description: "event"
}),
[WebhookEventTypeEnum.PAGE_CREATED]: intl.formatMessage({
defaultMessage: "Page created",
description: "event"
}),
[WebhookEventTypeEnum.PAGE_DELETED]: intl.formatMessage({
defaultMessage: "Page deleted",
description: "event"
}),
[WebhookEventTypeEnum.PAGE_UPDATED]: intl.formatMessage({
defaultMessage: "Page updated",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_AUTHORIZE]: intl.formatMessage({
defaultMessage: "Authorize payment",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_CAPTURE]: intl.formatMessage({
defaultMessage: "Capture payment",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_CONFIRM]: intl.formatMessage({
defaultMessage: "Confirm payment",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_LIST_GATEWAYS]: intl.formatMessage({
defaultMessage: "List payment gateways",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_PROCESS]: intl.formatMessage({
defaultMessage: "Process payment",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_REFUND]: intl.formatMessage({
defaultMessage: "Refund payment",
description: "event"
}),
[WebhookEventTypeEnum.PAYMENT_VOID]: intl.formatMessage({
defaultMessage: "Void payment",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_CREATED]: intl.formatMessage({
defaultMessage: "Product created",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_UPDATED]: intl.formatMessage({
defaultMessage: "Product updated",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_DELETED]: intl.formatMessage({
defaultMessage: "Product deleted",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_BACK_IN_STOCK]: intl.formatMessage({
defaultMessage: "Product variant back in stock",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_OUT_OF_STOCK]: intl.formatMessage({
defaultMessage: "Product variant out of stock",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_CREATED]: intl.formatMessage({
defaultMessage: "Product variant created",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_UPDATED]: intl.formatMessage({
defaultMessage: "Product variant updated",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_DELETED]: intl.formatMessage({
defaultMessage: "Product variant deleted",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_BACK_IN_STOCK]: intl.formatMessage({
defaultMessage: "Product variant back in stock",
description: "event"
}),
[WebhookEventTypeEnum.PRODUCT_VARIANT_OUT_OF_STOCK]: intl.formatMessage({
defaultMessage: "Product variant out of stock",
description: "event"
}),
[WebhookEventTypeEnum.SHIPPING_LIST_METHODS_FOR_CHECKOUT]: intl.formatMessage(
{
defaultMessage: "Shipping methods for checkout listed",
description: "event"
}
),
[WebhookEventTypeEnum.FULFILLMENT_CANCELED]: intl.formatMessage({
defaultMessage: "Fulfillment canceled",
description: "event"
}),
[WebhookEventTypeEnum.FULFILLMENT_CREATED]: intl.formatMessage({
defaultMessage: "Fulfillment created",
description: "event"
}),
[WebhookEventTypeEnum.FULFILLMENT_CANCELED]: intl.formatMessage({
defaultMessage: "Fulfillment canceled",
description: "event"
}),
[WebhookEventTypeEnum.INVOICE_REQUESTED]: intl.formatMessage({
defaultMessage: "Invoice requested",
description: "event"
}),
[WebhookEventTypeEnum.INVOICE_SENT]: intl.formatMessage({
defaultMessage: "Invoice sent",
description: "event"
}),
[WebhookEventTypeEnum.INVOICE_DELETED]: intl.formatMessage({
defaultMessage: "Invoice deleted",
description: "event"
}),
[WebhookEventTypeEnum.PAGE_CREATED]: intl.formatMessage({
defaultMessage: "Page created",
description: "event"
}),
[WebhookEventTypeEnum.PAGE_UPDATED]: intl.formatMessage({
defaultMessage: "Page updated",
description: "event"
}),
[WebhookEventTypeEnum.PAGE_DELETED]: intl.formatMessage({
defaultMessage: "Page deleted",
description: "event"
}),
[WebhookEventTypeEnum.NOTIFY_USER]: intl.formatMessage({
defaultMessage: "User notified",
description: "event"
}),
[WebhookEventTypeEnum.TRANSLATION_CREATED]: intl.formatMessage({
defaultMessage: "Translation created",
description: "event"
}),
[WebhookEventTypeEnum.TRANSLATION_UPDATED]: intl.formatMessage({
defaultMessage: "Translation updated",
description: "event"
}),
[WebhookEventTypeEnum.FULFILLMENT_CANCELED]: intl.formatMessage({
defaultMessage: "Translation updated",
description: "event"
})
};
const handleEventsChange = (event: ChangeEvent) =>
onChange({
target: {
name: "events",
value: toggle(event.target.name, data.events, (a, b) => a === b)
}
});
return ( return (
<Card> <Card>
<CardTitle <CardTitle title={intl.formatMessage(messages.events)} />
title={intl.formatMessage({
defaultMessage: "Events",
description: "section header"
})}
/>
<CardContent> <CardContent>
<Typography> <Typography variant="caption">
{intl.formatMessage({ <FormattedMessage {...messages.synchronousEvents} />
defaultMessage:
"Expand or restrict webhooks permissions to register certain events in Saleor system.",
description: "webhook events"
})}
</Typography> </Typography>
<ControlledCheckbox <VerticalSpacer />
checked={data.allEvents} <Typography variant="body1">
disabled={disabled} <FormattedMessage
label={translatedEvents.ANY_EVENTS} {...messages.assignPermissionsToSynchronousEvents}
name="allEvents" />
onChange={onChange} </Typography>
<VerticalSpacer />
<MultiAutocompleteSelectField
displayValues={mapSyncEventsToChoices(data.syncEvents)}
label={intl.formatMessage(messages.registeredEvents)}
choices={syncEventsChoices}
name="syncEvents"
value={data.syncEvents}
onChange={onSyncEventChange}
data-test="syncEvents"
testId="syncEvent"
/>
<VerticalSpacer spacing={2} />
<Hr />
<VerticalSpacer spacing={2} />
<Typography variant="caption">
<FormattedMessage {...messages.asynchronousEvents} />
</Typography>
<VerticalSpacer />
<Typography variant="body1">
<FormattedMessage
{...messages.assignPermissionsToAsynchronousEvents}
/>
</Typography>
<VerticalSpacer />
<MultiAutocompleteSelectField
displayValues={mapAsyncEventsToChoices(
data.asyncEvents,
data.asyncEvents
)}
label={intl.formatMessage(messages.registeredEvents)}
choices={asyncEventsChoices}
name="asyncEvents"
value={data.asyncEvents}
onChange={onAsyncEventChange}
data-test="asyncEvents"
testId="asyncEvent"
/> />
{!data.allEvents && (
<>
<Hr />
{eventsEnum.slice(1).map(event => (
<div key={event}>
<ControlledCheckbox
checked={data.events.includes(event)}
disabled={disabled}
label={translatedEvents[event]}
name={event}
onChange={handleEventsChange}
/>
</div>
))}
</>
)}
</CardContent> </CardContent>
</Card> </Card>
); );

View file

@ -0,0 +1,30 @@
import { defineMessages } from "react-intl";
export const messages = defineMessages({
events: {
defaultMessage: "Events",
description: "section header"
},
synchronousEvents: {
defaultMessage: "Synchronous events",
description: "section subheader"
},
asynchronousEvents: {
defaultMessage: "Asynchronous events",
description: "section subheader"
},
assignPermissionsToSynchronousEvents: {
defaultMessage:
"Assign permissions to register synchronous events for this webhook.",
description: "section description"
},
assignPermissionsToAsynchronousEvents: {
defaultMessage:
"Assign permissions to register asynchronous events for this webhook.",
description: "section description"
},
registeredEvents: {
defaultMessage: "Registered events",
description: "input label"
}
});

View file

@ -4,13 +4,13 @@ import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import { WebhookErrorFragment } from "@saleor/fragments/types/WebhookErrorFragment"; import { WebhookErrorFragment } from "@saleor/fragments/types/WebhookErrorFragment";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { makeStyles } from "@saleor/macaw-ui";
import { getFormErrors } from "@saleor/utils/errors"; import { getFormErrors } from "@saleor/utils/errors";
import getWebhookErrorMessage from "@saleor/utils/errors/webhooks"; import getWebhookErrorMessage from "@saleor/utils/errors/webhooks";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { FormData } from "../WebhooksDetailsPage"; import { FormData } from "../WebhookDetailsPage";
import { messages } from "./messages";
interface WebhookInfoProps { interface WebhookInfoProps {
data: FormData; data: FormData;
@ -19,51 +19,29 @@ interface WebhookInfoProps {
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
const useStyles = makeStyles(
() => ({
status: {
paddingTop: 20
},
title: {
fontSize: 16,
lineHeight: 1.9,
paddingBottom: 10
}
}),
{ name: "WebhookInfo" }
);
const WebhookInfo: React.FC<WebhookInfoProps> = ({ const WebhookInfo: React.FC<WebhookInfoProps> = ({
data, data,
disabled, disabled,
errors, errors,
onChange onChange
}) => { }) => {
const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["name", "targetUrl", "secretKey"], errors); const formErrors = getFormErrors(["name", "targetUrl", "secretKey"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle title={intl.formatMessage(messages.webhookInformation)} />
title={intl.formatMessage({
defaultMessage: "Webhook Information",
description: "section header"
})}
/>
<CardContent> <CardContent>
<Typography className={classes.title}> <Typography variant="caption">
{intl.formatMessage(commonMessages.generalInformations)} {intl.formatMessage(commonMessages.generalInformations)}
</Typography> </Typography>
<FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!formErrors.name} error={!!formErrors.name}
helperText={getWebhookErrorMessage(formErrors.name, intl)} helperText={getWebhookErrorMessage(formErrors.name, intl)}
label={intl.formatMessage({ label={intl.formatMessage(messages.webhookName)}
defaultMessage: "Webhook Name",
description: "webhook"
})}
fullWidth fullWidth
name="name" name="name"
value={data.name} value={data.name}
@ -72,27 +50,14 @@ const WebhookInfo: React.FC<WebhookInfoProps> = ({
<FormSpacer /> <FormSpacer />
<Hr /> <Hr />
<FormSpacer /> <FormSpacer />
<Typography className={classes.title}>
{intl.formatMessage({
defaultMessage: "Webhook specific information",
description: "webhook specific information"
})}
</Typography>
<FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!formErrors.targetUrl} error={!!formErrors.targetUrl}
helperText={ helperText={
getWebhookErrorMessage(formErrors.targetUrl, intl) || getWebhookErrorMessage(formErrors.targetUrl, intl) ||
intl.formatMessage({ intl.formatMessage(messages.targetUrlDescription)
defaultMessage: "This URL will receive webhook POST requests",
description: "webhook target url help text"
})
} }
label={intl.formatMessage({ label={intl.formatMessage(messages.targetUrl)}
defaultMessage: "Target URL",
description: "webhook"
})}
fullWidth fullWidth
name="targetUrl" name="targetUrl"
value={data.targetUrl} value={data.targetUrl}
@ -104,16 +69,9 @@ const WebhookInfo: React.FC<WebhookInfoProps> = ({
error={!!formErrors.secretKey} error={!!formErrors.secretKey}
helperText={ helperText={
getWebhookErrorMessage(formErrors.secretKey, intl) || getWebhookErrorMessage(formErrors.secretKey, intl) ||
intl.formatMessage({ intl.formatMessage(messages.secretKeyDescription)
defaultMessage:
"secret key is used to create a hash signature with each payload. *optional field",
description: "webhook secret key help text"
})
} }
label={intl.formatMessage({ label={intl.formatMessage(messages.secretKey)}
defaultMessage: "Secret Key",
description: "webhook"
})}
fullWidth fullWidth
name="secretKey" name="secretKey"
value={data.secretKey} value={data.secretKey}

View file

@ -0,0 +1,29 @@
import { defineMessages } from "react-intl";
export const messages = defineMessages({
webhookInformation: {
defaultMessage: "Webhook Information",
description: "section header"
},
webhookName: {
defaultMessage: "Webhook Name",
description: "webhook input label"
},
targetUrl: {
defaultMessage: "Target URL",
description: "webhook input label"
},
secretKey: {
defaultMessage: "Secret Key",
description: "webhook input label"
},
targetUrlDescription: {
defaultMessage: "This URL will receive webhook POST requests",
description: "webhook input help text"
},
secretKeyDescription: {
defaultMessage:
"secret key is used to create a hash signature with each payload. *optional field",
description: "webhook input help text"
}
});

View file

@ -5,7 +5,8 @@ import { ChangeEvent } from "@saleor/hooks/useForm";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { FormData } from "../WebhooksDetailsPage"; import { FormData } from "../WebhookDetailsPage";
import { messages } from "./messages";
interface WebhookStatusProps { interface WebhookStatusProps {
data: boolean; data: boolean;
@ -21,26 +22,14 @@ const WebhookStatus: React.FC<WebhookStatusProps> = ({
const intl = useIntl(); const intl = useIntl();
return ( return (
<Card> <Card>
<CardTitle <CardTitle title={intl.formatMessage(messages.webhookStatus)} />
title={intl.formatMessage({
defaultMessage: "Webhook Status",
description: "section header"
})}
/>
<CardContent> <CardContent>
<Typography> <Typography variant="body1">
{intl.formatMessage({ {intl.formatMessage(messages.webhookActiveDescription)}
defaultMessage:
"If you want to disable this webhook please uncheck the box below.",
description: "webhook active"
})}
</Typography> </Typography>
<ControlledCheckbox <ControlledCheckbox
name={"isActive" as keyof FormData} name={"isActive" as keyof FormData}
label={intl.formatMessage({ label={intl.formatMessage(messages.webhookActive)}
defaultMessage: "Webhook is active",
description: "webhooks active"
})}
checked={data} checked={data}
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}

View file

@ -0,0 +1,17 @@
import { defineMessages } from "react-intl";
export const messages = defineMessages({
webhookStatus: {
defaultMessage: "Webhook Status",
description: "section header"
},
webhookActive: {
defaultMessage: "Webhook is active",
description: "webhooks active label"
},
webhookActiveDescription: {
defaultMessage:
"If you want to disable this webhook please uncheck the box below.",
description: "webhook active description"
}
});

View file

@ -1,121 +0,0 @@
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer";
import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
import Savebar from "@saleor/components/Savebar";
import { WebhookErrorFragment } from "@saleor/fragments/types/WebhookErrorFragment";
import { Backlink } from "@saleor/macaw-ui";
import { getStringOrPlaceholder } from "@saleor/misc";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes";
import WebhookEvents from "@saleor/webhooks/components/WebhookEvents";
import WebhookInfo from "@saleor/webhooks/components/WebhookInfo";
import WebhookStatus from "@saleor/webhooks/components/WebhookStatus";
import { WebhookDetails_webhook } from "@saleor/webhooks/types/WebhookDetails";
import { isUnnamed } from "@saleor/webhooks/utils";
import React from "react";
import { useIntl } from "react-intl";
export interface FormData {
events: WebhookEventTypeEnum[];
isActive: boolean;
name: string;
secretKey: string | null;
targetUrl: string;
allEvents: boolean;
}
export interface WebhooksDetailsPageProps {
appName: string;
disabled: boolean;
errors: WebhookErrorFragment[];
webhook: WebhookDetails_webhook;
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
onSubmit: (data: FormData) => void;
}
const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({
appName,
disabled,
errors,
webhook,
saveButtonBarState,
onBack,
onSubmit
}) => {
const intl = useIntl();
const initialForm: FormData = {
allEvents: !!webhook?.events?.find(
event => event.eventType === WebhookEventTypeEnum.ANY_EVENTS
),
events:
webhook?.events
?.map(event => event.eventType)
.filter(event => event !== WebhookEventTypeEnum.ANY_EVENTS) || [],
isActive: !!webhook?.isActive,
name: webhook?.name || "",
secretKey: webhook?.secretKey || "",
targetUrl: webhook?.targetUrl || ""
};
return (
<Form initial={initialForm} onSubmit={onSubmit}>
{({ data, hasChanged, submit, change }) => (
<Container>
<Backlink onClick={onBack}>{appName}</Backlink>
<PageHeader
title={
isUnnamed(webhook)
? intl.formatMessage({
defaultMessage: "Unnamed Webhook Details",
description: "header"
})
: intl.formatMessage(
{
defaultMessage: "{webhookName} Details",
description: "header"
},
{
webhookName: getStringOrPlaceholder(webhook?.name)
}
)
}
/>
<Grid>
<div>
<WebhookInfo
data={data}
disabled={disabled}
errors={errors}
onChange={change}
/>
</div>
<div>
<WebhookEvents
data={data}
onChange={change}
disabled={disabled}
/>
<FormSpacer />
<WebhookStatus
data={data.isActive}
disabled={disabled}
onChange={change}
/>
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}
/>
</Container>
)}
</Form>
);
};
WebhooksDetailsPage.displayName = "WebhooksDetailsPage";
export default WebhooksDetailsPage;

View file

@ -1,2 +0,0 @@
export { default } from "./WebhooksDetailsPage";
export * from "./WebhooksDetailsPage";

View file

@ -7,7 +7,8 @@ export const webhook: WebhookDetails_webhook = {
id: "Jzx1ss23sEt==", id: "Jzx1ss23sEt==",
name: "Test App 2" name: "Test App 2"
}, },
events: [], syncEvents: [],
asyncEvents: [],
id: "Jzx123sEt==", id: "Jzx123sEt==",
isActive: true, isActive: true,
name: "Webhook Test 2", name: "Webhook Test 2",

37
src/webhooks/handlers.ts Normal file
View file

@ -0,0 +1,37 @@
import { ChangeEvent } from "@saleor/hooks/useForm";
import {
WebhookEventTypeAsync,
WebhookEventTypeSync
} from "@saleor/types/globalTypes";
import { toggle } from "@saleor/utils/lists";
import { filterSelectedAsyncEvents } from "./utils";
export const createSyncEventsSelectHandler = (
change: (event: ChangeEvent, cb?: () => void) => void,
syncEvents: WebhookEventTypeSync[]
) => (event: ChangeEvent) => {
const events = toggle(event.target.value, syncEvents, (a, b) => a === b);
change({
target: {
name: "syncEvents",
value: events
}
});
};
export const createAsyncEventsSelectHandler = (
change: (event: ChangeEvent, cb?: () => void) => void,
asyncEvents: WebhookEventTypeAsync[]
) => (event: ChangeEvent) => {
const events = toggle(event.target.value, asyncEvents, (a, b) => a === b);
const filteredEvents = filterSelectedAsyncEvents(events);
change({
target: {
name: "asyncEvents",
value: filteredEvents
}
});
};

View file

@ -12,7 +12,10 @@ const webhooksDetails = gql`
query WebhookDetails($id: ID!) { query WebhookDetails($id: ID!) {
webhook(id: $id) { webhook(id: $id) {
...WebhookFragment ...WebhookFragment
events { syncEvents {
eventType
}
asyncEvents {
eventType eventType
} }
secretKey secretKey

View file

@ -3,7 +3,7 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { WebhookEventTypeEnum } from "./../../types/globalTypes"; import { WebhookEventTypeSync, WebhookEventTypeAsync } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: WebhookDetails // GraphQL query operation: WebhookDetails
@ -15,9 +15,14 @@ export interface WebhookDetails_webhook_app {
name: string | null; name: string | null;
} }
export interface WebhookDetails_webhook_events { export interface WebhookDetails_webhook_syncEvents {
__typename: "WebhookEvent"; __typename: "WebhookEventSync";
eventType: WebhookEventTypeEnum; eventType: WebhookEventTypeSync;
}
export interface WebhookDetails_webhook_asyncEvents {
__typename: "WebhookEventAsync";
eventType: WebhookEventTypeAsync;
} }
export interface WebhookDetails_webhook { export interface WebhookDetails_webhook {
@ -26,7 +31,8 @@ export interface WebhookDetails_webhook {
name: string; name: string;
isActive: boolean; isActive: boolean;
app: WebhookDetails_webhook_app; app: WebhookDetails_webhook_app;
events: WebhookDetails_webhook_events[]; syncEvents: WebhookDetails_webhook_syncEvents[];
asyncEvents: WebhookDetails_webhook_asyncEvents[];
secretKey: string | null; secretKey: string | null;
targetUrl: string; targetUrl: string;
} }

View file

@ -1,5 +1,47 @@
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import { WebhookFragment } from "@saleor/fragments/types/WebhookFragment"; import { WebhookFragment } from "@saleor/fragments/types/WebhookFragment";
import {
WebhookEventTypeAsync,
WebhookEventTypeSync
} from "@saleor/types/globalTypes";
export function isUnnamed(webhook: WebhookFragment): boolean { export function isUnnamed(webhook: WebhookFragment): boolean {
return ["", null].includes(webhook?.name); return ["", null].includes(webhook?.name);
} }
export function mapSyncEventsToChoices(
events: WebhookEventTypeSync[]
): MultiAutocompleteChoiceType[] {
return events.map(event => ({
label: event,
value: event
}));
}
export function mapAsyncEventsToChoices(
events: WebhookEventTypeAsync[],
selectedEvents: WebhookEventTypeAsync[]
): MultiAutocompleteChoiceType[] {
const isAnyAsyncEventSelected = selectedEvents.includes(
WebhookEventTypeAsync.ANY_EVENTS
);
return events.map(event => ({
label: event,
value: event,
disabled:
event !== WebhookEventTypeAsync.ANY_EVENTS && isAnyAsyncEventSelected
}));
}
export const filterSelectedAsyncEvents = (
asyncEvents: WebhookEventTypeAsync[]
) => {
const anyEvent = asyncEvents.find(
event => event === WebhookEventTypeAsync.ANY_EVENTS
);
if (anyEvent) {
return [anyEvent];
}
return asyncEvents;
};

View file

@ -4,11 +4,11 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes"; import { WebhookEventTypeAsync } from "@saleor/types/globalTypes";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import WebhookCreatePage, { FormData } from "../components/WebhookCreatePage"; import WebhookDetailsPage, { FormData } from "../components/WebhookDetailsPage";
import { useWebhookCreateMutation } from "../mutations"; import { useWebhookCreateMutation } from "../mutations";
import { WebhookCreate as WebhookCreateData } from "../types/WebhookCreate"; import { WebhookCreate as WebhookCreateData } from "../types/WebhookCreate";
import { webhookUrl } from "../urls"; import { webhookUrl } from "../urls";
@ -44,9 +44,12 @@ export const WebhooksCreate: React.FC<WebhooksCreateProps> = ({ id }) => {
variables: { variables: {
input: { input: {
app: id, app: id,
events: data.allEvents syncEvents: data.syncEvents,
? [WebhookEventTypeEnum.ANY_EVENTS] asyncEvents: data.asyncEvents.includes(
: data.events, WebhookEventTypeAsync.ANY_EVENTS
)
? [WebhookEventTypeAsync.ANY_EVENTS]
: data.asyncEvents,
isActive: data.isActive, isActive: data.isActive,
name: data.name, name: data.name,
secretKey: data.secretKey, secretKey: data.secretKey,
@ -63,7 +66,7 @@ export const WebhooksCreate: React.FC<WebhooksCreateProps> = ({ id }) => {
description: "window title" description: "window title"
})} })}
/> />
<WebhookCreatePage <WebhookDetailsPage
appName={data?.app?.name} appName={data?.app?.name}
disabled={false} disabled={false}
errors={webhookCreateOpts.data?.webhookCreate.errors || []} errors={webhookCreateOpts.data?.webhookCreate.errors || []}

View file

@ -4,13 +4,13 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { WebhookEventTypeEnum } from "@saleor/types/globalTypes"; import { WebhookEventTypeAsync } from "@saleor/types/globalTypes";
import { WebhookUpdate } from "@saleor/webhooks/types/WebhookUpdate"; import { WebhookUpdate } from "@saleor/webhooks/types/WebhookUpdate";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { getStringOrPlaceholder } from "../../misc"; import { getStringOrPlaceholder } from "../../misc";
import WebhooksDetailsPage from "../components/WebhooksDetailsPage"; import WebhookDetailsPage from "../components/WebhookDetailsPage";
import { useWebhookUpdateMutation } from "../mutations"; import { useWebhookUpdateMutation } from "../mutations";
import { useWebhooksDetailsQuery } from "../queries"; import { useWebhooksDetailsQuery } from "../queries";
@ -57,7 +57,7 @@ export const WebhooksDetails: React.FC<WebhooksDetailsProps> = ({ id }) => {
<WindowTitle <WindowTitle
title={getStringOrPlaceholder(webhookDetails?.webhook?.name)} title={getStringOrPlaceholder(webhookDetails?.webhook?.name)}
/> />
<WebhooksDetailsPage <WebhookDetailsPage
appName={webhook?.app?.name} appName={webhook?.app?.name}
disabled={loading} disabled={loading}
errors={formErrors} errors={formErrors}
@ -69,9 +69,12 @@ export const WebhooksDetails: React.FC<WebhooksDetailsProps> = ({ id }) => {
variables: { variables: {
id, id,
input: { input: {
events: data.allEvents syncEvents: data.syncEvents,
? [WebhookEventTypeEnum.ANY_EVENTS] asyncEvents: data.asyncEvents.includes(
: data.events, WebhookEventTypeAsync.ANY_EVENTS
)
? [WebhookEventTypeAsync.ANY_EVENTS]
: data.asyncEvents,
isActive: data.isActive, isActive: data.isActive,
name: data.name, name: data.name,
secretKey: data.secretKey, secretKey: data.secretKey,