Add redirect to GraphiQL from product & order (#2940)
This commit is contained in:
parent
ffb16ff1ed
commit
b510d68fa9
12 changed files with 160 additions and 12 deletions
|
@ -18,6 +18,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Fix pagination errors on voucher and sale pages - #2317 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 redirect to GraphiQL from product & order details pages - #2940 by @zaiste
|
||||
|
||||
## 3.4
|
||||
|
||||
|
|
|
@ -1015,6 +1015,10 @@
|
|||
"context": "translations section name",
|
||||
"string": "Translations"
|
||||
},
|
||||
"5i/InH": {
|
||||
"context": "open new window button",
|
||||
"string": "Open this order in GraphiQL"
|
||||
},
|
||||
"5kvaFR": {
|
||||
"context": "product field",
|
||||
"string": "Export Variant SKU"
|
||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -54,6 +54,7 @@
|
|||
"jwt-decode": "^3.1.2",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lz-string": "^1.4.4",
|
||||
"marked": "^4.0.17",
|
||||
"moment-timezone": "^0.5.32",
|
||||
"qs": "^6.9.0",
|
||||
|
@ -26945,7 +26946,6 @@
|
|||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
"integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
|
@ -60149,8 +60149,7 @@
|
|||
"lz-string": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
|
||||
"integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==",
|
||||
"optional": true
|
||||
"integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ=="
|
||||
},
|
||||
"macos-release": {
|
||||
"version": "2.4.1",
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
"jwt-decode": "^3.1.2",
|
||||
"keycode": "^2.2.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lz-string": "^1.4.4",
|
||||
"marked": "^4.0.17",
|
||||
"moment-timezone": "^0.5.32",
|
||||
"qs": "^6.9.0",
|
||||
|
|
|
@ -24,7 +24,9 @@ import { SubmitPromise } from "@dashboard/hooks/useForm";
|
|||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
import { sectionNames } from "@dashboard/intl";
|
||||
import OrderChannelSectionCard from "@dashboard/orders/components/OrderChannelSectionCard";
|
||||
import { defaultGraphiQLQuery } from "@dashboard/orders/queries";
|
||||
import { orderListUrl } from "@dashboard/orders/urls";
|
||||
import { playgroundOpenHandler } from "@dashboard/utils/graphql";
|
||||
import { mapMetadataItemToInput } from "@dashboard/utils/maps";
|
||||
import useMetadataChangeTrigger from "@dashboard/utils/metadata/useMetadataChangeTrigger";
|
||||
import { Typography } from "@material-ui/core";
|
||||
|
@ -196,6 +198,13 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
order?.id,
|
||||
);
|
||||
|
||||
const openPlaygroundURL = playgroundOpenHandler({
|
||||
query: defaultGraphiQLQuery,
|
||||
headers: "",
|
||||
operationName: "",
|
||||
variables: `{ "id": "${order?.id}" }`,
|
||||
});
|
||||
|
||||
return (
|
||||
<Form confirmLeave initial={initial} onSubmit={handleSubmit}>
|
||||
{({ change, data, submit }) => {
|
||||
|
@ -211,9 +220,19 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
|||
inline
|
||||
title={<Title order={order} />}
|
||||
cardMenu={
|
||||
<CardMenu
|
||||
menuItems={[...selectCardMenuItems, ...extensionMenuItems]}
|
||||
/>
|
||||
<>
|
||||
<CardMenu
|
||||
menuItems={[
|
||||
...selectCardMenuItems,
|
||||
...extensionMenuItems,
|
||||
{
|
||||
label: intl.formatMessage(messages.openGraphiQL),
|
||||
onSelect: openPlaygroundURL,
|
||||
testId: "graphiql-redirect",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className={classes.date}>
|
||||
|
|
|
@ -16,4 +16,9 @@ export const messages = defineMessages({
|
|||
defaultMessage: "Return / Replace order",
|
||||
description: "return button",
|
||||
},
|
||||
openGraphiQL: {
|
||||
id: "5i/InH",
|
||||
defaultMessage: "Open this order in GraphiQL",
|
||||
description: "open new window button",
|
||||
},
|
||||
});
|
||||
|
|
|
@ -200,3 +200,18 @@ export const channelUsabilityData = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const defaultGraphiQLQuery = `query OrderDetails($id: ID!) {
|
||||
order(id: $id) {
|
||||
id
|
||||
number
|
||||
status
|
||||
isShippingRequired
|
||||
canFinalize
|
||||
created
|
||||
customerNote
|
||||
paymentStatus
|
||||
userEmail
|
||||
isPaid
|
||||
}
|
||||
}`;
|
||||
|
|
|
@ -117,5 +117,13 @@ describe("Product details page", () => {
|
|||
);
|
||||
// Assert
|
||||
expect(onSubmit.mock.calls[0][0].attributes[0].value.length).toEqual(0);
|
||||
|
||||
// Act
|
||||
const moreButton = screen.queryAllByTestId("show-more-button")[0];
|
||||
await user.click(moreButton);
|
||||
const graphiQLLink = screen.queryAllByTestId("graphiql-redirect")[0];
|
||||
|
||||
// Assert
|
||||
expect(graphiQLLink).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,10 +44,12 @@ import useStateFromProps from "@dashboard/hooks/useStateFromProps";
|
|||
import { sectionNames } from "@dashboard/intl";
|
||||
import { maybe } from "@dashboard/misc";
|
||||
import ProductExternalMediaDialog from "@dashboard/products/components/ProductExternalMediaDialog";
|
||||
import { defaultGraphiQLQuery } from "@dashboard/products/queries";
|
||||
import { productImageUrl, productListUrl } from "@dashboard/products/urls";
|
||||
import { ProductVariantListError } from "@dashboard/products/views/ProductUpdate/handlers/errors";
|
||||
import { UseProductUpdateHandlerError } from "@dashboard/products/views/ProductUpdate/handlers/useProductUpdateHandler";
|
||||
import { FetchMoreProps, RelayToFlat } from "@dashboard/types";
|
||||
import { playgroundOpenHandler } from "@dashboard/utils/graphql";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -59,6 +61,7 @@ import ProductOrganization from "../ProductOrganization";
|
|||
import ProductTaxes from "../ProductTaxes";
|
||||
import ProductVariants from "../ProductVariants";
|
||||
import ProductUpdateForm from "./form";
|
||||
import { messages } from "./messages";
|
||||
import ProductChannelsListingsDialog from "./ProductChannelsListingsDialog";
|
||||
import {
|
||||
ProductUpdateData,
|
||||
|
@ -242,6 +245,13 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
productId,
|
||||
);
|
||||
|
||||
const openPlaygroundURL = playgroundOpenHandler({
|
||||
query: defaultGraphiQLQuery,
|
||||
headers: "",
|
||||
operationName: "",
|
||||
variables: `{ "id": "${product?.id}" }`,
|
||||
});
|
||||
|
||||
return (
|
||||
<ProductUpdateForm
|
||||
isSimpleProduct={isSimpleProduct}
|
||||
|
@ -321,12 +331,17 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
{intl.formatMessage(sectionNames.products)}
|
||||
</Backlink>
|
||||
<PageHeader title={header}>
|
||||
{extensionMenuItems.length > 0 && (
|
||||
<CardMenu
|
||||
menuItems={extensionMenuItems}
|
||||
data-test-id="menu"
|
||||
/>
|
||||
)}
|
||||
<CardMenu
|
||||
menuItems={[
|
||||
...extensionMenuItems,
|
||||
{
|
||||
label: intl.formatMessage(messages.openGraphiQL),
|
||||
onSelect: openPlaygroundURL,
|
||||
testId: "graphiql-redirect",
|
||||
},
|
||||
]}
|
||||
data-test-id="menu"
|
||||
/>
|
||||
</PageHeader>
|
||||
<Grid richText>
|
||||
<div>
|
||||
|
|
9
src/products/components/ProductUpdatePage/messages.ts
Normal file
9
src/products/components/ProductUpdatePage/messages.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const messages = defineMessages({
|
||||
openGraphiQL: {
|
||||
id: "5i/InH",
|
||||
defaultMessage: "Open this order in GraphiQL",
|
||||
description: "open new window button",
|
||||
},
|
||||
});
|
|
@ -260,3 +260,12 @@ export const gridAttributes = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const defaultGraphiQLQuery = `query ProductDetails($id: ID!) {
|
||||
product(id: $id) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
description
|
||||
}
|
||||
}`;
|
||||
|
|
63
src/utils/graphql.ts
Normal file
63
src/utils/graphql.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import LzString from "lz-string";
|
||||
|
||||
export type EditorContent = Record<keyof typeof longKeysToShortKeys, string>;
|
||||
|
||||
type ShorterEditorContent = Record<
|
||||
typeof longKeysToShortKeys[keyof typeof longKeysToShortKeys],
|
||||
string
|
||||
>;
|
||||
|
||||
export function removeEmptyValues<T extends object>(
|
||||
editorContent: T,
|
||||
): Partial<T> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(editorContent).filter(([, val]) => !!val),
|
||||
) as Partial<T>;
|
||||
}
|
||||
|
||||
const longKeysToShortKeys = {
|
||||
query: "q",
|
||||
headers: "h",
|
||||
operationName: "o",
|
||||
variables: "v",
|
||||
} as const;
|
||||
|
||||
export const encodeGraphQLStatement = (editorContent: EditorContent) => {
|
||||
const shorterContent: ShorterEditorContent = {
|
||||
q: editorContent.query,
|
||||
h: editorContent.headers,
|
||||
o: editorContent.operationName,
|
||||
v: editorContent.variables,
|
||||
};
|
||||
const stringifiedContent = JSON.stringify(removeEmptyValues(shorterContent));
|
||||
|
||||
const editorContentToSaveInUrl =
|
||||
stringifiedContent === "{}"
|
||||
? ""
|
||||
: LzString.compressToEncodedURIComponent(stringifiedContent);
|
||||
|
||||
return `saleor/${editorContentToSaveInUrl}`;
|
||||
};
|
||||
|
||||
interface PlaygroundOpenHandlerInput {
|
||||
query: string;
|
||||
headers: string;
|
||||
operationName: string;
|
||||
variables: string;
|
||||
}
|
||||
|
||||
export const playgroundOpenHandler = ({
|
||||
query,
|
||||
headers,
|
||||
operationName,
|
||||
variables,
|
||||
}: PlaygroundOpenHandlerInput) => () => {
|
||||
const playgroundURL = new URL(process.env.API_URI);
|
||||
playgroundURL.hash = encodeGraphQLStatement({
|
||||
query,
|
||||
headers,
|
||||
operationName,
|
||||
variables,
|
||||
});
|
||||
window.open(playgroundURL, "_blank").focus();
|
||||
};
|
Loading…
Reference in a new issue