Add gift card permission to the manifest and block event (#733)
* Handle missing permissions and old Saleor Version * Throw an error when fetching app permissions fails
This commit is contained in:
parent
d4089ab519
commit
be4e7d2922
9 changed files with 256 additions and 13 deletions
7
.changeset/popular-clouds-play.md
Normal file
7
.changeset/popular-clouds-play.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"saleor-app-emails-and-messages": patch
|
||||
---
|
||||
|
||||
Manage gift card permission is now required to enable gift cards event.
|
||||
|
||||
Added message to interface for users with missing permission or Saleor version without the support for this event.
|
|
@ -0,0 +1,58 @@
|
|||
import { useAppBridge, actions } from "@saleor/app-sdk/app-bridge";
|
||||
import { SaleorVersionCompatibilityValidator } from "@saleor/apps-shared";
|
||||
import { PermissionEnum } from "../../generated/graphql";
|
||||
import { Text } from "@saleor/macaw-ui/next";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
|
||||
interface ManagePermissionsTextLinkProps {
|
||||
missingPermission: PermissionEnum;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns TextLink component with link to manage permissions if used in compatible dashboard version.
|
||||
* Otherwise returns text instructions to reinstall the app.
|
||||
*/
|
||||
export const ManagePermissionsTextLink = ({
|
||||
missingPermission,
|
||||
}: ManagePermissionsTextLinkProps) => {
|
||||
const { appBridgeState, appBridge } = useAppBridge();
|
||||
const dashboardVersion = appBridgeState?.dashboardVersion;
|
||||
|
||||
// Editing app permissions has been introduced in Saleor Dashboard 3.15
|
||||
const isPermissionManagementAvailable = dashboardVersion
|
||||
? new SaleorVersionCompatibilityValidator(">=3.15").isValid(dashboardVersion)
|
||||
: false;
|
||||
|
||||
const appId = appBridgeState?.id;
|
||||
|
||||
if (!isPermissionManagementAvailable || !appId) {
|
||||
return (
|
||||
<Text>
|
||||
To use this feature, the {missingPermission} permission is required. Please reinstall the
|
||||
app.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Text>
|
||||
To use this feature, the {missingPermission} permission is required. Please go to{" "}
|
||||
{/* TODO: Update the shared package to handle dashboard links */}
|
||||
<TextLink
|
||||
onClick={(e: Event) => {
|
||||
e.preventDefault();
|
||||
|
||||
appBridge?.dispatch(
|
||||
actions.Redirect({
|
||||
to: `/apps/${appId}`,
|
||||
})
|
||||
);
|
||||
}}
|
||||
href="#"
|
||||
>
|
||||
Manage App section
|
||||
</TextLink>
|
||||
, and grant the permission
|
||||
</Text>
|
||||
);
|
||||
};
|
34
apps/emails-and-messages/src/lib/fetch-app-permissions.ts
Normal file
34
apps/emails-and-messages/src/lib/fetch-app-permissions.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Client, gql } from "urql";
|
||||
import {
|
||||
FetchAppPermissionsDocument,
|
||||
FetchAppPermissionsQuery,
|
||||
PermissionEnum,
|
||||
} from "../../generated/graphql";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
|
||||
gql`
|
||||
query FetchAppPermissions {
|
||||
app {
|
||||
permissions {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const logger = createLogger({
|
||||
name: "fetchAppPermissions",
|
||||
});
|
||||
|
||||
export async function fetchAppPermissions(client: Client): Promise<PermissionEnum[]> {
|
||||
const { error, data } = await client
|
||||
.query<FetchAppPermissionsQuery>(FetchAppPermissionsDocument, {})
|
||||
.toPromise();
|
||||
|
||||
if (error) {
|
||||
logger.error(error, "Error fetching app permissions");
|
||||
throw new Error("Could not fetch the app permissions");
|
||||
}
|
||||
|
||||
return data?.app?.permissions?.map((p) => p.code) || [];
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import { vi, expect, describe, it } from "vitest";
|
||||
import { getEventFormStatus } from "./get-event-form-status";
|
||||
import { PermissionEnum } from "../../generated/graphql";
|
||||
|
||||
describe("getEventFormStatus", function () {
|
||||
it("No message or disable flag, when event other than GIFT_CARD_SENT is passed", () => {
|
||||
expect(
|
||||
getEventFormStatus({
|
||||
eventType: "ORDER_CREATED",
|
||||
appPermissions: [PermissionEnum.ManageGiftCard],
|
||||
featureFlags: {
|
||||
giftCardSentEvent: true,
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
tooltipMessage: undefined,
|
||||
isDisabled: false,
|
||||
});
|
||||
expect(
|
||||
getEventFormStatus({
|
||||
eventType: "ORDER_CREATED",
|
||||
appPermissions: [],
|
||||
featureFlags: {
|
||||
giftCardSentEvent: false,
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
isDisabled: false,
|
||||
missingPermission: undefined,
|
||||
requiredSaleorVersion: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("Return disable flag and lack of the permission message, when GIFT_CARD_SENT is passed and app has no manage gift card permission", () => {
|
||||
expect(
|
||||
getEventFormStatus({
|
||||
eventType: "GIFT_CARD_SENT",
|
||||
appPermissions: [],
|
||||
featureFlags: {
|
||||
giftCardSentEvent: true,
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
isDisabled: true,
|
||||
missingPermission: PermissionEnum.ManageGiftCard,
|
||||
requiredSaleorVersion: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("Return disable flag and unsupported Saleor version message, when GIFT_CARD_SENT is passed with missing feature flag", () => {
|
||||
expect(
|
||||
getEventFormStatus({
|
||||
eventType: "GIFT_CARD_SENT",
|
||||
appPermissions: [PermissionEnum.ManageGiftCard],
|
||||
featureFlags: {
|
||||
giftCardSentEvent: false,
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
isDisabled: true,
|
||||
missingPermission: undefined,
|
||||
requiredSaleorVersion: ">=3.13",
|
||||
});
|
||||
});
|
||||
});
|
40
apps/emails-and-messages/src/lib/get-event-form-status.ts
Normal file
40
apps/emails-and-messages/src/lib/get-event-form-status.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { PermissionEnum } from "../../generated/graphql";
|
||||
import { MessageEventTypes } from "../modules/event-handlers/message-event-types";
|
||||
import { FeatureFlagsState } from "../modules/feature-flag-service/get-feature-flags";
|
||||
|
||||
interface getEventFormStatusArgs {
|
||||
eventType: MessageEventTypes;
|
||||
featureFlags?: FeatureFlagsState;
|
||||
appPermissions?: PermissionEnum[];
|
||||
}
|
||||
|
||||
export const getEventFormStatus = ({
|
||||
eventType,
|
||||
featureFlags,
|
||||
appPermissions,
|
||||
}: getEventFormStatusArgs): {
|
||||
missingPermission: PermissionEnum | undefined;
|
||||
isDisabled: boolean;
|
||||
requiredSaleorVersion: string | undefined;
|
||||
} => {
|
||||
// Since GIFT_CARD_SENT is the only event with such validation, we can exit early
|
||||
if (eventType !== "GIFT_CARD_SENT") {
|
||||
return {
|
||||
isDisabled: false,
|
||||
missingPermission: undefined,
|
||||
requiredSaleorVersion: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const isUnsupported = !featureFlags?.giftCardSentEvent;
|
||||
|
||||
const hasGiftCardPermission = (appPermissions || []).includes(PermissionEnum.ManageGiftCard);
|
||||
|
||||
const isDisabled = isUnsupported || !hasGiftCardPermission;
|
||||
|
||||
return {
|
||||
isDisabled,
|
||||
missingPermission: hasGiftCardPermission ? undefined : PermissionEnum.ManageGiftCard,
|
||||
requiredSaleorVersion: isUnsupported ? ">=3.13" : undefined,
|
||||
};
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { router } from "../trpc/trpc-server";
|
||||
import { protectedWithConfigurationServices } from "../trpc/protected-client-procedure-with-services";
|
||||
import { fetchAppPermissions } from "../../lib/fetch-app-permissions";
|
||||
|
||||
export const appConfigurationRouter = router({
|
||||
featureFlags: protectedWithConfigurationServices.query(async ({ ctx }) => {
|
||||
|
@ -9,4 +10,12 @@ export const appConfigurationRouter = router({
|
|||
logger.debug("appConfigurationRouter.featureFlags called");
|
||||
return await ctx.featureFlagService.getFeatureFlags();
|
||||
}),
|
||||
appPermissions: protectedWithConfigurationServices.query(async ({ ctx }) => {
|
||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug("appConfigurationRouter.permissions called");
|
||||
const appPermissions = await fetchAppPermissions(ctx.apiClient);
|
||||
|
||||
return appPermissions;
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -19,6 +19,8 @@ import { Select } from "@saleor/react-hook-form-macaw";
|
|||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { messageEventTypesLabels } from "../../event-handlers/message-event-types";
|
||||
import { Table } from "../../../components/table";
|
||||
import { getEventFormStatus } from "../../../lib/get-event-form-status";
|
||||
import { ManagePermissionsTextLink } from "../../../components/manage-permissions-text-link";
|
||||
|
||||
interface SendgridEventsSectionProps {
|
||||
configuration: SendgridConfiguration;
|
||||
|
@ -28,6 +30,7 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
|||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
|
||||
const { data: featureFlags } = trpcClient.app.featureFlags.useQuery();
|
||||
const { data: appPermissions } = trpcClient.app.appPermissions.useQuery();
|
||||
|
||||
// Sort events by displayed label
|
||||
const eventsSorted = configuration.events.sort((a, b) =>
|
||||
|
@ -98,8 +101,12 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
|||
</Table.Header>
|
||||
<Table.Body>
|
||||
{eventsSorted.map((event, index) => {
|
||||
const isUnsupported =
|
||||
!featureFlags?.giftCardSentEvent && event.eventType === "GIFT_CARD_SENT";
|
||||
const { isDisabled, requiredSaleorVersion, missingPermission } =
|
||||
getEventFormStatus({
|
||||
appPermissions,
|
||||
featureFlags: featureFlags,
|
||||
eventType: event.eventType,
|
||||
});
|
||||
|
||||
return (
|
||||
<Table.Row key={event.eventType}>
|
||||
|
@ -109,14 +116,22 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
|||
<input
|
||||
type="checkbox"
|
||||
{...register(`events.${index}.active`)}
|
||||
disabled={isUnsupported}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{isUnsupported && (
|
||||
{requiredSaleorVersion ? (
|
||||
<Tooltip.Content side="left">
|
||||
Event is available in Saleor version 3.13 and above only.
|
||||
The feature requires Saleor version {requiredSaleorVersion}. Update
|
||||
the instance to enable.
|
||||
<Tooltip.Arrow />
|
||||
</Tooltip.Content>
|
||||
) : (
|
||||
missingPermission && (
|
||||
<Tooltip.Content side="left">
|
||||
<ManagePermissionsTextLink missingPermission={missingPermission} />
|
||||
<Tooltip.Arrow />
|
||||
</Tooltip.Content>
|
||||
)
|
||||
)}
|
||||
</Tooltip>
|
||||
</Table.Cell>
|
||||
|
@ -128,7 +143,7 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
|||
control={control}
|
||||
name={`events.${index}.template`}
|
||||
options={templateChoices}
|
||||
disabled={isUnsupported}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
|
|
|
@ -19,6 +19,8 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
||||
import { getEventFormStatus } from "../../../lib/get-event-form-status";
|
||||
import { ManagePermissionsTextLink } from "../../../components/manage-permissions-text-link";
|
||||
|
||||
interface SmtpEventsSectionProps {
|
||||
configuration: SmtpConfiguration;
|
||||
|
@ -29,6 +31,7 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
|||
const router = useRouter();
|
||||
|
||||
const { data: featureFlags } = trpcClient.app.featureFlags.useQuery();
|
||||
const { data: appPermissions } = trpcClient.app.appPermissions.useQuery();
|
||||
|
||||
// Sort events by displayed label
|
||||
const eventsSorted = configuration.events.sort((a, b) =>
|
||||
|
@ -87,8 +90,12 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
|||
</Table.Header>
|
||||
<Table.Body>
|
||||
{eventsSorted.map((event, index) => {
|
||||
const isUnsupported =
|
||||
!featureFlags?.giftCardSentEvent && event.eventType === "GIFT_CARD_SENT";
|
||||
const { isDisabled, requiredSaleorVersion, missingPermission } =
|
||||
getEventFormStatus({
|
||||
appPermissions,
|
||||
featureFlags: featureFlags,
|
||||
eventType: event.eventType,
|
||||
});
|
||||
|
||||
return (
|
||||
<Table.Row key={event.eventType}>
|
||||
|
@ -98,14 +105,22 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
|||
<input
|
||||
type="checkbox"
|
||||
{...register(`events.${index}.active`)}
|
||||
disabled={isUnsupported}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{isUnsupported && (
|
||||
{requiredSaleorVersion ? (
|
||||
<Tooltip.Content side="left">
|
||||
Event is available in Saleor version 3.13 and above only.
|
||||
The feature requires Saleor version {requiredSaleorVersion}. Update
|
||||
the instance to enable.
|
||||
<Tooltip.Arrow />
|
||||
</Tooltip.Content>
|
||||
) : (
|
||||
missingPermission && (
|
||||
<Tooltip.Content side="left">
|
||||
<ManagePermissionsTextLink missingPermission={missingPermission} />
|
||||
<Tooltip.Arrow />
|
||||
</Tooltip.Content>
|
||||
)
|
||||
)}
|
||||
</Tooltip>
|
||||
</Table.Cell>
|
||||
|
@ -121,7 +136,7 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
|||
smtpUrls.eventConfiguration(configuration.id, event.eventType)
|
||||
);
|
||||
}}
|
||||
disabled={isUnsupported}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Edit template
|
||||
</Button>
|
||||
|
|
|
@ -28,7 +28,7 @@ export default createManifestHandler({
|
|||
homepageUrl: "https://github.com/saleor/apps",
|
||||
id: "saleor.app.emails-and-messages",
|
||||
name: "Emails & Messages",
|
||||
permissions: ["MANAGE_ORDERS", "MANAGE_USERS"],
|
||||
permissions: ["MANAGE_ORDERS", "MANAGE_USERS", "MANAGE_GIFT_CARD"],
|
||||
/**
|
||||
* Requires 3.10 due to invoices event payload - in previous versions, order reference was missing
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue