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:
Jonatan Witoszek 2022-05-31 17:18:15 +02:00 committed by GitHub
parent 1a19289e43
commit 5138608f86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 726 additions and 778 deletions

View file

@ -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 - Added links instead of imperative navigation with onClick - #1969 by @taniotanio7
- Fixed clearing attribute values - #2047 by @witoszekdev - Fixed clearing attribute values - #2047 by @witoszekdev
- Fixed EditorJS integration in RichTextEditor input - #2052 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 - Added links to table pagination buttons - #2063 by @witoszekdev
- Using push instead of replace to history stack for pagination navigation - #2063 by @witoszekdev - Using push instead of replace to history stack for pagination navigation - #2063 by @witoszekdev

View file

@ -86,6 +86,9 @@
"context": "error message", "context": "error message",
"string": "This attribute is already assigned." "string": "This attribute is already assigned."
}, },
"+iV0gu": {
"string": "Internal Apps"
},
"+iVKR1": { "+iVKR1": {
"context": "assign attribute value button", "context": "assign attribute value button",
"string": "Assign value" "string": "Assign value"
@ -1658,6 +1661,10 @@
"BtErCZ": { "BtErCZ": {
"string": "Search Plugins..." "string": "Search Plugins..."
}, },
"BvmnJq": {
"context": "section header",
"string": "Third Party Apps"
},
"Bx367s": { "Bx367s": {
"context": "product is hidden", "context": "product is hidden",
"string": "Hidden" "string": "Hidden"
@ -2760,10 +2767,6 @@
"context": "filtering option", "context": "filtering option",
"string": "All Warehouses" "string": "All Warehouses"
}, },
"JufWFT": {
"context": "app installation error",
"string": "There was a problem during installation"
},
"Jwuu4X": { "Jwuu4X": {
"context": "select product informations to be exported", "context": "select product informations to be exported",
"string": "Information exported:" "string": "Information exported:"
@ -3930,10 +3933,6 @@
"context": "order subtotal price", "context": "order subtotal price",
"string": "Subtotal" "string": "Subtotal"
}, },
"TBaMo2": {
"context": "about app",
"string": "About"
},
"TC/EOG": { "TC/EOG": {
"context": "status section title", "context": "status section title",
"string": "Status in channel" "string": "Status in channel"
@ -4596,6 +4595,10 @@
"context": "filters error messages value required", "context": "filters error messages value required",
"string": "Choose a value" "string": "Choose a value"
}, },
"Xl0o2y": {
"context": "app installation error",
"string": "Problem occured during installation"
},
"XlPKAR": { "XlPKAR": {
"context": "WarehouseSettings private stock description", "context": "WarehouseSettings private stock description",
"string": "If enabled stock in this warehouse won't be shown" "string": "If enabled stock in this warehouse won't be shown"
@ -4809,10 +4812,6 @@
"context": "subsection header", "context": "subsection header",
"string": "Shipping Address" "string": "Shipping Address"
}, },
"ZeD2TK": {
"context": "section header",
"string": "Third-party Apps"
},
"Zg0dRo": { "Zg0dRo": {
"context": "dialog description", "context": "dialog description",
"string": "You have changed customer assigned to this order. What would you like to do with the shipping address?" "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", "context": "section header",
"string": "Plugin Information and Status" "string": "Plugin Information and Status"
}, },
"w4R/SO": {
"string": "Local Apps"
},
"w6Gau0": { "w6Gau0": {
"context": "deactivate named app", "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." "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": { "xJQX5t": {
"string": "No staff members found" "string": "No staff members found"
}, },
"xNfh4L": {
"context": "app permissions tooltip header",
"string": "App permissions"
},
"xOEZjV": { "xOEZjV": {
"context": "attribute's label", "context": "attribute's label",
"string": "Default Label" "string": "Default Label"

View 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>
);
};

View file

@ -4,16 +4,20 @@ import {
TableBody, TableBody,
TableCell, TableCell,
TableRow, TableRow,
Tooltip,
Typography Typography
} from "@material-ui/core"; } from "@material-ui/core";
import ErrorIcon from "@material-ui/icons/Error";
import { Button } from "@saleor/components/Button"; import { Button } from "@saleor/components/Button";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { IconButton } from "@saleor/components/IconButton"; import { IconButton } from "@saleor/components/IconButton";
import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper"; import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper";
import { AppsInstallationsQuery, JobStatusEnum } from "@saleor/graphql"; 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 { renderCollection } from "@saleor/misc";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
@ -82,17 +86,14 @@ const AppsInProgress: React.FC<AppsInProgressProps> = ({
> >
<Typography variant="body2" className={classes.error}> <Typography variant="body2" className={classes.error}>
<FormattedMessage <FormattedMessage
id="JufWFT" id="Xl0o2y"
defaultMessage="There was a problem during installation" defaultMessage="Problem occured during installation"
description="app installation error" description="app installation error"
/> />
<Tooltip <Tooltip title={message} variant="error">
title={<Typography variant="body2">{message}</Typography>} <TooltipMountWrapper>
classes={{ <Indicator icon="error" />
tooltip: classes.customTooltip </TooltipMountWrapper>
}}
>
<ErrorIcon />
</Tooltip> </Tooltip>
</Typography> </Typography>
<TableButtonWrapper> <TableButtonWrapper>

View file

@ -1,3 +1,4 @@
import { AppListContext } from "@saleor/apps/context";
import { import {
listActionsProps, listActionsProps,
pageListProps, pageListProps,
@ -36,6 +37,13 @@ const props: AppsListPageProps = {
storiesOf("Views / Apps / Apps list", module) storiesOf("Views / Apps / Apps list", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.addDecorator(story => (
<AppListContext.Provider
value={{ activateApp: () => undefined, deactivateApp: () => undefined }}
>
{story()}
</AppListContext.Provider>
))
.addDecorator(PaginatorContextDecorator) .addDecorator(PaginatorContextDecorator)
.add("default", () => <AppsListPage {...props} />) .add("default", () => <AppsListPage {...props} />)
.add("loading", () => ( .add("loading", () => (

View file

@ -10,7 +10,6 @@ import { useIntl } from "react-intl";
import AppsInProgress from "../AppsInProgress/AppsInProgress"; import AppsInProgress from "../AppsInProgress/AppsInProgress";
import CustomApps from "../CustomApps/CustomApps"; import CustomApps from "../CustomApps/CustomApps";
import InstalledApps from "../InstalledApps/InstalledApps"; import InstalledApps from "../InstalledApps/InstalledApps";
import Marketplace from "../Marketplace";
export interface AppsListPageProps extends ListProps { export interface AppsListPageProps extends ListProps {
installedAppsList: AppsListQuery["apps"]["edges"]; installedAppsList: AppsListQuery["apps"]["edges"];
@ -43,17 +42,6 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
return ( return (
<Container> <Container>
<PageHeader title={intl.formatMessage(sectionNames.apps)} /> <PageHeader title={intl.formatMessage(sectionNames.apps)} />
{!!appsInProgress?.length && (
<>
<AppsInProgress
appsList={appsInProgress}
disabled={loadingAppsInProgress}
onAppInstallRetry={onAppInstallRetry}
onRemove={onAppInProgressRemove}
/>
<CardSpacer />
</>
)}
<InstalledApps <InstalledApps
appsList={installedAppsList} appsList={installedAppsList}
onRemove={onInstalledAppRemove} onRemove={onInstalledAppRemove}
@ -65,8 +53,17 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
getCustomAppHref={getCustomAppHref} getCustomAppHref={getCustomAppHref}
onRemove={onCustomAppRemove} onRemove={onCustomAppRemove}
/> />
<CardSpacer /> {!!appsInProgress?.length && (
<Marketplace /> <>
<CardSpacer />
<AppsInProgress
appsList={appsInProgress}
disabled={loadingAppsInProgress}
onAppInstallRetry={onAppInstallRetry}
onRemove={onAppInProgressRemove}
/>
</>
)}
</Container> </Container>
); );
}; };

View file

@ -1,19 +1,18 @@
import { import {
Card, Card,
Switch,
TableBody, TableBody,
TableCell, TableCell,
TableFooter,
TableRow, TableRow,
Typography Typography
} from "@material-ui/core"; } from "@material-ui/core";
import { appDetailsUrl, appUrl } from "@saleor/apps/urls"; import { useAppListContext } from "@saleor/apps/context";
import { Button } from "@saleor/components/Button"; import { appUrl } from "@saleor/apps/urls";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { IconButton } from "@saleor/components/IconButton"; import { IconButton } from "@saleor/components/IconButton";
import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper"; import { TableButtonWrapper } from "@saleor/components/TableButtonWrapper/TableButtonWrapper";
import { TablePaginationWithContext } from "@saleor/components/TablePagination";
import TableRowLink from "@saleor/components/TableRowLink"; 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 { DeleteIcon, ResponsiveTable } from "@saleor/macaw-ui";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import { ListProps } from "@saleor/types"; import { ListProps } from "@saleor/types";
@ -22,14 +21,13 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { useStyles } from "../../styles"; import { useStyles } from "../../styles";
import { AppPermissions } from "../AppPermissions/AppPermissions";
import AppsSkeleton from "../AppsSkeleton"; import AppsSkeleton from "../AppsSkeleton";
import DeactivatedText from "../DeactivatedText";
export interface InstalledAppsProps extends ListProps { export interface InstalledAppsProps extends ListProps {
appsList: AppsListQuery["apps"]["edges"]; appsList: AppsListQuery["apps"]["edges"];
onRemove: (id: string) => void; onRemove: (id: string) => void;
} }
const numberOfColumns = 2;
const InstalledApps: React.FC<InstalledAppsProps> = ({ const InstalledApps: React.FC<InstalledAppsProps> = ({
appsList, appsList,
@ -41,26 +39,26 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const classes = useStyles(props); const classes = useStyles(props);
const { activateApp, deactivateApp } = useAppListContext();
const getHandleToggle = (app: AppListItemFragment) => () => {
if (app.isActive) {
deactivateApp(app.id);
} else {
activateApp(app.id);
}
};
return ( return (
<Card className={classes.apps}> <Card className={classes.apps}>
<CardTitle <CardTitle
title={intl.formatMessage({ title={intl.formatMessage({
id: "ZeD2TK", id: "BvmnJq",
defaultMessage: "Third-party Apps", defaultMessage: "Third Party Apps",
description: "section header" description: "section header"
})} })}
/> />
<ResponsiveTable> <ResponsiveTable>
<TableFooter>
<TableRow>
<TablePaginationWithContext
colSpan={numberOfColumns}
settings={settings}
onUpdateListSettings={onUpdateListSettings}
/>
</TableRow>
</TableFooter>
<TableBody> <TableBody>
{renderCollection( {renderCollection(
appsList, appsList,
@ -75,11 +73,6 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
<span data-tc="name" className={classes.appName}> <span data-tc="name" className={classes.appName}>
{app.node.name} {app.node.name}
</span> </span>
{!app.node.isActive && (
<div className={classes.statusWrapper}>
<DeactivatedText />
</div>
)}
</TableCell> </TableCell>
<TableCell className={classes.colAction}> <TableCell className={classes.colAction}>
{app.node.appUrl && ( {app.node.appUrl && (
@ -91,14 +84,12 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
</Typography> </Typography>
)} )}
<TableButtonWrapper> <TableButtonWrapper>
<Button href={appDetailsUrl(app.node.id)}> <Switch
<FormattedMessage checked={app.node.isActive}
id="TBaMo2" onChange={getHandleToggle(app.node)}
defaultMessage="About" />
description="about app"
/>
</Button>
</TableButtonWrapper> </TableButtonWrapper>
<AppPermissions permissions={app.node.permissions} />
<TableButtonWrapper> <TableButtonWrapper>
<IconButton <IconButton
variant="secondary" variant="secondary"

View file

@ -1,2 +0,0 @@
export * from "./Marketplace";
export { default } from "./Marketplace";

21
src/apps/context.ts Normal file
View 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;
};

View file

@ -17,7 +17,14 @@ export const appsList: AppsListQuery["apps"]["edges"] = [
isActive: true, isActive: true,
name: "app", name: "app",
type: AppTypeEnum.THIRDPARTY, 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, isActive: false,
name: "app1", name: "app1",
type: AppTypeEnum.THIRDPARTY, 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, isActive: true,
name: "app custom", name: "app custom",
type: AppTypeEnum.LOCAL, 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."
}
]
} }
} }
]; ];

View file

@ -26,11 +26,7 @@ export const appsList = gql`
totalCount totalCount
edges { edges {
node { node {
id ...AppListItem
name
isActive
type
appUrl
} }
} }
} }

View file

@ -75,13 +75,12 @@ export const useStyles = makeStyles(
padding: "0!important" padding: "0!important"
}, },
error: { error: {
"& svg": { "& button": {
bottom: theme.spacing(0.2), marginLeft: theme.spacing(0.6)
marginLeft: theme.spacing(0.6),
position: "relative"
}, },
color: theme.palette.error.main, color: theme.palette.error.main,
margin: theme.spacing(0, 1, 0.7, 0) marginRight: theme.spacing(1),
alignItems: "flex-end"
}, },
headerLinkContainer: { headerLinkContainer: {
"& svg": { "& svg": {

View file

@ -5,7 +5,12 @@ import { ActiveTab, Dialog, Pagination, SingleAction } from "../types";
export const MANIFEST_ATTR = "manifestUrl"; 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"; export type AppDetailsUrlDialog = "app-activate" | "app-deactivate";

View file

@ -1,13 +1,16 @@
import { useApolloClient } from "@apollo/client"; 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 { import {
AppDeleteFailedInstallationMutation,
AppDeleteMutation,
AppsInstallationsQuery, AppsInstallationsQuery,
AppsListQuery, AppsListQuery,
AppSortField, AppSortField,
AppTypeEnum, AppTypeEnum,
JobStatusEnum, JobStatusEnum,
OrderDirection, OrderDirection,
useAppActivateMutation,
useAppDeactivateMutation,
useAppDeleteFailedInstallationMutation, useAppDeleteFailedInstallationMutation,
useAppDeleteMutation, useAppDeleteMutation,
useAppRetryInstallMutation, useAppRetryInstallMutation,
@ -23,7 +26,6 @@ import usePaginator, {
PaginatorContext PaginatorContext
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import getAppErrorMessage from "@saleor/utils/errors/app";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -131,17 +133,36 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
}; };
const [retryInstallApp] = useAppRetryInstallMutation({ const [retryInstallApp] = useAppRetryInstallMutation({
onCompleted: data => { onCompleted: data => {
const errors = data.appRetryInstall.errors; if (!data?.appRetryInstall?.errors?.length) {
if (!errors.length) {
const appInstallation = data.appRetryInstall.appInstallation; const appInstallation = data.appRetryInstall.appInstallation;
setActiveInstallations(installations => [ setActiveInstallations(installations => [
...installations, ...installations,
{ id: appInstallation.id, name: appInstallation.appName } { 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 AppListUrlQueryParams
>(navigate, appsListUrl, params); >(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({ const [deleteApp, deleteAppOpts] = useAppDeleteMutation({
onCompleted: data => { onCompleted: data => {
onAppRemove(data); if (!data?.appDelete?.errors?.length) {
if (data.appDelete.app.type === AppTypeEnum.LOCAL) {
customAppsRefetch();
} else {
refetch();
}
closeModal();
refetchExtensionList();
removeAppNotify();
}
} }
}); });
const [ const [
@ -181,7 +190,11 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
deleteInProgressAppOpts deleteInProgressAppOpts
] = useAppDeleteFailedInstallationMutation({ ] = useAppDeleteFailedInstallationMutation({
onCompleted: data => { 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 handleActivateAppConfirm = () =>
const errors = data.appDeleteFailedInstallation.errors; activateApp({ variables: { id: params.id } });
if (errors.length === 0) {
removeAppNotify(); const handleDeactivateAppConfirm = () =>
appsInProgressRefetch(); deactivateApp({ variables: { id: params.id } });
closeModal();
} else {
errors.forEach(error =>
notify({
status: "error",
text: getAppErrorMessage(error, intl)
})
);
}
};
const onAppInstallRetry = (id: string) => const onAppInstallRetry = (id: string) =>
retryInstallApp({ variables: { id } }); retryInstallApp({ variables: { id } });
const installedApps = data?.apps?.edges; const installedApps = data?.apps?.edges;
const customApps = customAppsData?.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 ( return (
<PaginatorContext.Provider value={paginationValues}> <AppListContext.Provider value={context}>
<AppDeleteDialog <PaginatorContext.Provider value={paginationValues}>
confirmButtonState={deleteAppOpts.status} <AppDeleteDialog
name={getCurrentAppName( confirmButtonState={deleteAppOpts.status}
params.id, name={getCurrentAppName(
action === "remove-app" ? installedApps : customApps params.id,
)} action === "remove-app" ? installedApps : customApps
onClose={closeModal} )}
onConfirm={handleRemoveConfirm} onClose={closeModal}
type={action === "remove-app" ? "EXTERNAL" : "CUSTOM"} onConfirm={handleRemoveConfirm}
open={action === "remove-app" || action === "remove-custom-app"} type={action === "remove-app" ? "EXTERNAL" : "CUSTOM"}
/> open={action === "remove-app" || action === "remove-custom-app"}
<AppInProgressDeleteDialog />
confirmButtonState={deleteInProgressAppOpts.status} <AppActivateDialog
name={getAppInProgressName( confirmButtonState={activateAppResult.status}
params.id, name={getCurrentAppName(params.id, installedApps)}
appsInProgressData?.appsInstallations onClose={closeModal}
)} onConfirm={handleActivateAppConfirm}
onClose={closeModal} open={params.action === "app-activate"}
onConfirm={handleRemoveInProgressConfirm} />
open={action === "remove"} <AppDeactivateDialog
/> confirmButtonState={deactivateAppResult.status}
<AppsListPage name={getCurrentAppName(params.id, installedApps)}
installedAppsList={installedApps} onClose={closeModal}
customAppsList={customApps} onConfirm={handleDeactivateAppConfirm}
appsInProgressList={appsInProgressData} open={params.action === "app-deactivate"}
loadingAppsInProgress={loadingAppsInProgress} />
disabled={loading || customAppsLoading} <AppInProgressDeleteDialog
settings={settings} confirmButtonState={deleteInProgressAppOpts.status}
onUpdateListSettings={updateListSettings} name={getAppInProgressName(
onAppInstallRetry={onAppInstallRetry} params.id,
getCustomAppHref={id => customAppUrl(id)} appsInProgressData?.appsInstallations
onInstalledAppRemove={id => )}
openModal("remove-app", { onClose={closeModal}
id onConfirm={handleRemoveInProgressConfirm}
}) open={action === "remove"}
} />
onCustomAppRemove={id => <AppsListPage
openModal("remove-custom-app", { installedAppsList={installedApps}
id customAppsList={customApps}
}) appsInProgressList={appsInProgressData}
} loadingAppsInProgress={loadingAppsInProgress}
onAppInProgressRemove={id => disabled={loading || customAppsLoading}
openModal("remove", { settings={settings}
id onUpdateListSettings={updateListSettings}
}) onAppInstallRetry={onAppInstallRetry}
} getCustomAppHref={id => customAppUrl(id)}
/> onInstalledAppRemove={id =>
</PaginatorContext.Provider> openModal("remove-app", {
id
})
}
onCustomAppRemove={id =>
openModal("remove-custom-app", {
id
})
}
onAppInProgressRemove={id =>
openModal("remove", {
id
})
}
/>
</PaginatorContext.Provider>
</AppListContext.Provider>
); );
}; };

View file

@ -20,5 +20,15 @@ export const messages = defineMessages({
id: "5t/4um", id: "5t/4um",
defaultMessage: "Couldnt Install {name}", defaultMessage: "Couldnt Install {name}",
description: "message title" description: "message title"
},
appActivated: {
id: "D/+84n",
defaultMessage: "App activated",
description: "snackbar text"
},
appDeactivated: {
id: "USO8PB",
defaultMessage: "App deactivated",
description: "snackbar text"
} }
}); });

View file

@ -54,7 +54,7 @@ export interface AppListViewSettings {
export const defaultListSettings: AppListViewSettings = { export const defaultListSettings: AppListViewSettings = {
[ListViews.APPS_LIST]: { [ListViews.APPS_LIST]: {
rowNumber: 10 rowNumber: 100
}, },
[ListViews.ATTRIBUTE_VALUE_LIST]: { [ListViews.ATTRIBUTE_VALUE_LIST]: {
rowNumber: 10 rowNumber: 10

View file

@ -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
}
`;

View file

@ -47,6 +47,24 @@ export const AppFragmentDoc = gql`
} }
} }
${WebhookFragmentDoc}`; ${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` export const AttributeFragmentDoc = gql`
fragment Attribute on Attribute { fragment Attribute on Attribute {
id id
@ -3135,16 +3153,12 @@ export const AppsListDocument = gql`
totalCount totalCount
edges { edges {
node { node {
id ...AppListItem
name
isActive
type
appUrl
} }
} }
} }
} }
`; ${AppListItemFragmentDoc}`;
/** /**
* __useAppsListQuery__ * __useAppsListQuery__

View file

@ -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; }>; 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 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 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 }; 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 };

View file

@ -19,8 +19,8 @@ export const commonMessages = defineMessages({
defaultMessage: "Channel" defaultMessage: "Channel"
}, },
customApps: { customApps: {
id: "w4R/SO", id: "+iV0gu",
defaultMessage: "Local Apps" defaultMessage: "Internal Apps"
}, },
dashboard: { dashboard: {
id: "hzSNj4", id: "hzSNj4",

File diff suppressed because it is too large Load diff