diff --git a/src/containers/SearchServiceAccount/index.tsx b/src/containers/SearchServiceAccount/index.tsx new file mode 100644 index 000000000..7b59b27ec --- /dev/null +++ b/src/containers/SearchServiceAccount/index.tsx @@ -0,0 +1,24 @@ +import gql from "graphql-tag"; + +import BaseSearch from "../BaseSearch"; +import { + SearchServiceAccount, + SearchServiceAccountVariables +} from "./types/SearchServiceAccount"; + +export const searchServiceAccount = gql` + query SearchServiceAccount($after: String, $first: Int!, $query: String!) { + serviceAccounts(after: $after, first: $first, filter: { search: $query }) { + edges { + node { + id + name + } + } + } + } +`; + +export default BaseSearch( + searchServiceAccount +); diff --git a/src/containers/SearchServiceAccount/types/searchServiceAccount.ts b/src/containers/SearchServiceAccount/types/searchServiceAccount.ts new file mode 100644 index 000000000..fbe54676e --- /dev/null +++ b/src/containers/SearchServiceAccount/types/searchServiceAccount.ts @@ -0,0 +1,33 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: SearchServiceAccount +// ==================================================== + +export interface SearchServiceAccount_serviceAccounts_edges_node { + __typename: "ServiceAccount"; + id: string; + name: string | null; +} + +export interface SearchServiceAccount_serviceAccounts_edges { + __typename: "ServiceAccountCountableEdge"; + node: SearchServiceAccount_serviceAccounts_edges_node; +} + +export interface SearchServiceAccount_serviceAccounts { + __typename: "ServiceAccountCountableConnection"; + edges: SearchServiceAccount_serviceAccounts_edges[]; +} + +export interface SearchServiceAccount { + serviceAccounts: SearchServiceAccount_serviceAccounts | null; +} + +export interface SearchServiceAccountVariables { + after?: string | null; + first: number; + query: string; +} diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index d39c8d65f..d3754b168 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -662,6 +662,11 @@ export interface SeoInput { description?: string | null; } +export interface ServiceAccountFilterInput { + search?: string | null; + isActive?: boolean | null; +} + export interface ServiceAccountInput { name?: string | null; isActive?: boolean | null; diff --git a/src/webhooks/components/WebhookCreatePage/WebhookCreatePage.tsx b/src/webhooks/components/WebhookCreatePage/WebhookCreatePage.tsx index af32e669d..2c5c993fe 100644 --- a/src/webhooks/components/WebhookCreatePage/WebhookCreatePage.tsx +++ b/src/webhooks/components/WebhookCreatePage/WebhookCreatePage.tsx @@ -6,10 +6,12 @@ import FormSpacer from "@saleor/components/FormSpacer"; import Grid from "@saleor/components/Grid"; import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; +import useStateFromProps from "@saleor/hooks/useStateFromProps"; import { sectionNames } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import { UserError } from "@saleor/types"; import { WebhookEventTypeEnum } from "@saleor/types/globalTypes"; +import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import WebhookEvents from "@saleor/webhooks/components/WebhookEvents"; import WebhookInfo from "@saleor/webhooks/components/WebhookInfo"; import WebhookStatus from "@saleor/webhooks/components/WebhookStatus"; @@ -30,8 +32,12 @@ export interface FormData { export interface WebhookCreatePageProps { disabled: boolean; errors: UserError[]; - services: ServiceList_serviceAccounts_edges_node[]; + services?: Array<{ + id: string; + name: string; + }>; saveButtonBarState: ConfirmButtonTransitionState; + fetchServiceAccount: (data: string) => void; onBack: () => void; onSubmit: (data: FormData) => void; } @@ -41,6 +47,7 @@ const WebhookCreatePage: React.StatelessComponent = ({ errors, saveButtonBarState, services, + fetchServiceAccount, onBack, onSubmit }) => { @@ -55,52 +62,74 @@ const WebhookCreatePage: React.StatelessComponent = ({ serviceAccount: "", targetUrl: "" }; + const [ + selectedServiceAcccounts, + setSelectedServiceAcccounts + ] = useStateFromProps(""); + const servicesChoiceList = maybe( + () => + services.map(node => ({ + label: node.name, + value: node.id + })), + [] + ); return (
- {({ data, errors, hasChanged, submit, change }) => ( - - - {intl.formatMessage(sectionNames.plugins)} - - - -
- services, [])} - errors={errors} - onChange={change} - /> -
-
- - - -
-
- -
- )} + {({ data, errors, hasChanged, submit, change }) => { + const handleServiceSelect = createSingleAutocompleteSelectHandler( + change, + setSelectedServiceAcccounts, + servicesChoiceList + ); + return ( + + + {intl.formatMessage(sectionNames.webhooks)} + + + +
+ +
+
+ + + +
+
+ +
+ ); + }}
); }; diff --git a/src/webhooks/components/WebhookCreatePage/WebhooksCreatePage.stories.tsx b/src/webhooks/components/WebhookCreatePage/WebhooksCreatePage.stories.tsx index 114b584cb..004b7dab5 100644 --- a/src/webhooks/components/WebhookCreatePage/WebhooksCreatePage.stories.tsx +++ b/src/webhooks/components/WebhookCreatePage/WebhooksCreatePage.stories.tsx @@ -8,6 +8,7 @@ import WebhookCreatePage, { WebhookCreatePageProps } from "./WebhookCreatePage"; const props: WebhookCreatePageProps = { disabled: false, errors: [], + fetchServiceAccount: () => undefined, onBack: () => undefined, onSubmit: () => undefined, saveButtonBarState: "default", diff --git a/src/webhooks/components/WebhookEvents/WebhookEvents.tsx b/src/webhooks/components/WebhookEvents/WebhookEvents.tsx index 589f30bf2..8c42d96cc 100644 --- a/src/webhooks/components/WebhookEvents/WebhookEvents.tsx +++ b/src/webhooks/components/WebhookEvents/WebhookEvents.tsx @@ -15,7 +15,7 @@ interface WebhookEventsProps { events: string[]; }; disabled: boolean; - onChange: (event: React.ChangeEvent, cb?: () => void) => void; + onChange: (event: ChangeEvent, cb?: () => void) => void; } const WebhookEvents: React.StatelessComponent = ({ @@ -26,13 +26,18 @@ const WebhookEvents: React.StatelessComponent = ({ const intl = useIntl(); const eventsEnum = Object.values(WebhookEventTypeEnum); - const handleAllEventsChange = () => - onChange({ - target: { - name: "events", - value: WebhookEventTypeEnum.ALL_EVENTS - } - } as any); + const handleAllEventsChange = (event: ChangeEvent) => + onChange(event, () => + onChange({ + target: { + name: "events", + value: event.target.value + ? WebhookEventTypeEnum.ALL_EVENTS + : data.events + } + } as any) + ); + const handleEventsChange = (event: ChangeEvent) => { onChange({ target: { @@ -70,21 +75,24 @@ const WebhookEvents: React.StatelessComponent = ({ name="allEvents" onChange={handleAllEventsChange} /> -
- {!data.allEvents && - eventsEnum.slice(1).map(event => { - return ( -
- -
- ); - })} + {!data.allEvents && ( + <> +
+ {eventsEnum.slice(1).map(event => { + return ( +
+ +
+ ); + })} + + )} ); diff --git a/src/webhooks/components/WebhookInfo/WebhookInfo.tsx b/src/webhooks/components/WebhookInfo/WebhookInfo.tsx index 49a732d03..ec2c92118 100644 --- a/src/webhooks/components/WebhookInfo/WebhookInfo.tsx +++ b/src/webhooks/components/WebhookInfo/WebhookInfo.tsx @@ -9,20 +9,24 @@ import { useIntl } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; import FormSpacer from "@saleor/components/FormSpacer"; import Hr from "@saleor/components/Hr"; -import SingleSelectField from "@saleor/components/SingleSelectField"; -import { SingleAutocompleteSelectField } from "@saleor/components/SingleAutocompleteSelectField"; +import { + SingleAutocompleteChoiceType, + SingleAutocompleteSelectField +} from "@saleor/components/SingleAutocompleteSelectField"; +import { ChangeEvent } from "@saleor/hooks/useForm"; import { commonMessages } from "@saleor/intl"; import { FormErrors } from "@saleor/types"; import { FormData } from "../WebhooksDetailsPage"; -import { ServiceList_serviceAccounts_edges_node } from "../../types/ServiceList"; - interface WebhookInfoProps { data: FormData; disabled: boolean; - services: ServiceList_serviceAccounts_edges_node[]; + serviceDisplayValue: string; + services: SingleAutocompleteChoiceType[]; errors: FormErrors<"name" | "targetUrl" | "secretKey">; onChange: (event: React.ChangeEvent) => void; + serviceOnChange: (event: ChangeEvent) => void; + fetchServiceAccount: (data: string) => void; } const useStyles = makeStyles(() => ({ @@ -40,8 +44,11 @@ const WebhookInfo: React.StatelessComponent = ({ data, disabled, services, + serviceDisplayValue, + fetchServiceAccount, errors, - onChange + onChange, + serviceOnChange }) => { const classes = useStyles({}); const intl = useIntl(); @@ -82,17 +89,15 @@ const WebhookInfo: React.StatelessComponent = ({ ({ - label: service.name, - value: service.id - }))} + choices={services} + fetchChoices={fetchServiceAccount} InputProps={{ autoComplete: "off" }} diff --git a/src/webhooks/components/WebhookStatus/WebhookStatus.tsx b/src/webhooks/components/WebhookStatus/WebhookStatus.tsx index cc54ec6e1..ffbead75f 100644 --- a/src/webhooks/components/WebhookStatus/WebhookStatus.tsx +++ b/src/webhooks/components/WebhookStatus/WebhookStatus.tsx @@ -3,16 +3,16 @@ import CardContent from "@material-ui/core/CardContent"; import Typography from "@material-ui/core/Typography"; import CardTitle from "@saleor/components/CardTitle"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; -import { FormChange } from "@saleor/hooks/useForm"; +import { ChangeEvent } from "@saleor/hooks/useForm"; import React from "react"; import { useIntl } from "react-intl"; import { FormData } from "../WebhooksDetailsPage"; interface WebhookStatusProps { - data: FormData; + data: boolean; disabled: boolean; - onChange: (event: FormChange) => void; + onChange: (event: ChangeEvent, cb?: () => void) => void; } const WebhookStatus: React.FC = ({ @@ -43,7 +43,7 @@ const WebhookStatus: React.FC = ({ defaultMessage: "Webhook is active", description: "webhooks active" })} - checked={data.isActive} + checked={data} onChange={onChange} disabled={disabled} /> diff --git a/src/webhooks/components/WebhooksDetailsPage/WebhooksDetailsPage.stories.tsx b/src/webhooks/components/WebhooksDetailsPage/WebhooksDetailsPage.stories.tsx index fd32a6622..9dc02a58c 100644 --- a/src/webhooks/components/WebhooksDetailsPage/WebhooksDetailsPage.stories.tsx +++ b/src/webhooks/components/WebhooksDetailsPage/WebhooksDetailsPage.stories.tsx @@ -10,17 +10,24 @@ import WebhooksDetailsPage, { const props: WebhooksDetailsPageProps = { disabled: false, errors: [], + fetchServiceAccount: () => undefined, onBack: () => undefined, onDelete: () => undefined, onSubmit: () => undefined, saveButtonBarState: "default", - services: [] + services: [], + webhook: null }; storiesOf("Views / Webhook / Webhook details", module) .addDecorator(Decorator) .add("default", () => ) .add("loading", () => ( - + )) .add("form errors", () => ( ; saveButtonBarState: ConfirmButtonTransitionState; onBack: () => void; onDelete: () => void; + fetchServiceAccount: (data: string) => void; onSubmit: (data: FormData) => void; } @@ -48,6 +54,7 @@ const WebhooksDetailsPage: React.StatelessComponent< webhook, saveButtonBarState, services, + fetchServiceAccount, onBack, onDelete, onSubmit @@ -62,60 +69,82 @@ const WebhooksDetailsPage: React.StatelessComponent< isActive: maybe(() => webhook.isActive, false), name: maybe(() => webhook.name, ""), secretKey: maybe(() => webhook.secretKey, ""), - serviceAccount: maybe(() => webhook.serviceAccount.id, ""), + serviceAccount: maybe(() => webhook.serviceAccount.name, ""), targetUrl: maybe(() => webhook.targetUrl, "") }; + const [ + selectedServiceAcccounts, + setSelectedServiceAcccounts + ] = useStateFromProps(maybe(() => webhook.serviceAccount.name, "")); + const servicesChoiceList = maybe( + () => + services.map(node => ({ + label: node.name, + value: node.id + })), + [] + ); return (
- {({ data, errors, hasChanged, submit, change }) => ( - - - {intl.formatMessage(sectionNames.plugins)} - - webhook.name, "...") - } - )} - /> - -
- services, [])} - errors={errors} - onChange={change} - /> -
-
- - - -
-
- -
- )} + {({ data, errors, hasChanged, submit, change }) => { + const handleServiceSelect = createSingleAutocompleteSelectHandler( + change, + setSelectedServiceAcccounts, + servicesChoiceList + ); + return ( + + + {intl.formatMessage(sectionNames.webhooks)} + + webhook.name, "...") + } + )} + /> + +
+ +
+
+ + + +
+
+ +
+ ); + }}
); }; diff --git a/src/webhooks/types/WebhookDetails.ts b/src/webhooks/types/WebhookDetails.ts new file mode 100644 index 000000000..657d47ca0 --- /dev/null +++ b/src/webhooks/types/WebhookDetails.ts @@ -0,0 +1,39 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { WebhookEventTypeEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: WebhookDetails +// ==================================================== + +export interface WebhookDetails_webhook_events { + __typename: "WebhookEvent"; + eventType: WebhookEventTypeEnum | null; +} + +export interface WebhookDetails_webhook_serviceAccount { + __typename: "ServiceAccount"; + id: string; + name: string | null; +} + +export interface WebhookDetails_webhook { + __typename: "Webhook"; + id: string; + name: string | null; + events: (WebhookDetails_webhook_events | null)[] | null; + isActive: boolean; + secretKey: string | null; + targetUrl: string; + serviceAccount: WebhookDetails_webhook_serviceAccount; +} + +export interface WebhookDetails { + webhook: WebhookDetails_webhook | null; +} + +export interface WebhookDetailsVariables { + id: string; +} diff --git a/src/webhooks/views/WebhooksCreate.tsx b/src/webhooks/views/WebhooksCreate.tsx index fc58d5dff..3b6628ec5 100644 --- a/src/webhooks/views/WebhooksCreate.tsx +++ b/src/webhooks/views/WebhooksCreate.tsx @@ -1,4 +1,5 @@ import { WindowTitle } from "@saleor/components/WindowTitle"; +import SearchServiceAccount from "@saleor/containers/SearchServiceAccount"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; @@ -6,7 +7,7 @@ import { WebhookEventTypeEnum } from "@saleor/types/globalTypes"; import { WebhookCreate as WebhookCreateData } from "@saleor/webhooks/types/WebhookCreate"; import React from "react"; import { useIntl } from "react-intl"; - +import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config"; import { getMutationState, maybe } from "../../misc"; import WebhookCreatePage, { FormData } from "../components/WebhookCreatePage"; import { TypedWebhookCreate } from "../mutations"; @@ -40,55 +41,62 @@ export const WebhooksCreate: React.StatelessComponent< const handleBack = () => navigate(webhooksListUrl()); return ( - - {(WebhookCreate, webhookCreateOpts) => { - const handleSubmit = (data: FormData) => - WebhookCreate({ - variables: { - input: { - events: data.allEvents - ? [WebhookEventTypeEnum.ALL_EVENTS] - : data.events, - isActive: data.isActive, - name: data.name, - secretKey: data.secretKey, - serviceAccount: data.serviceAccount, - targetUrl: data.targetUrl - } - } - }); + + {({ search: searchServiceAccount, result: searchServiceAccountOpt }) => ( + + {(WebhookCreate, webhookCreateOpts) => { + const handleSubmit = (data: FormData) => + WebhookCreate({ + variables: { + input: { + events: data.allEvents + ? [WebhookEventTypeEnum.ALL_EVENTS] + : data.events, + isActive: data.isActive, + name: data.name, + secretKey: data.secretKey, + serviceAccount: data.serviceAccount, + targetUrl: data.targetUrl + } + } + }); - const formTransitionState = getMutationState( - webhookCreateOpts.called, - webhookCreateOpts.loading, - maybe(() => webhookCreateOpts.data.webhookCreate.errors) - ); + const formTransitionState = getMutationState( + webhookCreateOpts.called, + webhookCreateOpts.loading, + maybe(() => webhookCreateOpts.data.webhookCreate.errors) + ); - return ( - <> - - webhookCreateOpts.data.webhookCreate.errors, - [] - )} - services={maybe(() => - data.serviceAccounts.edges.map(edge => edge.node) - )} - onBack={handleBack} - onSubmit={handleSubmit} - saveButtonBarState={formTransitionState} - /> - - ); - }} - + return ( + <> + + webhookCreateOpts.data.webhookCreate.errors, + [] + )} + fetchServiceAccount={searchServiceAccount} + services={maybe(() => + searchServiceAccountOpt.data.serviceAccounts.edges.map( + edge => edge.node + ) + )} + onBack={handleBack} + onSubmit={handleSubmit} + saveButtonBarState={formTransitionState} + /> + + ); + }} + + )} + ); }; WebhooksCreate.displayName = "WebhooksCreate"; diff --git a/src/webhooks/views/WebhooksDetails.tsx b/src/webhooks/views/WebhooksDetails.tsx index ed93db46b..f6511ea98 100644 --- a/src/webhooks/views/WebhooksDetails.tsx +++ b/src/webhooks/views/WebhooksDetails.tsx @@ -1,4 +1,5 @@ import { WindowTitle } from "@saleor/components/WindowTitle"; +import SearchServiceAccount from "@saleor/containers/SearchServiceAccount"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; @@ -8,7 +9,7 @@ import { WebhookDelete } from "@saleor/webhooks/types/WebhookDelete"; import { WebhookUpdate } from "@saleor/webhooks/types/WebhookUpdate"; import React from "react"; import { useIntl } from "react-intl"; - +import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config"; import { getMutationState, maybe } from "../../misc"; import WebhooksDetailsPage from "../components/WebhooksDetailsPage"; import { TypedWebhookDelete, TypedWebhookUpdate } from "../mutations"; @@ -70,87 +71,94 @@ export const WebhooksDetails: React.StatelessComponent< }; return ( - - {(webhookUpdate, webhookUpdateOpts) => ( - - {(webhookDelete, webhookDeleteOpts) => ( - - {WebhookDetails => { - const formTransitionState = getMutationState( - webhookUpdateOpts.called, - webhookUpdateOpts.loading, - maybe(() => webhookUpdateOpts.data.webhookUpdate.errors) - ); + + {({ search: searchServiceAccount, result: searchServiceAccountOpt }) => ( + + {(webhookUpdate, webhookUpdateOpts) => ( + + {(webhookDelete, webhookDeleteOpts) => ( + + {WebhookDetails => { + const formTransitionState = getMutationState( + webhookUpdateOpts.called, + webhookUpdateOpts.loading, + maybe(() => webhookUpdateOpts.data.webhookUpdate.errors) + ); - const handleRemoveConfirm = () => - webhookDelete({ - variables: { - id - } - }); + const handleRemoveConfirm = () => + webhookDelete({ + variables: { + id + } + }); - const formErrors = maybe( - () => webhookUpdateOpts.data.webhookUpdate.errors, - [] - ); + const formErrors = maybe( + () => webhookUpdateOpts.data.webhookUpdate.errors, + [] + ); - const deleteTransitionState = getMutationState( - webhookDeleteOpts.called, - webhookDeleteOpts.loading, - maybe(() => webhookDeleteOpts.data.webhookDelete.errors) - ); + const deleteTransitionState = getMutationState( + webhookDeleteOpts.called, + webhookDeleteOpts.loading, + maybe(() => webhookDeleteOpts.data.webhookDelete.errors) + ); - return ( - <> - WebhookDetails.data.webhook.name)} - /> - WebhookDetails.data.webhook)} - services={maybe(() => - data.serviceAccounts.edges.map(edge => edge.node) - )} - onBack={() => navigate(webhooksListUrl())} - onDelete={() => openModal("remove")} - onSubmit={data => { - webhookUpdate({ - variables: { - id, - input: { - events: data.allEvents - ? [WebhookEventTypeEnum.ALL_EVENTS] - : data.events, - isActive: data.isActive, - name: data.name, - secretKey: data.secretKey, - serviceAccount: data.serviceAccount, - targetUrl: data.targetUrl - } - } - }); - }} - /> - WebhookDetails.data.webhook.name, - "..." - )} - onClose={closeModal} - onConfirm={handleRemoveConfirm} - open={params.action === "remove"} - /> - - ); - }} - + return ( + <> + WebhookDetails.data.webhook.name)} + /> + WebhookDetails.data.webhook)} + fetchServiceAccount={searchServiceAccount} + services={maybe(() => + searchServiceAccountOpt.data.serviceAccounts.edges.map( + edge => edge.node + ) + )} + onBack={() => navigate(webhooksListUrl())} + onDelete={() => openModal("remove")} + onSubmit={data => { + webhookUpdate({ + variables: { + id, + input: { + events: data.allEvents + ? [WebhookEventTypeEnum.ALL_EVENTS] + : data.events, + isActive: data.isActive, + name: data.name, + secretKey: data.secretKey, + serviceAccount: data.serviceAccount, + targetUrl: data.targetUrl + } + } + }); + }} + /> + WebhookDetails.data.webhook.name, + "..." + )} + onClose={closeModal} + onConfirm={handleRemoveConfirm} + open={params.action === "remove"} + /> + + ); + }} + + )} + )} - + )} - + ); }; WebhooksDetails.displayName = "WebhooksDetails";