Extract permissions for subscription query (#3155)

This commit is contained in:
Jakub Neander 2023-02-21 12:38:28 +01:00 committed by GitHub
parent 1410919c6f
commit d4d9b1b91c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1155 additions and 34 deletions

View file

@ -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 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 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 - Add redirect to GraphiQL from product & order details pages - #2940 by @zaiste
- Extract permissions for subscription query - #3155 by @zaiste
## 3.4 ## 3.4

View file

@ -3820,10 +3820,6 @@
"context": "label for radio button", "context": "label for radio button",
"string": "Product prices are entered without tax" "string": "Product prices are entered without tax"
}, },
"QpSQ5w": {
"context": "Webhook subscription query card title",
"string": "Payload Query"
},
"Qph0GE": { "Qph0GE": {
"context": "dialog content", "context": "dialog content",
"string": "Add a new address:" "string": "Add a new address:"
@ -6356,6 +6352,10 @@
"context": "attribute properties regarding dashboard", "context": "attribute properties regarding dashboard",
"string": "Dashboard Properties" "string": "Dashboard Properties"
}, },
"lEG/12": {
"context": "Webhook subscription query card title",
"string": "Subscription Query"
},
"lF+VJQ": { "lF+VJQ": {
"context": "Shipment information card header", "context": "Shipment information card header",
"string": "Shipment information" "string": "Shipment information"
@ -6672,6 +6672,10 @@
"context": "tax class rates list label when no countries are assigned", "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." "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": { "nioOBQ": {
"context": "delete custom app", "context": "delete custom app",
"string": "Deleting {name}, you will delete all the data and webhooks regarding this app." "string": "Deleting {name}, you will delete all the data and webhooks regarding this app."

View file

@ -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(
<ApolloMockedProvider>
<PermissionAlert {...props} />
</ApolloMockedProvider>,
);
// 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();
});
});

View file

@ -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<PermissionAlertProps> = ({ 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 (
<div data-test-id="permission-alert">
{permissions.length > 0 && (
<Alert
title={intl.formatMessage({
id: "ngSJ7N",
defaultMessage:
"Your subscription query requires the following permissions:",
description: "alert title",
})}
variant="warning"
close={false}
>
<Typography>
<div style={{ display: "flex", gap: "12px" }}>
{permissions.map(permission => (
<Pill
size="small"
color="generic"
outlined
label={permission}
/>
))}
</div>
</Typography>
</Alert>
)}
</div>
);
};
PermissionAlert.displayName = "PermissionAlert";
export default PermissionAlert;

View file

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

View file

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

View file

@ -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<string, PermissionChildNode>;
}
type PermissionMap = Record<string, PermissionNode>;
// 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 = <T>(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 = <T>(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
: []),
],
[],
);
};

View file

@ -26,6 +26,7 @@ import { parse, print } from "graphql";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import PermissionAlert from "../PermissionAlert";
import WebhookSubscriptionQuery from "../WebhookSubscriptionQuery"; import WebhookSubscriptionQuery from "../WebhookSubscriptionQuery";
import { getHeaderTitle } from "./messages"; import { getHeaderTitle } from "./messages";
@ -131,6 +132,8 @@ const WebhookDetailsPage: React.FC<WebhookDetailsPageProps> = ({
setQuery={setQuery} setQuery={setQuery}
data={data} data={data}
/> />
<FormSpacer />
<PermissionAlert query={query} />
</Box> </Box>
</Content> </Content>
<Savebar <Savebar

View file

@ -49,7 +49,6 @@ describe("WebhookSubscriptionQuery", () => {
subscriptionQuery: "", subscriptionQuery: "",
}, },
}; };
// const user = userEvent.setup();
// Act // Act
render( render(
@ -62,31 +61,4 @@ describe("WebhookSubscriptionQuery", () => {
expect(screen.queryByTestId("graphiql-container")).toBeInTheDocument(); expect(screen.queryByTestId("graphiql-container")).toBeInTheDocument();
expect(screen.queryByTestId("graphiql-container2")).not.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(<WebhookSubscriptionQuery {...props} />);
const graphiQLContainer = getByTestId("graphiql-container");
user.type(graphiQLContainer, "{}");
// Assert
expect(props.setQuery).toHaveBeenCalled();
});
*/
}); });

View file

@ -14,8 +14,8 @@ import { useStyles } from "./styles";
const messages = defineMessages({ const messages = defineMessages({
title: { title: {
id: "QpSQ5w", id: "lEG/12",
defaultMessage: "Payload Query", defaultMessage: "Subscription Query",
description: "Webhook subscription query card title", description: "Webhook subscription query card title",
}, },
}); });

View file

@ -83,6 +83,7 @@ const handleQuery = (
.split("_") .split("_")
.map(chunk => capitalize(chunk)) .map(chunk => capitalize(chunk))
.join(""); .join("");
setQuery( setQuery(
print(parse(`subscription { event { ... on ${event} { __typename } } }`)), print(parse(`subscription { event { ... on ${event} { __typename } } }`)),
); );

View file

@ -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
// }
// }
// }
// }
// `;

View file

@ -4,6 +4,7 @@ import React from "react";
import { import {
addressMocks, addressMocks,
appsMocks, appsMocks,
introspectionMocks,
pageTypesMocks, pageTypesMocks,
warehousesMocks, warehousesMocks,
} from "./mocks"; } from "./mocks";
@ -13,6 +14,7 @@ const mocks: MockedResponse[] = [
...addressMocks, ...addressMocks,
...warehousesMocks, ...warehousesMocks,
...pageTypesMocks, ...pageTypesMocks,
...introspectionMocks,
]; ];
export const ApolloMockedProvider = ({ children }) => ( export const ApolloMockedProvider = ({ children }) => (

View file

@ -3,3 +3,4 @@ export * from "./apps";
export * from "./pageTypes"; export * from "./pageTypes";
export * from "./products"; export * from "./products";
export * from "./warehouses"; export * from "./warehouses";
export * from "./introspection";

View file

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