Manual app permissions setting by the user (#3829)
* Remove react-markdown, render app about section as string * Add modal with permissions - WIP * Extract components * add permissions diff * add mutation * add notification * fix dialog scrolling * Extract messages in permissions dialog * test for useGetAvailableAppPermissions.ts * add test to state * fix enums * add changeset and extract root messages * Update wicked-berries-watch.md * fix linter * fix ts * cr fixes
This commit is contained in:
parent
158b22d1ed
commit
1cb6e8b5fc
25 changed files with 1057 additions and 565 deletions
5
.changeset/rude-foxes-drop.md
Normal file
5
.changeset/rude-foxes-drop.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-dashboard": minor
|
||||
---
|
||||
|
||||
Add possibility to manually edit permissions of the app. Now every user with MANAGE_APPS permission can grant any permission to the app via App -> Manage App view or remove permissions previously assigned.
|
5
.changeset/wicked-berries-watch.md
Normal file
5
.changeset/wicked-berries-watch.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-dashboard": minor
|
||||
---
|
||||
|
||||
App's "about" section will be rendered as a plain text, instead of a markdown
|
|
@ -770,6 +770,9 @@
|
|||
"3PVGWj": {
|
||||
"string": "Filter preset"
|
||||
},
|
||||
"3SVI5p": {
|
||||
"string": "Warning"
|
||||
},
|
||||
"3Sz1/t": {
|
||||
"context": "dialog header",
|
||||
"string": "Delete Pages"
|
||||
|
@ -870,6 +873,9 @@
|
|||
"context": "voucher discount",
|
||||
"string": "Specific products"
|
||||
},
|
||||
"47hJzu": {
|
||||
"string": "Updated app permissions"
|
||||
},
|
||||
"483Xnh": {
|
||||
"context": "open full-screen",
|
||||
"string": "Open"
|
||||
|
@ -1250,6 +1256,9 @@
|
|||
"6udlH+": {
|
||||
"string": "Order draft successfully created"
|
||||
},
|
||||
"6uy2gU": {
|
||||
"string": "Manually change permission for the app."
|
||||
},
|
||||
"6xC/Ls": {
|
||||
"context": "informations about product seo, header",
|
||||
"string": "SEO Information"
|
||||
|
@ -3820,6 +3829,9 @@
|
|||
"context": "description",
|
||||
"string": "No results found"
|
||||
},
|
||||
"PlAdWI": {
|
||||
"string": "You are going to"
|
||||
},
|
||||
"Pnj+JH": {
|
||||
"context": "key-value field input",
|
||||
"string": "Value"
|
||||
|
@ -4234,6 +4246,9 @@
|
|||
"context": "export filtered items to csv file",
|
||||
"string": "Current search ({number})"
|
||||
},
|
||||
"SceSNp": {
|
||||
"string": "Remove following permissions:"
|
||||
},
|
||||
"Sjd7wm": {
|
||||
"context": "product filter label",
|
||||
"string": "Product"
|
||||
|
@ -5400,6 +5415,9 @@
|
|||
"context": "error message",
|
||||
"string": "Email address is not set"
|
||||
},
|
||||
"abpvEI": {
|
||||
"string": "Removing permissions may cause app to break."
|
||||
},
|
||||
"ac+Y98": {
|
||||
"context": "app settings error",
|
||||
"string": "Failed to fetch app settings"
|
||||
|
@ -5441,6 +5459,9 @@
|
|||
"context": "range input label",
|
||||
"string": "Postal codes (end)"
|
||||
},
|
||||
"azj0kR": {
|
||||
"string": "Adding permission allows app to have more access to your data."
|
||||
},
|
||||
"b+jcaN": {
|
||||
"string": "There are still fulfillments created for this order. Cancel the fulfillments first before you cancel the order."
|
||||
},
|
||||
|
@ -5666,6 +5687,9 @@
|
|||
"context": "column picker search no results message",
|
||||
"string": "No results found"
|
||||
},
|
||||
"cS1wAx": {
|
||||
"string": "I know what I'm doing - confirm"
|
||||
},
|
||||
"cVjewM": {
|
||||
"context": "label for radio button",
|
||||
"string": "Product prices are entered with tax"
|
||||
|
@ -6201,6 +6225,9 @@
|
|||
"context": "voucher",
|
||||
"string": "Times used"
|
||||
},
|
||||
"hAoqp6": {
|
||||
"string": "Failed to save permissions. Refresh the page and try again."
|
||||
},
|
||||
"hHOI7D": {
|
||||
"context": "product type name",
|
||||
"string": "Type Name"
|
||||
|
@ -6535,6 +6562,9 @@
|
|||
"jvKNMP": {
|
||||
"string": "Discount Code"
|
||||
},
|
||||
"jvo0vs": {
|
||||
"string": "Save"
|
||||
},
|
||||
"jxoMLL": {
|
||||
"context": "product field",
|
||||
"string": "Collections"
|
||||
|
@ -7190,6 +7220,9 @@
|
|||
"oYGfnY": {
|
||||
"string": "ZIP / Postal code"
|
||||
},
|
||||
"oboeOT": {
|
||||
"string": "Add following permissions"
|
||||
},
|
||||
"of/+iV": {
|
||||
"context": "transaction event type, refund was reversed, funds are back to store account",
|
||||
"string": "Refund reversed"
|
||||
|
@ -7206,6 +7239,9 @@
|
|||
"context": "unassign product from sale and save, button",
|
||||
"string": "Unassign and save"
|
||||
},
|
||||
"orvpWh": {
|
||||
"string": "Go back"
|
||||
},
|
||||
"osPBn1": {
|
||||
"context": "currency filter label",
|
||||
"string": "Currency"
|
||||
|
@ -7324,6 +7360,9 @@
|
|||
"context": "checkbox label description",
|
||||
"string": "Expiration date will be automatically set, once gift card is issued"
|
||||
},
|
||||
"psmnv9": {
|
||||
"string": "Edit permissions"
|
||||
},
|
||||
"ptPPVk": {
|
||||
"string": "No languages found"
|
||||
},
|
||||
|
@ -7550,6 +7589,9 @@
|
|||
"context": "button",
|
||||
"string": "Delete variant"
|
||||
},
|
||||
"rbrahO": {
|
||||
"string": "Close"
|
||||
},
|
||||
"reP5Uf": {
|
||||
"context": "global config plugin status popup description",
|
||||
"string": "Global plugins are set across all channels in your ecommerce. Only status is shown for those types of plugins"
|
||||
|
@ -8421,6 +8463,9 @@
|
|||
"context": "section header",
|
||||
"string": "Eligible Products"
|
||||
},
|
||||
"xrKHS6": {
|
||||
"string": "Success"
|
||||
},
|
||||
"xrPv2K": {
|
||||
"context": "by preposition",
|
||||
"string": "by"
|
||||
|
|
578
package-lock.json
generated
578
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -83,7 +83,6 @@
|
|||
"react-inlinesvg": "^3.0.2",
|
||||
"react-intl": "^5.21.2",
|
||||
"react-jss": "^10.0.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-moment": "^1.0.0",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"react-router": "^5.0.1",
|
||||
|
|
|
@ -2,12 +2,11 @@ import Skeleton from "@dashboard/components/Skeleton";
|
|||
import { Box, BoxProps, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
type AboutCardProps = {
|
||||
aboutApp?: string | null;
|
||||
aboutApp: string | null;
|
||||
loading: boolean;
|
||||
} & BoxProps;
|
||||
|
||||
|
@ -24,7 +23,7 @@ export const AboutCard: React.FC<AboutCardProps> = ({
|
|||
}
|
||||
|
||||
if (aboutApp) {
|
||||
return <ReactMarkdown source={aboutApp} />;
|
||||
return <Text>{aboutApp}</Text>;
|
||||
}
|
||||
|
||||
if (!aboutApp) {
|
||||
|
|
|
@ -45,6 +45,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
|
|||
/>
|
||||
<AboutCard margin={6} aboutApp={data?.aboutApp} loading={loading} />
|
||||
<PermissionsCard
|
||||
appId={data.id}
|
||||
margin={6}
|
||||
permissions={data?.permissions}
|
||||
loading={loading}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { AppPermissionsDialog } from "@dashboard/apps/components/AppPermissionsDialog";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import { Box, BoxProps, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { Box, BoxProps, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import messages from "./messages";
|
||||
|
@ -12,22 +13,42 @@ type PermissionsCardProps = {
|
|||
code: PermissionEnum;
|
||||
}> | null;
|
||||
loading: boolean;
|
||||
appId: string; // todo wrap with App Context
|
||||
} & BoxProps;
|
||||
|
||||
export const PermissionsCard: React.FC<PermissionsCardProps> = ({
|
||||
permissions,
|
||||
loading,
|
||||
appId,
|
||||
...boxProps
|
||||
}) => {
|
||||
const [editPermissionDialogOpen, setEditPermissionDialogOpen] =
|
||||
useState(false);
|
||||
const intl = useIntl();
|
||||
|
||||
const editPermissionsButton = (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
onClick={() => setEditPermissionDialogOpen(true)}
|
||||
>
|
||||
{intl.formatMessage(messages.editPermissionsButton)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const renderContent = () => {
|
||||
if (loading) {
|
||||
return <Skeleton />;
|
||||
}
|
||||
|
||||
if (permissions && permissions.length === 0) {
|
||||
return <Text>{intl.formatMessage(messages.appNoPermissions)}</Text>;
|
||||
return (
|
||||
<>
|
||||
<Text marginBottom={4} as={"p"}>
|
||||
{intl.formatMessage(messages.appNoPermissions)}
|
||||
</Text>
|
||||
{editPermissionsButton}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (permissions && permissions.length > 0) {
|
||||
|
@ -43,6 +64,7 @@ export const PermissionsCard: React.FC<PermissionsCardProps> = ({
|
|||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{editPermissionsButton}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -51,11 +73,20 @@ export const PermissionsCard: React.FC<PermissionsCardProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{editPermissionDialogOpen && (
|
||||
<AppPermissionsDialog
|
||||
appId={appId}
|
||||
onClose={() => setEditPermissionDialogOpen(false)}
|
||||
assignedPermissions={permissions?.map(p => p.code) ?? []}
|
||||
/>
|
||||
)}
|
||||
<Box {...boxProps}>
|
||||
<Text variant={"heading"} marginBottom={4} as={"h2"}>
|
||||
{intl.formatMessage(messages.appPermissionsTitle)}
|
||||
</Text>
|
||||
<Box>{renderContent()}</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -43,4 +43,8 @@ export default defineMessages({
|
|||
id: "b088Xv",
|
||||
defaultMessage: "App doesn't provide a description.",
|
||||
},
|
||||
editPermissionsButton: {
|
||||
defaultMessage: "Edit permissions",
|
||||
id: "psmnv9",
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
import { AppPermissionsDialogConfirmation } from "@dashboard/apps/components/AppPermissionsDialog/AppPermissionsDialogConfirmation";
|
||||
import { AppPermissionsDialogPermissionPicker } from "@dashboard/apps/components/AppPermissionsDialog/AppPermissionsDialogPermissionPicker";
|
||||
import { useAppPermissionsDialogState } from "@dashboard/apps/components/AppPermissionsDialog/AppPermissionsDialogState";
|
||||
import { AppPermissionsDialogMessages } from "@dashboard/apps/components/AppPermissionsDialog/messages";
|
||||
import { useGetAvailableAppPermissions } from "@dashboard/apps/components/AppPermissionsDialog/useGetAvailableAppPermissions";
|
||||
import {
|
||||
PermissionEnum,
|
||||
useAppQuery,
|
||||
useAppUpdatePermissionsMutation,
|
||||
} from "@dashboard/graphql";
|
||||
import useNotifier from "@dashboard/hooks/useNotifier";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@material-ui/core";
|
||||
import { Skeleton } from "@material-ui/lab";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import React, { useEffect } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const messages = AppPermissionsDialogMessages.dialogRoot;
|
||||
|
||||
interface AppPermissionsDialogProps {
|
||||
onClose: () => void;
|
||||
assignedPermissions: PermissionEnum[];
|
||||
appId: string;
|
||||
}
|
||||
|
||||
export const AppPermissionsDialog = ({
|
||||
assignedPermissions,
|
||||
onClose,
|
||||
appId,
|
||||
}: AppPermissionsDialogProps) => {
|
||||
const { availablePermissions } = useGetAvailableAppPermissions();
|
||||
const { formatMessage } = useIntl();
|
||||
const {
|
||||
updateSelected,
|
||||
onConfirmSelection,
|
||||
state,
|
||||
onBackFromConfirmation,
|
||||
selectedPermissions,
|
||||
onMutationError,
|
||||
onApprove,
|
||||
} = useAppPermissionsDialogState(assignedPermissions);
|
||||
|
||||
const { refetch } = useAppQuery({ variables: { id: appId }, skip: true });
|
||||
|
||||
const notify = useNotifier();
|
||||
|
||||
const [mutate] = useAppUpdatePermissionsMutation({
|
||||
onError(err) {
|
||||
onMutationError(err.message);
|
||||
},
|
||||
onCompleted(data) {
|
||||
if (data.appUpdate?.errors.length) {
|
||||
onMutationError(
|
||||
data.appUpdate?.errors[0].message ??
|
||||
formatMessage(messages.fallbackErrorText),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
refetch().then(onClose);
|
||||
|
||||
notify({
|
||||
status: "success",
|
||||
title: formatMessage(messages.successNotificationTitle),
|
||||
autohide: 1000,
|
||||
text: formatMessage(messages.successNotificationBody),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (state.type === "saving") {
|
||||
mutate({
|
||||
variables: {
|
||||
permissions: state.selected,
|
||||
id: appId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [state.type, appId]);
|
||||
|
||||
const renderDialogContent = () => {
|
||||
switch (state.type) {
|
||||
case "pick-permissions":
|
||||
return (
|
||||
<AppPermissionsDialogPermissionPicker
|
||||
onClose={onClose}
|
||||
onChange={updateSelected}
|
||||
onSubmit={onConfirmSelection}
|
||||
allPermissions={availablePermissions}
|
||||
selected={selectedPermissions}
|
||||
/>
|
||||
);
|
||||
case "confirm-permissions":
|
||||
return (
|
||||
<AppPermissionsDialogConfirmation
|
||||
addedPermissions={state.addedPermissions}
|
||||
removedPermissions={state.removedPermissions}
|
||||
onApprove={onApprove}
|
||||
onBack={onBackFromConfirmation}
|
||||
/>
|
||||
);
|
||||
|
||||
case "saving":
|
||||
return <Skeleton />;
|
||||
case "error":
|
||||
return (
|
||||
<Box padding={4}>
|
||||
<Text as={"p"} color={"textCriticalDefault"}>
|
||||
{state.error}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={true} onClose={onClose} fullWidth maxWidth={"sm"}>
|
||||
<DialogTitle disableTypography>
|
||||
{formatMessage(messages.heading)}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box display={"grid"} gridAutoFlow={"row"}>
|
||||
<Text as={"p"}>{formatMessage(messages.info)}</Text>
|
||||
<Box
|
||||
borderRadius={2}
|
||||
marginBottom={6}
|
||||
marginTop={4}
|
||||
padding={4}
|
||||
backgroundColor={"surfaceCriticalSubdued"}
|
||||
>
|
||||
<Text
|
||||
marginBottom={2}
|
||||
as={"p"}
|
||||
color={"textCriticalDefault"}
|
||||
variant={"bodyStrong"}
|
||||
>
|
||||
{formatMessage(messages.warningHeading)}
|
||||
</Text>
|
||||
<Text as={"p"}>{formatMessage(messages.warningParagraph1)}</Text>
|
||||
<Text as={"p"}>{formatMessage(messages.warningParagraph2)}</Text>
|
||||
</Box>
|
||||
{renderDialogContent()}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
import { AppPermissionsDialogMessages } from "@dashboard/apps/components/AppPermissionsDialog/messages";
|
||||
import { useGetAvailableAppPermissions } from "@dashboard/apps/components/AppPermissionsDialog/useGetAvailableAppPermissions";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const messages = AppPermissionsDialogMessages.confirmation;
|
||||
|
||||
interface Props {
|
||||
removedPermissions: PermissionEnum[];
|
||||
addedPermissions: PermissionEnum[];
|
||||
onBack(): void;
|
||||
onApprove(): void;
|
||||
}
|
||||
|
||||
export const AppPermissionsDialogConfirmation = ({
|
||||
removedPermissions,
|
||||
addedPermissions,
|
||||
onBack,
|
||||
onApprove,
|
||||
}: Props) => {
|
||||
const isPermissionsAdded = addedPermissions.length > 0;
|
||||
const isPermissionsRemoved = removedPermissions.length > 0;
|
||||
const intl = useIntl();
|
||||
|
||||
const { mapCodesToNames } = useGetAvailableAppPermissions();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text marginBottom={2} as={"p"}>
|
||||
{intl.formatMessage(messages.summaryText)}
|
||||
</Text>
|
||||
{isPermissionsRemoved && (
|
||||
<Box marginBottom={4}>
|
||||
<Text variant={"bodyStrong"}>
|
||||
{intl.formatMessage(messages.removePermissions)}
|
||||
</Text>
|
||||
{mapCodesToNames(removedPermissions).map(perm => (
|
||||
<Text as={"p"} key={perm}>
|
||||
{perm}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{isPermissionsAdded && (
|
||||
<Box>
|
||||
<Text variant={"bodyStrong"}>
|
||||
{intl.formatMessage(messages.addPermissions)}
|
||||
</Text>
|
||||
{mapCodesToNames(addedPermissions).map(perm => (
|
||||
<Text as={"p"} key={perm}>
|
||||
{perm}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Box display={"flex"} justifyContent={"flex-end"} gap={2} marginTop={6}>
|
||||
<Button
|
||||
variant={"tertiary"}
|
||||
onClick={() => {
|
||||
onBack();
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage(messages.backButton)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onApprove();
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage(messages.confirmButton)}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,80 @@
|
|||
import { AppPermissionsDialogMessages } from "@dashboard/apps/components/AppPermissionsDialog/messages";
|
||||
import { AppPermission } from "@dashboard/apps/components/AppPermissionsDialog/types";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import { Box, Button, Checkbox, List, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const messages = AppPermissionsDialogMessages.permissionsPicker;
|
||||
|
||||
interface AppPermissionsDialogPermissionPickerProps {
|
||||
allPermissions: AppPermission[];
|
||||
selected: PermissionEnum[];
|
||||
onSubmit(): void;
|
||||
onChange(codes: PermissionEnum[]): void;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export const AppPermissionsDialogPermissionPicker = ({
|
||||
onSubmit,
|
||||
onChange,
|
||||
allPermissions,
|
||||
selected,
|
||||
onClose,
|
||||
}: AppPermissionsDialogPermissionPickerProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
onSubmit();
|
||||
}}
|
||||
onChange={e => {
|
||||
const formdata = new FormData(e.currentTarget);
|
||||
|
||||
// @ts-expect-error - for some reason TS doesnt see keys, values, entries methods on formdata. TODO
|
||||
const values = Array.from(formdata.keys()) as PermissionEnum[];
|
||||
|
||||
onChange(values);
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{allPermissions.map(perm => {
|
||||
const isAssigned = Boolean(selected.find(p => p === perm.code));
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
key={perm.code}
|
||||
paddingY={1}
|
||||
paddingX={2}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
as={"label"}
|
||||
backgroundColor={
|
||||
isAssigned ? "decorativeSurfaceSubdued3" : undefined
|
||||
}
|
||||
>
|
||||
<Checkbox
|
||||
name={perm.code}
|
||||
defaultChecked={isAssigned}
|
||||
marginRight={4}
|
||||
/>
|
||||
<Text variant={isAssigned ? "bodyStrong" : "body"}>
|
||||
{perm.name}
|
||||
</Text>
|
||||
</List.Item>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<Box display={"flex"} justifyContent={"flex-end"} gap={2}>
|
||||
<Button onClick={onClose} type={"button"} variant={"tertiary"}>
|
||||
{intl.formatMessage(messages.closeButton)}
|
||||
</Button>
|
||||
<Button type={"submit"}>
|
||||
{intl.formatMessage(messages.saveButton)}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,121 @@
|
|||
import { useAppPermissionsDialogState } from "@dashboard/apps/components/AppPermissionsDialog/AppPermissionsDialogState";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import { renderHook } from "@testing-library/react-hooks";
|
||||
|
||||
describe("useAppPermissionsDialogState", () => {
|
||||
it("Creates state with initial permissions - empty", () => {
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useAppPermissionsDialogState([]));
|
||||
|
||||
expect(current.state.type).toEqual("pick-permissions");
|
||||
expect(current.state.selected).toEqual([]);
|
||||
});
|
||||
|
||||
it("Creates state with initial permissions", () => {
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() =>
|
||||
useAppPermissionsDialogState([
|
||||
PermissionEnum.MANAGE_CHANNELS,
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
]),
|
||||
);
|
||||
|
||||
expect(current.state.type).toEqual("pick-permissions");
|
||||
expect(current.state.selected).toEqual([
|
||||
"MANAGE_CHANNELS",
|
||||
"MANAGE_ORDERS",
|
||||
]);
|
||||
});
|
||||
|
||||
describe("Transitions to confirmation screen with proper diff", () => {
|
||||
test("One added permission", async () => {
|
||||
const {
|
||||
result: { current },
|
||||
waitFor,
|
||||
} = renderHook(() =>
|
||||
useAppPermissionsDialogState([
|
||||
PermissionEnum.MANAGE_CHANNELS,
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
]),
|
||||
);
|
||||
|
||||
current.updateSelected([
|
||||
PermissionEnum.MANAGE_CHANNELS,
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
PermissionEnum.HANDLE_CHECKOUTS,
|
||||
]);
|
||||
|
||||
current.onConfirmSelection();
|
||||
|
||||
waitFor(() => {
|
||||
expect(current.state.type).toEqual("confirm-permissions");
|
||||
|
||||
if (current.state.type === "confirm-permissions") {
|
||||
expect(current.state.removedPermissions).toEqual([]);
|
||||
expect(current.state.addedPermissions).toEqual(["HANDLE_CHECKOUTS"]);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("One removed permission", async () => {
|
||||
const {
|
||||
result: { current },
|
||||
waitFor,
|
||||
} = renderHook(() =>
|
||||
useAppPermissionsDialogState([
|
||||
PermissionEnum.MANAGE_CHANNELS,
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
]),
|
||||
);
|
||||
|
||||
current.updateSelected([PermissionEnum.MANAGE_CHANNELS]);
|
||||
|
||||
current.onConfirmSelection();
|
||||
|
||||
waitFor(() => {
|
||||
expect(current.state.type).toEqual("confirm-permissions");
|
||||
|
||||
if (current.state.type === "confirm-permissions") {
|
||||
expect(current.state.removedPermissions).toEqual(["MANAGE_ORDERS"]);
|
||||
expect(current.state.addedPermissions).toEqual([""]);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("One added and one removed permission", async () => {
|
||||
const {
|
||||
result: { current },
|
||||
waitFor,
|
||||
} = renderHook(() =>
|
||||
useAppPermissionsDialogState([
|
||||
PermissionEnum.MANAGE_CHANNELS,
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
]),
|
||||
);
|
||||
|
||||
current.updateSelected([
|
||||
PermissionEnum.MANAGE_CHANNELS,
|
||||
PermissionEnum.MANAGE_CHECKOUTS,
|
||||
]);
|
||||
|
||||
current.onConfirmSelection();
|
||||
|
||||
waitFor(() => {
|
||||
expect(current.state.type).toEqual("confirm-permissions");
|
||||
|
||||
if (current.state.type === "confirm-permissions") {
|
||||
expect(current.state.removedPermissions).toEqual(["MANAGE_ORDERS"]);
|
||||
expect(current.state.addedPermissions).toEqual(["HANDLE_CHECKOUTS"]);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
import { getPermissionsDiff } from "@dashboard/apps/components/AppPermissionsDialog/getPermissionsDiff";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import { useState } from "react";
|
||||
|
||||
type State =
|
||||
| {
|
||||
type: "pick-permissions";
|
||||
selected: PermissionEnum[];
|
||||
}
|
||||
| {
|
||||
type: "confirm-permissions";
|
||||
selected: PermissionEnum[];
|
||||
addedPermissions: PermissionEnum[];
|
||||
removedPermissions: PermissionEnum[];
|
||||
}
|
||||
| {
|
||||
selected: PermissionEnum[];
|
||||
type: "saving";
|
||||
}
|
||||
| {
|
||||
selected: PermissionEnum[];
|
||||
type: "error";
|
||||
error: string;
|
||||
};
|
||||
|
||||
export const useAppPermissionsDialogState = (
|
||||
initialPermissions: PermissionEnum[],
|
||||
) => {
|
||||
const [state, setState] = useState<State>({
|
||||
type: "pick-permissions",
|
||||
selected: initialPermissions,
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
stateType: state.type,
|
||||
selectedPermissions: state.selected,
|
||||
updateSelected(newPermissions: PermissionEnum[]) {
|
||||
if (state.type !== "pick-permissions") {
|
||||
throw new Error("Invalid state");
|
||||
}
|
||||
|
||||
setState({
|
||||
type: "pick-permissions",
|
||||
selected: newPermissions,
|
||||
});
|
||||
},
|
||||
onConfirmSelection() {
|
||||
if (state.type !== "pick-permissions") {
|
||||
throw new Error("Invalid state");
|
||||
}
|
||||
|
||||
const diff = getPermissionsDiff(initialPermissions, state.selected);
|
||||
|
||||
setState({
|
||||
type: "confirm-permissions",
|
||||
selected: state.selected,
|
||||
addedPermissions: diff.added,
|
||||
removedPermissions: diff.removed,
|
||||
});
|
||||
},
|
||||
onApprove() {
|
||||
if (state.type !== "confirm-permissions") {
|
||||
throw new Error("Invalid state");
|
||||
}
|
||||
|
||||
setState({
|
||||
type: "saving",
|
||||
selected: state.selected,
|
||||
});
|
||||
},
|
||||
onBackFromConfirmation() {
|
||||
if (state.type !== "confirm-permissions") {
|
||||
throw new Error("Invalid state");
|
||||
}
|
||||
|
||||
setState({
|
||||
type: "pick-permissions",
|
||||
selected: state.selected,
|
||||
});
|
||||
},
|
||||
onMutationError(message: string) {
|
||||
if (state.type !== "saving") {
|
||||
throw new Error("Invalid state");
|
||||
}
|
||||
|
||||
setState({
|
||||
type: "error",
|
||||
error: message,
|
||||
selected: state.selected,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
import { getPermissionsDiff } from "@dashboard/apps/components/AppPermissionsDialog/getPermissionsDiff";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
|
||||
describe("getPermissionsDiff", () => {
|
||||
describe("Correctly resolves added permissions", () => {
|
||||
test("From empty to one new", () => {
|
||||
const { added, removed } = getPermissionsDiff(
|
||||
[],
|
||||
[PermissionEnum.HANDLE_CHECKOUTS],
|
||||
);
|
||||
|
||||
expect(added).toEqual([PermissionEnum.HANDLE_CHECKOUTS]);
|
||||
expect(removed).toEqual([]);
|
||||
});
|
||||
|
||||
test("From 0 to 3 new", () => {
|
||||
const { added, removed } = getPermissionsDiff(
|
||||
[],
|
||||
[
|
||||
PermissionEnum.IMPERSONATE_USER,
|
||||
PermissionEnum.HANDLE_PAYMENTS,
|
||||
PermissionEnum.MANAGE_APPS,
|
||||
],
|
||||
);
|
||||
|
||||
expect(added).toEqual([
|
||||
PermissionEnum.IMPERSONATE_USER,
|
||||
PermissionEnum.HANDLE_PAYMENTS,
|
||||
PermissionEnum.MANAGE_APPS,
|
||||
]);
|
||||
expect(removed).toEqual([]);
|
||||
});
|
||||
|
||||
test("From 1 to 2 new and 1 existing", () => {
|
||||
const { added, removed } = getPermissionsDiff(
|
||||
[PermissionEnum.HANDLE_CHECKOUTS],
|
||||
[
|
||||
PermissionEnum.HANDLE_CHECKOUTS,
|
||||
PermissionEnum.HANDLE_PAYMENTS,
|
||||
PermissionEnum.MANAGE_APPS,
|
||||
],
|
||||
);
|
||||
|
||||
expect(added).toEqual([
|
||||
PermissionEnum.HANDLE_PAYMENTS,
|
||||
PermissionEnum.MANAGE_APPS,
|
||||
]);
|
||||
expect(removed).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Correctly resolves removed permissions", () => {
|
||||
test("Doesnt remove anything if the same", () => {
|
||||
const { added, removed } = getPermissionsDiff(
|
||||
[PermissionEnum.HANDLE_CHECKOUTS],
|
||||
[PermissionEnum.HANDLE_CHECKOUTS],
|
||||
);
|
||||
|
||||
expect(added).toEqual([]);
|
||||
expect(removed).toEqual([]);
|
||||
});
|
||||
|
||||
test("Removes one", () => {
|
||||
const { added, removed } = getPermissionsDiff(
|
||||
[PermissionEnum.HANDLE_CHECKOUTS],
|
||||
[],
|
||||
);
|
||||
|
||||
expect(added).toEqual([]);
|
||||
expect(removed).toEqual([PermissionEnum.HANDLE_CHECKOUTS]);
|
||||
});
|
||||
});
|
||||
|
||||
test("Removes one and adds ", () => {
|
||||
const { added, removed } = getPermissionsDiff(
|
||||
[PermissionEnum.HANDLE_CHECKOUTS, PermissionEnum.HANDLE_PAYMENTS],
|
||||
[PermissionEnum.HANDLE_CHECKOUTS, PermissionEnum.HANDLE_TAXES],
|
||||
);
|
||||
|
||||
expect(added).toEqual([PermissionEnum.HANDLE_TAXES]);
|
||||
expect(removed).toEqual([PermissionEnum.HANDLE_PAYMENTS]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import difference from "lodash/difference";
|
||||
|
||||
export const getPermissionsDiff = (
|
||||
initialPermissionsCodes: PermissionEnum[],
|
||||
newPermissionsCodes: PermissionEnum[],
|
||||
): {
|
||||
added: PermissionEnum[];
|
||||
removed: PermissionEnum[];
|
||||
} => {
|
||||
const removed = difference(initialPermissionsCodes, newPermissionsCodes);
|
||||
const added = difference(newPermissionsCodes, initialPermissionsCodes);
|
||||
|
||||
return {
|
||||
added,
|
||||
removed,
|
||||
};
|
||||
};
|
1
src/apps/components/AppPermissionsDialog/index.ts
Normal file
1
src/apps/components/AppPermissionsDialog/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./AppPermissionsDialog";
|
78
src/apps/components/AppPermissionsDialog/messages.ts
Normal file
78
src/apps/components/AppPermissionsDialog/messages.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
const confirmation = defineMessages({
|
||||
summaryText: {
|
||||
defaultMessage: "You are going to",
|
||||
id: "PlAdWI",
|
||||
},
|
||||
addPermissions: {
|
||||
defaultMessage: "Add following permissions",
|
||||
id: "oboeOT",
|
||||
},
|
||||
removePermissions: {
|
||||
defaultMessage: "Remove following permissions:",
|
||||
id: "SceSNp",
|
||||
},
|
||||
backButton: {
|
||||
defaultMessage: "Go back",
|
||||
id: "orvpWh",
|
||||
},
|
||||
confirmButton: {
|
||||
defaultMessage: "I know what I'm doing - confirm",
|
||||
id: "cS1wAx",
|
||||
},
|
||||
});
|
||||
|
||||
const permissionsPicker = defineMessages({
|
||||
closeButton: {
|
||||
defaultMessage: "Close",
|
||||
id: "rbrahO",
|
||||
},
|
||||
saveButton: {
|
||||
defaultMessage: "Save",
|
||||
id: "jvo0vs",
|
||||
},
|
||||
});
|
||||
|
||||
const dialogRoot = defineMessages({
|
||||
heading: {
|
||||
defaultMessage: "Edit permissions",
|
||||
id: "psmnv9",
|
||||
},
|
||||
info: {
|
||||
defaultMessage: "Manually change permission for the app.",
|
||||
id: "6uy2gU",
|
||||
},
|
||||
warningHeading: {
|
||||
defaultMessage: "Warning",
|
||||
id: "3SVI5p",
|
||||
},
|
||||
warningParagraph1: {
|
||||
defaultMessage:
|
||||
"Adding permission allows app to have more access to your data.",
|
||||
id: "azj0kR",
|
||||
},
|
||||
warningParagraph2: {
|
||||
defaultMessage: "Removing permissions may cause app to break.",
|
||||
id: "abpvEI",
|
||||
},
|
||||
successNotificationTitle: {
|
||||
defaultMessage: "Success",
|
||||
id: "xrKHS6",
|
||||
},
|
||||
successNotificationBody: {
|
||||
defaultMessage: "Updated app permissions",
|
||||
id: "47hJzu",
|
||||
},
|
||||
fallbackErrorText: {
|
||||
defaultMessage:
|
||||
"Failed to save permissions. Refresh the page and try again.",
|
||||
id: "hAoqp6",
|
||||
},
|
||||
});
|
||||
|
||||
export const AppPermissionsDialogMessages = {
|
||||
confirmation,
|
||||
permissionsPicker,
|
||||
dialogRoot,
|
||||
};
|
6
src/apps/components/AppPermissionsDialog/types.ts
Normal file
6
src/apps/components/AppPermissionsDialog/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
|
||||
export interface AppPermission {
|
||||
code: PermissionEnum;
|
||||
name: string;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
import { useGetAvailableAppPermissions } from "@dashboard/apps/components/AppPermissionsDialog/useGetAvailableAppPermissions";
|
||||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import useShop from "@dashboard/hooks/useShop";
|
||||
import { renderHook } from "@testing-library/react-hooks";
|
||||
|
||||
type PermissionsFromApi = Array<{
|
||||
__typename: "Permission";
|
||||
code: PermissionEnum;
|
||||
name: string;
|
||||
}>;
|
||||
|
||||
const getMockUseShopHookResult = () => {
|
||||
const permissions: PermissionsFromApi = [
|
||||
{
|
||||
__typename: "Permission",
|
||||
name: "Manage Orders",
|
||||
code: PermissionEnum.MANAGE_ORDERS,
|
||||
},
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.HANDLE_TAXES,
|
||||
name: "Handle Taxes",
|
||||
},
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_CHANNELS,
|
||||
name: "Manage Channels",
|
||||
},
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_APPS,
|
||||
name: "Manage Apps",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
permissions,
|
||||
};
|
||||
};
|
||||
|
||||
jest.mock("@dashboard/hooks/useShop");
|
||||
|
||||
describe("useGetAvailableAppPermissions", () => {
|
||||
beforeEach(() => {
|
||||
(useShop as jest.Mock).mockImplementationOnce(getMockUseShopHookResult);
|
||||
});
|
||||
|
||||
it("Exposes permissons provided from useShop hook", () => {
|
||||
const hookResult = renderHook(() => useGetAvailableAppPermissions());
|
||||
|
||||
expect(hookResult.result.current.availablePermissions).toEqual([
|
||||
{
|
||||
name: "Manage Orders",
|
||||
code: PermissionEnum.MANAGE_ORDERS,
|
||||
},
|
||||
{
|
||||
name: "Handle Taxes",
|
||||
code: PermissionEnum.HANDLE_TAXES,
|
||||
},
|
||||
{
|
||||
code: PermissionEnum.MANAGE_CHANNELS,
|
||||
name: "Manage Channels",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("Filters out MANAGE_APPS permission, because app should not have one", () => {
|
||||
const hookResult = renderHook(() => useGetAvailableAppPermissions());
|
||||
|
||||
const resultPermissions = hookResult.result.current.availablePermissions;
|
||||
const manageAppsPermission = resultPermissions.find(
|
||||
perm => perm.code === PermissionEnum.MANAGE_APPS,
|
||||
);
|
||||
|
||||
expect(manageAppsPermission).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("mapCodesToNames method", () => {
|
||||
it("Maps provided code enums and returns its names from the API", () => {
|
||||
const hookResult = renderHook(() => useGetAvailableAppPermissions());
|
||||
|
||||
const resultNames = hookResult.result.current.mapCodesToNames([
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
PermissionEnum.HANDLE_TAXES,
|
||||
]);
|
||||
|
||||
expect(resultNames).toEqual(["Manage Orders", "Handle Taxes"]);
|
||||
});
|
||||
|
||||
it("Throws if useShop is not available", () => {
|
||||
jest.resetAllMocks();
|
||||
(useShop as jest.Mock).mockImplementationOnce(() => undefined);
|
||||
|
||||
const hookResult = renderHook(() => useGetAvailableAppPermissions());
|
||||
|
||||
expect(() =>
|
||||
hookResult.result.current.mapCodesToNames([
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
]),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import { PermissionEnum } from "@dashboard/graphql";
|
||||
import useShop from "@dashboard/hooks/useShop";
|
||||
|
||||
export const useGetAvailableAppPermissions = () => {
|
||||
const shopData = useShop();
|
||||
|
||||
/**
|
||||
* App can't have MANAGE_APPS so filter it out
|
||||
*/
|
||||
const availablePermissions = shopData.permissions
|
||||
.filter(perm => perm.code !== "MANAGE_APPS")
|
||||
.map(p => ({
|
||||
code: p.code,
|
||||
name: p.name,
|
||||
}));
|
||||
|
||||
const mapCodesToNames = (codes: PermissionEnum[]) => {
|
||||
const permissions = shopData?.permissions;
|
||||
|
||||
if (!permissions) {
|
||||
throw new Error(
|
||||
"Shop data from useShop hook is not available. mapCodesToNames method must be used after query resolves",
|
||||
);
|
||||
}
|
||||
|
||||
return codes.map(c => {
|
||||
const relatedPermission = permissions.find(p => {
|
||||
return p.code === c;
|
||||
});
|
||||
|
||||
if (!relatedPermission) {
|
||||
throw new Error(
|
||||
"Trying to match permission enum from app that doesnt match available permissions from API",
|
||||
);
|
||||
}
|
||||
|
||||
return relatedPermission.name;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
availablePermissions,
|
||||
mapCodesToNames,
|
||||
};
|
||||
};
|
|
@ -8,13 +8,13 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
import { messages } from "./messages";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
interface Props {
|
||||
interface InstallWithManifestFormButtonProps {
|
||||
onSubmitted(manifestUrl: string): void;
|
||||
}
|
||||
|
||||
export const InstallWithManifestFormButton: React.FC<Props> = ({
|
||||
onSubmitted,
|
||||
}) => {
|
||||
export const InstallWithManifestFormButton: React.FC<
|
||||
InstallWithManifestFormButtonProps
|
||||
> = ({ onSubmitted }) => {
|
||||
const styles = useStyles();
|
||||
const intl = useIntl();
|
||||
|
||||
|
|
|
@ -157,3 +157,19 @@ export const appDeactivateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const appUpdatePermissions = gql`
|
||||
mutation AppUpdatePermissions($id: ID!, $permissions: [PermissionEnum!]!) {
|
||||
appUpdate(id: $id, input: { permissions: $permissions }) {
|
||||
app {
|
||||
permissions {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -3585,6 +3585,48 @@ export function useAppDeactivateMutation(baseOptions?: ApolloReactHooks.Mutation
|
|||
export type AppDeactivateMutationHookResult = ReturnType<typeof useAppDeactivateMutation>;
|
||||
export type AppDeactivateMutationResult = Apollo.MutationResult<Types.AppDeactivateMutation>;
|
||||
export type AppDeactivateMutationOptions = Apollo.BaseMutationOptions<Types.AppDeactivateMutation, Types.AppDeactivateMutationVariables>;
|
||||
export const AppUpdatePermissionsDocument = gql`
|
||||
mutation AppUpdatePermissions($id: ID!, $permissions: [PermissionEnum!]!) {
|
||||
appUpdate(id: $id, input: {permissions: $permissions}) {
|
||||
app {
|
||||
permissions {
|
||||
code
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type AppUpdatePermissionsMutationFn = Apollo.MutationFunction<Types.AppUpdatePermissionsMutation, Types.AppUpdatePermissionsMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useAppUpdatePermissionsMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useAppUpdatePermissionsMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useAppUpdatePermissionsMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [appUpdatePermissionsMutation, { data, loading, error }] = useAppUpdatePermissionsMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* permissions: // value for 'permissions'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useAppUpdatePermissionsMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<Types.AppUpdatePermissionsMutation, Types.AppUpdatePermissionsMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return ApolloReactHooks.useMutation<Types.AppUpdatePermissionsMutation, Types.AppUpdatePermissionsMutationVariables>(AppUpdatePermissionsDocument, options);
|
||||
}
|
||||
export type AppUpdatePermissionsMutationHookResult = ReturnType<typeof useAppUpdatePermissionsMutation>;
|
||||
export type AppUpdatePermissionsMutationResult = Apollo.MutationResult<Types.AppUpdatePermissionsMutation>;
|
||||
export type AppUpdatePermissionsMutationOptions = Apollo.BaseMutationOptions<Types.AppUpdatePermissionsMutation, Types.AppUpdatePermissionsMutationVariables>;
|
||||
export const AppsListDocument = gql`
|
||||
query AppsList($before: String, $after: String, $first: Int, $last: Int, $sort: AppSortingInput, $filter: AppFilterInput) {
|
||||
apps(
|
||||
|
|
|
@ -7968,6 +7968,14 @@ export type AppDeactivateMutationVariables = Exact<{
|
|||
|
||||
export type AppDeactivateMutation = { __typename: 'Mutation', appDeactivate: { __typename: 'AppDeactivate', errors: Array<{ __typename: 'AppError', field: string | null, message: string | null, code: AppErrorCode, permissions: Array<PermissionEnum> | null }> } | null };
|
||||
|
||||
export type AppUpdatePermissionsMutationVariables = Exact<{
|
||||
id: Scalars['ID'];
|
||||
permissions: Array<PermissionEnum> | PermissionEnum;
|
||||
}>;
|
||||
|
||||
|
||||
export type AppUpdatePermissionsMutation = { __typename: 'Mutation', appUpdate: { __typename: 'AppUpdate', app: { __typename: 'App', permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null } | null, errors: Array<{ __typename: 'AppError', message: string | null }> } | null };
|
||||
|
||||
export type AppsListQueryVariables = Exact<{
|
||||
before?: InputMaybe<Scalars['String']>;
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
|
|
Loading…
Reference in a new issue