diff --git a/CHANGELOG.md b/CHANGELOG.md index 306169573..5c3ba1a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable, unreleased changes to this project will be documented in this file. - Add GraphiQL editor to webhook form for defining the subscription query #2885 by @2can @zaiste - Add redirect to GraphiQL from product & order details pages - #2940 by @zaiste - Extract permissions for subscription query - #3155 by @zaiste +- Add custom request headers to webhook form - #3107 by @2can ## 3.4 diff --git a/introspection.json b/introspection.json index 31e4c9298..dd658199e 100644 --- a/introspection.json +++ b/introspection.json @@ -3811,6 +3811,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "INVALID_CUSTOM_HEADERS", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "MANIFEST_URL_CANT_CONNECT", "description": null, @@ -56083,13 +56089,9 @@ "name": "oldPassword", "description": "Current user password.", "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "defaultValue": null, "isDeprecated": false, @@ -112428,6 +112430,18 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "customHeaders", + "description": "Custom headers, which will be added to HTTP request.\n\nAdded in Saleor 3.12.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "JSONString", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -112649,6 +112663,18 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "customHeaders", + "description": "Custom headers, which will be added to HTTP request. There is a limitation of 5 headers per webhook and 998 characters per header.Only \"X-*\" and \"Authorization*\" keys are allowed.\n\nAdded in Saleor 3.12.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.", + "type": { + "kind": "SCALAR", + "name": "JSONString", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "interfaces": null, @@ -113007,6 +113033,12 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "INVALID_CUSTOM_HEADERS", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -115715,6 +115747,18 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "customHeaders", + "description": "Custom headers, which will be added to HTTP request. There is a limitation of 5 headers per webhook and 998 characters per header.Only \"X-*\" and \"Authorization*\" keys are allowed.\n\nAdded in Saleor 3.12.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.", + "type": { + "kind": "SCALAR", + "name": "JSONString", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "interfaces": null, diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 043f147ba..880f463ee 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -156,6 +156,10 @@ "context": "product type shipping settings, section header", "string": "Shipping" }, + "/4bJkA": { + "context": "header field value, header", + "string": "Value" + }, "/5r4he": { "context": "label for button", "string": "Add country" @@ -550,6 +554,10 @@ "context": "gift card not found message", "string": "Couldn't find gift card" }, + "2BHjVL": { + "context": "header", + "string": "Custom request headers" + }, "2CBcub": { "context": "CreateVariantTitle manage", "string": "Manage" @@ -1443,6 +1451,10 @@ "9UHfux": { "string": "Voucher Specific Information" }, + "9Y5i/8": { + "context": "number of webhook headers in model", + "string": "{number,plural,one{{number} header} other{{number} custom request headers}}" + }, "9Y6vg+": { "context": "dialog header", "string": "Add product" @@ -3375,6 +3387,10 @@ "context": "channels section name", "string": "Channels" }, + "No4lyL": { + "context": "header field name, header", + "string": "Name" + }, "Np7J92": { "context": "deactivate local named app", "string": "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app." @@ -5185,6 +5201,10 @@ "b+jcaN": { "string": "There are still fulfillments created for this order. Cancel the fulfillments first before you cancel the order." }, + "b1t9bM": { + "context": "empty headers text", + "string": "No custom request headers created for this webhook. Use the button below to add new custom request header." + }, "b1zuN9": { "string": "Price" }, @@ -5980,6 +6000,10 @@ "context": "sale start date", "string": "Starts" }, + "iERn5G": { + "context": "header name input", + "string": "Should start with `x-` or `authorization`" + }, "iEeIhY": { "context": "draft order", "string": "Customer" @@ -7495,6 +7519,10 @@ "uMpv1v": { "string": "Fulfillment successfully cancelled" }, + "uQNm59": { + "context": "add header,button", + "string": "Add custom request header" + }, "uQk8gB": { "context": "export all items to csv file", "string": "All gift cards ({number})" @@ -7776,6 +7804,10 @@ "context": "see error log label in notification", "string": "See error log" }, + "wChjN/": { + "context": "accepted header names", + "string": "Headers with in following format are accepted: `authorization*`, `x-*`" + }, "wDUBLR": { "context": "order refund amount", "string": "Proposed refund amount" diff --git a/schema.graphql b/schema.graphql index a7cd2673b..3e9651b80 100644 --- a/schema.graphql +++ b/schema.graphql @@ -727,6 +727,7 @@ enum AppErrorCode { INVALID_PERMISSION INVALID_URL_FORMAT INVALID_MANIFEST_FORMAT + INVALID_CUSTOM_HEADERS MANIFEST_URL_CANT_CONNECT NOT_FOUND REQUIRED @@ -12947,7 +12948,7 @@ type Mutation { newPassword: String! """Current user password.""" - oldPassword: String! + oldPassword: String ): PasswordChange """ @@ -26045,6 +26046,15 @@ type Webhook implements Node { """Used to define payloads for specific events.""" subscriptionQuery: String + + """ + Custom headers, which will be added to HTTP request. + + Added in Saleor 3.12. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + customHeaders: JSONString } """ @@ -26099,6 +26109,15 @@ input WebhookCreateInput { Note: this API is currently in Feature Preview and can be subject to changes at later point. """ query: String + + """ + Custom headers, which will be added to HTTP request. There is a limitation of 5 headers per webhook and 998 characters per header.Only "X-*" and "Authorization*" keys are allowed. + + Added in Saleor 3.12. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + customHeaders: JSONString } """ @@ -26178,6 +26197,7 @@ enum WebhookErrorCode { MISSING_SUBSCRIPTION UNABLE_TO_PARSE MISSING_EVENT + INVALID_CUSTOM_HEADERS } """Webhook event.""" @@ -27380,6 +27400,15 @@ input WebhookUpdateInput { Note: this API is currently in Feature Preview and can be subject to changes at later point. """ query: String + + """ + Custom headers, which will be added to HTTP request. There is a limitation of 5 headers per webhook and 998 characters per header.Only "X-*" and "Authorization*" keys are allowed. + + Added in Saleor 3.12. + + Note: this API is currently in Feature Preview and can be subject to changes at later point. + """ + customHeaders: JSONString } """Represents weight value in a specific weight unit.""" diff --git a/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx b/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx index de7889bdf..2f7c8434f 100644 --- a/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx +++ b/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx @@ -27,6 +27,7 @@ import React, { useEffect, useState } from "react"; import { useIntl } from "react-intl"; import PermissionAlert from "../PermissionAlert"; +import WebhookHeaders from "../WebhookHeaders"; import WebhookSubscriptionQuery from "../WebhookSubscriptionQuery"; import { getHeaderTitle } from "./messages"; @@ -38,6 +39,7 @@ export interface WebhookFormData { secretKey?: string; targetUrl: string; subscriptionQuery: string; + customHeaders: string; } export interface WebhookDetailsPageProps { @@ -63,7 +65,7 @@ const WebhookDetailsPage: React.FC = ({ let prettified: string; try { - prettified = print(parse(webhook?.subscriptionQuery)); + prettified = print(parse(webhook?.subscriptionQuery || "")); } catch { prettified = webhook?.subscriptionQuery || ""; } @@ -76,6 +78,7 @@ const WebhookDetailsPage: React.FC = ({ secretKey: webhook?.secretKey || "", targetUrl: webhook?.targetUrl || "", subscriptionQuery: prettified || "", + customHeaders: webhook?.customHeaders || "{}", }; const backUrl = CustomAppUrls.resolveAppUrl(appId); @@ -134,6 +137,8 @@ const WebhookDetailsPage: React.FC = ({ /> + + undefined, +}; + +const Component = () => { + const { change, data } = useForm(props.data, jest.fn()); + + return ( + + + + ); +}; + +const getExpandIcon = () => screen.getByTestId("expand"); + +describe("WebhookHeaders", () => { + it("is available on the webhook page", async () => { + // Arrange + render(); + + // Assert + expect(screen.queryByTestId("webhook-headers-editor")).toBeInTheDocument(); + }); + + it("can expand field", async () => { + // Arrange + render(); + const user = userEvent.setup(); + const isExpandedAttribute = "data-test-expanded"; + const editor = screen.getByTestId("webhook-headers-editor"); + // Assert + expect(editor).toHaveAttribute(isExpandedAttribute, "true"); + // Act + await act(async () => { + await user.click(getExpandIcon()); + }); + + // Assert + expect(editor).toHaveAttribute(isExpandedAttribute, "false"); + }); +}); diff --git a/src/custom-apps/components/WebhookHeaders/WebhookHeaders.tsx b/src/custom-apps/components/WebhookHeaders/WebhookHeaders.tsx new file mode 100644 index 000000000..3693aedcc --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/WebhookHeaders.tsx @@ -0,0 +1,159 @@ +import { Button } from "@dashboard/components/Button"; +import CardTitle from "@dashboard/components/CardTitle"; +import Skeleton from "@dashboard/components/Skeleton"; +import TableRowLink from "@dashboard/components/TableRowLink"; +import { FormChange } from "@dashboard/hooks/useForm"; +import { + Card, + CardActions, + CardContent, + Table, + TableCell, + TableHead, + Typography, +} from "@material-ui/core"; +import { ExpandIcon, IconButton } from "@saleor/macaw-ui"; +import clsx from "clsx"; +import React, { useEffect, useMemo, useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { WebhookFormData } from "../WebhookDetailsPage"; +import { messages } from "./messages"; +import useStyles from "./styles"; +import { mapHeaders, stringifyHeaders } from "./utils"; +import WebhookHeadersTableBody from "./WebhookHeadersTableBody"; + +export interface WebhookHeadersProps { + data: WebhookFormData; + onChange: FormChange; +} + +const WebhookHeaders: React.FC = ({ + data: { customHeaders }, + onChange, +}) => { + const intl = useIntl(); + const [expanded, setExpanded] = useState(false); + const classes = useStyles(); + const headers = useMemo(() => mapHeaders(customHeaders), [customHeaders]); + + useEffect(() => { + if (headers.length > 0) { + setExpanded(true); + } + }, [headers.length]); + + const add = () => { + const items = [...headers]; + items.push({ name: "", value: "", error: false }); + + onChange({ + target: { + name: "customHeaders", + value: stringifyHeaders(items), + }, + }); + }; + + return ( + + + {intl.formatMessage(messages.header)} + setExpanded(!expanded)} + > + + + + } + /> + {headers === undefined ? ( + + + + ) : ( + <> + + {headers.length > 0 && ( + + + + )} + + {expanded && ( + <> + {headers.length === 0 ? ( + + + + + + ) : ( + <> + + + + + + + + + + + + + + + + + + + + + +
+ + )} + + + + + )} + + )} +
+ ); +}; + +WebhookHeaders.displayName = "WebhookHeaders"; +export default WebhookHeaders; diff --git a/src/custom-apps/components/WebhookHeaders/WebhookHeadersTableBody.tsx b/src/custom-apps/components/WebhookHeaders/WebhookHeadersTableBody.tsx new file mode 100644 index 000000000..6be1f7be2 --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/WebhookHeadersTableBody.tsx @@ -0,0 +1,125 @@ +import TableRowLink from "@dashboard/components/TableRowLink"; +import { FormChange } from "@dashboard/hooks/useForm"; +import { removeAtIndex, updateAtIndex } from "@dashboard/utils/lists"; +import { TableBody, TableCell, TextField } from "@material-ui/core"; +import { DeleteIcon, IconButton } from "@saleor/macaw-ui"; +import clsx from "clsx"; +import React, { ChangeEvent } from "react"; +import { useIntl } from "react-intl"; + +import { messages } from "./messages"; +import useStyles from "./styles"; +import { Header, stringifyHeaders } from "./utils"; + +const nameSeparator = ":"; +const nameInputPrefix = "name"; +const valueInputPrefix = "value"; + +export interface WebhookHeadersTableBodyProps { + onChange: FormChange; + headers: Header[]; +} + +const WebhookHeadersTableBody: React.FC = ({ + onChange, + headers, +}) => { + const classes = useStyles(); + const intl = useIntl(); + + const updateWebhookItem = (target: EventTarget & HTMLTextAreaElement) => { + const { name, value } = target; + const [field, index] = name.split(nameSeparator); + + const item: Header = headers[index]; + + // lowercase header name + if (field === nameInputPrefix) { + item[field] = value.toLowerCase(); + } else { + item[field] = value; + } + + return { + item, + index: parseInt(index, 10), + }; + }; + + const change = ({ target }: ChangeEvent) => { + const { item, index } = updateWebhookItem(target); + + onChange({ + target: { + name: "customHeaders", + value: stringifyHeaders(updateAtIndex(item, headers, index)), + }, + }); + }; + + return ( + + {headers.map((field, fieldIndex) => ( + + + + + + + + + + onChange({ + target: { + name: "customHeaders", + value: stringifyHeaders(removeAtIndex(headers, fieldIndex)), + }, + }) + } + > + + + + + ))} + + ); +}; + +WebhookHeadersTableBody.displayName = "WebhookHeadersTableRow"; +export default WebhookHeadersTableBody; diff --git a/src/custom-apps/components/WebhookHeaders/index.ts b/src/custom-apps/components/WebhookHeaders/index.ts new file mode 100644 index 000000000..a2da8068b --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WebhookHeaders"; +export * from "./WebhookHeaders"; diff --git a/src/custom-apps/components/WebhookHeaders/messages.ts b/src/custom-apps/components/WebhookHeaders/messages.ts new file mode 100644 index 000000000..cd9940592 --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/messages.ts @@ -0,0 +1,52 @@ +import { defineMessages } from "react-intl"; + +export const messages = defineMessages({ + header: { + id: "2BHjVL", + defaultMessage: "Custom request headers", + description: "header", + }, + noHeaders: { + id: "b1t9bM", + defaultMessage: + "No custom request headers created for this webhook. Use the button below to add new custom request header.", + description: "empty headers text", + }, + acceptedFormat: { + id: "wChjN/", + defaultMessage: + "Headers with in following format are accepted: `authorization*`, `x-*`", + description: "accepted header names", + }, + headerName: { + defaultMessage: "Name", + id: "No4lyL", + description: "header field name, header", + }, + headerNameError: { + id: "iERn5G", + defaultMessage: "Should start with `x-` or `authorization`", + description: "header name input", + }, + headerValue: { + id: "/4bJkA", + defaultMessage: "Value", + description: "header field value, header", + }, + actions: { + id: "nEixpu", + defaultMessage: "Actions", + description: "table action", + }, + add: { + id: "uQNm59", + defaultMessage: "Add custom request header", + description: "add header,button", + }, + headersCount: { + id: "9Y5i/8", + defaultMessage: + "{number,plural,one{{number} header} other{{number} custom request headers}}", + description: "number of webhook headers in model", + }, +}); diff --git a/src/custom-apps/components/WebhookHeaders/styles.ts b/src/custom-apps/components/WebhookHeaders/styles.ts new file mode 100644 index 000000000..82bed0676 --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/styles.ts @@ -0,0 +1,94 @@ +import { makeStyles } from "@saleor/macaw-ui"; + +const useStyles = makeStyles( + theme => { + const colAction: React.CSSProperties = { + textAlign: "right", + width: 130, + }; + const colName: React.CSSProperties = { + width: 250, + textAlign: "right", + }; + + return { + tableCell: { + paddingTop: theme.spacing(3), + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1), + + "& .MuiFormHelperText-root": { + margin: 0, + }, + + "&.MuiTableCell-root:first-child:not(.MuiTableCell-paddingCheckbox)": { + paddingRight: theme.spacing(0), + paddingTop: theme.spacing(3), + }, + }, + colAction: { + "&:last-child": { + ...colAction, + paddingRight: theme.spacing(3), + }, + }, + colActionHeader: { + ...colAction, + }, + colName: { + ...colName, + }, + colNameHeader: { + ...colName, + }, + colValue: {}, + actions: { + "&&": { + paddingBottom: theme.spacing(2), + paddingTop: theme.spacing(2), + }, + }, + content: { + paddingBottom: 0, + paddingTop: theme.spacing(), + }, + emptyContainer: { + paddingBottom: 0, + paddingTop: 0, + }, + expandBtn: { + position: "relative", + left: theme.spacing(1), + top: -2, + transition: theme.transitions.create("transform", { + duration: theme.transitions.duration.shorter, + }), + border: 0, + }, + header: { + "&&": { + paddingBottom: theme.spacing(1), + }, + }, + input: { + padding: theme.spacing(1.5, 2), + }, + table: { + marginTop: theme.spacing(2), + tableLayout: "fixed", + + head: { + padding: 0, + }, + }, + rotate: { + transform: "rotate(-180deg)", + }, + }; + }, + { + name: "WebhookHeaders", + }, +); + +export default useStyles; diff --git a/src/custom-apps/components/WebhookHeaders/utils.test.ts b/src/custom-apps/components/WebhookHeaders/utils.test.ts new file mode 100644 index 000000000..143044b64 --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/utils.test.ts @@ -0,0 +1,35 @@ +import { + mapHeaders, + stringifyHeaders, +} from "@dashboard/custom-apps/components/WebhookHeaders/utils"; + +export const customHeaders = '{"x-auth-token":"ABC","authorization":"XYZ"}'; + +const parsedHeaders = [ + { + name: "x-auth-token", + value: "ABC", + error: false, + }, + { + name: "authorization", + value: "XYZ", + error: false, + }, +]; + +describe("mapHeaders", () => { + it("should map customHeaders string to Object", () => { + const headers = mapHeaders(customHeaders); + + expect(headers).toEqual(parsedHeaders); + }); +}); + +describe("stringifyHeaders", () => { + it("should return stringified headers", () => { + const stringified = stringifyHeaders(parsedHeaders); + + expect(stringified).toEqual(customHeaders); + }); +}); diff --git a/src/custom-apps/components/WebhookHeaders/utils.ts b/src/custom-apps/components/WebhookHeaders/utils.ts new file mode 100644 index 000000000..511ce5f5c --- /dev/null +++ b/src/custom-apps/components/WebhookHeaders/utils.ts @@ -0,0 +1,35 @@ +import keyBy from "lodash/keyBy"; +import mapValues from "lodash/mapValues"; + +export interface Header { + name: string; + value: string; + error?: boolean; +} + +export const stringifyHeaders = (headers: Header[]): string => + JSON.stringify(mapValues(keyBy(headers, "name"), "value")); + +const validateName = (name: string) => { + if ( + name.toLowerCase().match("(^x$)|(^x-)|(^authorization$)|(^authorization-)") + ) { + return false; + } + + if (name === "") { + return false; + } + + return true; +}; + +export const mapHeaders = (customHeaders: string): Header[] => { + const parsedHeaders = JSON.parse(customHeaders); + + return Object.keys(parsedHeaders).map(key => ({ + name: key, + value: parsedHeaders[key], + error: validateName(key), + })); +}; diff --git a/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.test.tsx b/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.test.tsx index 5a8982a75..d6c98c893 100644 --- a/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.test.tsx +++ b/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.test.tsx @@ -47,6 +47,7 @@ describe("WebhookSubscriptionQuery", () => { name: "", targetUrl: "", subscriptionQuery: "", + customHeaders: "", }, }; diff --git a/src/custom-apps/fixtures.ts b/src/custom-apps/fixtures.ts index 60438971d..aaa0dbe48 100644 --- a/src/custom-apps/fixtures.ts +++ b/src/custom-apps/fixtures.ts @@ -16,4 +16,5 @@ export const webhook: WebhookDetailsFragment = { subscriptionQuery: "subscription { event { ... on ProductUpdated { product { name } } } }", targetUrl: "http://www.getsaleor.com", + customHeaders: "{}", }; diff --git a/src/custom-apps/views/CustomAppWebhookDetails.tsx b/src/custom-apps/views/CustomAppWebhookDetails.tsx index 67f1e68d8..732f2ae98 100644 --- a/src/custom-apps/views/CustomAppWebhookDetails.tsx +++ b/src/custom-apps/views/CustomAppWebhookDetails.tsx @@ -63,6 +63,7 @@ export const CustomAppWebhookDetails: React.FC< secretKey: data.secretKey, targetUrl: data.targetUrl, query: data.subscriptionQuery, + customHeaders: data.customHeaders, }, }, }), diff --git a/src/fragments/webhooks.ts b/src/fragments/webhooks.ts index 0ced39a65..c3a60a54a 100644 --- a/src/fragments/webhooks.ts +++ b/src/fragments/webhooks.ts @@ -24,5 +24,6 @@ export const webhookDetailsFragment = gql` secretKey targetUrl subscriptionQuery + customHeaders } `; diff --git a/src/graphql/hooks.generated.ts b/src/graphql/hooks.generated.ts index e85bb44dc..5055258c6 100644 --- a/src/graphql/hooks.generated.ts +++ b/src/graphql/hooks.generated.ts @@ -2841,6 +2841,7 @@ export const WebhookDetailsFragmentDoc = gql` secretKey targetUrl subscriptionQuery + customHeaders } ${WebhookFragmentDoc}`; export const AppCreateDocument = gql` diff --git a/src/graphql/typePolicies.generated.ts b/src/graphql/typePolicies.generated.ts index 43a554333..2c038cfab 100644 --- a/src/graphql/typePolicies.generated.ts +++ b/src/graphql/typePolicies.generated.ts @@ -5545,7 +5545,7 @@ export type WarehouseUpdatedFieldPolicy = { recipient?: FieldPolicy | FieldReadFunction, warehouse?: FieldPolicy | FieldReadFunction }; -export type WebhookKeySpecifier = ('id' | 'name' | 'events' | 'syncEvents' | 'asyncEvents' | 'app' | 'eventDeliveries' | 'targetUrl' | 'isActive' | 'secretKey' | 'subscriptionQuery' | WebhookKeySpecifier)[]; +export type WebhookKeySpecifier = ('id' | 'name' | 'events' | 'syncEvents' | 'asyncEvents' | 'app' | 'eventDeliveries' | 'targetUrl' | 'isActive' | 'secretKey' | 'subscriptionQuery' | 'customHeaders' | WebhookKeySpecifier)[]; export type WebhookFieldPolicy = { id?: FieldPolicy | FieldReadFunction, name?: FieldPolicy | FieldReadFunction, @@ -5557,7 +5557,8 @@ export type WebhookFieldPolicy = { targetUrl?: FieldPolicy | FieldReadFunction, isActive?: FieldPolicy | FieldReadFunction, secretKey?: FieldPolicy | FieldReadFunction, - subscriptionQuery?: FieldPolicy | FieldReadFunction + subscriptionQuery?: FieldPolicy | FieldReadFunction, + customHeaders?: FieldPolicy | FieldReadFunction }; export type WebhookCreateKeySpecifier = ('webhookErrors' | 'errors' | 'webhook' | WebhookCreateKeySpecifier)[]; export type WebhookCreateFieldPolicy = { diff --git a/src/graphql/types.generated.ts b/src/graphql/types.generated.ts index dccb34347..ac35ecfcd 100644 --- a/src/graphql/types.generated.ts +++ b/src/graphql/types.generated.ts @@ -179,6 +179,7 @@ export enum AppErrorCode { INVALID_PERMISSION = 'INVALID_PERMISSION', INVALID_URL_FORMAT = 'INVALID_URL_FORMAT', INVALID_MANIFEST_FORMAT = 'INVALID_MANIFEST_FORMAT', + INVALID_CUSTOM_HEADERS = 'INVALID_CUSTOM_HEADERS', MANIFEST_URL_CANT_CONNECT = 'MANIFEST_URL_CANT_CONNECT', NOT_FOUND = 'NOT_FOUND', REQUIRED = 'REQUIRED', @@ -5813,6 +5814,14 @@ export type WebhookCreateInput = { * Note: this API is currently in Feature Preview and can be subject to changes at later point. */ query?: InputMaybe; + /** + * Custom headers, which will be added to HTTP request. There is a limitation of 5 headers per webhook and 998 characters per header.Only "X-*" and "Authorization*" keys are allowed. + * + * Added in Saleor 3.12. + * + * Note: this API is currently in Feature Preview and can be subject to changes at later point. + */ + customHeaders?: InputMaybe; }; /** An enumeration. */ @@ -5839,7 +5848,8 @@ export enum WebhookErrorCode { SYNTAX = 'SYNTAX', MISSING_SUBSCRIPTION = 'MISSING_SUBSCRIPTION', UNABLE_TO_PARSE = 'UNABLE_TO_PARSE', - MISSING_EVENT = 'MISSING_EVENT' + MISSING_EVENT = 'MISSING_EVENT', + INVALID_CUSTOM_HEADERS = 'INVALID_CUSTOM_HEADERS' } /** Enum determining type of webhook. */ @@ -6712,6 +6722,14 @@ export type WebhookUpdateInput = { * Note: this API is currently in Feature Preview and can be subject to changes at later point. */ query?: InputMaybe; + /** + * Custom headers, which will be added to HTTP request. There is a limitation of 5 headers per webhook and 998 characters per header.Only "X-*" and "Authorization*" keys are allowed. + * + * Added in Saleor 3.12. + * + * Note: this API is currently in Feature Preview and can be subject to changes at later point. + */ + customHeaders?: InputMaybe; }; /** An enumeration. */ @@ -7236,7 +7254,7 @@ export type WebhookCreateMutationVariables = Exact<{ }>; -export type WebhookCreateMutation = { __typename: 'Mutation', webhookCreate: { __typename: 'WebhookCreate', errors: Array<{ __typename: 'WebhookError', code: WebhookErrorCode, field: string | null, message: string | null }>, webhook: { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } } | null } | null }; +export type WebhookCreateMutation = { __typename: 'Mutation', webhookCreate: { __typename: 'WebhookCreate', errors: Array<{ __typename: 'WebhookError', code: WebhookErrorCode, field: string | null, message: string | null }>, webhook: { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, customHeaders: any | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } } | null } | null }; export type WebhookUpdateMutationVariables = Exact<{ id: Scalars['ID']; @@ -7244,7 +7262,7 @@ export type WebhookUpdateMutationVariables = Exact<{ }>; -export type WebhookUpdateMutation = { __typename: 'Mutation', webhookUpdate: { __typename: 'WebhookUpdate', errors: Array<{ __typename: 'WebhookError', code: WebhookErrorCode, field: string | null, message: string | null }>, webhook: { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } } | null } | null }; +export type WebhookUpdateMutation = { __typename: 'Mutation', webhookUpdate: { __typename: 'WebhookUpdate', errors: Array<{ __typename: 'WebhookError', code: WebhookErrorCode, field: string | null, message: string | null }>, webhook: { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, customHeaders: any | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } } | null } | null }; export type WebhookDeleteMutationVariables = Exact<{ id: Scalars['ID']; @@ -7258,7 +7276,7 @@ export type WebhookDetailsQueryVariables = Exact<{ }>; -export type WebhookDetailsQuery = { __typename: 'Query', webhook: { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } } | null }; +export type WebhookDetailsQuery = { __typename: 'Query', webhook: { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, customHeaders: any | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } } | null }; export type UpdateCustomerMutationVariables = Exact<{ id: Scalars['ID']; @@ -7999,7 +8017,7 @@ export type WarehouseDetailsFragment = { __typename: 'Warehouse', isPrivate: boo export type WebhookFragment = { __typename: 'Webhook', id: string, name: string, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }; -export type WebhookDetailsFragment = { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } }; +export type WebhookDetailsFragment = { __typename: 'Webhook', secretKey: string | null, targetUrl: string, subscriptionQuery: string | null, customHeaders: any | null, id: string, name: string, isActive: boolean, syncEvents: Array<{ __typename: 'WebhookEventSync', eventType: WebhookEventTypeSyncEnum }>, asyncEvents: Array<{ __typename: 'WebhookEventAsync', eventType: WebhookEventTypeAsyncEnum }>, app: { __typename: 'App', id: string, name: string | null } }; export type WeightFragment = { __typename: 'Weight', unit: WeightUnitsEnum, value: number };