Apps list page enchancements (#2035)
* Remove marketplace from Apps list * Move apps in progress to bottom * Remove pagination from InstalledApps * Add apps permissions tooltip * Activate/deactivate InstalledApps from list * Add changes description to CHANGELOG * Update package.json to include macaw required changes * Upadte fixtures * Rename Local Apps -> Third Party Apps * Update macaw, fix TS errors * Refactor AppPermission component to use permission fragment * Add fragment for app list query, refactor InstalledApps props type * Fix check for usage within context inside useAppListContext * Remove redundant errors check in mutation hooks inside AppsList * Update extracted messages * Fix AppListPage stories failing * Fix Tooltip not working in failed installed apps * Update messages
This commit is contained in:
parent
1a19289e43
commit
5138608f86
21 changed files with 726 additions and 778 deletions
|
@ -6,6 +6,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Added links instead of imperative navigation with onClick - #1969 by @taniotanio7
|
||||
- Fixed clearing attribute values - #2047 by @witoszekdev
|
||||
- Fixed EditorJS integration in RichTextEditor input - #2052 by @witoszekdev
|
||||
- Improvements to the app list page: added toggle and permision preview - #2035 by @witoszekdev
|
||||
- Added links to table pagination buttons - #2063 by @witoszekdev
|
||||
- Using push instead of replace to history stack for pagination navigation - #2063 by @witoszekdev
|
||||
|
||||
|
|
|
@ -86,6 +86,9 @@
|
|||
"context": "error message",
|
||||
"string": "This attribute is already assigned."
|
||||
},
|
||||
"+iV0gu": {
|
||||
"string": "Internal Apps"
|
||||
},
|
||||
"+iVKR1": {
|
||||
"context": "assign attribute value button",
|
||||
"string": "Assign value"
|
||||
|
@ -1658,6 +1661,10 @@
|
|||
"BtErCZ": {
|
||||
"string": "Search Plugins..."
|
||||
},
|
||||
"BvmnJq": {
|
||||
"context": "section header",
|
||||
"string": "Third Party Apps"
|
||||
},
|
||||
"Bx367s": {
|
||||
"context": "product is hidden",
|
||||
"string": "Hidden"
|
||||
|
@ -2760,10 +2767,6 @@
|
|||
"context": "filtering option",
|
||||
"string": "All Warehouses"
|
||||
},
|
||||
"JufWFT": {
|
||||
"context": "app installation error",
|
||||
"string": "There was a problem during installation"
|
||||
},
|
||||
"Jwuu4X": {
|
||||
"context": "select product informations to be exported",
|
||||
"string": "Information exported:"
|
||||
|
@ -3930,10 +3933,6 @@
|
|||
"context": "order subtotal price",
|
||||
"string": "Subtotal"
|
||||
},
|
||||
"TBaMo2": {
|
||||
"context": "about app",
|
||||
"string": "About"
|
||||
},
|
||||
"TC/EOG": {
|
||||
"context": "status section title",
|
||||
"string": "Status in channel"
|
||||
|
@ -4596,6 +4595,10 @@
|
|||
"context": "filters error messages value required",
|
||||
"string": "Choose a value"
|
||||
},
|
||||
"Xl0o2y": {
|
||||
"context": "app installation error",
|
||||
"string": "Problem occured during installation"
|
||||
},
|
||||
"XlPKAR": {
|
||||
"context": "WarehouseSettings private stock description",
|
||||
"string": "If enabled stock in this warehouse won't be shown"
|
||||
|
@ -4809,10 +4812,6 @@
|
|||
"context": "subsection header",
|
||||
"string": "Shipping Address"
|
||||
},
|
||||
"ZeD2TK": {
|
||||
"context": "section header",
|
||||
"string": "Third-party Apps"
|
||||
},
|
||||
"Zg0dRo": {
|
||||
"context": "dialog description",
|
||||
"string": "You have changed customer assigned to this order. What would you like to do with the shipping address?"
|
||||
|
@ -7505,9 +7504,6 @@
|
|||
"context": "section header",
|
||||
"string": "Plugin Information and Status"
|
||||
},
|
||||
"w4R/SO": {
|
||||
"string": "Local Apps"
|
||||
},
|
||||
"w6Gau0": {
|
||||
"context": "deactivate named app",
|
||||
"string": "Are you sure you want to disable {name}? Your data will be kept until you reactivate the app. You will be still billed for the app."
|
||||
|
@ -7630,6 +7626,10 @@
|
|||
"xJQX5t": {
|
||||
"string": "No staff members found"
|
||||
},
|
||||
"xNfh4L": {
|
||||
"context": "app permissions tooltip header",
|
||||
"string": "App permissions"
|
||||
},
|
||||
"xOEZjV": {
|
||||
"context": "attribute's label",
|
||||
"string": "Default Label"
|
||||
|
|
49
src/apps/components/AppPermissions/AppPermissions.tsx
Normal file
49
src/apps/components/AppPermissions/AppPermissions.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { AppPermissionFragment } from "@saleor/graphql";
|
||||
import {
|
||||
IconButton,
|
||||
makeStyles,
|
||||
PermissionsIcon,
|
||||
Tooltip
|
||||
} from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
() => ({
|
||||
list: {
|
||||
margin: 0,
|
||||
paddingLeft: "16px"
|
||||
}
|
||||
}),
|
||||
{ name: "AppPermissions" }
|
||||
);
|
||||
|
||||
interface AppPermissionsProps {
|
||||
permissions: AppPermissionFragment[];
|
||||
}
|
||||
|
||||
export const AppPermissions = ({ permissions }: AppPermissionsProps) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Tooltip
|
||||
header={
|
||||
<FormattedMessage
|
||||
defaultMessage="App permissions"
|
||||
id="xNfh4L"
|
||||
description="app permissions tooltip header"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<ul className={classes.list}>
|
||||
{permissions.map(permission => (
|
||||
<li key={permission.code}>{permission.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<IconButton variant="secondary" color="primary">
|
||||
<PermissionsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
|
@ -4,16 +4,20 @@ import {
|
|||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import ErrorIcon from "@material-ui/icons/Error";
|
||||
import { Button } from "@saleor/components/Button";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { IconButton } from "@saleor/components/IconButton";
|
||||
import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper";
|
||||
import { AppsInstallationsQuery, JobStatusEnum } from "@saleor/graphql";
|
||||
import { DeleteIcon, ResponsiveTable } from "@saleor/macaw-ui";
|
||||
import {
|
||||
DeleteIcon,
|
||||
Indicator,
|
||||
ResponsiveTable,
|
||||
Tooltip,
|
||||
TooltipMountWrapper
|
||||
} from "@saleor/macaw-ui";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
@ -82,17 +86,14 @@ const AppsInProgress: React.FC<AppsInProgressProps> = ({
|
|||
>
|
||||
<Typography variant="body2" className={classes.error}>
|
||||
<FormattedMessage
|
||||
id="JufWFT"
|
||||
defaultMessage="There was a problem during installation"
|
||||
id="Xl0o2y"
|
||||
defaultMessage="Problem occured during installation"
|
||||
description="app installation error"
|
||||
/>
|
||||
<Tooltip
|
||||
title={<Typography variant="body2">{message}</Typography>}
|
||||
classes={{
|
||||
tooltip: classes.customTooltip
|
||||
}}
|
||||
>
|
||||
<ErrorIcon />
|
||||
<Tooltip title={message} variant="error">
|
||||
<TooltipMountWrapper>
|
||||
<Indicator icon="error" />
|
||||
</TooltipMountWrapper>
|
||||
</Tooltip>
|
||||
</Typography>
|
||||
<TableButtonWrapper>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { AppListContext } from "@saleor/apps/context";
|
||||
import {
|
||||
listActionsProps,
|
||||
pageListProps,
|
||||
|
@ -36,6 +37,13 @@ const props: AppsListPageProps = {
|
|||
|
||||
storiesOf("Views / Apps / Apps list", module)
|
||||
.addDecorator(Decorator)
|
||||
.addDecorator(story => (
|
||||
<AppListContext.Provider
|
||||
value={{ activateApp: () => undefined, deactivateApp: () => undefined }}
|
||||
>
|
||||
{story()}
|
||||
</AppListContext.Provider>
|
||||
))
|
||||
.addDecorator(PaginatorContextDecorator)
|
||||
.add("default", () => <AppsListPage {...props} />)
|
||||
.add("loading", () => (
|
||||
|
|
|
@ -10,7 +10,6 @@ import { useIntl } from "react-intl";
|
|||
import AppsInProgress from "../AppsInProgress/AppsInProgress";
|
||||
import CustomApps from "../CustomApps/CustomApps";
|
||||
import InstalledApps from "../InstalledApps/InstalledApps";
|
||||
import Marketplace from "../Marketplace";
|
||||
|
||||
export interface AppsListPageProps extends ListProps {
|
||||
installedAppsList: AppsListQuery["apps"]["edges"];
|
||||
|
@ -43,17 +42,6 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
|
|||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.apps)} />
|
||||
{!!appsInProgress?.length && (
|
||||
<>
|
||||
<AppsInProgress
|
||||
appsList={appsInProgress}
|
||||
disabled={loadingAppsInProgress}
|
||||
onAppInstallRetry={onAppInstallRetry}
|
||||
onRemove={onAppInProgressRemove}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</>
|
||||
)}
|
||||
<InstalledApps
|
||||
appsList={installedAppsList}
|
||||
onRemove={onInstalledAppRemove}
|
||||
|
@ -65,8 +53,17 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
|
|||
getCustomAppHref={getCustomAppHref}
|
||||
onRemove={onCustomAppRemove}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Marketplace />
|
||||
{!!appsInProgress?.length && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<AppsInProgress
|
||||
appsList={appsInProgress}
|
||||
disabled={loadingAppsInProgress}
|
||||
onAppInstallRetry={onAppInstallRetry}
|
||||
onRemove={onAppInProgressRemove}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import {
|
||||
Card,
|
||||
Switch,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import { appDetailsUrl, appUrl } from "@saleor/apps/urls";
|
||||
import { Button } from "@saleor/components/Button";
|
||||
import { useAppListContext } from "@saleor/apps/context";
|
||||
import { appUrl } from "@saleor/apps/urls";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { IconButton } from "@saleor/components/IconButton";
|
||||
import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper";
|
||||
import { TablePaginationWithContext } from "@saleor/components/TablePagination";
|
||||
import TableRowLink from "@saleor/components/TableRowLink";
|
||||
import { AppsListQuery } from "@saleor/graphql";
|
||||
import { AppListItemFragment, AppsListQuery } from "@saleor/graphql";
|
||||
import { DeleteIcon, ResponsiveTable } from "@saleor/macaw-ui";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { ListProps } from "@saleor/types";
|
||||
|
@ -22,14 +21,13 @@ import React from "react";
|
|||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { useStyles } from "../../styles";
|
||||
import { AppPermissions } from "../AppPermissions/AppPermissions";
|
||||
import AppsSkeleton from "../AppsSkeleton";
|
||||
import DeactivatedText from "../DeactivatedText";
|
||||
|
||||
export interface InstalledAppsProps extends ListProps {
|
||||
appsList: AppsListQuery["apps"]["edges"];
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
const numberOfColumns = 2;
|
||||
|
||||
const InstalledApps: React.FC<InstalledAppsProps> = ({
|
||||
appsList,
|
||||
|
@ -41,26 +39,26 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
|
|||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
const { activateApp, deactivateApp } = useAppListContext();
|
||||
|
||||
const getHandleToggle = (app: AppListItemFragment) => () => {
|
||||
if (app.isActive) {
|
||||
deactivateApp(app.id);
|
||||
} else {
|
||||
activateApp(app.id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.apps}>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "ZeD2TK",
|
||||
defaultMessage: "Third-party Apps",
|
||||
id: "BvmnJq",
|
||||
defaultMessage: "Third Party Apps",
|
||||
description: "section header"
|
||||
})}
|
||||
/>
|
||||
<ResponsiveTable>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePaginationWithContext
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
appsList,
|
||||
|
@ -75,11 +73,6 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
|
|||
<span data-tc="name" className={classes.appName}>
|
||||
{app.node.name}
|
||||
</span>
|
||||
{!app.node.isActive && (
|
||||
<div className={classes.statusWrapper}>
|
||||
<DeactivatedText />
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
{app.node.appUrl && (
|
||||
|
@ -91,14 +84,12 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
|
|||
</Typography>
|
||||
)}
|
||||
<TableButtonWrapper>
|
||||
<Button href={appDetailsUrl(app.node.id)}>
|
||||
<FormattedMessage
|
||||
id="TBaMo2"
|
||||
defaultMessage="About"
|
||||
description="about app"
|
||||
/>
|
||||
</Button>
|
||||
<Switch
|
||||
checked={app.node.isActive}
|
||||
onChange={getHandleToggle(app.node)}
|
||||
/>
|
||||
</TableButtonWrapper>
|
||||
<AppPermissions permissions={app.node.permissions} />
|
||||
<TableButtonWrapper>
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./Marketplace";
|
||||
export { default } from "./Marketplace";
|
21
src/apps/context.ts
Normal file
21
src/apps/context.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import React from "react";
|
||||
|
||||
export interface AppListContextValues {
|
||||
activateApp: (appId: string) => void;
|
||||
deactivateApp: (appId: string) => void;
|
||||
}
|
||||
|
||||
export const AppListContext = React.createContext<
|
||||
AppListContextValues | undefined
|
||||
>(undefined);
|
||||
|
||||
export const useAppListContext = () => {
|
||||
const context = React.useContext(AppListContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useAppListContext must be used within a AppListContext");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
|
@ -17,7 +17,14 @@ export const appsList: AppsListQuery["apps"]["edges"] = [
|
|||
isActive: true,
|
||||
name: "app",
|
||||
type: AppTypeEnum.THIRDPARTY,
|
||||
appUrl: null
|
||||
appUrl: null,
|
||||
permissions: [
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_USERS,
|
||||
name: "Manage customers."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -28,7 +35,19 @@ export const appsList: AppsListQuery["apps"]["edges"] = [
|
|||
isActive: false,
|
||||
name: "app1",
|
||||
type: AppTypeEnum.THIRDPARTY,
|
||||
appUrl: "http://localhost:3000"
|
||||
appUrl: "http://localhost:3000",
|
||||
permissions: [
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_ORDERS,
|
||||
name: "Manage orders."
|
||||
},
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_USERS,
|
||||
name: "Manage customers."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -42,7 +61,19 @@ export const customAppsList: AppsListQuery["apps"]["edges"] = [
|
|||
isActive: true,
|
||||
name: "app custom",
|
||||
type: AppTypeEnum.LOCAL,
|
||||
appUrl: null
|
||||
appUrl: null,
|
||||
permissions: [
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_ORDERS,
|
||||
name: "Manage orders."
|
||||
},
|
||||
{
|
||||
__typename: "Permission",
|
||||
code: PermissionEnum.MANAGE_USERS,
|
||||
name: "Manage customers."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -26,11 +26,7 @@ export const appsList = gql`
|
|||
totalCount
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
isActive
|
||||
type
|
||||
appUrl
|
||||
...AppListItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,13 +75,12 @@ export const useStyles = makeStyles(
|
|||
padding: "0!important"
|
||||
},
|
||||
error: {
|
||||
"& svg": {
|
||||
bottom: theme.spacing(0.2),
|
||||
marginLeft: theme.spacing(0.6),
|
||||
position: "relative"
|
||||
"& button": {
|
||||
marginLeft: theme.spacing(0.6)
|
||||
},
|
||||
color: theme.palette.error.main,
|
||||
margin: theme.spacing(0, 1, 0.7, 0)
|
||||
marginRight: theme.spacing(1),
|
||||
alignItems: "flex-end"
|
||||
},
|
||||
headerLinkContainer: {
|
||||
"& svg": {
|
||||
|
|
|
@ -5,7 +5,12 @@ import { ActiveTab, Dialog, Pagination, SingleAction } from "../types";
|
|||
|
||||
export const MANIFEST_ATTR = "manifestUrl";
|
||||
|
||||
export type AppListUrlDialog = "remove" | "remove-app" | "remove-custom-app";
|
||||
export type AppListUrlDialog =
|
||||
| "remove"
|
||||
| "remove-app"
|
||||
| "remove-custom-app"
|
||||
| "app-activate"
|
||||
| "app-deactivate";
|
||||
|
||||
export type AppDetailsUrlDialog = "app-activate" | "app-deactivate";
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { useApolloClient } from "@apollo/client";
|
||||
import AppActivateDialog from "@saleor/apps/components/AppActivateDialog";
|
||||
import AppDeactivateDialog from "@saleor/apps/components/AppDeactivateDialog";
|
||||
import { AppListContext, AppListContextValues } from "@saleor/apps/context";
|
||||
import {
|
||||
AppDeleteFailedInstallationMutation,
|
||||
AppDeleteMutation,
|
||||
AppsInstallationsQuery,
|
||||
AppsListQuery,
|
||||
AppSortField,
|
||||
AppTypeEnum,
|
||||
JobStatusEnum,
|
||||
OrderDirection,
|
||||
useAppActivateMutation,
|
||||
useAppDeactivateMutation,
|
||||
useAppDeleteFailedInstallationMutation,
|
||||
useAppDeleteMutation,
|
||||
useAppRetryInstallMutation,
|
||||
|
@ -23,7 +26,6 @@ import usePaginator, {
|
|||
PaginatorContext
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import getAppErrorMessage from "@saleor/utils/errors/app";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -131,17 +133,36 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
|||
};
|
||||
const [retryInstallApp] = useAppRetryInstallMutation({
|
||||
onCompleted: data => {
|
||||
const errors = data.appRetryInstall.errors;
|
||||
if (!errors.length) {
|
||||
if (!data?.appRetryInstall?.errors?.length) {
|
||||
const appInstallation = data.appRetryInstall.appInstallation;
|
||||
setActiveInstallations(installations => [
|
||||
...installations,
|
||||
{ id: appInstallation.id, name: appInstallation.appName }
|
||||
]);
|
||||
} else {
|
||||
errors.forEach(error =>
|
||||
notify({ status: "error", text: getAppErrorMessage(error, intl) })
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
const [activateApp, activateAppResult] = useAppActivateMutation({
|
||||
onCompleted: data => {
|
||||
if (!data?.appActivate?.errors?.length) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(messages.appActivated)
|
||||
});
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({
|
||||
onCompleted: data => {
|
||||
if (!data?.appDeactivate?.errors?.length) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(messages.appDeactivated)
|
||||
});
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -150,30 +171,18 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
|||
AppListUrlQueryParams
|
||||
>(navigate, appsListUrl, params);
|
||||
|
||||
const onAppRemove = (data: AppDeleteMutation) => {
|
||||
const errors = data.appDelete.errors;
|
||||
if (errors.length === 0) {
|
||||
if (data.appDelete.app.type === AppTypeEnum.LOCAL) {
|
||||
customAppsRefetch();
|
||||
} else {
|
||||
refetch();
|
||||
}
|
||||
closeModal();
|
||||
refetchExtensionList();
|
||||
removeAppNotify();
|
||||
} else {
|
||||
errors.forEach(error =>
|
||||
notify({
|
||||
status: "error",
|
||||
text: getAppErrorMessage(error, intl)
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const [deleteApp, deleteAppOpts] = useAppDeleteMutation({
|
||||
onCompleted: data => {
|
||||
onAppRemove(data);
|
||||
if (!data?.appDelete?.errors?.length) {
|
||||
if (data.appDelete.app.type === AppTypeEnum.LOCAL) {
|
||||
customAppsRefetch();
|
||||
} else {
|
||||
refetch();
|
||||
}
|
||||
closeModal();
|
||||
refetchExtensionList();
|
||||
removeAppNotify();
|
||||
}
|
||||
}
|
||||
});
|
||||
const [
|
||||
|
@ -181,7 +190,11 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
|||
deleteInProgressAppOpts
|
||||
] = useAppDeleteFailedInstallationMutation({
|
||||
onCompleted: data => {
|
||||
onAppInProgressRemove(data);
|
||||
if (!data?.appDeleteFailedInstallation?.errors?.length) {
|
||||
removeAppNotify();
|
||||
appsInProgressRefetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -256,77 +269,92 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
|
|||
});
|
||||
};
|
||||
|
||||
const onAppInProgressRemove = (data: AppDeleteFailedInstallationMutation) => {
|
||||
const errors = data.appDeleteFailedInstallation.errors;
|
||||
if (errors.length === 0) {
|
||||
removeAppNotify();
|
||||
appsInProgressRefetch();
|
||||
closeModal();
|
||||
} else {
|
||||
errors.forEach(error =>
|
||||
notify({
|
||||
status: "error",
|
||||
text: getAppErrorMessage(error, intl)
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleActivateAppConfirm = () =>
|
||||
activateApp({ variables: { id: params.id } });
|
||||
|
||||
const handleDeactivateAppConfirm = () =>
|
||||
deactivateApp({ variables: { id: params.id } });
|
||||
|
||||
const onAppInstallRetry = (id: string) =>
|
||||
retryInstallApp({ variables: { id } });
|
||||
|
||||
const installedApps = data?.apps?.edges;
|
||||
const customApps = customAppsData?.apps?.edges;
|
||||
|
||||
const context: AppListContextValues = React.useMemo(
|
||||
() => ({
|
||||
activateApp: id => openModal("app-activate", { id }),
|
||||
deactivateApp: id => openModal("app-deactivate", { id })
|
||||
}),
|
||||
[activateApp, deactivateApp]
|
||||
);
|
||||
|
||||
return (
|
||||
<PaginatorContext.Provider value={paginationValues}>
|
||||
<AppDeleteDialog
|
||||
confirmButtonState={deleteAppOpts.status}
|
||||
name={getCurrentAppName(
|
||||
params.id,
|
||||
action === "remove-app" ? installedApps : customApps
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveConfirm}
|
||||
type={action === "remove-app" ? "EXTERNAL" : "CUSTOM"}
|
||||
open={action === "remove-app" || action === "remove-custom-app"}
|
||||
/>
|
||||
<AppInProgressDeleteDialog
|
||||
confirmButtonState={deleteInProgressAppOpts.status}
|
||||
name={getAppInProgressName(
|
||||
params.id,
|
||||
appsInProgressData?.appsInstallations
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveInProgressConfirm}
|
||||
open={action === "remove"}
|
||||
/>
|
||||
<AppsListPage
|
||||
installedAppsList={installedApps}
|
||||
customAppsList={customApps}
|
||||
appsInProgressList={appsInProgressData}
|
||||
loadingAppsInProgress={loadingAppsInProgress}
|
||||
disabled={loading || customAppsLoading}
|
||||
settings={settings}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onAppInstallRetry={onAppInstallRetry}
|
||||
getCustomAppHref={id => customAppUrl(id)}
|
||||
onInstalledAppRemove={id =>
|
||||
openModal("remove-app", {
|
||||
id
|
||||
})
|
||||
}
|
||||
onCustomAppRemove={id =>
|
||||
openModal("remove-custom-app", {
|
||||
id
|
||||
})
|
||||
}
|
||||
onAppInProgressRemove={id =>
|
||||
openModal("remove", {
|
||||
id
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PaginatorContext.Provider>
|
||||
<AppListContext.Provider value={context}>
|
||||
<PaginatorContext.Provider value={paginationValues}>
|
||||
<AppDeleteDialog
|
||||
confirmButtonState={deleteAppOpts.status}
|
||||
name={getCurrentAppName(
|
||||
params.id,
|
||||
action === "remove-app" ? installedApps : customApps
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveConfirm}
|
||||
type={action === "remove-app" ? "EXTERNAL" : "CUSTOM"}
|
||||
open={action === "remove-app" || action === "remove-custom-app"}
|
||||
/>
|
||||
<AppActivateDialog
|
||||
confirmButtonState={activateAppResult.status}
|
||||
name={getCurrentAppName(params.id, installedApps)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleActivateAppConfirm}
|
||||
open={params.action === "app-activate"}
|
||||
/>
|
||||
<AppDeactivateDialog
|
||||
confirmButtonState={deactivateAppResult.status}
|
||||
name={getCurrentAppName(params.id, installedApps)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleDeactivateAppConfirm}
|
||||
open={params.action === "app-deactivate"}
|
||||
/>
|
||||
<AppInProgressDeleteDialog
|
||||
confirmButtonState={deleteInProgressAppOpts.status}
|
||||
name={getAppInProgressName(
|
||||
params.id,
|
||||
appsInProgressData?.appsInstallations
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={handleRemoveInProgressConfirm}
|
||||
open={action === "remove"}
|
||||
/>
|
||||
<AppsListPage
|
||||
installedAppsList={installedApps}
|
||||
customAppsList={customApps}
|
||||
appsInProgressList={appsInProgressData}
|
||||
loadingAppsInProgress={loadingAppsInProgress}
|
||||
disabled={loading || customAppsLoading}
|
||||
settings={settings}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onAppInstallRetry={onAppInstallRetry}
|
||||
getCustomAppHref={id => customAppUrl(id)}
|
||||
onInstalledAppRemove={id =>
|
||||
openModal("remove-app", {
|
||||
id
|
||||
})
|
||||
}
|
||||
onCustomAppRemove={id =>
|
||||
openModal("remove-custom-app", {
|
||||
id
|
||||
})
|
||||
}
|
||||
onAppInProgressRemove={id =>
|
||||
openModal("remove", {
|
||||
id
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PaginatorContext.Provider>
|
||||
</AppListContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -20,5 +20,15 @@ export const messages = defineMessages({
|
|||
id: "5t/4um",
|
||||
defaultMessage: "Couldn’t Install {name}",
|
||||
description: "message title"
|
||||
},
|
||||
appActivated: {
|
||||
id: "D/+84n",
|
||||
defaultMessage: "App activated",
|
||||
description: "snackbar text"
|
||||
},
|
||||
appDeactivated: {
|
||||
id: "USO8PB",
|
||||
defaultMessage: "App deactivated",
|
||||
description: "snackbar text"
|
||||
}
|
||||
});
|
||||
|
|
|
@ -54,7 +54,7 @@ export interface AppListViewSettings {
|
|||
|
||||
export const defaultListSettings: AppListViewSettings = {
|
||||
[ListViews.APPS_LIST]: {
|
||||
rowNumber: 10
|
||||
rowNumber: 100
|
||||
},
|
||||
[ListViews.ATTRIBUTE_VALUE_LIST]: {
|
||||
rowNumber: 10
|
||||
|
|
|
@ -31,3 +31,23 @@ export const appFragment = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const appListItemFragment = gql`
|
||||
fragment AppListItem on App {
|
||||
id
|
||||
name
|
||||
isActive
|
||||
type
|
||||
appUrl
|
||||
permissions {
|
||||
...AppPermission
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const appPermissionFragment = gql`
|
||||
fragment AppPermission on Permission {
|
||||
name
|
||||
code
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -47,6 +47,24 @@ export const AppFragmentDoc = gql`
|
|||
}
|
||||
}
|
||||
${WebhookFragmentDoc}`;
|
||||
export const AppPermissionFragmentDoc = gql`
|
||||
fragment AppPermission on Permission {
|
||||
name
|
||||
code
|
||||
}
|
||||
`;
|
||||
export const AppListItemFragmentDoc = gql`
|
||||
fragment AppListItem on App {
|
||||
id
|
||||
name
|
||||
isActive
|
||||
type
|
||||
appUrl
|
||||
permissions {
|
||||
...AppPermission
|
||||
}
|
||||
}
|
||||
${AppPermissionFragmentDoc}`;
|
||||
export const AttributeFragmentDoc = gql`
|
||||
fragment Attribute on Attribute {
|
||||
id
|
||||
|
@ -3135,16 +3153,12 @@ export const AppsListDocument = gql`
|
|||
totalCount
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
isActive
|
||||
type
|
||||
appUrl
|
||||
...AppListItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
${AppListItemFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useAppsListQuery__
|
||||
|
|
|
@ -5251,7 +5251,7 @@ export type AppsListQueryVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type AppsListQuery = { __typename: 'Query', apps: { __typename: 'AppCountableConnection', totalCount: number | null, pageInfo: { __typename: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null, endCursor: string | null }, edges: Array<{ __typename: 'AppCountableEdge', node: { __typename: 'App', id: string, name: string | null, isActive: boolean | null, type: AppTypeEnum | null, appUrl: string | null } }> } | null };
|
||||
export type AppsListQuery = { __typename: 'Query', apps: { __typename: 'AppCountableConnection', totalCount: number | null, pageInfo: { __typename: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null, endCursor: string | null }, edges: Array<{ __typename: 'AppCountableEdge', node: { __typename: 'App', id: string, name: string | null, isActive: boolean | null, type: AppTypeEnum | null, appUrl: string | null, permissions: Array<{ __typename: 'Permission', name: string, code: PermissionEnum }> | null } }> } | null };
|
||||
|
||||
export type AppsInstallationsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
@ -5909,6 +5909,10 @@ export type AddressFragment = { __typename: 'Address', city: string, cityArea: s
|
|||
|
||||
export type AppFragment = { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, tokens: Array<{ __typename: 'AppToken', authToken: string | null, id: string, name: string | null }> | null, webhooks: Array<{ __typename: 'Webhook', id: string, name: string, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null };
|
||||
|
||||
export type AppListItemFragment = { __typename: 'App', id: string, name: string | null, isActive: boolean | null, type: AppTypeEnum | null, appUrl: string | null, permissions: Array<{ __typename: 'Permission', name: string, code: PermissionEnum }> | null };
|
||||
|
||||
export type AppPermissionFragment = { __typename: 'Permission', name: string, code: PermissionEnum };
|
||||
|
||||
export type AttributeValueFragment = { __typename: 'AttributeValue', id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null };
|
||||
|
||||
export type AttributeValueDetailsFragment = { __typename: 'AttributeValue', richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null };
|
||||
|
|
|
@ -19,8 +19,8 @@ export const commonMessages = defineMessages({
|
|||
defaultMessage: "Channel"
|
||||
},
|
||||
customApps: {
|
||||
id: "w4R/SO",
|
||||
defaultMessage: "Local Apps"
|
||||
id: "+iV0gu",
|
||||
defaultMessage: "Internal Apps"
|
||||
},
|
||||
dashboard: {
|
||||
id: "hzSNj4",
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue