diff --git a/CHANGELOG.md b/CHANGELOG.md
index be556b60b..306169573 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Add format tip for text attribute rows - #2340 by @orzechdev
- 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
## 3.4
diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json
index 0513a6dd9..043f147ba 100644
--- a/locale/defaultMessages.json
+++ b/locale/defaultMessages.json
@@ -3820,10 +3820,6 @@
"context": "label for radio button",
"string": "Product prices are entered without tax"
},
- "QpSQ5w": {
- "context": "Webhook subscription query card title",
- "string": "Payload Query"
- },
"Qph0GE": {
"context": "dialog content",
"string": "Add a new address:"
@@ -6356,6 +6352,10 @@
"context": "attribute properties regarding dashboard",
"string": "Dashboard Properties"
},
+ "lEG/12": {
+ "context": "Webhook subscription query card title",
+ "string": "Subscription Query"
+ },
"lF+VJQ": {
"context": "Shipment information card header",
"string": "Shipment information"
@@ -6672,6 +6672,10 @@
"context": "tax class rates list label when no countries are assigned",
"string": "There are no countries using this tax class yet, use {tab} tab to assign tax rates."
},
+ "ngSJ7N": {
+ "context": "alert title",
+ "string": "Your subscription query requires the following permissions:"
+ },
"nioOBQ": {
"context": "delete custom app",
"string": "Deleting {name}, you will delete all the data and webhooks regarding this app."
diff --git a/src/custom-apps/components/PermissionAlert/PermissionAlert.test.tsx b/src/custom-apps/components/PermissionAlert/PermissionAlert.test.tsx
new file mode 100644
index 000000000..0e8794906
--- /dev/null
+++ b/src/custom-apps/components/PermissionAlert/PermissionAlert.test.tsx
@@ -0,0 +1,70 @@
+import "@testing-library/jest-dom";
+
+import { Fetcher } from "@graphiql/toolkit";
+import { ApolloMockedProvider } from "@test/ApolloMockedProvider";
+import { render, screen } from "@testing-library/react";
+import React from "react";
+
+import PermissionAlert from "./PermissionAlert";
+
+jest.mock("@graphiql/toolkit", () => ({
+ clear: jest.fn(),
+ createGraphiQLFetcher: jest.fn(_x => jest.fn() as Fetcher),
+}));
+
+jest.mock("react-intl", () => ({
+ useIntl: jest.fn(() => ({
+ formatMessage: jest.fn(x => x.defaultMessage),
+ })),
+ defineMessages: jest.fn(x => x),
+}));
+
+jest.mock("@saleor/macaw-ui", () => ({
+ useTheme: jest.fn(() => () => ({})),
+ useStyles: jest.fn(() => () => ({})),
+ makeStyles: jest.fn(() => () => ({})),
+ DialogHeader: jest.fn(() => () => <>>),
+}));
+
+beforeEach(() => {
+ window.localStorage.clear();
+});
+
+describe("WebhookSubscriptionQuery", () => {
+ it("is available on the webhook page", async () => {
+ // Arrange
+ const props = {
+ query: `subscription {
+ event {
+ ... on SaleUpdated {
+ version
+ sale {
+ name
+ }
+ }
+ ... on OrderCreated {
+ version
+ order {
+ invoices {
+ number
+ }
+ }
+ }
+ }
+ }
+ `,
+ };
+
+ render(
+
+
+ ,
+ );
+
+ // FIXME async components don't work with the current setup
+ // await waitFor(() => new Promise((res) => setTimeout(res, 500)))
+
+ // Assert
+ expect(screen.queryByTestId("permission-alert")).toBeInTheDocument();
+ });
+});
diff --git a/src/custom-apps/components/PermissionAlert/PermissionAlert.tsx b/src/custom-apps/components/PermissionAlert/PermissionAlert.tsx
new file mode 100644
index 000000000..1568681ae
--- /dev/null
+++ b/src/custom-apps/components/PermissionAlert/PermissionAlert.tsx
@@ -0,0 +1,59 @@
+import { useQuery } from "@apollo/client";
+import { Typography } from "@material-ui/core";
+import { Alert, Pill } from "@saleor/macaw-ui";
+import React from "react";
+import { useIntl } from "react-intl";
+
+import {
+ buildPermissionMap,
+ getPermissions,
+ IntrospectionQuery,
+} from "./utils";
+
+export interface PermissionAlertProps {
+ query: string;
+}
+
+const PermissionAlert: React.FC = ({ query }) => {
+ const intl = useIntl();
+
+ const { data } = useQuery(IntrospectionQuery, {
+ fetchPolicy: "network-only",
+ });
+ const elements = data?.__schema?.types || [];
+ const permissionMapping = buildPermissionMap(elements);
+ const permissions = getPermissions(query, permissionMapping);
+
+ return (
+
+ {permissions.length > 0 && (
+
+
+
+ {permissions.map(permission => (
+
+ ))}
+
+
+
+ )}
+
+ );
+};
+
+PermissionAlert.displayName = "PermissionAlert";
+export default PermissionAlert;
diff --git a/src/custom-apps/components/PermissionAlert/index.ts b/src/custom-apps/components/PermissionAlert/index.ts
new file mode 100644
index 000000000..279ce05b9
--- /dev/null
+++ b/src/custom-apps/components/PermissionAlert/index.ts
@@ -0,0 +1,2 @@
+export * from "./PermissionAlert";
+export { default } from "./PermissionAlert";
diff --git a/src/custom-apps/components/PermissionAlert/utils.test.ts b/src/custom-apps/components/PermissionAlert/utils.test.ts
new file mode 100644
index 000000000..a1ccdb295
--- /dev/null
+++ b/src/custom-apps/components/PermissionAlert/utils.test.ts
@@ -0,0 +1,30 @@
+import { extractPermissions } from "./utils";
+
+describe("Permission Parsing", () => {
+ it("should extract permissions from the meta `description` if available", () => {
+ // Arrange
+ // -> Order.invoices
+ // https://docs.saleor.io/docs/3.x/api-reference/objects/order
+ const description = `List of order invoices. Can be fetched for orders created in Saleor 3.2 and later, for other orders requires one of the following permissions: MANAGE_ORDERS, OWNER.`;
+
+ // Act
+ const permissions = extractPermissions(description);
+
+ // Assert
+ expect(permissions).toHaveLength(2);
+ expect(permissions[0]).toEqual("MANAGE_ORDERS");
+ });
+
+ it("should return empty list if the `description` doesn't mention permissions", () => {
+ // Arrange
+ // -> Order.number
+ // https://docs.saleor.io/docs/3.x/api-reference/objects/order
+ const description = `User-friendly number of an order.`;
+
+ // Act
+ const permissions = extractPermissions(description);
+
+ // Assert
+ expect(permissions).toHaveLength(0);
+ });
+});
diff --git a/src/custom-apps/components/PermissionAlert/utils.ts b/src/custom-apps/components/PermissionAlert/utils.ts
new file mode 100644
index 000000000..303e1d455
--- /dev/null
+++ b/src/custom-apps/components/PermissionAlert/utils.ts
@@ -0,0 +1,156 @@
+import { gql } from "@apollo/client";
+import { FieldNode, parse, SelectionNode, visit } from "graphql";
+
+interface IntrospectionNode {
+ kind: string;
+ name: string;
+ description: string;
+ fields: Array<{
+ name: string;
+ description: string;
+ }>;
+}
+
+interface PermissionChildNode {
+ permissions: string[];
+ children: string[];
+}
+
+interface PermissionNode {
+ permissions: string[];
+ children: Record;
+}
+
+type PermissionMap = Record;
+
+// cannot be in `queries.ts` as codegen cannot handle `__schema`
+export const IntrospectionQuery = gql`
+ query PermissionIntrospection {
+ __schema {
+ types {
+ kind
+ name
+ description
+ fields(includeDeprecated: false) {
+ name
+ description
+ }
+ }
+ }
+ }
+`;
+
+const uniq = (value: T, index: number, self: T[]) =>
+ self.indexOf(value) === index;
+
+// Right now, permissions are appended at the end of `description`
+// for each field in the result of the introspection query. The format
+// is kept consistent, so it's possible to use a RegEx to extract them
+// As we move forward, there will be changes in the core to make it
+// separated and independant, yet easily accessible
+export const extractPermissions = (description?: string) => {
+ const match = (description || "").match(/following permissions(.*): (.*?)\./);
+ const permissions = match ? match[2].split(",") : [];
+
+ return permissions;
+};
+
+export const getPermissions = (
+ query: string,
+ permissionMapping: PermissionMap,
+) => {
+ const cursors = extractCursorsFromQuery(query);
+ return cursors.map(findPermission(permissionMapping)).flat().filter(uniq);
+};
+
+export const buildPermissionMap = (
+ elements: IntrospectionNode[],
+): PermissionMap =>
+ elements
+ .filter(({ kind }) => kind === "OBJECT")
+ .filter(
+ ({ name }) =>
+ !/(Created|Create|Delete|Deleted|Update|Updated)$/.test(name),
+ )
+ .reduce((saved, { name, description, fields }) => {
+ const permissions = extractPermissions(description);
+
+ const children = fields.reduce((prev, { name, description }) => {
+ const permissions = extractPermissions(description);
+
+ return {
+ ...prev,
+ [name.toLowerCase()]: { permissions },
+ };
+ }, {});
+
+ return {
+ ...saved,
+ [name.toLowerCase()]: {
+ permissions,
+ children,
+ },
+ };
+ }, {});
+
+const byKind = (name: string) => (element: SelectionNode) =>
+ element.kind === name;
+const extractValue = (element: FieldNode) => element.name.value;
+const isNotEvent = (value: string) => value !== "event";
+
+export const extractCursorsFromQuery = (query: string) => {
+ const cursors: string[][] = [];
+
+ try {
+ const ast = parse(query);
+
+ visit(ast, {
+ Field(node, _key, _parent, _path, ancestors) {
+ if (node.name.value !== "__typename") {
+ const cursor = ancestors
+ .filter(byKind("Field"))
+ .map(extractValue)
+ .filter(isNotEvent);
+
+ if (cursor.length > 0) {
+ cursors.push([...cursor, node.name.value]);
+ }
+ }
+ },
+ });
+ } catch (error) {
+ // explicit silent
+ }
+
+ return cursors;
+};
+
+const groupBy = (list: T[], groupSize = 2) =>
+ list
+ .slice(groupSize - 1)
+ .map((_, index) => list.slice(index, index + groupSize));
+
+// Permission Map is a tree like nested structure. As we want to
+// visit each level, we split the cursor provided by the user
+// into chunks (groups) to check if there are permissions for
+// each element between root and leafs
+// e.g.
+// ['query', 'order', 'invoices', 'name']
+// becomes
+// [['query', 'order'], ['order', 'invoices'], ['invoices', 'name']]
+// so we can check first ir `order` contains permission, then `invoices`
+// and then `name`
+export const findPermission =
+ (permissions: PermissionMap) => (cursor: string[]) => {
+ const groups = groupBy(["query", ...cursor]);
+
+ return groups.reduce(
+ (saved, [parent, child]) => [
+ ...saved,
+ ...(permissions[parent] && permissions[parent].children[child]
+ ? permissions[parent].children[child].permissions
+ : []),
+ ],
+ [],
+ );
+ };
diff --git a/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx b/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx
index 20acf360a..de7889bdf 100644
--- a/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx
+++ b/src/custom-apps/components/WebhookDetailsPage/WebhookDetailsPage.tsx
@@ -26,6 +26,7 @@ import { parse, print } from "graphql";
import React, { useEffect, useState } from "react";
import { useIntl } from "react-intl";
+import PermissionAlert from "../PermissionAlert";
import WebhookSubscriptionQuery from "../WebhookSubscriptionQuery";
import { getHeaderTitle } from "./messages";
@@ -131,6 +132,8 @@ const WebhookDetailsPage: React.FC = ({
setQuery={setQuery}
data={data}
/>
+
+ {
subscriptionQuery: "",
},
};
- // const user = userEvent.setup();
// Act
render(
@@ -62,31 +61,4 @@ describe("WebhookSubscriptionQuery", () => {
expect(screen.queryByTestId("graphiql-container")).toBeInTheDocument();
expect(screen.queryByTestId("graphiql-container2")).not.toBeInTheDocument();
});
-
- /*
- it("triggers setQuery when user enters text", async () => {
- // Arrange
- const props = {
- query: '',
- setQuery: jest.fn(),
- data: {
- syncEvents: [] as WebhookEventTypeSyncEnum[],
- asyncEvents: [] as WebhookEventTypeAsyncEnum[],
- isActive: false,
- name: '',
- targetUrl: '',
- subscriptionQuery: ''
- }
- };
- const user = userEvent.setup();
-
- // Act
- const { getByTestId } = render();
- const graphiQLContainer = getByTestId("graphiql-container");
- user.type(graphiQLContainer, "{}");
-
- // Assert
- expect(props.setQuery).toHaveBeenCalled();
- });
- */
});
diff --git a/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.tsx b/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.tsx
index 33cfa2555..266f7d43d 100644
--- a/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.tsx
+++ b/src/custom-apps/components/WebhookSubscriptionQuery/WebhookSubscriptionQuery.tsx
@@ -14,8 +14,8 @@ import { useStyles } from "./styles";
const messages = defineMessages({
title: {
- id: "QpSQ5w",
- defaultMessage: "Payload Query",
+ id: "lEG/12",
+ defaultMessage: "Subscription Query",
description: "Webhook subscription query card title",
},
});
diff --git a/src/custom-apps/handlers.ts b/src/custom-apps/handlers.ts
index d18b53dcc..102a6aa63 100644
--- a/src/custom-apps/handlers.ts
+++ b/src/custom-apps/handlers.ts
@@ -83,6 +83,7 @@ const handleQuery = (
.split("_")
.map(chunk => capitalize(chunk))
.join("");
+
setQuery(
print(parse(`subscription { event { ... on ${event} { __typename } } }`)),
);
diff --git a/src/custom-apps/queries.ts b/src/custom-apps/queries.ts
index 11b1ede9a..262002826 100644
--- a/src/custom-apps/queries.ts
+++ b/src/custom-apps/queries.ts
@@ -7,3 +7,22 @@ export const webhooksDetails = gql`
}
}
`;
+
+// TODO As of Feb 15, 2023 GraphQL Codegen is unable to
+// handle the introspection queries i.e. queries with the
+// `__schema`. Thus, the following query is defined in
+// `./utils.ts` as-is, without codegen handling it
+
+// export const introspectionQueryList = gql`
+// query IntrospectionQueryList {
+// __schema {
+// queryType {
+// name
+// fields {
+// name
+// description
+// }
+// }
+// }
+// }
+// `;
diff --git a/testUtils/ApolloMockedProvider.tsx b/testUtils/ApolloMockedProvider.tsx
index bc9fed0fd..7c5a6d0e4 100644
--- a/testUtils/ApolloMockedProvider.tsx
+++ b/testUtils/ApolloMockedProvider.tsx
@@ -4,6 +4,7 @@ import React from "react";
import {
addressMocks,
appsMocks,
+ introspectionMocks,
pageTypesMocks,
warehousesMocks,
} from "./mocks";
@@ -13,6 +14,7 @@ const mocks: MockedResponse[] = [
...addressMocks,
...warehousesMocks,
...pageTypesMocks,
+ ...introspectionMocks,
];
export const ApolloMockedProvider = ({ children }) => (
diff --git a/testUtils/mocks/index.ts b/testUtils/mocks/index.ts
index 43c5befd4..f8b9aa24c 100644
--- a/testUtils/mocks/index.ts
+++ b/testUtils/mocks/index.ts
@@ -3,3 +3,4 @@ export * from "./apps";
export * from "./pageTypes";
export * from "./products";
export * from "./warehouses";
+export * from "./introspection";
diff --git a/testUtils/mocks/introspection.ts b/testUtils/mocks/introspection.ts
new file mode 100644
index 000000000..5c26986de
--- /dev/null
+++ b/testUtils/mocks/introspection.ts
@@ -0,0 +1,801 @@
+import { MockedResponse } from "@apollo/client/testing";
+import { IntrospectionQuery } from "@dashboard/custom-apps/components/PermissionAlert/utils";
+
+export const introspectionMocks: MockedResponse[] = [
+ {
+ request: {
+ query: IntrospectionQuery,
+ variables: {},
+ },
+ result: {
+ data: {
+ __schema: {
+ types: [
+ {
+ kind: "OBJECT",
+ name: "Query",
+ description: null,
+ fields: [
+ {
+ name: "product",
+ description:
+ "Look up a product by ID. Requires one of the following permissions to include the unpublished items: MANAGE_ORDERS, MANAGE_DISCOUNTS, MANAGE_PRODUCTS.",
+ },
+ {
+ name: "products",
+ description:
+ "List of the shop's products. Requires one of the following permissions to include the unpublished items: MANAGE_ORDERS, MANAGE_DISCOUNTS, MANAGE_PRODUCTS.",
+ },
+ {
+ name: "order",
+ description: "Look up an order by ID or external reference.",
+ },
+ {
+ name: "orders",
+ description:
+ "List of orders.\n\nRequires one of the following permissions: MANAGE_ORDERS.",
+ },
+ {
+ name: "sale",
+ description:
+ "Look up a sale by ID.\n\nRequires one of the following permissions: MANAGE_DISCOUNTS.",
+ },
+ {
+ name: "sales",
+ description:
+ "List of the shop's sales.\n\nRequires one of the following permissions: MANAGE_DISCOUNTS.",
+ },
+ ],
+ },
+ {
+ kind: "OBJECT",
+ name: "Product",
+ description:
+ "Represents an individual item for sale in the storefront.",
+ fields: [
+ {
+ name: "id",
+ description: null,
+ },
+ {
+ name: "privateMetadata",
+ description:
+ "List of private metadata items. Requires staff permissions to access.",
+ },
+ {
+ name: "privateMetafield",
+ description:
+ "A single key from private metadata. Requires staff permissions to access.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "privateMetafields",
+ description:
+ "Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metadata",
+ description:
+ "List of public metadata items. Can be accessed without permissions.",
+ },
+ {
+ name: "metafield",
+ description:
+ "A single key from public metadata.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metafields",
+ description:
+ "Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "seoTitle",
+ description: null,
+ },
+ {
+ name: "seoDescription",
+ description: null,
+ },
+ {
+ name: "name",
+ description: null,
+ },
+ {
+ name: "description",
+ description:
+ "Description of the product.\n\nRich text format. For reference see https://editorjs.io/",
+ },
+ {
+ name: "productType",
+ description: null,
+ },
+ {
+ name: "slug",
+ description: null,
+ },
+ {
+ name: "category",
+ description: null,
+ },
+ {
+ name: "created",
+ description: null,
+ },
+ {
+ name: "updatedAt",
+ description: null,
+ },
+ {
+ name: "weight",
+ description: null,
+ },
+ {
+ name: "defaultVariant",
+ description: null,
+ },
+ {
+ name: "rating",
+ description: null,
+ },
+ {
+ name: "channel",
+ description:
+ "Channel given to retrieve this product. Also used by federation gateway to resolve this object in a federated query.",
+ },
+ {
+ name: "thumbnail",
+ description: null,
+ },
+ {
+ name: "pricing",
+ description:
+ "Lists the storefront product's pricing, the current price and discounts, only meant for displaying.",
+ },
+ {
+ name: "isAvailable",
+ description:
+ "Whether the product is in stock and visible or not.",
+ },
+ {
+ name: "attribute",
+ description:
+ "Get a single attribute attached to product by attribute slug.\n\nAdded in Saleor 3.9.",
+ },
+ {
+ name: "attributes",
+ description: "List of attributes assigned to this product.",
+ },
+ {
+ name: "channelListings",
+ description:
+ "List of availability in channels for the product.\n\nRequires one of the following permissions: MANAGE_PRODUCTS.",
+ },
+ {
+ name: "mediaById",
+ description: "Get a single product media by ID.",
+ },
+ {
+ name: "variants",
+ description:
+ "List of variants for the product. Requires the following permissions to include the unpublished items: MANAGE_ORDERS, MANAGE_DISCOUNTS, MANAGE_PRODUCTS.",
+ },
+ {
+ name: "media",
+ description: "List of media for the product.",
+ },
+ {
+ name: "collections",
+ description:
+ "List of collections for the product. Requires the following permissions to include the unpublished items: MANAGE_ORDERS, MANAGE_DISCOUNTS, MANAGE_PRODUCTS.",
+ },
+ {
+ name: "translation",
+ description:
+ "Returns translated product fields for the given language code.",
+ },
+ {
+ name: "availableForPurchaseAt",
+ description: "Date when product is available for purchase.",
+ },
+ {
+ name: "isAvailableForPurchase",
+ description: "Whether the product is available for purchase.",
+ },
+ {
+ name: "taxClass",
+ description:
+ "Tax class assigned to this product type. All products of this product type use this tax class, unless it's overridden in the `Product` type.\n\nRequires one of the following permissions: AUTHENTICATED_STAFF_USER.",
+ },
+ {
+ name: "externalReference",
+ description:
+ "External ID of this product. \n\nAdded in Saleor 3.10.",
+ },
+ ],
+ },
+ {
+ kind: "OBJECT",
+ name: "ProductType",
+ description:
+ "Represents a type of product. It defines what attributes are available to products of this type.",
+ fields: [
+ {
+ name: "id",
+ description: null,
+ },
+ {
+ name: "privateMetadata",
+ description:
+ "List of private metadata items. Requires staff permissions to access.",
+ },
+ {
+ name: "privateMetafield",
+ description:
+ "A single key from private metadata. Requires staff permissions to access.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "privateMetafields",
+ description:
+ "Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metadata",
+ description:
+ "List of public metadata items. Can be accessed without permissions.",
+ },
+ {
+ name: "metafield",
+ description:
+ "A single key from public metadata.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metafields",
+ description:
+ "Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "name",
+ description: null,
+ },
+ {
+ name: "slug",
+ description: null,
+ },
+ {
+ name: "hasVariants",
+ description: null,
+ },
+ {
+ name: "isShippingRequired",
+ description: null,
+ },
+ {
+ name: "isDigital",
+ description: null,
+ },
+ {
+ name: "weight",
+ description: null,
+ },
+ {
+ name: "kind",
+ description: "The product type kind.",
+ },
+ {
+ name: "taxClass",
+ description:
+ "Tax class assigned to this product type. All products of this product type use this tax class, unless it's overridden in the `Product` type.\n\nRequires one of the following permissions: AUTHENTICATED_STAFF_USER.",
+ },
+ {
+ name: "assignedVariantAttributes",
+ description:
+ "Variant attributes of that product type with attached variant selection.\n\nAdded in Saleor 3.1.",
+ },
+ {
+ name: "productAttributes",
+ description: "Product attributes of that product type.",
+ },
+ {
+ name: "availableAttributes",
+ description:
+ "List of attributes which can be assigned to this product type.\n\nRequires one of the following permissions: MANAGE_PRODUCTS.",
+ },
+ ],
+ },
+ {
+ kind: "ENUM",
+ name: "ProductTypeKindEnum",
+ description: "An enumeration.",
+ fields: null,
+ },
+ {
+ kind: "OBJECT",
+ name: "Sale",
+ description:
+ "Sales allow creating discounts for categories, collections or products and are visible to all the customers.",
+ fields: [
+ {
+ name: "id",
+ description: null,
+ },
+ {
+ name: "privateMetadata",
+ description:
+ "List of private metadata items. Requires staff permissions to access.",
+ },
+ {
+ name: "privateMetafield",
+ description:
+ "A single key from private metadata. Requires staff permissions to access.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "privateMetafields",
+ description:
+ "Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metadata",
+ description:
+ "List of public metadata items. Can be accessed without permissions.",
+ },
+ {
+ name: "metafield",
+ description:
+ "A single key from public metadata.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metafields",
+ description:
+ "Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "name",
+ description: null,
+ },
+ {
+ name: "type",
+ description: null,
+ },
+ {
+ name: "startDate",
+ description: null,
+ },
+ {
+ name: "endDate",
+ description: null,
+ },
+ {
+ name: "created",
+ description: null,
+ },
+ {
+ name: "updatedAt",
+ description: null,
+ },
+ {
+ name: "categories",
+ description: "List of categories this sale applies to.",
+ },
+ {
+ name: "collections",
+ description:
+ "List of collections this sale applies to.\n\nRequires one of the following permissions: MANAGE_DISCOUNTS.",
+ },
+ {
+ name: "products",
+ description:
+ "List of products this sale applies to.\n\nRequires one of the following permissions: MANAGE_DISCOUNTS.",
+ },
+ {
+ name: "variants",
+ description:
+ "List of product variants this sale applies to.\n\nAdded in Saleor 3.1.\n\nRequires one of the following permissions: MANAGE_DISCOUNTS.",
+ },
+ {
+ name: "translation",
+ description:
+ "Returns translated sale fields for the given language code.",
+ },
+ {
+ name: "channelListings",
+ description:
+ "List of channels available for the sale.\n\nRequires one of the following permissions: MANAGE_DISCOUNTS.",
+ },
+ {
+ name: "discountValue",
+ description: "Sale value.",
+ },
+ {
+ name: "currency",
+ description: "Currency code for sale.",
+ },
+ ],
+ },
+ {
+ kind: "ENUM",
+ name: "SaleType",
+ description: null,
+ fields: null,
+ },
+ {
+ kind: "OBJECT",
+ name: "Order",
+ description: "Represents an order in the shop.",
+ fields: [
+ {
+ name: "id",
+ description: null,
+ },
+ {
+ name: "privateMetadata",
+ description:
+ "List of private metadata items. Requires staff permissions to access.",
+ },
+ {
+ name: "privateMetafield",
+ description:
+ "A single key from private metadata. Requires staff permissions to access.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "privateMetafields",
+ description:
+ "Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metadata",
+ description:
+ "List of public metadata items. Can be accessed without permissions.",
+ },
+ {
+ name: "metafield",
+ description:
+ "A single key from public metadata.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metafields",
+ description:
+ "Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "created",
+ description: null,
+ },
+ {
+ name: "updatedAt",
+ description: null,
+ },
+ {
+ name: "status",
+ description: null,
+ },
+ {
+ name: "user",
+ description:
+ "User who placed the order. This field is set only for orders placed by authenticated users. Can be fetched for orders created in Saleor 3.2 and later, for other orders requires one of the following permissions: MANAGE_USERS, MANAGE_ORDERS, OWNER.",
+ },
+ {
+ name: "trackingClientId",
+ description: null,
+ },
+ {
+ name: "billingAddress",
+ description:
+ "Billing address. The full data can be access for orders created in Saleor 3.2 and later, for other orders requires one of the following permissions: MANAGE_ORDERS, OWNER.",
+ },
+ {
+ name: "shippingAddress",
+ description:
+ "Shipping address. The full data can be access for orders created in Saleor 3.2 and later, for other orders requires one of the following permissions: MANAGE_ORDERS, OWNER.",
+ },
+ {
+ name: "shippingMethodName",
+ description: null,
+ },
+ {
+ name: "collectionPointName",
+ description: null,
+ },
+ {
+ name: "channel",
+ description: null,
+ },
+ {
+ name: "fulfillments",
+ description: "List of shipments for the order.",
+ },
+ {
+ name: "lines",
+ description: "List of order lines.",
+ },
+ {
+ name: "actions",
+ description:
+ "List of actions that can be performed in the current state of an order.",
+ },
+ {
+ name: "shippingMethods",
+ description: "Shipping methods related to this order.",
+ },
+ {
+ name: "availableCollectionPoints",
+ description:
+ "Collection points that can be used for this order.\n\nAdded in Saleor 3.1.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "invoices",
+ description:
+ "List of order invoices. Can be fetched for orders created in Saleor 3.2 and later, for other orders requires one of the following permissions: MANAGE_ORDERS, OWNER.",
+ },
+ {
+ name: "number",
+ description: "User-friendly number of an order.",
+ },
+ {
+ name: "original",
+ description:
+ "The ID of the order that was the base for this order.",
+ },
+ {
+ name: "origin",
+ description: "The order origin.",
+ },
+ {
+ name: "isPaid",
+ description: "Informs if an order is fully paid.",
+ },
+ {
+ name: "paymentStatus",
+ description: "Internal payment status.",
+ },
+ {
+ name: "paymentStatusDisplay",
+ description: "User-friendly payment status.",
+ },
+ {
+ name: "authorizeStatus",
+ description:
+ "The authorize status of the order.\n\nAdded in Saleor 3.4.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "chargeStatus",
+ description:
+ "The charge status of the order.\n\nAdded in Saleor 3.4.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "taxExemption",
+ description:
+ "Returns True if order has to be exempt from taxes.\n\nAdded in Saleor 3.8.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "transactions",
+ description:
+ "List of transactions for the order. Requires one of the following permissions: MANAGE_ORDERS, HANDLE_PAYMENTS.\n\nAdded in Saleor 3.4.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "payments",
+ description: "List of payments for the order.",
+ },
+ {
+ name: "total",
+ description: "Total amount of the order.",
+ },
+ {
+ name: "undiscountedTotal",
+ description: "Undiscounted total amount of the order.",
+ },
+ {
+ name: "shippingPrice",
+ description: "Total price of shipping.",
+ },
+ {
+ name: "shippingTaxRate",
+ description: "The shipping tax rate value.",
+ },
+ {
+ name: "shippingTaxClass",
+ description:
+ "Denormalized tax class assigned to the shipping method.\n\nAdded in Saleor 3.9.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.\n\nRequires one of the following permissions: AUTHENTICATED_STAFF_USER.",
+ },
+ {
+ name: "shippingTaxClassName",
+ description:
+ "Denormalized name of the tax class assigned to the shipping method.\n\nAdded in Saleor 3.9.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "shippingTaxClassMetadata",
+ description:
+ "Denormalized public metadata of the shipping method's tax class.\n\nAdded in Saleor 3.9.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "shippingTaxClassPrivateMetadata",
+ description:
+ "Denormalized private metadata of the shipping method's tax class. Requires staff permissions to access.\n\nAdded in Saleor 3.9.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "voucher",
+ description: null,
+ },
+ {
+ name: "giftCards",
+ description: "List of user gift cards.",
+ },
+ {
+ name: "customerNote",
+ description: null,
+ },
+ {
+ name: "weight",
+ description: null,
+ },
+ {
+ name: "redirectUrl",
+ description: null,
+ },
+ {
+ name: "subtotal",
+ description: "The sum of line prices not including shipping.",
+ },
+ {
+ name: "statusDisplay",
+ description: "User-friendly order status.",
+ },
+ {
+ name: "canFinalize",
+ description:
+ "Informs whether a draft order can be finalized(turned into a regular order).",
+ },
+ {
+ name: "totalAuthorized",
+ description: "Amount authorized for the order.",
+ },
+ {
+ name: "totalCaptured",
+ description: "Amount captured by payment.",
+ },
+ {
+ name: "events",
+ description:
+ "List of events associated with the order.\n\nRequires one of the following permissions: MANAGE_ORDERS.",
+ },
+ {
+ name: "totalBalance",
+ description:
+ "The difference between the paid and the order total amount.",
+ },
+ {
+ name: "userEmail",
+ description:
+ "Email address of the customer. The full data can be access for orders created in Saleor 3.2 and later, for other orders requires one of the following permissions: MANAGE_ORDERS, OWNER.",
+ },
+ {
+ name: "isShippingRequired",
+ description: "Returns True, if order requires shipping.",
+ },
+ {
+ name: "deliveryMethod",
+ description:
+ "The delivery method selected for this order.\n\nAdded in Saleor 3.1.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "languageCodeEnum",
+ description: "Order language code.",
+ },
+ {
+ name: "discounts",
+ description: "List of all discounts assigned to the order.",
+ },
+ {
+ name: "errors",
+ description:
+ "List of errors that occurred during order validation.",
+ },
+ {
+ name: "displayGrossPrices",
+ description:
+ "Determines whether checkout prices should include taxes when displayed in a storefront.\n\nAdded in Saleor 3.9.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "externalReference",
+ description:
+ "External ID of this order. \n\nAdded in Saleor 3.10.",
+ },
+ {
+ name: "checkoutId",
+ description:
+ "ID of the checkout that the order was created from. \n\nAdded in Saleor 3.11.",
+ },
+ ],
+ },
+ {
+ kind: "ENUM",
+ name: "OrderStatus",
+ description: "An enumeration.",
+ fields: null,
+ },
+ {
+ kind: "OBJECT",
+ name: "Invoice",
+ description: "Represents an Invoice.",
+ fields: [
+ {
+ name: "privateMetadata",
+ description:
+ "List of private metadata items. Requires staff permissions to access.",
+ },
+ {
+ name: "privateMetafield",
+ description:
+ "A single key from private metadata. Requires staff permissions to access.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "privateMetafields",
+ description:
+ "Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metadata",
+ description:
+ "List of public metadata items. Can be accessed without permissions.",
+ },
+ {
+ name: "metafield",
+ description:
+ "A single key from public metadata.\n\nTip: Use GraphQL aliases to fetch multiple keys.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "metafields",
+ description:
+ "Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.\n\nAdded in Saleor 3.3.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
+ },
+ {
+ name: "status",
+ description: "Job status.",
+ },
+ {
+ name: "createdAt",
+ description: null,
+ },
+ {
+ name: "updatedAt",
+ description: null,
+ },
+ {
+ name: "message",
+ description: null,
+ },
+ {
+ name: "id",
+ description: "The ID of the object.",
+ },
+ {
+ name: "number",
+ description: null,
+ },
+ {
+ name: "externalUrl",
+ description: null,
+ },
+ {
+ name: "url",
+ description: "URL to download an invoice.",
+ },
+ {
+ name: "order",
+ description:
+ "Order related to the invoice.\n\nAdded in Saleor 3.10.",
+ },
+ ],
+ },
+ ],
+ },
+ },
+ extensions: {
+ cost: {
+ requestedQueryCost: 0,
+ maximumAvailable: 50000,
+ },
+ },
+ },
+ },
+];
+
+export default introspectionMocks;