Copy filters automatically to GraphiQL playground (#3385)

This commit is contained in:
Jakub Neander 2023-03-23 15:53:48 +01:00 committed by GitHub
parent 9690313d16
commit 82d15f4441
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 287 additions and 45 deletions

View file

@ -26,6 +26,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Add DevMode panel (trigger: CMD+') - #3333 by @zaiste - Add DevMode panel (trigger: CMD+') - #3333 by @zaiste
- Migrate to `es2020` for TypeScript - #3386 by @zaiste - Migrate to `es2020` for TypeScript - #3386 by @zaiste
- Fix styling for GraphiQL on the webhook page - #3389 by @zaiste - Fix styling for GraphiQL on the webhook page - #3389 by @zaiste
- Copy filters automatically to GraphiQL playground - #3385 by @zaiste
## 3.4 ## 3.4

View file

@ -8010,6 +8010,10 @@
"vEYtiq": { "vEYtiq": {
"string": "Category Name" "string": "Category Name"
}, },
"vEwjub": {
"context": "button",
"string": "Open in GraphiQL"
},
"vM9quW": { "vM9quW": {
"context": "order payment", "context": "order payment",
"string": "Paid with Gift Card" "string": "Paid with Gift Card"

View file

@ -1,8 +1,11 @@
import useAppState from "@dashboard/hooks/useAppState"; import useAppState from "@dashboard/hooks/useAppState";
import { DevModeQuery } from "@dashboard/orders/queries";
import { getFilterVariables } from "@dashboard/orders/views/OrderList/filters";
import { LinearProgress } from "@material-ui/core"; import { LinearProgress } from "@material-ui/core";
import { useActionBar } from "@saleor/macaw-ui"; import { useActionBar } from "@saleor/macaw-ui";
import { Box } from "@saleor/macaw-ui/next"; import { Box } from "@saleor/macaw-ui/next";
import React, { useState } from "react"; import React, { useState } from "react";
import { useLocation } from "react-router";
import { DevModePanel } from "../DevModePanel/DevModePanel"; import { DevModePanel } from "../DevModePanel/DevModePanel";
import { useDevModeContext } from "../DevModePanel/hooks"; import { useDevModeContext } from "../DevModePanel/hooks";
@ -11,6 +14,7 @@ import Navigator from "../Navigator";
import { Sidebar } from "../Sidebar"; import { Sidebar } from "../Sidebar";
import { contentMaxWidth } from "./consts"; import { contentMaxWidth } from "./consts";
import { useStyles } from "./styles"; import { useStyles } from "./styles";
import { extractQueryParams } from "./util";
interface AppLayoutProps { interface AppLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -23,9 +27,32 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
const [appState] = useAppState(); const [appState] = useAppState();
const [isNavigatorVisible, setNavigatorVisibility] = useState(false); const [isNavigatorVisible, setNavigatorVisibility] = useState(false);
const { isDevModeVisible, setDevModeVisibility } = useDevModeContext(); const {
isDevModeVisible,
setDevModeVisibility,
setDevModeContent,
setVariables,
} = useDevModeContext();
useDevModeKeyTrigger(() => setDevModeVisibility(!isDevModeVisible)); const params = extractQueryParams(useLocation().search);
useDevModeKeyTrigger(({ shift }) => {
if (shift) {
setDevModeContent(DevModeQuery);
const variables = JSON.stringify(
{
filter: getFilterVariables(params),
},
null,
2,
);
setVariables(variables);
} else {
setDevModeContent("");
setVariables("");
}
setDevModeVisibility(!isDevModeVisible);
});
return ( return (
<> <>

View file

@ -0,0 +1,25 @@
import { extractQueryParams } from "./util";
describe("extractQueryParams", () => {
test("parses a query string with an explicitly indexed list into an object of key-value pairs", () => {
const queryString =
"q=apple&color[0]=red&color[1]=green&shape[]=round&shape[]=oval";
const expected = {
q: "apple",
color: ["red", "green"],
shape: ["round", "oval"],
};
expect(extractQueryParams(queryString)).toEqual(expected);
});
test("parses a query string into an object of key-value pairs (overwrites non array elements!)", () => {
const queryString =
"q=apple&color=red&color=green&shape[]=round&shape[]=oval";
const expected = {
q: "apple",
color: "green",
shape: ["round", "oval"],
};
expect(extractQueryParams(queryString)).toEqual(expected);
});
});

View file

@ -0,0 +1,23 @@
export const extractQueryParams = (queryString: string) => {
const urlSearchParams = new URLSearchParams(queryString);
const queryParams = {};
urlSearchParams.forEach((value, key) => {
const arrayKeyRegex = /^(.+)\[\d*\]$/;
const match = key.match(arrayKeyRegex);
if (match) {
const arrayKey = match[1];
if (!queryParams.hasOwnProperty(arrayKey)) {
queryParams[arrayKey] = [];
}
queryParams[arrayKey].push(value);
} else {
queryParams[key] = value;
}
});
return queryParams;
};

View file

@ -1,10 +1,14 @@
import { useEffect } from "react"; import { useEffect } from "react";
export const useDevModeKeyTrigger = (callback?: () => void) => { type DevModeKeyTriggerCallback = ({ shift }: { shift: boolean }) => void;
export const useDevModeKeyTrigger = (callback?: DevModeKeyTriggerCallback) => {
useEffect(() => { useEffect(() => {
const handler = (event: KeyboardEvent) => { const handler = (event: KeyboardEvent) => {
if (event.metaKey && event.code === "Quote") { if (event.shiftKey && event.metaKey && event.code === "Quote") {
callback(); callback({ shift: true });
} else if (event.metaKey && event.code === "Quote") {
callback({ shift: false });
} }
}; };

View file

@ -9993,6 +9993,99 @@ export function useChannelUsabilityDataLazyQuery(baseOptions?: ApolloReactHooks.
export type ChannelUsabilityDataQueryHookResult = ReturnType<typeof useChannelUsabilityDataQuery>; export type ChannelUsabilityDataQueryHookResult = ReturnType<typeof useChannelUsabilityDataQuery>;
export type ChannelUsabilityDataLazyQueryHookResult = ReturnType<typeof useChannelUsabilityDataLazyQuery>; export type ChannelUsabilityDataLazyQueryHookResult = ReturnType<typeof useChannelUsabilityDataLazyQuery>;
export type ChannelUsabilityDataQueryResult = Apollo.QueryResult<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>; export type ChannelUsabilityDataQueryResult = Apollo.QueryResult<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>;
export const OrderDetailsGraphiQlDocument = gql`
query OrderDetailsGraphiQL($id: ID!) {
order(id: $id) {
id
number
status
isShippingRequired
canFinalize
created
customerNote
paymentStatus
userEmail
isPaid
}
}
`;
/**
* __useOrderDetailsGraphiQlQuery__
*
* To run a query within a React component, call `useOrderDetailsGraphiQlQuery` and pass it any options that fit your needs.
* When your component renders, `useOrderDetailsGraphiQlQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useOrderDetailsGraphiQlQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useOrderDetailsGraphiQlQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.OrderDetailsGraphiQlQuery, Types.OrderDetailsGraphiQlQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types.OrderDetailsGraphiQlQuery, Types.OrderDetailsGraphiQlQueryVariables>(OrderDetailsGraphiQlDocument, options);
}
export function useOrderDetailsGraphiQlLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.OrderDetailsGraphiQlQuery, Types.OrderDetailsGraphiQlQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types.OrderDetailsGraphiQlQuery, Types.OrderDetailsGraphiQlQueryVariables>(OrderDetailsGraphiQlDocument, options);
}
export type OrderDetailsGraphiQlQueryHookResult = ReturnType<typeof useOrderDetailsGraphiQlQuery>;
export type OrderDetailsGraphiQlLazyQueryHookResult = ReturnType<typeof useOrderDetailsGraphiQlLazyQuery>;
export type OrderDetailsGraphiQlQueryResult = Apollo.QueryResult<Types.OrderDetailsGraphiQlQuery, Types.OrderDetailsGraphiQlQueryVariables>;
export const DevModeRunDocument = gql`
query DevModeRun($filter: OrderFilterInput, $sortBy: OrderSortingInput) {
orders(first: 10, filter: $filter, sortBy: $sortBy) {
edges {
node {
id
number
status
isShippingRequired
canFinalize
created
customerNote
paymentStatus
userEmail
isPaid
}
}
}
}
`;
/**
* __useDevModeRunQuery__
*
* To run a query within a React component, call `useDevModeRunQuery` and pass it any options that fit your needs.
* When your component renders, `useDevModeRunQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useDevModeRunQuery({
* variables: {
* filter: // value for 'filter'
* sortBy: // value for 'sortBy'
* },
* });
*/
export function useDevModeRunQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<Types.DevModeRunQuery, Types.DevModeRunQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types.DevModeRunQuery, Types.DevModeRunQueryVariables>(DevModeRunDocument, options);
}
export function useDevModeRunLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.DevModeRunQuery, Types.DevModeRunQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types.DevModeRunQuery, Types.DevModeRunQueryVariables>(DevModeRunDocument, options);
}
export type DevModeRunQueryHookResult = ReturnType<typeof useDevModeRunQuery>;
export type DevModeRunLazyQueryHookResult = ReturnType<typeof useDevModeRunLazyQuery>;
export type DevModeRunQueryResult = Apollo.QueryResult<Types.DevModeRunQuery, Types.DevModeRunQueryVariables>;
export const PageTypeUpdateDocument = gql` export const PageTypeUpdateDocument = gql`
mutation PageTypeUpdate($id: ID!, $input: PageTypeUpdateInput!) { mutation PageTypeUpdate($id: ID!, $input: PageTypeUpdateInput!) {
pageTypeUpdate(id: $id, input: $input) { pageTypeUpdate(id: $id, input: $input) {

View file

@ -8640,6 +8640,21 @@ export type ChannelUsabilityDataQueryVariables = Exact<{
export type ChannelUsabilityDataQuery = { __typename: 'Query', products: { __typename: 'ProductCountableConnection', totalCount: number | null } | null }; export type ChannelUsabilityDataQuery = { __typename: 'Query', products: { __typename: 'ProductCountableConnection', totalCount: number | null } | null };
export type OrderDetailsGraphiQlQueryVariables = Exact<{
id: Scalars['ID'];
}>;
export type OrderDetailsGraphiQlQuery = { __typename: 'Query', order: { __typename: 'Order', id: string, number: string, status: OrderStatus, isShippingRequired: boolean, canFinalize: boolean, created: any, customerNote: string, paymentStatus: PaymentChargeStatusEnum, userEmail: string | null, isPaid: boolean } | null };
export type DevModeRunQueryVariables = Exact<{
filter?: InputMaybe<OrderFilterInput>;
sortBy?: InputMaybe<OrderSortingInput>;
}>;
export type DevModeRunQuery = { __typename: 'Query', orders: { __typename: 'OrderCountableConnection', edges: Array<{ __typename: 'OrderCountableEdge', node: { __typename: 'Order', id: string, number: string, status: OrderStatus, isShippingRequired: boolean, canFinalize: boolean, created: any, customerNote: string, paymentStatus: PaymentChargeStatusEnum, userEmail: string | null, isPaid: boolean } }> } | null };
export type PageTypeUpdateMutationVariables = Exact<{ export type PageTypeUpdateMutationVariables = Exact<{
id: Scalars['ID']; id: Scalars['ID'];
input: PageTypeUpdateInput; input: PageTypeUpdateInput;

View file

@ -78,6 +78,7 @@ const props: OrderListPageProps = {
...sortPageProps.sort, ...sortPageProps.sort,
sort: OrderListUrlSortField.number, sort: OrderListUrlSortField.number,
}, },
params: {},
}; };
storiesOf("Orders / Order list", module) storiesOf("Orders / Order list", module)

View file

@ -7,11 +7,17 @@ import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
import { TopNav } from "@dashboard/components/AppLayout/TopNav"; import { TopNav } from "@dashboard/components/AppLayout/TopNav";
import { ButtonWithSelect } from "@dashboard/components/ButtonWithSelect"; import { ButtonWithSelect } from "@dashboard/components/ButtonWithSelect";
import CardMenu from "@dashboard/components/CardMenu"; import CardMenu from "@dashboard/components/CardMenu";
import { useDevModeContext } from "@dashboard/components/DevModePanel/hooks";
import FilterBar from "@dashboard/components/FilterBar"; import FilterBar from "@dashboard/components/FilterBar";
import { ListPageLayout } from "@dashboard/components/Layouts"; import { ListPageLayout } from "@dashboard/components/Layouts";
import { OrderListQuery, RefreshLimitsQuery } from "@dashboard/graphql"; import { OrderListQuery, RefreshLimitsQuery } from "@dashboard/graphql";
import { sectionNames } from "@dashboard/intl"; import { sectionNames } from "@dashboard/intl";
import { OrderListUrlSortField } from "@dashboard/orders/urls"; import { DevModeQuery } from "@dashboard/orders/queries";
import {
OrderListUrlQueryParams,
OrderListUrlSortField,
} from "@dashboard/orders/urls";
import { getFilterVariables } from "@dashboard/orders/views/OrderList/filters";
import { import {
FilterPageProps, FilterPageProps,
PageListProps, PageListProps,
@ -40,6 +46,7 @@ export interface OrderListPageProps
orders: RelayToFlat<OrderListQuery["orders"]>; orders: RelayToFlat<OrderListQuery["orders"]>;
onSettingsOpen: () => void; onSettingsOpen: () => void;
onAdd: () => void; onAdd: () => void;
params: OrderListUrlQueryParams;
} }
const useStyles = makeStyles( const useStyles = makeStyles(
@ -65,6 +72,7 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
onTabChange, onTabChange,
onTabDelete, onTabDelete,
onTabSave, onTabSave,
params,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -78,6 +86,24 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
const extensionMenuItems = mapToMenuItems(ORDER_OVERVIEW_MORE_ACTIONS); const extensionMenuItems = mapToMenuItems(ORDER_OVERVIEW_MORE_ACTIONS);
const extensionCreateButtonItems = mapToMenuItems(ORDER_OVERVIEW_CREATE); const extensionCreateButtonItems = mapToMenuItems(ORDER_OVERVIEW_CREATE);
const context = useDevModeContext();
const openPlaygroundURL = () => {
context.setDevModeContent(DevModeQuery);
const variables = JSON.stringify(
{
filter: getFilterVariables(params),
// TODO add sorting: Issue #3409
// strange error when uncommenting this line
// sortBy: getSortQueryVariables(params)
},
null,
2,
);
context.setVariables(variables);
context.setDevModeVisibility(true);
};
return ( return (
<ListPageLayout> <ListPageLayout>
<TopNav title={intl.formatMessage(sectionNames.orders)}> <TopNav title={intl.formatMessage(sectionNames.orders)}>
@ -85,6 +111,14 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
<CardMenu <CardMenu
className={classes.settings} className={classes.settings}
menuItems={[ menuItems={[
{
label: intl.formatMessage({
id: "vEwjub",
defaultMessage: "Open in GraphiQL",
description: "button",
}),
onSelect: openPlaygroundURL,
},
{ {
label: intl.formatMessage({ label: intl.formatMessage({
id: "WbV1Xm", id: "WbV1Xm",

View file

@ -201,7 +201,8 @@ export const channelUsabilityData = gql`
} }
`; `;
export const defaultGraphiQLQuery = `query OrderDetails($id: ID!) { export const defaultGraphiQLQuery = /* GraphQL */ `
query OrderDetailsGraphiQL($id: ID!) {
order(id: $id) { order(id: $id) {
id id
number number
@ -214,4 +215,26 @@ export const defaultGraphiQLQuery = `query OrderDetails($id: ID!) {
userEmail userEmail
isPaid isPaid
} }
}`; }
`;
export const DevModeQuery = /* GraphQL */ `
query DevModeRun($filter: OrderFilterInput, $sortBy: OrderSortingInput) {
orders(first: 10, filter: $filter, sortBy: $sortBy) {
edges {
node {
id
number
status
isShippingRequired
canFinalize
created
customerNote
paymentStatus
userEmail
isPaid
}
}
}
}
`;

View file

@ -93,11 +93,8 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const currentTab = getFiltersCurrentTab(params, tabs); const currentTab = getFiltersCurrentTab(params, tabs);
const [ const [changeFilters, resetFilters, handleSearchChange] =
changeFilters, createFilterHandlers({
resetFilters,
handleSearchChange,
] = createFilterHandlers({
createUrl: orderListUrl, createUrl: orderListUrl,
getFilterQueryParam, getFilterQueryParam,
navigate, navigate,
@ -178,6 +175,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
tabs={getFilterTabs().map(tab => tab.name)} tabs={getFilterTabs().map(tab => tab.name)}
onAll={resetFilters} onAll={resetFilters}
onSettingsOpen={() => navigate(orderSettingsPath)} onSettingsOpen={() => navigate(orderSettingsPath)}
params={params}
/> />
<SaveFilterTabDialog <SaveFilterTabDialog
open={params.action === "save-search"} open={params.action === "save-search"}

View file

@ -108,7 +108,7 @@ export function getFilterVariables(
params: OrderListUrlFilters, params: OrderListUrlFilters,
): OrderFilterInput { ): OrderFilterInput {
return { return {
channels: (params.channel as unknown) as string[], channels: params.channel as unknown as string[],
created: getGteLteVariables({ created: getGteLteVariables({
gte: params.createdFrom, gte: params.createdFrom,
lte: params.createdTo, lte: params.createdTo,
@ -198,17 +198,11 @@ export function getFilterQueryParam(
} }
} }
export const { export const { deleteFilterTab, getFilterTabs, saveFilterTab } =
deleteFilterTab, createFilterTabUtils<OrderListUrlFilters>(ORDER_FILTERS_KEY);
getFilterTabs,
saveFilterTab,
} = createFilterTabUtils<OrderListUrlFilters>(ORDER_FILTERS_KEY);
export const { export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } =
areFiltersApplied, createFilterUtils<OrderListUrlQueryParams, OrderListUrlFilters>({
getActiveFilters,
getFiltersCurrentTab,
} = createFilterUtils<OrderListUrlQueryParams, OrderListUrlFilters>({
...OrderListUrlFiltersEnum, ...OrderListUrlFiltersEnum,
...OrderListUrlFiltersWithMultipleValues, ...OrderListUrlFiltersWithMultipleValues,
...OrderListFitersWithKeyValueValues, ...OrderListFitersWithKeyValueValues,