Add WebhookHeaders component (#3107)

* Add custom request headers to webhook form
This commit is contained in:
Bartłomiej Wiaduch 2023-02-22 14:14:51 +01:00 committed by GitHub
parent 0093f8dad4
commit ab2ce01c8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 713 additions and 16 deletions

View file

@ -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

View file

@ -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
}
},
"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,

View file

@ -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"

View file

@ -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."""

View file

@ -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<WebhookDetailsPageProps> = ({
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<WebhookDetailsPageProps> = ({
secretKey: webhook?.secretKey || "",
targetUrl: webhook?.targetUrl || "",
subscriptionQuery: prettified || "",
customHeaders: webhook?.customHeaders || "{}",
};
const backUrl = CustomAppUrls.resolveAppUrl(appId);
@ -134,6 +137,8 @@ const WebhookDetailsPage: React.FC<WebhookDetailsPageProps> = ({
/>
<FormSpacer />
<PermissionAlert query={query} />
<FormSpacer />
<WebhookHeaders data={data} onChange={change} />
</Box>
</Content>
<Savebar

View file

@ -0,0 +1,60 @@
import useForm from "@dashboard/hooks/useForm";
import Wrapper from "@test/wrapper";
import { act, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { customHeaders } from "./utils.test";
import WebhookHeaders, { WebhookHeadersProps } from "./WebhookHeaders";
export const props: WebhookHeadersProps = {
data: {
syncEvents: [],
asyncEvents: [],
isActive: true,
name: "Test webhook",
targetUrl: "http://localhost:3000",
subscriptionQuery: "",
customHeaders,
},
onChange: () => undefined,
};
const Component = () => {
const { change, data } = useForm(props.data, jest.fn());
return (
<Wrapper>
<WebhookHeaders data={data} onChange={change} />
</Wrapper>
);
};
const getExpandIcon = () => screen.getByTestId("expand");
describe("WebhookHeaders", () => {
it("is available on the webhook page", async () => {
// Arrange
render(<Component />);
// Assert
expect(screen.queryByTestId("webhook-headers-editor")).toBeInTheDocument();
});
it("can expand field", async () => {
// Arrange
render(<Component />);
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");
});
});

View file

@ -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<WebhookHeadersProps> = ({
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 (
<Card data-test-id="webhook-headers-editor" data-test-expanded={expanded}>
<CardTitle
className={classes.header}
title={
<>
{intl.formatMessage(messages.header)}
<IconButton
className={clsx(classes.expandBtn, {
[classes.rotate]: expanded,
})}
hoverOutline={false}
variant="secondary"
data-test-id="expand"
onClick={() => setExpanded(!expanded)}
>
<ExpandIcon />
</IconButton>
</>
}
/>
{headers === undefined ? (
<CardContent>
<Skeleton />
</CardContent>
) : (
<>
<CardContent className={classes.content}>
{headers.length > 0 && (
<Typography color="textSecondary" variant="body2">
<FormattedMessage
{...messages.headersCount}
values={{
number: headers.length,
}}
/>
</Typography>
)}
</CardContent>
{expanded && (
<>
{headers.length === 0 ? (
<CardContent className={classes.emptyContainer}>
<Typography variant="body2" color="textSecondary">
<FormattedMessage {...messages.noHeaders} />
</Typography>
</CardContent>
) : (
<>
<CardContent>
<Typography variant="body2">
<FormattedMessage {...messages.acceptedFormat} />
</Typography>
</CardContent>
<Table className={classes.table}>
<TableHead>
<TableRowLink>
<TableCell
className={clsx(
classes.colNameHeader,
classes.tableCell,
)}
>
<FormattedMessage {...messages.headerName} />
</TableCell>
<TableCell
className={clsx(classes.colValue, classes.tableCell)}
>
<FormattedMessage {...messages.headerValue} />
</TableCell>
<TableCell className={classes.colActionHeader}>
<FormattedMessage {...messages.actions} />
</TableCell>
</TableRowLink>
</TableHead>
<WebhookHeadersTableBody
onChange={onChange}
headers={headers}
/>
</Table>
</>
)}
<CardActions className={classes.actions}>
<Button
variant="secondary"
data-test-id="add-header"
onClick={add}
>
<FormattedMessage {...messages.add} />
</Button>
</CardActions>
</>
)}
</>
)}
</Card>
);
};
WebhookHeaders.displayName = "WebhookHeaders";
export default WebhookHeaders;

View file

@ -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<WebhookHeadersTableBodyProps> = ({
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<HTMLTextAreaElement>) => {
const { item, index } = updateWebhookItem(target);
onChange({
target: {
name: "customHeaders",
value: stringifyHeaders(updateAtIndex(item, headers, index)),
},
});
};
return (
<TableBody>
{headers.map((field, fieldIndex) => (
<TableRowLink data-test-id="field" key={fieldIndex}>
<TableCell className={clsx(classes.colName, classes.tableCell)}>
<TextField
InputProps={{
classes: {
input: classes.input,
},
}}
inputProps={{
"aria-label": `${nameInputPrefix}${nameSeparator}${fieldIndex}`,
}}
name={`${nameInputPrefix}${nameSeparator}${fieldIndex}`}
fullWidth
onChange={change}
value={field.name}
error={field.error}
helperText={
(field.error && intl.formatMessage(messages.headerNameError)) ||
" "
}
/>
</TableCell>
<TableCell className={clsx(classes.colValue, classes.tableCell)}>
<TextField
InputProps={{
classes: {
input: classes.input,
},
}}
inputProps={{
"aria-label": `${valueInputPrefix}${nameSeparator}${fieldIndex}`,
}}
name={`${valueInputPrefix}${nameSeparator}${fieldIndex}`}
fullWidth
onChange={change}
value={field.value}
helperText={" "}
/>
</TableCell>
<TableCell className={classes.colAction}>
<IconButton
variant="secondary"
data-test-id={"delete-field-" + fieldIndex}
onClick={() =>
onChange({
target: {
name: "customHeaders",
value: stringifyHeaders(removeAtIndex(headers, fieldIndex)),
},
})
}
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRowLink>
))}
</TableBody>
);
};
WebhookHeadersTableBody.displayName = "WebhookHeadersTableRow";
export default WebhookHeadersTableBody;

View file

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

View file

@ -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",
},
});

View file

@ -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;

View file

@ -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);
});
});

View file

@ -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),
}));
};

View file

@ -47,6 +47,7 @@ describe("WebhookSubscriptionQuery", () => {
name: "",
targetUrl: "",
subscriptionQuery: "",
customHeaders: "",
},
};

View file

@ -16,4 +16,5 @@ export const webhook: WebhookDetailsFragment = {
subscriptionQuery:
"subscription { event { ... on ProductUpdated { product { name } } } }",
targetUrl: "http://www.getsaleor.com",
customHeaders: "{}",
};

View file

@ -63,6 +63,7 @@ export const CustomAppWebhookDetails: React.FC<
secretKey: data.secretKey,
targetUrl: data.targetUrl,
query: data.subscriptionQuery,
customHeaders: data.customHeaders,
},
},
}),

View file

@ -24,5 +24,6 @@ export const webhookDetailsFragment = gql`
secretKey
targetUrl
subscriptionQuery
customHeaders
}
`;

View file

@ -2841,6 +2841,7 @@ export const WebhookDetailsFragmentDoc = gql`
secretKey
targetUrl
subscriptionQuery
customHeaders
}
${WebhookFragmentDoc}`;
export const AppCreateDocument = gql`

View file

@ -5545,7 +5545,7 @@ export type WarehouseUpdatedFieldPolicy = {
recipient?: FieldPolicy<any> | FieldReadFunction<any>,
warehouse?: FieldPolicy<any> | FieldReadFunction<any>
};
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<any> | FieldReadFunction<any>,
name?: FieldPolicy<any> | FieldReadFunction<any>,
@ -5557,7 +5557,8 @@ export type WebhookFieldPolicy = {
targetUrl?: FieldPolicy<any> | FieldReadFunction<any>,
isActive?: FieldPolicy<any> | FieldReadFunction<any>,
secretKey?: FieldPolicy<any> | FieldReadFunction<any>,
subscriptionQuery?: FieldPolicy<any> | FieldReadFunction<any>
subscriptionQuery?: FieldPolicy<any> | FieldReadFunction<any>,
customHeaders?: FieldPolicy<any> | FieldReadFunction<any>
};
export type WebhookCreateKeySpecifier = ('webhookErrors' | 'errors' | 'webhook' | WebhookCreateKeySpecifier)[];
export type WebhookCreateFieldPolicy = {

View file

@ -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<Scalars['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?: InputMaybe<Scalars['JSONString']>;
};
/** 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<Scalars['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?: InputMaybe<Scalars['JSONString']>;
};
/** 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 };