Fix strict null check in Apps (#2911)

* Fix apps strict null checks

* Set strictNullChecks to false

* Improve strict null checks

* Replace typ to Extension
This commit is contained in:
poulch 2022-12-29 13:51:54 +01:00 committed by GitHub
parent 39ea58f02b
commit bf2a2035d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 190 additions and 139 deletions

View file

@ -15,4 +15,4 @@ const props: AppActivateDialogProps = {
storiesOf("Views / Apps / Activate app", module) storiesOf("Views / Apps / Activate app", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <AppActivateDialog {...props} />) .add("default", () => <AppActivateDialog {...props} />)
.add("unnamed app", () => <AppActivateDialog {...props} name={null} />); .add("unnamed app", () => <AppActivateDialog {...props} name="" />);

View file

@ -17,4 +17,4 @@ const props: AppDeactivateDialogProps = {
storiesOf("Views / Apps / Deactivate app", module) storiesOf("Views / Apps / Deactivate app", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <AppDeactivateDialog {...props} />) .add("default", () => <AppDeactivateDialog {...props} />)
.add("unnamed app", () => <AppDeactivateDialog {...props} name={null} />); .add("unnamed app", () => <AppDeactivateDialog {...props} name="" />);

View file

@ -16,4 +16,4 @@ const props: AppDeleteDialogProps = {
storiesOf("Views / Apps / Delete app", module) storiesOf("Views / Apps / Delete app", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <AppDeleteDialog {...props} />) .add("default", () => <AppDeleteDialog {...props} />)
.add("unnamed app", () => <AppDeleteDialog {...props} name={null} />); .add("unnamed app", () => <AppDeleteDialog {...props} name="" />);

View file

@ -63,7 +63,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
<div className={classes.appHeaderLinks}> <div className={classes.appHeaderLinks}>
<ExternalLink <ExternalLink
className={classes.headerLinkContainer} className={classes.headerLinkContainer}
href={data.supportUrl} href={data.supportUrl || ""}
target="_blank" target="_blank"
> >
<SVG src={supportIcon} /> <SVG src={supportIcon} />
@ -109,7 +109,11 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
})} })}
/> />
<CardContent> <CardContent>
{!loading ? <ReactMarkdown source={data?.aboutApp} /> : <Skeleton />} {!loading ? (
<ReactMarkdown source={data?.aboutApp ?? ""} />
) : (
<Skeleton />
)}
</CardContent> </CardContent>
</Card> </Card>
<CardSpacer /> <CardSpacer />
@ -146,7 +150,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
</Card> </Card>
<CardSpacer /> <CardSpacer />
{(loading || data?.dataPrivacyUrl) && ( {data?.dataPrivacyUrl && (
<Card> <Card>
<CardTitle <CardTitle
title={intl.formatMessage({ title={intl.formatMessage({
@ -159,7 +163,7 @@ export const AppDetailsPage: React.FC<AppDetailsPageProps> = ({
{!loading ? ( {!loading ? (
<ExternalLink <ExternalLink
className={classes.linkContainer} className={classes.linkContainer}
href={data?.dataPrivacyUrl} href={data.dataPrivacyUrl}
target="_blank" target="_blank"
> >
<FormattedMessage <FormattedMessage

View file

@ -38,7 +38,7 @@ export const AppFrame: React.FC<Props> = ({
refetch, refetch,
}) => { }) => {
const shop = useShop(); const shop = useShop();
const frameRef = React.useRef<HTMLIFrameElement>(); const frameRef = React.useRef<HTMLIFrameElement>(null);
const { themeType } = useTheme(); const { themeType } = useTheme();
const classes = useStyles(); const classes = useStyles();
const appOrigin = getOrigin(src); const appOrigin = getOrigin(src);

View file

@ -34,7 +34,7 @@ const isAppDeepUrlChange = (appId: string, from: string, to: string) => {
}; };
export const useAppActions = ( export const useAppActions = (
frameEl: React.MutableRefObject<HTMLIFrameElement>, frameEl: React.MutableRefObject<HTMLIFrameElement | null>,
appOrigin: string, appOrigin: string,
appId: string, appId: string,
) => { ) => {
@ -125,7 +125,7 @@ export const useAppActions = (
}; };
const postToExtension = (event: Events) => { const postToExtension = (event: Events) => {
if (frameEl.current) { if (frameEl?.current?.contentWindow) {
frameEl.current.contentWindow.postMessage(event, appOrigin); frameEl.current.contentWindow.postMessage(event, appOrigin);
} }
}; };

View file

@ -9,7 +9,10 @@ interface AppToken {
const TIME_BEFORE_REFRESH = 30 * 1000; // 30 seconds const TIME_BEFORE_REFRESH = 30 * 1000; // 30 seconds
const useTokenRefresh = (token?: string, refetch?: () => void) => { const useTokenRefresh = (token?: string, refetch?: () => void) => {
let decoded: AppToken; let decoded: AppToken = {
exp: 0,
iat: 0,
};
// For some reason jwt_decode causes seemingly unrelated error in tests // For some reason jwt_decode causes seemingly unrelated error in tests
// It seems like at some point undefined token is passed // It seems like at some point undefined token is passed
@ -26,11 +29,13 @@ const useTokenRefresh = (token?: string, refetch?: () => void) => {
const refreshTimeout = useRef<null | ReturnType<typeof setTimeout>>(null); const refreshTimeout = useRef<null | ReturnType<typeof setTimeout>>(null);
const tokenLife = (decoded?.exp - decoded?.iat) * 1000; // in ms const tokenLife = ((decoded?.exp || 0) - (decoded?.iat || 0)) * 1000; // in ms
const refreshTime = tokenLife - TIME_BEFORE_REFRESH; const refreshTime = tokenLife - TIME_BEFORE_REFRESH;
const setUpTimeout = () => { const setUpTimeout = () => {
if (refetch) {
refetch(); refetch();
}
createTimeout(); createTimeout();
}; };
@ -50,7 +55,11 @@ const useTokenRefresh = (token?: string, refetch?: () => void) => {
createTimeout(); createTimeout();
} }
return () => !!refetch && decodedSuccesfully && deleteTimeout(); return () => {
if (!!refetch && decodedSuccesfully) {
deleteTimeout();
}
};
}, [token]); }, [token]);
}; };

View file

@ -17,6 +17,4 @@ const props: AppInProgressDeleteDialogProps = {
storiesOf("Views / Apps / Delete app failed installation", module) storiesOf("Views / Apps / Delete app failed installation", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <AppInProgressDeleteDialog {...props} />) .add("default", () => <AppInProgressDeleteDialog {...props} />)
.add("unnamed app", () => ( .add("unnamed app", () => <AppInProgressDeleteDialog {...props} name="" />);
<AppInProgressDeleteDialog {...props} name={null} />
));

View file

@ -9,7 +9,7 @@ const props: AppInstallPageProps = {
data: installApp, data: installApp,
loading: false, loading: false,
navigateToAppsList: () => undefined, navigateToAppsList: () => undefined,
onSubmit: () => undefined, onSubmit: () => Promise.resolve([]),
}; };
storiesOf("Views / Apps / Install App", module) storiesOf("Views / Apps / Install App", module)

View file

@ -17,10 +17,12 @@ import { FormattedMessage, useIntl } from "react-intl";
import { useStyles } from "../../styles"; import { useStyles } from "../../styles";
export interface AppInstallPageProps { export interface AppInstallPageProps {
data: AppFetchMutation["appFetchManifest"]["manifest"]; data: NonNullable<AppFetchMutation["appFetchManifest"]>["manifest"];
loading: boolean; loading: boolean;
navigateToAppsList: () => void; navigateToAppsList: () => void;
onSubmit: () => SubmitPromise<AppInstallMutation["appInstall"]["errors"]>; onSubmit: () => SubmitPromise<
NonNullable<AppInstallMutation["appInstall"]>["errors"]
>;
} }
export const AppInstallPage: React.FC<AppInstallPageProps> = ({ export const AppInstallPage: React.FC<AppInstallPageProps> = ({

View file

@ -7,7 +7,7 @@ import AppPage, { AppPageProps } from "./AppPage";
const props: AppPageProps = { const props: AppPageProps = {
data: appDetails, data: appDetails,
url: appDetails.appUrl, url: appDetails.appUrl!,
aboutHref: "", aboutHref: "",
onError: () => undefined, onError: () => undefined,
}; };
@ -16,5 +16,5 @@ storiesOf("Views / Apps / App", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <AppPage {...props} />) .add("default", () => <AppPage {...props} />)
.add("settings", () => ( .add("settings", () => (
<AppPage {...props} url={appDetails.configurationUrl} /> <AppPage {...props} url={appDetails.configurationUrl!} />
)); ));

View file

@ -80,9 +80,9 @@ export const AppPage: React.FC<AppPageProps> = ({
{url && ( {url && (
<AppFrame <AppFrame
src={url} src={url}
appToken={data.accessToken} appToken={data?.accessToken ?? ""}
onError={onError} onError={onError}
appId={data.id} appId={data?.id ?? ""}
refetch={refetch} refetch={refetch}
/> />
)} )}

View file

@ -51,14 +51,24 @@ const AppsInProgress: React.FC<AppsInProgressProps> = ({
/> />
<ResponsiveTable> <ResponsiveTable>
<TableBody> <TableBody>
{renderCollection(appsList, ({ status, appName, id, message }) => ( {renderCollection(
appsList,
({
status,
appName,
id,
message,
}: AppsInstallationsQuery["appsInstallations"][number]) => (
<TableRowLink key={id} className={classes.tableRow}> <TableRowLink key={id} className={classes.tableRow}>
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
<span data-tc="name">{appName}</span> <span data-tc="name">{appName}</span>
</TableCell> </TableCell>
{status === JobStatusEnum.PENDING && ( {status === JobStatusEnum.PENDING && (
<TableCell <TableCell
className={clsx(classes.colAction, classes.colInstallAction)} className={clsx(
classes.colAction,
classes.colInstallAction,
)}
> >
<Typography variant="body2" className={classes.text}> <Typography variant="body2" className={classes.text}>
<FormattedMessage <FormattedMessage
@ -74,7 +84,10 @@ const AppsInProgress: React.FC<AppsInProgressProps> = ({
)} )}
{status === JobStatusEnum.FAILED && ( {status === JobStatusEnum.FAILED && (
<TableCell <TableCell
className={clsx(classes.colAction, classes.colInstallAction)} className={clsx(
classes.colAction,
classes.colInstallAction,
)}
> >
<Typography variant="body2" className={classes.error}> <Typography variant="body2" className={classes.error}>
<FormattedMessage <FormattedMessage
@ -109,7 +122,8 @@ const AppsInProgress: React.FC<AppsInProgressProps> = ({
</TableCell> </TableCell>
)} )}
</TableRowLink> </TableRowLink>
))} ),
)}
</TableBody> </TableBody>
</ResponsiveTable> </ResponsiveTable>
</Card> </Card>

View file

@ -47,7 +47,7 @@ storiesOf("Views / Apps / Apps list", module)
{...props} {...props}
appsInProgressList={undefined} appsInProgressList={undefined}
disabled={true} disabled={true}
installedAppsList={undefined} installedAppsList={[]}
/> />
)) ))
.add("no data", () => ( .add("no data", () => (

View file

@ -78,15 +78,19 @@ const AppsListPage: React.FC<AppsListPageProps> = ({
[installedAppsList, fetchedSaleorApps], [installedAppsList, fetchedSaleorApps],
); );
const saleorApps = useMemo( const saleorApps = useMemo<AppListItemFragment[]>(
() => () =>
fetchedSaleorApps (fetchedSaleorApps || []).reduce<AppListItemFragment[]>((acc, app) => {
?.map(app => const foundedApp = installedAppsList?.find(installedApp =>
installedAppsList?.find(installedApp =>
installedApp.manifestUrl?.includes(app.hostname), installedApp.manifestUrl?.includes(app.hostname),
), );
)
.filter(Boolean), if (foundedApp) {
acc.push(foundedApp);
}
return acc;
}, []),
[installedAppsList, fetchedSaleorApps], [installedAppsList, fetchedSaleorApps],
); );

View file

@ -16,4 +16,9 @@ export const ExternalAppContext = React.createContext<{
appData: AppData | undefined; appData: AppData | undefined;
setOpen: React.Dispatch<React.SetStateAction<boolean>>; setOpen: React.Dispatch<React.SetStateAction<boolean>>;
setAppData: React.Dispatch<React.SetStateAction<AppData | undefined>>; setAppData: React.Dispatch<React.SetStateAction<AppData | undefined>>;
}>(undefined); }>({
open: false,
appData: undefined,
setOpen: () => null,
setAppData: () => null,
});

View file

@ -7,7 +7,7 @@ export interface HorizontalSpacerProps {
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
container: ({ spacing }: HorizontalSpacerProps) => ({ container: ({ spacing }: Required<HorizontalSpacerProps>) => ({
width: theme.spacing(spacing), width: theme.spacing(spacing),
}), }),
}), }),

View file

@ -104,11 +104,11 @@ const InstalledApps: React.FC<InstalledAppsProps> = ({
)} )}
<TableButtonWrapper> <TableButtonWrapper>
<Switch <Switch
checked={app.isActive} checked={!!app.isActive}
onChange={getHandleToggle(app)} onChange={getHandleToggle(app)}
/> />
</TableButtonWrapper> </TableButtonWrapper>
<AppPermissions permissions={app.permissions} /> <AppPermissions permissions={app.permissions || []} />
<TableButtonWrapper> <TableButtonWrapper>
<IconButton <IconButton
variant="secondary" variant="secondary"

View file

@ -7,7 +7,7 @@ export interface VerticalSpacerProps {
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
container: ({ spacing }: VerticalSpacerProps) => ({ container: ({ spacing }: Required<VerticalSpacerProps>) => ({
height: theme.spacing(spacing), height: theme.spacing(spacing),
}), }),
}), }),

View file

@ -99,7 +99,7 @@ export const appsInProgress: AppsInstallationsQuery["appsInstallations"] = [
}, },
]; ];
export const appDetails: AppQuery["app"] = { export const appDetails: NonNullable<AppQuery["app"]> = {
__typename: "App", __typename: "App",
aboutApp: "Lorem ipsum", aboutApp: "Lorem ipsum",
accessToken: "token", accessToken: "token",
@ -134,7 +134,9 @@ export const appDetails: AppQuery["app"] = {
webhooks: [], webhooks: [],
}; };
export const installApp: AppFetchMutation["appFetchManifest"]["manifest"] = { export const installApp: NonNullable<
AppFetchMutation["appFetchManifest"]
>["manifest"] = {
__typename: "Manifest", __typename: "Manifest",
about: "Lorem ipsum", about: "Lorem ipsum",
appUrl: null, appUrl: null,

View file

@ -14,7 +14,7 @@ import { AppDetailsUrlMountQueryParams } from "./urls";
export interface Extension { export interface Extension {
id: string; id: string;
app: RelayToFlat<ExtensionListQuery["appExtensions"]>[0]["app"]; app: RelayToFlat<NonNullable<ExtensionListQuery["appExtensions"]>>[0]["app"];
accessToken: string; accessToken: string;
permissions: PermissionEnum[]; permissions: PermissionEnum[];
label: string; label: string;
@ -54,14 +54,14 @@ export const extensionMountPoints = {
}; };
const filterAndMapToTarget = ( const filterAndMapToTarget = (
extensions: RelayToFlat<ExtensionListQuery["appExtensions"]>, extensions: RelayToFlat<NonNullable<ExtensionListQuery["appExtensions"]>>,
openApp: (appData: AppData) => void, openApp: (appData: AppData) => void,
): ExtensionWithParams[] => ): ExtensionWithParams[] =>
extensions.map( extensions.map(
({ id, accessToken, permissions, url, label, mount, target, app }) => ({ ({ id, accessToken, permissions, url, label, mount, target, app }) => ({
id, id,
app, app,
accessToken, accessToken: accessToken || "",
permissions: permissions.map(({ code }) => code), permissions: permissions.map(({ code }) => code),
url, url,
label, label,
@ -69,7 +69,7 @@ const filterAndMapToTarget = (
open: (params: AppDetailsUrlMountQueryParams) => open: (params: AppDetailsUrlMountQueryParams) =>
openApp({ openApp({
id: app.id, id: app.id,
appToken: accessToken, appToken: accessToken || "",
src: url, src: url,
label, label,
target, target,
@ -153,7 +153,7 @@ export const useExtensions = <T extends AppExtensionMountEnum>(
}); });
const extensions = filterAndMapToTarget( const extensions = filterAndMapToTarget(
mapEdgesToItems(data?.appExtensions) || [], mapEdgesToItems(data?.appExtensions ?? undefined) || [],
openApp, openApp,
); );

View file

@ -37,14 +37,14 @@ export const App: React.FC<AppProps> = ({ id }) => {
const appCompleteUrl = getAppCompleteUrlFromDashboardUrl( const appCompleteUrl = getAppCompleteUrlFromDashboardUrl(
location.pathname, location.pathname,
data?.app.appUrl, data?.app?.appUrl || "",
id, id,
); );
return ( return (
<AppPage <AppPage
data={data?.app} data={data?.app || null}
url={appCompleteUrl} url={appCompleteUrl || ""}
aboutHref={appDetailsUrl(id)} aboutHref={appDetailsUrl(id)}
refetch={refetch} refetch={refetch}
onError={() => onError={() =>

View file

@ -51,7 +51,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
refetch(); refetch();
closeModal(); closeModal();
} else { } else {
if (appExists) { if (appExists && errors) {
errors.forEach(error => errors.forEach(error =>
notify({ notify({
status: "error", status: "error",
@ -65,7 +65,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({ const [deactivateApp, deactivateAppResult] = useAppDeactivateMutation({
onCompleted: data => { onCompleted: data => {
const errors = data?.appDeactivate?.errors; const errors = data?.appDeactivate?.errors;
if (errors.length === 0) { if (errors?.length === 0) {
notify({ notify({
status: "success", status: "success",
text: intl.formatMessage(appMessages.appDeactivated), text: intl.formatMessage(appMessages.appDeactivated),
@ -73,7 +73,7 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
refetch(); refetch();
closeModal(); closeModal();
} else { } else {
if (appExists) { if (appExists && errors) {
errors.forEach(error => errors.forEach(error =>
notify({ notify({
status: "error", status: "error",
@ -105,20 +105,20 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
<> <>
<AppActivateDialog <AppActivateDialog
confirmButtonState={activateAppResult.status} confirmButtonState={activateAppResult.status}
name={data?.app.name} name={data?.app?.name || ""}
onClose={closeModal} onClose={closeModal}
onConfirm={handleActivateConfirm} onConfirm={handleActivateConfirm}
open={params.action === "app-activate"} open={params.action === "app-activate"}
/> />
<AppDeactivateDialog <AppDeactivateDialog
confirmButtonState={deactivateAppResult.status} confirmButtonState={deactivateAppResult.status}
name={data?.app.name} name={data?.app?.name || ""}
onClose={closeModal} onClose={closeModal}
onConfirm={handleDeactivateConfirm} onConfirm={handleDeactivateConfirm}
open={params.action === "app-deactivate"} open={params.action === "app-deactivate"}
/> />
<AppDetailsPage <AppDetailsPage
data={data?.app} data={data?.app || null}
loading={loading} loading={loading}
navigateToApp={() => navigate(appUrl(id))} navigateToApp={() => navigate(appUrl(id))}
onAppActivateOpen={() => openModal("app-activate")} onAppActivateOpen={() => openModal("app-activate")}

View file

@ -24,7 +24,9 @@ interface InstallAppCreateProps extends RouteComponentProps {
export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
params, params,
}) => { }) => {
const [, setActiveInstallations] = useLocalStorage("activeInstallations", []); const [, setActiveInstallations] = useLocalStorage<
Array<Record<"id" | "name", string>>
>("activeInstallations", []);
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl(); const intl = useIntl();
@ -32,7 +34,7 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
const [fetchManifest, fetchManifestOpts] = useAppFetchMutation({ const [fetchManifest, fetchManifestOpts] = useAppFetchMutation({
onCompleted: data => { onCompleted: data => {
if (data.appFetchManifest.errors.length) { if (data?.appFetchManifest?.errors.length) {
data.appFetchManifest.errors.forEach(error => { data.appFetchManifest.errors.forEach(error => {
notify({ notify({
status: "error", status: "error",
@ -44,15 +46,20 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
}); });
const [installApp] = useAppInstallMutation({ const [installApp] = useAppInstallMutation({
onCompleted: data => { onCompleted: data => {
const installationData = data.appInstall.appInstallation; const installationData = data?.appInstall?.appInstallation;
if (data.appInstall.errors.length === 0) { if (data.appInstall?.errors.length === 0) {
if (installationData) {
setActiveInstallations(activeInstallations => [ setActiveInstallations(activeInstallations => [
...activeInstallations, ...activeInstallations,
{ id: installationData.id, name: installationData.appName }, {
id: installationData.id,
name: installationData.appName,
},
]); ]);
}
navigateToAppsList(); navigateToAppsList();
} else { } else {
data.appInstall.errors.forEach(error => { (data?.appInstall?.errors ?? []).forEach(error => {
notify({ notify({
status: "error", status: "error",
text: getAppErrorMessage(error, intl), text: getAppErrorMessage(error, intl),
@ -72,7 +79,7 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
input: { input: {
appName: manifest?.name, appName: manifest?.name,
manifestUrl, manifestUrl,
permissions: manifest?.permissions.map( permissions: manifest?.permissions?.map(
permission => permission.code, permission => permission.code,
), ),
}, },
@ -97,7 +104,7 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
<AppInstallErrorPage onBack={() => navigate("/")} /> <AppInstallErrorPage onBack={() => navigate("/")} />
) : ( ) : (
<AppInstallPage <AppInstallPage
data={fetchManifestOpts?.data?.appFetchManifest?.manifest} data={fetchManifestOpts?.data?.appFetchManifest?.manifest ?? null}
navigateToAppsList={navigateToAppsList} navigateToAppsList={navigateToAppsList}
onSubmit={handleSubmit} onSubmit={handleSubmit}
loading={fetchManifestOpts?.loading} loading={fetchManifestOpts?.loading}

View file

@ -29,8 +29,8 @@ export const AppSettings: React.FC<AppSettingsProps> = ({ id }) => {
return ( return (
<AppPage <AppPage
data={data?.app} data={data?.app ?? null}
url={data?.app.configurationUrl} url={data?.app?.configurationUrl ?? ""}
aboutHref={appDetailsUrl(id)} aboutHref={appDetailsUrl(id)}
refetch={refetch} refetch={refetch}
onError={() => onError={() =>

View file

@ -22,6 +22,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
createPaginationState, createPaginationState,
PageInfo,
PaginatorContext, PaginatorContext,
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { findById } from "@saleor/misc"; import { findById } from "@saleor/misc";
@ -45,7 +46,7 @@ import { messages } from "./messages";
const getAppInProgressName = ( const getAppInProgressName = (
id: string, id: string,
collection?: AppsInstallationsQuery["appsInstallations"], collection?: AppsInstallationsQuery["appsInstallations"],
) => collection?.find(app => app.id === id)?.appName; ) => collection?.find(app => app.id === id)?.appName || id;
interface AppsListProps { interface AppsListProps {
params: AppListUrlQueryParams; params: AppListUrlQueryParams;
} }
@ -92,7 +93,7 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
}); });
const paginationValues = usePaginator({ const paginationValues = usePaginator({
pageInfo: data?.apps?.pageInfo, pageInfo: data?.apps?.pageInfo as PageInfo,
paginationState, paginationState,
queryString: params, queryString: params,
}); });
@ -113,12 +114,17 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
const [retryInstallApp] = useAppRetryInstallMutation({ const [retryInstallApp] = useAppRetryInstallMutation({
onCompleted: data => { onCompleted: data => {
if (!data?.appRetryInstall?.errors?.length) { if (!data?.appRetryInstall?.errors?.length) {
const appInstallation = data.appRetryInstall.appInstallation; const appInstallation = data.appRetryInstall?.appInstallation;
if (appInstallation) {
setActiveInstallations(installations => [ setActiveInstallations(installations => [
...installations, ...installations,
{ id: appInstallation.id, name: appInstallation.appName }, {
id: appInstallation.id,
name: appInstallation.appName,
},
]); ]);
} }
}
}, },
}); });
const [activateApp, activateAppResult] = useAppActivateMutation({ const [activateApp, activateAppResult] = useAppActivateMutation({
@ -226,14 +232,14 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
const handleRemoveInProgressConfirm = () => const handleRemoveInProgressConfirm = () =>
deleteInProgressApp({ deleteInProgressApp({
variables: { variables: {
id: params.id, id: params?.id || "",
}, },
}); });
const handleRemoveConfirm = () => const handleRemoveConfirm = () =>
deleteApp({ deleteApp({
variables: { variables: {
id: params.id, id: params?.id || "",
}, },
}); });
@ -245,10 +251,10 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
}; };
const handleActivateAppConfirm = () => const handleActivateAppConfirm = () =>
activateApp({ variables: { id: params.id } }); activateApp({ variables: { id: params?.id || "" } });
const handleDeactivateAppConfirm = () => const handleDeactivateAppConfirm = () =>
deactivateApp({ variables: { id: params.id } }); deactivateApp({ variables: { id: params?.id || "" } });
const onAppInstallRetry = (id: string) => const onAppInstallRetry = (id: string) =>
retryInstallApp({ variables: { id } }); retryInstallApp({ variables: { id } });
@ -261,8 +267,8 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
[activateApp, deactivateApp], [activateApp, deactivateApp],
); );
const installedApps = mapEdgesToItems(data?.apps); const installedApps = mapEdgesToItems(data?.apps || { edges: [] }) || [];
const currentAppName = findById(params.id, installedApps)?.name; const currentAppName = findById(params?.id || "", installedApps)?.name || "";
return ( return (
<AppListContext.Provider value={context}> <AppListContext.Provider value={context}>
@ -292,7 +298,7 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
<AppInProgressDeleteDialog <AppInProgressDeleteDialog
confirmButtonState={deleteInProgressAppOpts.status} confirmButtonState={deleteInProgressAppOpts.status}
name={getAppInProgressName( name={getAppInProgressName(
params.id, params.id || "",
appsInProgressData?.appsInstallations, appsInProgressData?.appsInstallations,
)} )}
onClose={closeModal} onClose={closeModal}

View file

@ -47,7 +47,7 @@ function usePaginator({
paginationState, paginationState,
pageInfo, pageInfo,
}: UsePaginatorArgs) { }: UsePaginatorArgs) {
const newPageInfo = useMemo<PageInfo>( const newPageInfo = useMemo<PageInfo | undefined>(
() => () =>
pageInfo pageInfo
? { ? {