Allow subscription query for sync events (#3099)
Allow subscription query for sync events
This commit is contained in:
parent
9f54a7840c
commit
0fef41b04f
15 changed files with 250 additions and 157 deletions
|
@ -21,6 +21,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Add redirect to GraphiQL from product & order details pages - #2940 by @zaiste
|
- Add redirect to GraphiQL from product & order details pages - #2940 by @zaiste
|
||||||
- Extract permissions for subscription query - #3155 by @zaiste
|
- Extract permissions for subscription query - #3155 by @zaiste
|
||||||
- Add custom request headers to webhook form - #3107 by @2can
|
- Add custom request headers to webhook form - #3107 by @2can
|
||||||
|
- Allow subscription query for sync events - #3099 by @2can
|
||||||
|
|
||||||
## 3.4
|
## 3.4
|
||||||
|
|
||||||
|
|
|
@ -1618,10 +1618,6 @@
|
||||||
"context": "sale status",
|
"context": "sale status",
|
||||||
"string": "Active"
|
"string": "Active"
|
||||||
},
|
},
|
||||||
"ApNw0L": {
|
|
||||||
"context": "Dry run objects unavailable",
|
|
||||||
"string": "The following objects are currently not available for dry run:"
|
|
||||||
},
|
|
||||||
"AqHafs": {
|
"AqHafs": {
|
||||||
"context": "provided email input placeholder",
|
"context": "provided email input placeholder",
|
||||||
"string": "Provided email address"
|
"string": "Provided email address"
|
||||||
|
@ -1731,6 +1727,10 @@
|
||||||
"context": "button label",
|
"context": "button label",
|
||||||
"string": "Settings"
|
"string": "Settings"
|
||||||
},
|
},
|
||||||
|
"BYTvv/": {
|
||||||
|
"context": "Dry run events unavailable",
|
||||||
|
"string": "The following events from provided query are currently not available for dry run:"
|
||||||
|
},
|
||||||
"BZ7BkQ": {
|
"BZ7BkQ": {
|
||||||
"context": "capture payment, button",
|
"context": "capture payment, button",
|
||||||
"string": "Capture"
|
"string": "Capture"
|
||||||
|
@ -4187,6 +4187,10 @@
|
||||||
"context": "pricing card title",
|
"context": "pricing card title",
|
||||||
"string": "Pricing"
|
"string": "Pricing"
|
||||||
},
|
},
|
||||||
|
"TnnDjx": {
|
||||||
|
"context": "Dry run sync events alert",
|
||||||
|
"string": "Dry run currently is not available for synchronous events"
|
||||||
|
},
|
||||||
"TnyLrZ": {
|
"TnyLrZ": {
|
||||||
"context": "PageTypeDeleteWarningDialog with items multiple description",
|
"context": "PageTypeDeleteWarningDialog with items multiple description",
|
||||||
"string": "You are about to delete multiple page types. Some of them are assigned to pages. Deleting those page types will also delete those pages"
|
"string": "You are about to delete multiple page types. Some of them are assigned to pages. Deleting those page types will also delete those pages"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { MockedProvider, MockedResponse } from "@apollo/client/testing";
|
import { MockedProvider, MockedResponse } from "@apollo/client/testing";
|
||||||
import { WebhookEventTypeAsyncEnum } from "@dashboard/graphql";
|
import { WebhookEventTypeSyncEnum } from "@dashboard/graphql";
|
||||||
import { ThemeProvider } from "@saleor/macaw-ui";
|
import { ThemeProvider } from "@saleor/macaw-ui";
|
||||||
import productsMocks from "@test/mocks/products";
|
import productsMocks from "@test/mocks/products";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
|
@ -23,8 +23,8 @@ describe("DryRun", () => {
|
||||||
query: "",
|
query: "",
|
||||||
showDialog: true,
|
showDialog: true,
|
||||||
setShowDialog: jest.fn(),
|
setShowDialog: jest.fn(),
|
||||||
asyncEvents: [] as WebhookEventTypeAsyncEnum[],
|
|
||||||
setResult: jest.fn(),
|
setResult: jest.fn(),
|
||||||
|
syncEvents: [] as WebhookEventTypeSyncEnum[],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Grid from "@dashboard/components/Grid";
|
||||||
import { useStyles } from "@dashboard/custom-apps/components/WebhookEvents/styles";
|
import { useStyles } from "@dashboard/custom-apps/components/WebhookEvents/styles";
|
||||||
import {
|
import {
|
||||||
useTriggerWebhookDryRunMutation,
|
useTriggerWebhookDryRunMutation,
|
||||||
WebhookEventTypeAsyncEnum,
|
WebhookEventTypeSyncEnum,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import {
|
import {
|
||||||
capitalize,
|
capitalize,
|
||||||
|
@ -26,30 +26,33 @@ import React, { Dispatch, SetStateAction, useState } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import DryRunItemsList from "../DryRunItemsList/DryRunItemsList";
|
import DryRunItemsList from "../DryRunItemsList/DryRunItemsList";
|
||||||
|
import { DocumentMap } from "../DryRunItemsList/utils";
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
import { getObjects } from "./utils";
|
import { getUnavailableObjects } from "./utils";
|
||||||
|
|
||||||
interface DryRunProps {
|
interface DryRunProps {
|
||||||
query: string;
|
query: string;
|
||||||
showDialog: boolean;
|
showDialog: boolean;
|
||||||
setShowDialog: Dispatch<SetStateAction<boolean>>;
|
setShowDialog: Dispatch<SetStateAction<boolean>>;
|
||||||
asyncEvents: WebhookEventTypeAsyncEnum[];
|
|
||||||
setResult: Dispatch<SetStateAction<string>>;
|
setResult: Dispatch<SetStateAction<string>>;
|
||||||
|
syncEvents: WebhookEventTypeSyncEnum[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DryRun: React.FC<DryRunProps> = ({
|
const DryRun: React.FC<DryRunProps> = ({
|
||||||
setResult,
|
setResult,
|
||||||
showDialog,
|
showDialog,
|
||||||
setShowDialog,
|
setShowDialog,
|
||||||
query,
|
query,
|
||||||
|
syncEvents,
|
||||||
}: DryRunProps) => {
|
}: DryRunProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [objectId, setObjectId] = useState<string | null>(null);
|
const [objectId, setObjectId] = useState<string | null>(null);
|
||||||
const [triggerWebhookDryRun] = useTriggerWebhookDryRunMutation();
|
const [triggerWebhookDryRun] = useTriggerWebhookDryRunMutation();
|
||||||
const availableObjects = getObjects(query);
|
const availableObjects = Object.keys(DocumentMap).map(object =>
|
||||||
const unavailableObjects = getObjects(query, false);
|
capitalize(object.split("_").join(" ").toLowerCase()),
|
||||||
|
);
|
||||||
|
const unavailableObjects = getUnavailableObjects(query);
|
||||||
const [object, setObject] = useState<string | null>(null);
|
const [object, setObject] = useState<string | null>(null);
|
||||||
|
|
||||||
const dryRun = async () => {
|
const dryRun = async () => {
|
||||||
|
@ -71,6 +74,23 @@ export const DryRun: React.FC<DryRunProps> = ({
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (syncEvents.length > 0) {
|
||||||
|
return (
|
||||||
|
<Dialog open={showDialog} fullWidth maxWidth="md" data-test-id="dry-run">
|
||||||
|
<DialogHeader onClose={closeDialog}>
|
||||||
|
{intl.formatMessage(messages.header)}
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogContent style={{ overflow: "scroll" }}>
|
||||||
|
<Alert variant="error" close={false}>
|
||||||
|
<Typography>
|
||||||
|
{intl.formatMessage(messages.unavailableSyncEvents)}
|
||||||
|
</Typography>
|
||||||
|
</Alert>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showDialog} fullWidth maxWidth="md" data-test-id="dry-run">
|
<Dialog open={showDialog} fullWidth maxWidth="md" data-test-id="dry-run">
|
||||||
<DialogHeader onClose={closeDialog}>
|
<DialogHeader onClose={closeDialog}>
|
||||||
|
@ -84,8 +104,8 @@ export const DryRun: React.FC<DryRunProps> = ({
|
||||||
{!!unavailableObjects.length && (
|
{!!unavailableObjects.length && (
|
||||||
<Alert variant="warning" close={false}>
|
<Alert variant="warning" close={false}>
|
||||||
<Typography>
|
<Typography>
|
||||||
{intl.formatMessage(messages.unavailableObjects)}
|
{intl.formatMessage(messages.unavailableEvents)}
|
||||||
|
<br />
|
||||||
<strong>{unavailableObjects.join(", ")}</strong>
|
<strong>{unavailableObjects.join(", ")}</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -158,7 +178,7 @@ export const DryRun: React.FC<DryRunProps> = ({
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={dryRun}
|
onClick={dryRun}
|
||||||
disabled={!availableObjects.length}
|
disabled={!object}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.run)}
|
{intl.formatMessage(messages.run)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -6,16 +6,21 @@ export const messages = defineMessages({
|
||||||
defaultMessage: "Dry run",
|
defaultMessage: "Dry run",
|
||||||
description: "Dry run dialog header",
|
description: "Dry run dialog header",
|
||||||
},
|
},
|
||||||
|
unavailableSyncEvents: {
|
||||||
|
id: "TnnDjx",
|
||||||
|
defaultMessage: "Dry run currently is not available for synchronous events",
|
||||||
|
description: "Dry run sync events alert",
|
||||||
|
},
|
||||||
selectObject: {
|
selectObject: {
|
||||||
id: "+RffqY",
|
id: "+RffqY",
|
||||||
defaultMessage: "Select object type to perform dry run on provided query",
|
defaultMessage: "Select object type to perform dry run on provided query",
|
||||||
description: "Dry run dialog object title",
|
description: "Dry run dialog object title",
|
||||||
},
|
},
|
||||||
unavailableObjects: {
|
unavailableEvents: {
|
||||||
id: "ApNw0L",
|
id: "BYTvv/",
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"The following objects are currently not available for dry run:",
|
"The following events from provided query are currently not available for dry run:",
|
||||||
description: "Dry run objects unavailable",
|
description: "Dry run events unavailable",
|
||||||
},
|
},
|
||||||
objects: {
|
objects: {
|
||||||
id: "uccjUM",
|
id: "uccjUM",
|
||||||
|
|
24
src/components/DryRun/utils.test.ts
Normal file
24
src/components/DryRun/utils.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { getUnavailableObjects } from "./utils";
|
||||||
|
|
||||||
|
describe("getUnavailableObjects", () => {
|
||||||
|
it("should return unavailable for dry run events from provided query", () => {
|
||||||
|
const query = `
|
||||||
|
subscription {
|
||||||
|
event {
|
||||||
|
... on ProductUpdated {
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
... on ProductDeleted {
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
... on AddressUpdated {
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const events = getUnavailableObjects(query);
|
||||||
|
|
||||||
|
expect(events).toEqual(["AddressUpdated"]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +1,8 @@
|
||||||
import { AsyncWebhookTypes } from "@dashboard/custom-apps/components/WebhookEvents";
|
import { getWebhookTypes } from "@dashboard/custom-apps/components/WebhookEvents/utils";
|
||||||
|
import { WebhookEventTypeAsyncEnum } from "@dashboard/graphql";
|
||||||
import { InlineFragmentNode, ObjectFieldNode, parse, visit } from "graphql";
|
import { InlineFragmentNode, ObjectFieldNode, parse, visit } from "graphql";
|
||||||
import uniq from "lodash/uniq";
|
|
||||||
|
|
||||||
import { ExcludedDocumentMap } from "../DryRunItemsList/utils";
|
import { DocumentMap, ExcludedDocumentMap } from "../DryRunItemsList/utils";
|
||||||
|
|
||||||
const getEventsFromQuery = (query: string) => {
|
const getEventsFromQuery = (query: string) => {
|
||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
|
@ -32,28 +32,32 @@ const getEventsFromQuery = (query: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getObjects = (query: string, available = true) => {
|
export const getUnavailableObjects = (query: string) => {
|
||||||
const queryEvents = getEventsFromQuery(query);
|
const queryEvents = getEventsFromQuery(query);
|
||||||
|
|
||||||
return uniq(
|
return queryEvents.reduce((acc, event) => {
|
||||||
queryEvents.map(event => {
|
const formattedEvent = event
|
||||||
const object = event.split(/(?=[A-Z])/).slice(0, -1);
|
.split(/(?=[A-Z])/)
|
||||||
if (
|
.join("_")
|
||||||
Object.keys(AsyncWebhookTypes)
|
.toUpperCase();
|
||||||
.filter(object =>
|
if (checkEventPresence(formattedEvent)) {
|
||||||
available
|
acc.push(event);
|
||||||
? !Object.keys(ExcludedDocumentMap).includes(object.toUpperCase())
|
}
|
||||||
: Object.keys(ExcludedDocumentMap).includes(object.toUpperCase()),
|
|
||||||
)
|
|
||||||
.includes(object.join("_").toUpperCase())
|
|
||||||
) {
|
|
||||||
return object.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
return acc;
|
||||||
.split(/(?=[A-Z])/)
|
}, []);
|
||||||
.slice(0, -2)
|
};
|
||||||
.join(" ");
|
|
||||||
}),
|
const checkEventPresence = (event: string) => {
|
||||||
).filter(object => object.length > 0);
|
const webhookTypes = getWebhookTypes(Object.keys(WebhookEventTypeAsyncEnum));
|
||||||
|
const availableObjects = Object.keys(DocumentMap);
|
||||||
|
const excludedObjects = Object.keys(webhookTypes).filter(
|
||||||
|
object => !availableObjects.includes(object),
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.keys(ExcludedDocumentMap).forEach(
|
||||||
|
object => !excludedObjects.includes(object) && excludedObjects.push(object),
|
||||||
|
);
|
||||||
|
|
||||||
|
return excludedObjects.some(object => event.startsWith(object));
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,17 +18,21 @@ import { useIntl } from "react-intl";
|
||||||
import Avatar from "../TableCellAvatar/Avatar";
|
import Avatar from "../TableCellAvatar/Avatar";
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
import { DocumentMap, TData, TVariables } from "./utils";
|
import { DocumentMap, TData, TVariables } from "./utils";
|
||||||
interface DryRunItemsListProps {
|
|
||||||
|
export interface DryRunItemsListProps {
|
||||||
objectId: string;
|
objectId: string;
|
||||||
setObjectId: React.Dispatch<any>;
|
setObjectId: React.Dispatch<any>;
|
||||||
object: string;
|
object: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DryRunItemsList = (props: DryRunItemsListProps) => {
|
const DryRunItemsList: React.FC<DryRunItemsListProps> = ({
|
||||||
|
object,
|
||||||
|
objectId,
|
||||||
|
setObjectId,
|
||||||
|
}: DryRunItemsListProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { checkbox } = useListWidths();
|
const { checkbox } = useListWidths();
|
||||||
const { object, objectId, setObjectId } = props;
|
|
||||||
const objectDocument = DocumentMap[object];
|
const objectDocument = DocumentMap[object];
|
||||||
const objectCollection =
|
const objectCollection =
|
||||||
objectDocument.collection ?? camelCase(`${object.toLowerCase()}s`);
|
objectDocument.collection ?? camelCase(`${object.toLowerCase()}s`);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { WebhookEventTypeAsyncEnum } from "@dashboard/graphql";
|
import { WebhookFormData } from "@dashboard/custom-apps/components/WebhookDetailsPage";
|
||||||
import {
|
import {
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
GraphiQLProvider,
|
GraphiQLProvider,
|
||||||
|
@ -78,7 +78,7 @@ export function GraphiQL({
|
||||||
visiblePlugin,
|
visiblePlugin,
|
||||||
defaultHeaders,
|
defaultHeaders,
|
||||||
...props
|
...props
|
||||||
}: GraphiQLProps & { asyncEvents: WebhookEventTypeAsyncEnum[] }) {
|
}: GraphiQLProps & { data: WebhookFormData }) {
|
||||||
// Ensure props are correct
|
// Ensure props are correct
|
||||||
if (typeof fetcher !== "function") {
|
if (typeof fetcher !== "function") {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
|
@ -130,7 +130,7 @@ export function GraphiQL({
|
||||||
setShowDialog={setShowDialog}
|
setShowDialog={setShowDialog}
|
||||||
query={query}
|
query={query}
|
||||||
setResult={setResult}
|
setResult={setResult}
|
||||||
asyncEvents={props.asyncEvents}
|
syncEvents={props.data.syncEvents}
|
||||||
/>
|
/>
|
||||||
</GraphiQLProvider>
|
</GraphiQLProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -96,17 +96,18 @@ const WebhookDetailsPage: React.FC<WebhookDetailsPageProps> = ({
|
||||||
return (
|
return (
|
||||||
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
|
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
|
||||||
{({ data, submit, change }) => {
|
{({ data, submit, change }) => {
|
||||||
const handleSyncEventsSelect = createSyncEventsSelectHandler(
|
const handleSyncEventsSelect = createSyncEventsSelectHandler({
|
||||||
change,
|
change,
|
||||||
data.syncEvents,
|
data,
|
||||||
setQuery,
|
|
||||||
);
|
|
||||||
const handleAsyncEventsSelect = createAsyncEventsSelectHandler(
|
|
||||||
change,
|
|
||||||
data.asyncEvents,
|
|
||||||
query,
|
query,
|
||||||
setQuery,
|
setQuery,
|
||||||
);
|
});
|
||||||
|
const handleAsyncEventsSelect = createAsyncEventsSelectHandler({
|
||||||
|
change,
|
||||||
|
data,
|
||||||
|
query,
|
||||||
|
setQuery,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DetailedContent useSingleColumn>
|
<DetailedContent useSingleColumn>
|
||||||
|
@ -127,6 +128,7 @@ const WebhookDetailsPage: React.FC<WebhookDetailsPageProps> = ({
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<WebhookEvents
|
<WebhookEvents
|
||||||
data={data}
|
data={data}
|
||||||
|
setQuery={setQuery}
|
||||||
onSyncEventChange={handleSyncEventsSelect}
|
onSyncEventChange={handleSyncEventsSelect}
|
||||||
onAsyncEventChange={handleAsyncEventsSelect}
|
onAsyncEventChange={handleAsyncEventsSelect}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,23 +19,26 @@ import {
|
||||||
Pill,
|
Pill,
|
||||||
useListWidths,
|
useListWidths,
|
||||||
} from "@saleor/macaw-ui";
|
} from "@saleor/macaw-ui";
|
||||||
import React, { useState } from "react";
|
import React, { Dispatch, SetStateAction, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
import { useStyles } from "./styles";
|
import { useStyles } from "./styles";
|
||||||
|
import { EventTypes, getEventName } from "./utils";
|
||||||
|
|
||||||
interface WebhookEventsProps {
|
interface WebhookEventsProps {
|
||||||
data: {
|
data: {
|
||||||
syncEvents: WebhookEventTypeSyncEnum[];
|
syncEvents: WebhookEventTypeSyncEnum[];
|
||||||
asyncEvents: WebhookEventTypeAsyncEnum[];
|
asyncEvents: WebhookEventTypeAsyncEnum[];
|
||||||
};
|
};
|
||||||
|
setQuery: Dispatch<SetStateAction<string>>;
|
||||||
onSyncEventChange: (event: ChangeEvent) => void;
|
onSyncEventChange: (event: ChangeEvent) => void;
|
||||||
onAsyncEventChange: (event: ChangeEvent) => void;
|
onAsyncEventChange: (event: ChangeEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WebhookEvents: React.FC<WebhookEventsProps> = ({
|
const WebhookEvents: React.FC<WebhookEventsProps> = ({
|
||||||
data,
|
data,
|
||||||
|
setQuery,
|
||||||
onSyncEventChange,
|
onSyncEventChange,
|
||||||
onAsyncEventChange,
|
onAsyncEventChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -56,14 +59,19 @@ const WebhookEvents: React.FC<WebhookEventsProps> = ({
|
||||||
|
|
||||||
const handleTabChange = value => {
|
const handleTabChange = value => {
|
||||||
setObject(null);
|
setObject(null);
|
||||||
|
setQuery("");
|
||||||
setTab(value);
|
setTab(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const countEvents = object => {
|
const countEvents = object => {
|
||||||
const selected = tab === "sync" ? data.syncEvents : data.asyncEvents;
|
const selected = tab === "sync" ? data.syncEvents : data.asyncEvents;
|
||||||
const objectEvents = EventTypes[tab][object].map(
|
const objectEvents = EventTypes[tab][object].map(event => {
|
||||||
event => `${object}_${event}`,
|
if (event === object) {
|
||||||
);
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${object}_${event}`;
|
||||||
|
});
|
||||||
|
|
||||||
return objectEvents.filter((event: never) => selected.includes(event))
|
return objectEvents.filter((event: never) => selected.includes(event))
|
||||||
.length;
|
.length;
|
||||||
|
@ -177,62 +185,3 @@ const WebhookEvents: React.FC<WebhookEventsProps> = ({
|
||||||
};
|
};
|
||||||
WebhookEvents.displayName = "WebhookEvents";
|
WebhookEvents.displayName = "WebhookEvents";
|
||||||
export default WebhookEvents;
|
export default WebhookEvents;
|
||||||
|
|
||||||
type Actions = string[];
|
|
||||||
|
|
||||||
export const AsyncWebhookTypes: Record<string, Actions> = {
|
|
||||||
ADDRESS: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
APP: ["INSTALLED", "UPDATED", "DELETED"],
|
|
||||||
ATTRIBUTE: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
CATEGORY: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
CHANNEL: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
GIFT_CARD: ["CREATED", "UPDATED", "DELETED", "STATUS_CHANGED"],
|
|
||||||
|
|
||||||
CHECKOUT: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
COLLECTION: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
CUSTOMER: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
FULFILLMENT: ["CREATED"],
|
|
||||||
INVOICE: ["DELETED", "REQUESTED", "SENT"],
|
|
||||||
MENU: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
ORDER: [
|
|
||||||
"CANCELLED",
|
|
||||||
"CONFIRMED",
|
|
||||||
"CREATED",
|
|
||||||
"FULFILLED",
|
|
||||||
"FULLY_PAID",
|
|
||||||
"UPDATED",
|
|
||||||
],
|
|
||||||
PAGE: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
PRODUCT: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
PRODUCT_VARIANT: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
SALE: ["CREATED", "UPDATED", "DELETED", "TOGGLE"],
|
|
||||||
SHIPPING_PRICE: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
SHIPPING_ZONE: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
STAFF: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
TRANSLATION: ["ACTION_REQUEST", "CREATED", "UPDATED"],
|
|
||||||
VOUCHER: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
WAREHOUSE: ["CREATED", "UPDATED", "DELETED"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const SyncWebhookTypes: Record<string, Actions> = {
|
|
||||||
PAYMENT: [
|
|
||||||
"AUTHORIZE",
|
|
||||||
"CAPTURE",
|
|
||||||
"CONFIRM",
|
|
||||||
"LIST_GATEWAYS",
|
|
||||||
"PROCESS",
|
|
||||||
"REFUND",
|
|
||||||
"VOID",
|
|
||||||
],
|
|
||||||
CHECKOUT: ["CALCULATE_TAXES", "FILTER_SHIPPING_METHODS"],
|
|
||||||
ORDER: ["CALCULATE_TAXES", "FILTER_SHIPPING_METHODS"],
|
|
||||||
SHIPPING: ["LIST_METHODS_FOR_CHECKOUT"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const EventTypes = {
|
|
||||||
async: AsyncWebhookTypes,
|
|
||||||
sync: SyncWebhookTypes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEventName = (object: string, event: string) =>
|
|
||||||
[object, event].join("_").toUpperCase() as WebhookEventTypeSyncEnum;
|
|
||||||
|
|
23
src/custom-apps/components/WebhookEvents/utils.test.ts
Normal file
23
src/custom-apps/components/WebhookEvents/utils.test.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { getWebhookTypes } from "./utils";
|
||||||
|
|
||||||
|
const TestKeys = [
|
||||||
|
"DRAFT_ORDER_CREATED",
|
||||||
|
"DRAFT_ORDER_UPDATED",
|
||||||
|
"PRODUCT_CREATED",
|
||||||
|
"PRODUCT_UPDATED",
|
||||||
|
"PRODUCT_VARIANT_UPDATED",
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("getWebhookTypes", () => {
|
||||||
|
it("should map array of enum keys to objects with events ", () => {
|
||||||
|
const TestWebhookTypes = getWebhookTypes(TestKeys);
|
||||||
|
|
||||||
|
expect(Object.keys(TestWebhookTypes)).toEqual(["DRAFT_ORDER", "PRODUCT"]);
|
||||||
|
expect(TestWebhookTypes.DRAFT_ORDER).toEqual(["CREATED", "UPDATED"]);
|
||||||
|
expect(TestWebhookTypes.PRODUCT).toEqual([
|
||||||
|
"CREATED",
|
||||||
|
"UPDATED",
|
||||||
|
"VARIANT_UPDATED",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
46
src/custom-apps/components/WebhookEvents/utils.ts
Normal file
46
src/custom-apps/components/WebhookEvents/utils.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
WebhookEventTypeAsyncEnum,
|
||||||
|
WebhookEventTypeSyncEnum,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
|
|
||||||
|
type Actions = string[];
|
||||||
|
|
||||||
|
export const getWebhookTypes = (webhookEvents: string[]) => {
|
||||||
|
const multiWords = ["DRAFT_ORDER", "GIFT_CARD", "ANY_EVENTS"];
|
||||||
|
|
||||||
|
return webhookEvents.reduce<Record<string, Actions>>((acc, key) => {
|
||||||
|
const keywords = key.split("_");
|
||||||
|
const multiKeyword = keywords.slice(0, 2).join("_");
|
||||||
|
|
||||||
|
const [keyword, sliceSize] = multiWords.includes(multiKeyword)
|
||||||
|
? [multiKeyword, 2]
|
||||||
|
: [keywords[0], 1];
|
||||||
|
|
||||||
|
const event = keywords.slice(sliceSize).join("_");
|
||||||
|
const events = acc[keyword] || [];
|
||||||
|
events.push(!!event.length ? event : multiKeyword);
|
||||||
|
acc[keyword] = events;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AsyncWebhookTypes: Record<string, Actions> = getWebhookTypes(
|
||||||
|
Object.keys(WebhookEventTypeAsyncEnum),
|
||||||
|
);
|
||||||
|
|
||||||
|
const SyncWebhookTypes: Record<string, Actions> = getWebhookTypes(
|
||||||
|
Object.keys(WebhookEventTypeSyncEnum),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const EventTypes = {
|
||||||
|
async: AsyncWebhookTypes,
|
||||||
|
sync: SyncWebhookTypes,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getEventName = (object: string, event: string) => {
|
||||||
|
if (object === event) {
|
||||||
|
return object.toUpperCase() as WebhookEventTypeSyncEnum;
|
||||||
|
}
|
||||||
|
return [object, event].join("_").toUpperCase() as WebhookEventTypeSyncEnum;
|
||||||
|
};
|
|
@ -4,7 +4,6 @@ import CardTitle from "@dashboard/components/CardTitle";
|
||||||
import { useExplorerPlugin } from "@graphiql/plugin-explorer";
|
import { useExplorerPlugin } from "@graphiql/plugin-explorer";
|
||||||
import { createGraphiQLFetcher } from "@graphiql/toolkit";
|
import { createGraphiQLFetcher } from "@graphiql/toolkit";
|
||||||
import { Card, CardContent } from "@material-ui/core";
|
import { Card, CardContent } from "@material-ui/core";
|
||||||
import clsx from "clsx";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, useIntl } from "react-intl";
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -45,13 +44,8 @@ const WebhookSubscriptionQuery: React.FC<WebhookSubscriptionQueryProps> = ({
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className={classes.card}>
|
||||||
className={clsx(classes.card, data.syncEvents.length && classes.disabled)}
|
<CardTitle title={intl.formatMessage(messages.title)} />
|
||||||
>
|
|
||||||
<CardTitle
|
|
||||||
title={intl.formatMessage(messages.title)}
|
|
||||||
className={classes.cardTitle}
|
|
||||||
/>
|
|
||||||
<CardContent className={classes.cardContent}>
|
<CardContent className={classes.cardContent}>
|
||||||
<GraphiQL
|
<GraphiQL
|
||||||
data-test-id="graphiql-webhook"
|
data-test-id="graphiql-webhook"
|
||||||
|
@ -62,7 +56,7 @@ const WebhookSubscriptionQuery: React.FC<WebhookSubscriptionQueryProps> = ({
|
||||||
onEditQuery={setQuery}
|
onEditQuery={setQuery}
|
||||||
plugins={[explorerPlugin]}
|
plugins={[explorerPlugin]}
|
||||||
isHeadersEditorEnabled={false}
|
isHeadersEditorEnabled={false}
|
||||||
asyncEvents={data.asyncEvents}
|
data={data}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -12,28 +12,36 @@ import {
|
||||||
print,
|
print,
|
||||||
visit,
|
visit,
|
||||||
} from "graphql";
|
} from "graphql";
|
||||||
|
import isEmpty from "lodash/isEmpty";
|
||||||
|
import React, { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
import { WebhookFormData } from "./components/WebhookDetailsPage";
|
||||||
import { filterSelectedAsyncEvents } from "./utils";
|
import { filterSelectedAsyncEvents } from "./utils";
|
||||||
|
|
||||||
|
interface CreateSyncEventsSelectHandler {
|
||||||
|
change: (event: ChangeEvent, cb?: () => void) => void;
|
||||||
|
data: WebhookFormData;
|
||||||
|
query: string;
|
||||||
|
setQuery: Dispatch<SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
export const createSyncEventsSelectHandler =
|
export const createSyncEventsSelectHandler =
|
||||||
(
|
({ change, data, query, setQuery }: CreateSyncEventsSelectHandler) =>
|
||||||
change: (event: ChangeEvent, cb?: () => void) => void,
|
|
||||||
syncEvents: WebhookEventTypeSyncEnum[],
|
|
||||||
setQuery: React.Dispatch<React.SetStateAction<string>>,
|
|
||||||
) =>
|
|
||||||
(event: ChangeEvent) => {
|
(event: ChangeEvent) => {
|
||||||
|
const { syncEvents, asyncEvents } = data;
|
||||||
const events = toggle(event.target.value, syncEvents, (a, b) => a === b);
|
const events = toggle(event.target.value, syncEvents, (a, b) => a === b);
|
||||||
|
|
||||||
// Clear query
|
|
||||||
setQuery("");
|
|
||||||
|
|
||||||
// Clear asyncEvents
|
// Clear asyncEvents
|
||||||
change({
|
if (!isEmpty(asyncEvents)) {
|
||||||
target: {
|
setQuery("");
|
||||||
name: "asyncEvents",
|
|
||||||
value: [],
|
change({
|
||||||
},
|
target: {
|
||||||
});
|
name: "asyncEvents",
|
||||||
|
value: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
change({
|
change({
|
||||||
target: {
|
target: {
|
||||||
|
@ -41,26 +49,35 @@ export const createSyncEventsSelectHandler =
|
||||||
value: events,
|
value: events,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
handleQuery(events, query, setQuery);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface CreateAsyncEventsSelectHandler {
|
||||||
|
change: (event: ChangeEvent, cb?: () => void) => void;
|
||||||
|
data: WebhookFormData;
|
||||||
|
query: string;
|
||||||
|
setQuery: Dispatch<SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
export const createAsyncEventsSelectHandler =
|
export const createAsyncEventsSelectHandler =
|
||||||
(
|
({ change, data, query, setQuery }: CreateAsyncEventsSelectHandler) =>
|
||||||
change: (event: ChangeEvent, cb?: () => void) => void,
|
|
||||||
asyncEvents: WebhookEventTypeAsyncEnum[],
|
|
||||||
query: string,
|
|
||||||
setQuery: React.Dispatch<React.SetStateAction<string>>,
|
|
||||||
) =>
|
|
||||||
(event: ChangeEvent) => {
|
(event: ChangeEvent) => {
|
||||||
|
const { syncEvents, asyncEvents } = data;
|
||||||
const events = toggle(event.target.value, asyncEvents, (a, b) => a === b);
|
const events = toggle(event.target.value, asyncEvents, (a, b) => a === b);
|
||||||
const filteredEvents = filterSelectedAsyncEvents(events);
|
const filteredEvents = filterSelectedAsyncEvents(events);
|
||||||
|
|
||||||
// Clear syncEvents
|
// Clear syncEvents
|
||||||
change({
|
if (!isEmpty(syncEvents)) {
|
||||||
target: {
|
setQuery("");
|
||||||
name: "syncEvents",
|
|
||||||
value: [],
|
change({
|
||||||
},
|
target: {
|
||||||
});
|
name: "syncEvents",
|
||||||
|
value: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
change({
|
change({
|
||||||
target: {
|
target: {
|
||||||
|
@ -73,7 +90,7 @@ export const createAsyncEventsSelectHandler =
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQuery = (
|
const handleQuery = (
|
||||||
events: WebhookEventTypeAsyncEnum[],
|
events: WebhookEventTypeAsyncEnum[] | WebhookEventTypeSyncEnum[],
|
||||||
query: string,
|
query: string,
|
||||||
setQuery: React.Dispatch<React.SetStateAction<string>>,
|
setQuery: React.Dispatch<React.SetStateAction<string>>,
|
||||||
) => {
|
) => {
|
||||||
|
|
Loading…
Reference in a new issue