Fix minor bugs in plugin details view

This commit is contained in:
dominik-zeglen 2020-08-19 13:35:35 +02:00
parent c4d70dde70
commit 48e9fe1366
13 changed files with 244 additions and 150 deletions

View file

@ -4,10 +4,7 @@ import Switch from "@material-ui/core/Switch";
import React from "react";
const useStyles = makeStyles(
theme => ({
label: {
marginLeft: theme.spacing(3.5)
},
() => ({
labelText: {
fontSize: 14
}
@ -51,7 +48,7 @@ export const ControlledSwitch: React.FC<ControlledSwitchProps> = props => {
/>
}
label={
<div className={classes.label}>
<div>
{uncheckedLabel ? (
checked ? (
label

View file

@ -127,3 +127,10 @@ export const exportErrorFragment = gql`
field
}
`;
export const pluginErrorFragment = gql`
fragment PluginErrorFragment on PluginError {
code
field
}
`;

View file

@ -0,0 +1,15 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { PluginErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: PluginErrorFragment
// ====================================================
export interface PluginErrorFragment {
__typename: "PluginError";
code: PluginErrorCode;
field: string | null;
}

View file

@ -6,7 +6,10 @@ import CardTitle from "@saleor/components/CardTitle";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr";
import { PluginErrorFragment } from "@saleor/fragments/types/PluginErrorFragment";
import { commonMessages } from "@saleor/intl";
import { PluginErrorCode } from "@saleor/types/globalTypes";
import getPluginErrorMessage from "@saleor/utils/errors/plugins";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
@ -15,6 +18,7 @@ import { FormData } from "../PluginsDetailsPage";
interface PluginInfoProps {
data: FormData;
description: string;
errors: PluginErrorFragment[];
name: string;
onChange: (event: React.ChangeEvent<any>) => void;
}
@ -35,11 +39,17 @@ const useStyles = makeStyles(
const PluginInfo: React.FC<PluginInfoProps> = ({
data,
description,
errors,
name,
onChange
}) => {
const classes = useStyles({});
const intl = useIntl();
const misconfiguredError = errors.find(
err => err.code === PluginErrorCode.PLUGIN_MISCONFIGURED
);
return (
<Card>
<CardTitle
@ -80,6 +90,11 @@ const PluginInfo: React.FC<PluginInfoProps> = ({
checked={data.active}
onChange={onChange}
/>
{misconfiguredError && (
<Typography color="error">
{getPluginErrorMessage(misconfiguredError, intl)}
</Typography>
)}
</CardContent>
</Card>
);

View file

@ -2,8 +2,11 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import makeStyles from "@material-ui/core/styles/makeStyles";
import TextField from "@material-ui/core/TextField";
import Tooltip from "@material-ui/core/Tooltip";
import Typography from "@material-ui/core/Typography";
import InfoIcon from "@material-ui/icons/Info";
import CardTitle from "@saleor/components/CardTitle";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import ControlledSwitch from "@saleor/components/ControlledSwitch";
import { Plugin_plugin_configuration } from "@saleor/plugins/types/Plugin";
import { UserError } from "@saleor/types";
import { ConfigurationTypeFieldEnum } from "@saleor/types/globalTypes";
@ -32,7 +35,9 @@ const useStyles = makeStyles(
item: {
"&:not(:last-child)": {
marginBottom: theme.spacing(3)
}
},
alignItems: "center",
display: "flex"
},
itemLabel: {
fontWeight: 500
@ -71,17 +76,30 @@ const PluginSettings: React.FC<PluginSettingsProps> = ({
return (
<div className={classes.item} key={field.name}>
{fieldData.type === ConfigurationTypeFieldEnum.BOOLEAN ? (
<ControlledCheckbox
name={field.name}
label={fieldData.label}
checked={
typeof field.value !== "boolean"
? field.value === "true"
: field.value
}
onChange={onChange}
disabled={disabled}
/>
<>
<ControlledSwitch
name={field.name}
label={fieldData.label}
checked={
typeof field.value !== "boolean"
? field.value === "true"
: field.value
}
onChange={onChange}
disabled={disabled}
/>
{fieldData.helpText && (
<Tooltip
title={
<Typography variant="body2">
{fieldData.helpText}
</Typography>
}
>
<InfoIcon />
</Tooltip>
)}
</>
) : (
<TextField
disabled={disabled}

View file

@ -9,11 +9,11 @@ import Grid from "@saleor/components/Grid";
import Hr from "@saleor/components/Hr";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { PluginErrorFragment } from "@saleor/fragments/types/PluginErrorFragment";
import { ChangeEvent } from "@saleor/hooks/useForm";
import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { getStringOrPlaceholder } from "@saleor/misc";
import { isSecretField } from "@saleor/plugins/utils";
import { UserError } from "@saleor/types";
import { ConfigurationItemInput } from "@saleor/types/globalTypes";
import React from "react";
import { useIntl } from "react-intl";
@ -30,7 +30,7 @@ export interface FormData {
export interface PluginsDetailsPageProps {
disabled: boolean;
errors: UserError[];
errors: PluginErrorFragment[];
plugin: Plugin_plugin;
saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void;
@ -65,15 +65,13 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = props => {
const classes = useStyles(props);
const intl = useIntl();
const initialForm: FormData = {
active: maybe(() => plugin.active, false),
configuration: maybe(() =>
plugin.configuration
.filter(field => !isSecretField(plugin.configuration, field.name))
.map(field => ({
...field,
value: field.value || ""
}))
)
active: plugin?.active || false,
configuration: plugin?.configuration
?.filter(field => !isSecretField(plugin?.configuration || [], field.name))
.map(field => ({
...field,
value: field.value || ""
}))
};
return (
@ -108,7 +106,7 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = props => {
description: "header"
},
{
pluginName: maybe(() => plugin.name, "...")
pluginName: getStringOrPlaceholder(plugin?.name)
}
)}
/>
@ -129,8 +127,9 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = props => {
</div>
<PluginInfo
data={data}
description={maybe(() => plugin.description, "")}
name={maybe(() => plugin.name, "")}
description={plugin?.description || ""}
errors={errors}
name={plugin?.name || ""}
onChange={onChange}
/>
{data.configuration && (
@ -143,25 +142,17 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = props => {
description: "section header"
})}
</Typography>
<Typography>
{intl.formatMessage({
defaultMessage:
"This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers."
})}
</Typography>
</div>
<div>
<PluginSettings
data={data}
fields={maybe(() => plugin.configuration, [])}
fields={plugin?.configuration || []}
errors={errors}
disabled={disabled}
onChange={onChange}
/>
{maybe(() =>
plugin.configuration.some(field =>
isSecretField(plugin.configuration, field.name)
)
{plugin?.configuration.some(field =>
isSecretField(plugin.configuration, field.name)
) && (
<>
<CardSpacer />

View file

@ -1,3 +1,4 @@
import { pluginErrorFragment } from "@saleor/fragments/errors";
import { pluginsDetailsFragment } from "@saleor/fragments/plugins";
import gql from "graphql-tag";
@ -6,11 +7,11 @@ import { PluginUpdate, PluginUpdateVariables } from "./types/PluginUpdate";
const pluginUpdate = gql`
${pluginsDetailsFragment}
${pluginErrorFragment}
mutation PluginUpdate($id: ID!, $input: PluginUpdateInput!) {
pluginUpdate(id: $id, input: $input) {
errors {
field
message
errors: pluginsErrors {
...PluginErrorFragment
}
plugin {
...PluginsDetailsFragment

View file

@ -2,16 +2,16 @@
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { PluginUpdateInput, ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
import { PluginUpdateInput, PluginErrorCode, ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: PluginUpdate
// ====================================================
export interface PluginUpdate_pluginUpdate_errors {
__typename: "Error";
__typename: "PluginError";
code: PluginErrorCode;
field: string | null;
message: string | null;
}
export interface PluginUpdate_pluginUpdate_plugin_configuration {

View file

@ -9,7 +9,6 @@ import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandl
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { maybe } from "../../misc";
import PluginsDetailsPage from "../components/PluginsDetailsPage";
import PluginSecretFieldDialog from "../components/PluginSecretFieldDialog";
import { TypedPluginUpdate } from "../mutations";
@ -73,10 +72,7 @@ export const PluginsDetails: React.FC<PluginsDetailsProps> = ({
{pluginDetails => (
<TypedPluginUpdate onCompleted={handleUpdate}>
{(pluginUpdate, pluginUpdateOpts) => {
const formErrors = maybe(
() => pluginUpdateOpts.data.pluginUpdate.errors,
[]
);
const formErrors = pluginUpdateOpts.data?.pluginUpdate.errors || [];
const handleFieldUpdate = (value: string) =>
pluginUpdate({
@ -95,16 +91,14 @@ export const PluginsDetails: React.FC<PluginsDetailsProps> = ({
return (
<>
<WindowTitle
title={maybe(() => pluginDetails.data.plugin.name)}
/>
<WindowTitle title={pluginDetails.data?.plugin?.name} />
<PluginsDetailsPage
disabled={pluginDetails.loading}
errors={formErrors}
saveButtonBarState={
!params.action ? pluginUpdateOpts.status : "default"
}
plugin={maybe(() => pluginDetails.data.plugin)}
plugin={pluginDetails.data?.plugin}
onBack={() => navigate(pluginListUrl())}
onClear={id =>
openModal("clear", {
@ -131,7 +125,7 @@ export const PluginsDetails: React.FC<PluginsDetailsProps> = ({
})
}
/>
{maybe(() => pluginDetails.data.plugin.configuration) && (
{pluginDetails.data?.plugin?.configuration && (
<>
<ActionDialog
confirmButtonState={
@ -153,10 +147,8 @@ export const PluginsDetails: React.FC<PluginsDetailsProps> = ({
confirmButtonState={
!!params.action ? pluginUpdateOpts.status : "default"
}
field={maybe(() =>
pluginDetails.data.plugin.configuration.find(
field => field.name === params.id
)
field={pluginDetails.data?.plugin?.configuration.find(
field => field.name === params.id
)}
onClose={closeModal}
onConfirm={formData => handleFieldUpdate(formData.value)}

View file

@ -111209,11 +111209,6 @@ exports[`Storyshots Views / Plugins / Plugin details default 1`] = `
>
Plugin Settings
</div>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers.
</div>
</div>
<div>
<div
@ -111335,48 +111330,54 @@ exports[`Storyshots Views / Plugins / Plugin details default 1`] = `
class="MuiFormControlLabel-root-id"
>
<span
aria-disabled="false"
class="MuiButtonBase-root-id MuiIconButton-root-id PrivateSwitchBase-root-id MuiCheckbox-root-id MuiCheckbox-colorPrimary-id PrivateSwitchBase-checked-id MuiCheckbox-checked-id MuiIconButton-colorPrimary-id"
class="MuiSwitch-root-id"
>
<span
class="MuiIconButton-label-id"
aria-disabled="false"
class="MuiButtonBase-root-id MuiIconButton-root-id PrivateSwitchBase-root-id MuiSwitch-switchBase-id MuiSwitch-colorPrimary-id PrivateSwitchBase-checked-id MuiSwitch-checked-id"
>
<input
checked=""
class="PrivateSwitchBase-input-id"
data-indeterminate="false"
name="Use sandbox"
type="checkbox"
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
<span
class="MuiIconButton-label-id"
>
<rect
fill="currentColor"
height="14"
width="14"
x="5"
y="5"
<input
checked=""
class="PrivateSwitchBase-input-id MuiSwitch-input-id"
name="Use sandbox"
type="checkbox"
/>
<path
clip-rule="evenodd"
d="M 16.7527 9.33783 L 10.86618 15.7595 L 8 12.32006 L 8.76822 11.67988 L 10.90204 14.24046 L 16.0155 8.66211 L 16.7527 9.33783 Z"
fill="white"
fill-rule="evenodd"
<span
class="MuiSwitch-thumb-id"
/>
</svg>
</span>
</span>
<span
class="MuiSwitch-track-id"
/>
</span>
<span
class="MuiTypography-root-id MuiFormControlLabel-label-id MuiTypography-body1-id"
>
Use sandbox
<div>
<span
class="ControlledSwitch-labelText-id"
>
Use sandbox
</span>
<div />
</div>
</span>
</label>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
/>
</svg>
</div>
</div>
</div>
@ -111660,6 +111661,11 @@ exports[`Storyshots Views / Plugins / Plugin details form errors 1`] = `
Set plugin as Active
</span>
</label>
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id"
>
Plugin is misconfigured and cannot be activated
</div>
</div>
</div>
<hr
@ -111671,11 +111677,6 @@ exports[`Storyshots Views / Plugins / Plugin details form errors 1`] = `
>
Plugin Settings
</div>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
This adress will be used to generate invoices and calculate shipping rates. Email adress you provide here will be used as a contact adress for your customers.
</div>
</div>
<div>
<div
@ -111797,48 +111798,54 @@ exports[`Storyshots Views / Plugins / Plugin details form errors 1`] = `
class="MuiFormControlLabel-root-id"
>
<span
aria-disabled="false"
class="MuiButtonBase-root-id MuiIconButton-root-id PrivateSwitchBase-root-id MuiCheckbox-root-id MuiCheckbox-colorPrimary-id PrivateSwitchBase-checked-id MuiCheckbox-checked-id MuiIconButton-colorPrimary-id"
class="MuiSwitch-root-id"
>
<span
class="MuiIconButton-label-id"
aria-disabled="false"
class="MuiButtonBase-root-id MuiIconButton-root-id PrivateSwitchBase-root-id MuiSwitch-switchBase-id MuiSwitch-colorPrimary-id PrivateSwitchBase-checked-id MuiSwitch-checked-id"
>
<input
checked=""
class="PrivateSwitchBase-input-id"
data-indeterminate="false"
name="Use sandbox"
type="checkbox"
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
<span
class="MuiIconButton-label-id"
>
<rect
fill="currentColor"
height="14"
width="14"
x="5"
y="5"
<input
checked=""
class="PrivateSwitchBase-input-id MuiSwitch-input-id"
name="Use sandbox"
type="checkbox"
/>
<path
clip-rule="evenodd"
d="M 16.7527 9.33783 L 10.86618 15.7595 L 8 12.32006 L 8.76822 11.67988 L 10.90204 14.24046 L 16.0155 8.66211 L 16.7527 9.33783 Z"
fill="white"
fill-rule="evenodd"
<span
class="MuiSwitch-thumb-id"
/>
</svg>
</span>
</span>
<span
class="MuiSwitch-track-id"
/>
</span>
<span
class="MuiTypography-root-id MuiFormControlLabel-label-id MuiTypography-body1-id"
>
Use sandbox
<div>
<span
class="ControlledSwitch-labelText-id"
>
Use sandbox
</span>
<div />
</div>
</span>
</label>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
/>
</svg>
</div>
</div>
</div>
@ -114712,9 +114719,7 @@ exports[`Storyshots Views / Product types / Product type details default 1`] = `
<span
class="MuiTypography-root-id MuiFormControlLabel-label-id MuiTypography-body1-id"
>
<div
class="ControlledSwitch-label-id"
>
<div>
<span
class="ControlledSwitch-labelText-id"
>
@ -115469,9 +115474,7 @@ exports[`Storyshots Views / Product types / Product type details form errors 1`]
<span
class="MuiTypography-root-id MuiFormControlLabel-label-id MuiTypography-body1-id"
>
<div
class="ControlledSwitch-label-id"
>
<div>
<span
class="ControlledSwitch-labelText-id"
>
@ -116013,9 +116016,7 @@ exports[`Storyshots Views / Product types / Product type details loading 1`] = `
<span
class="MuiTypography-root-id MuiFormControlLabel-label-id MuiFormControlLabel-disabled-id MuiTypography-body1-id"
>
<div
class="ControlledSwitch-label-id"
>
<div>
<span
class="ControlledSwitch-labelText-id"
>
@ -116396,9 +116397,7 @@ exports[`Storyshots Views / Product types / Product type details no attributes 1
<span
class="MuiTypography-root-id MuiFormControlLabel-label-id MuiTypography-body1-id"
>
<div
class="ControlledSwitch-label-id"
>
<div>
<span
class="ControlledSwitch-labelText-id"
>

View file

@ -1,3 +1,4 @@
import { PluginErrorCode } from "@saleor/types/globalTypes";
import { storiesOf } from "@storybook/react";
import React from "react";
@ -7,7 +8,6 @@ import PluginsDetailsPage, {
} from "../../../plugins/components/PluginsDetailsPage";
import { plugin } from "../../../plugins/fixtures";
import Decorator from "../../Decorator";
import { formError } from "../../misc";
const props: PluginsDetailsPageProps = {
disabled: false,
@ -29,11 +29,20 @@ storiesOf("Views / Plugins / Plugin details", module)
.add("form errors", () => (
<PluginsDetailsPage
{...props}
errors={([
"active",
"Username or account",
"Password or license"
] as Array<keyof FormData>).map(formError)}
errors={[
...(["active", "Username or account", "Password or license"] as Array<
keyof FormData
>).map(field => ({
__typename: "PluginError" as "PluginError",
code: PluginErrorCode.INVALID,
field
})),
{
__typename: "PluginError" as "PluginError",
code: PluginErrorCode.PLUGIN_MISCONFIGURED,
field: null
}
]}
/>
))
.add("not configurable", () => (

View file

@ -665,6 +665,15 @@ export enum PermissionGroupSortField {
NAME = "NAME",
}
export enum PluginErrorCode {
GRAPHQL_ERROR = "GRAPHQL_ERROR",
INVALID = "INVALID",
NOT_FOUND = "NOT_FOUND",
PLUGIN_MISCONFIGURED = "PLUGIN_MISCONFIGURED",
REQUIRED = "REQUIRED",
UNIQUE = "UNIQUE",
}
export enum PluginSortField {
IS_ACTIVE = "IS_ACTIVE",
NAME = "NAME",

View file

@ -0,0 +1,41 @@
import { PluginErrorFragment } from "@saleor/fragments/types/PluginErrorFragment";
import { commonMessages } from "@saleor/intl";
import { PluginErrorCode } from "@saleor/types/globalTypes";
import { defineMessages, IntlShape } from "react-intl";
import commonErrorMessages from "./common";
const messages = defineMessages({
misconfigured: {
defaultMessage: "Plugin is misconfigured and cannot be activated"
},
unique: {
defaultMessage: "This field needs to be unique"
}
});
function getPluginErrorMessage(
err: PluginErrorFragment,
intl: IntlShape
): string {
if (err) {
switch (err.code) {
case PluginErrorCode.PLUGIN_MISCONFIGURED:
return intl.formatMessage(messages.misconfigured);
case PluginErrorCode.GRAPHQL_ERROR:
return intl.formatMessage(commonErrorMessages.graphqlError);
case PluginErrorCode.INVALID:
return intl.formatMessage(commonErrorMessages.invalid);
case PluginErrorCode.REQUIRED:
return intl.formatMessage(commonMessages.requiredField);
case PluginErrorCode.UNIQUE:
return intl.formatMessage(messages.unique);
default:
return intl.formatMessage(commonErrorMessages.unknownError);
}
}
return undefined;
}
export default getPluginErrorMessage;