src/apps refactors batch (#3773)

* Add refactors to apps folder

Remove default exports in apps/views

Rename apps/views components, ensure they have similar convention

Refactor apps indexes files

Rename marketplace mentions to appstore

Rename useMarketplaceApps to useAppstoreApps

Rename some marketplace mentions to appstore

fix test

* Add changeset
This commit is contained in:
Lukasz Ostrowski 2023-06-27 09:29:40 +02:00 committed by GitHub
parent 66bab6feab
commit 58a3c26f7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 185 additions and 194 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---
Applied refactors on "apps" module. Renamed some "marketplace" symbols to "appstore". Replaced some "Default" exports to named ones. Didn't introduce any visual or functional changes.

View file

@ -2,27 +2,29 @@ import {
AppDetailsUrlQueryParams, AppDetailsUrlQueryParams,
AppInstallUrlQueryParams, AppInstallUrlQueryParams,
} from "@dashboard/apps/urls"; } from "@dashboard/apps/urls";
import AppInstallView from "@dashboard/apps/views/AppInstall";
import { sectionNames } from "@dashboard/intl"; import { sectionNames } from "@dashboard/intl";
import { parse as parseQs } from "qs"; import { parse as parseQs } from "qs";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import {
AppInstallView,
AppListView,
AppManageView,
AppView,
} from "src/apps/views";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { AppListUrlQueryParams, AppPaths } from "./urls"; import { AppListUrlQueryParams, AppPaths } from "./urls";
import AppDetailsView from "./views/AppDetails";
import AppListView from "./views/AppList";
import AppView from "./views/AppView";
const AppDetails: React.FC<RouteComponentProps<{ id: string }>> = ({ const AppManageRoute: React.FC<RouteComponentProps<{ id: string }>> = ({
match, match,
}) => { }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: AppDetailsUrlQueryParams = qs; const params: AppDetailsUrlQueryParams = qs;
return ( return (
<AppDetailsView id={decodeURIComponent(match.params.id)} params={params} /> <AppManageView id={decodeURIComponent(match.params.id)} params={params} />
); );
}; };
@ -30,38 +32,40 @@ const AppViewRoute: React.FC<RouteComponentProps<{ id: string }>> = ({
match, match,
}) => <AppView id={decodeURIComponent(match.params.id)} />; }) => <AppView id={decodeURIComponent(match.params.id)} />;
const AppInstall: React.FC<RouteComponentProps> = props => { const AppInstallRoute: React.FC<RouteComponentProps> = props => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: AppInstallUrlQueryParams = qs; const params: AppInstallUrlQueryParams = qs;
return <AppInstallView params={params} {...props} />; return <AppInstallView params={params} {...props} />;
}; };
const AppList: React.FC<RouteComponentProps> = () => { const AppListRoute: React.FC<RouteComponentProps> = () => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: AppListUrlQueryParams = qs; const params: AppListUrlQueryParams = qs;
return <AppListView params={params} />; return <AppListView params={params} />;
}; };
const Apps = () => { export const AppsSectionRoot = () => {
const intl = useIntl(); const intl = useIntl();
return ( return (
<> <>
<WindowTitle title={intl.formatMessage(sectionNames.apps)} /> <WindowTitle title={intl.formatMessage(sectionNames.apps)} />
<Switch> <Switch>
<Route exact path={AppPaths.appListPath} component={AppList} /> <Route exact path={AppPaths.appListPath} component={AppListRoute} />
<Route exact path={AppPaths.appInstallPath} component={AppInstall} /> <Route
exact
path={AppPaths.appInstallPath}
component={AppInstallRoute}
/>
<Route <Route
exact exact
path={AppPaths.resolveAppDetailsPath(":id")} path={AppPaths.resolveAppDetailsPath(":id")}
component={AppDetails} component={AppManageRoute}
/> />
<Route path={AppPaths.resolveAppPath(":id")} component={AppViewRoute} /> <Route path={AppPaths.resolveAppPath(":id")} component={AppViewRoute} />
</Switch> </Switch>
</> </>
); );
}; };
export default Apps;

View file

@ -1,5 +1,8 @@
/**
* Interfaces for shapes of data fetched from AppStore API.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
export namespace GetV2SaleorAppsResponse { export namespace AppstoreApi {
export interface SaleorAppBase { export interface SaleorAppBase {
name: { name: {
en: string; en: string;

View file

@ -1,4 +1,4 @@
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types"; import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { AppInstallationFragment } from "@dashboard/graphql"; import { AppInstallationFragment } from "@dashboard/graphql";
import { Skeleton } from "@material-ui/lab"; import { Skeleton } from "@material-ui/lab";
import { Box } from "@saleor/macaw-ui/next"; import { Box } from "@saleor/macaw-ui/next";
@ -8,7 +8,7 @@ import React from "react";
import AppListRow from "../AppListRow"; import AppListRow from "../AppListRow";
interface AllAppListProps { interface AllAppListProps {
appList?: GetV2SaleorAppsResponse.SaleorApp[]; appList?: AppstoreApi.SaleorApp[];
appInstallationList?: AppInstallationFragment[]; appInstallationList?: AppInstallationFragment[];
navigateToAppInstallPage?: (manifestUrl: string) => void; navigateToAppInstallPage?: (manifestUrl: string) => void;
navigateToGithubForkPage?: (githubForkUrl: string) => void; navigateToGithubForkPage?: (githubForkUrl: string) => void;

View file

@ -1,4 +1,4 @@
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types"; import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { import {
AppInstallationFragment, AppInstallationFragment,
AppListItemFragment, AppListItemFragment,
@ -7,6 +7,6 @@ import {
export interface AppListPageSections { export interface AppListPageSections {
appsInstallations?: AppInstallationFragment[]; appsInstallations?: AppInstallationFragment[];
installedApps?: AppListItemFragment[]; installedApps?: AppListItemFragment[];
installableMarketplaceApps?: GetV2SaleorAppsResponse.ReleasedSaleorApp[]; installableMarketplaceApps?: AppstoreApi.ReleasedSaleorApp[];
comingSoonMarketplaceApps?: GetV2SaleorAppsResponse.ComingSoonSaleorApp[]; comingSoonMarketplaceApps?: AppstoreApi.ComingSoonSaleorApp[];
} }

View file

@ -1,10 +1,10 @@
import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { import {
appsInProgress, appsInProgress,
comingSoonApp, comingSoonApp,
installedAppsList, installedAppsList,
releasedApp, releasedApp,
} from "@dashboard/apps/fixtures"; } from "@dashboard/apps/fixtures";
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types";
import { import {
AppListItemFragment, AppListItemFragment,
AppTypeEnum, AppTypeEnum,
@ -213,27 +213,26 @@ describe("App List verified installed apps util", () => {
brand: null, brand: null,
}, },
]; ];
const installableMarketplaceApps: GetV2SaleorAppsResponse.ReleasedSaleorApp[] = const installableMarketplaceApps: AppstoreApi.ReleasedSaleorApp[] = [
[ {
{ name: {
name: { en: "Test app",
en: "Test app",
},
description: {
en: "Test app description",
},
logo: {
source: "https://www.example.com/logo",
color: "#000000",
},
integrations: [],
manifestUrl: "https://www.example.com/manifest",
privacyUrl: "https://www.example.com/privacy",
supportUrl: "https://www.example.com/support",
repositoryUrl: "https://www.example.com/repository",
githubForkUrl: "https://www.example.com/repository/fork",
}, },
]; description: {
en: "Test app description",
},
logo: {
source: "https://www.example.com/logo",
color: "#000000",
},
integrations: [],
manifestUrl: "https://www.example.com/manifest",
privacyUrl: "https://www.example.com/privacy",
supportUrl: "https://www.example.com/support",
repositoryUrl: "https://www.example.com/repository",
githubForkUrl: "https://www.example.com/repository/fork",
},
];
// Act // Act
const verifiedInstalledApps = getVerifiedInstalledApps( const verifiedInstalledApps = getVerifiedInstalledApps(
@ -303,45 +302,44 @@ describe("App List verified installable marketplace apps util", () => {
brand: null, brand: null,
}, },
]; ];
const installableMarketplaceApps: GetV2SaleorAppsResponse.ReleasedSaleorApp[] = const installableMarketplaceApps: AppstoreApi.ReleasedSaleorApp[] = [
[ {
{ name: {
name: { en: "Test app",
en: "Test app",
},
description: {
en: "Test app description",
},
logo: {
source: "https://www.example.com/logo",
color: "#000000",
},
integrations: [],
manifestUrl: "https://www.example.com/manifest",
privacyUrl: "https://www.example.com/privacy",
supportUrl: "https://www.example.com/support",
repositoryUrl: "https://www.example.com/repository",
githubForkUrl: "https://www.example.com/repository/fork",
}, },
{ description: {
name: { en: "Test app description",
en: "Test app",
},
description: {
en: "Test app description",
},
logo: {
source: "https://www.example-2.com/logo",
color: "#000000",
},
integrations: [],
manifestUrl: "https://www.example-2.com/manifest",
privacyUrl: "https://www.example-2.com/privacy",
supportUrl: "https://www.example-2.com/support",
repositoryUrl: "https://www.example-2.com/repository",
githubForkUrl: "https://www.example-2.com/repository/fork",
}, },
]; logo: {
source: "https://www.example.com/logo",
color: "#000000",
},
integrations: [],
manifestUrl: "https://www.example.com/manifest",
privacyUrl: "https://www.example.com/privacy",
supportUrl: "https://www.example.com/support",
repositoryUrl: "https://www.example.com/repository",
githubForkUrl: "https://www.example.com/repository/fork",
},
{
name: {
en: "Test app",
},
description: {
en: "Test app description",
},
logo: {
source: "https://www.example-2.com/logo",
color: "#000000",
},
integrations: [],
manifestUrl: "https://www.example-2.com/manifest",
privacyUrl: "https://www.example-2.com/privacy",
supportUrl: "https://www.example-2.com/support",
repositoryUrl: "https://www.example-2.com/repository",
githubForkUrl: "https://www.example-2.com/repository/fork",
},
];
// Act // Act
const verifiedInstallableMarketplaceApps = const verifiedInstallableMarketplaceApps =

View file

@ -1,5 +1,4 @@
// @ts-strict-ignore import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types";
import { AppInstallation, InstalledApp } from "@dashboard/apps/types"; import { AppInstallation, InstalledApp } from "@dashboard/apps/types";
import { import {
AppInstallationFragment, AppInstallationFragment,
@ -25,7 +24,7 @@ export const resolveSectionsAvailability = ({
const findAppInMarketplace = ( const findAppInMarketplace = (
manifestUrl: string | null, manifestUrl: string | null,
installableMarketplaceApps?: GetV2SaleorAppsResponse.ReleasedSaleorApp[], installableMarketplaceApps?: AppstoreApi.ReleasedSaleorApp[],
) => { ) => {
if (!manifestUrl) { if (!manifestUrl) {
return undefined; return undefined;
@ -38,7 +37,7 @@ const findAppInMarketplace = (
export const getVerifiedInstalledApps = ( export const getVerifiedInstalledApps = (
installedApps?: AppListItemFragment[], installedApps?: AppListItemFragment[],
installableMarketplaceApps?: GetV2SaleorAppsResponse.ReleasedSaleorApp[], installableMarketplaceApps?: AppstoreApi.ReleasedSaleorApp[],
): InstalledApp[] | undefined => ): InstalledApp[] | undefined =>
installedApps?.map(app => { installedApps?.map(app => {
const marketplaceApp = findAppInMarketplace( const marketplaceApp = findAppInMarketplace(
@ -54,7 +53,7 @@ export const getVerifiedInstalledApps = (
export const getVerifiedAppsInstallations = ( export const getVerifiedAppsInstallations = (
appsInstallations?: AppInstallationFragment[], appsInstallations?: AppInstallationFragment[],
installableMarketplaceApps?: GetV2SaleorAppsResponse.ReleasedSaleorApp[], installableMarketplaceApps?: AppstoreApi.ReleasedSaleorApp[],
): AppInstallation[] | undefined => ): AppInstallation[] | undefined =>
appsInstallations?.map(appInstallation => { appsInstallations?.map(appInstallation => {
const marketplaceApp = findAppInMarketplace( const marketplaceApp = findAppInMarketplace(
@ -74,14 +73,14 @@ export const getVerifiedAppsInstallations = (
* not relying on one page of installed apps list. * not relying on one page of installed apps list.
*/ */
const isAppNotInstalled = ( const isAppNotInstalled = (
manifestUrl: string, manifestUrl: string | null,
installedApps?: AppListItemFragment[], installedApps?: AppListItemFragment[],
) => installedApps?.every(app => app.manifestUrl !== manifestUrl); ) => installedApps?.every(app => app.manifestUrl !== manifestUrl);
export const getVerifiedInstallableMarketplaceApps = ( export const getVerifiedInstallableMarketplaceApps = (
installedApps?: AppListItemFragment[], installedApps?: AppListItemFragment[],
installableMarketplaceApps?: GetV2SaleorAppsResponse.ReleasedSaleorApp[], installableMarketplaceApps?: AppstoreApi.ReleasedSaleorApp[],
): GetV2SaleorAppsResponse.ReleasedSaleorApp[] | undefined => ): AppstoreApi.ReleasedSaleorApp[] | undefined =>
installableMarketplaceApps?.filter(app => installableMarketplaceApps?.filter(app =>
isAppNotInstalled(app.manifestUrl, installedApps), isAppNotInstalled(app.manifestUrl, installedApps),
); );

View file

@ -1,11 +1,11 @@
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types"; import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { Box, Text } from "@saleor/macaw-ui/next"; import { Box, Text } from "@saleor/macaw-ui/next";
import React from "react"; import React from "react";
import { AppLogo } from "./AppLogo"; import { AppLogo } from "./AppLogo";
interface AppListCardDescriptionProps { interface AppListCardDescriptionProps {
app: GetV2SaleorAppsResponse.SaleorApp; app: AppstoreApi.SaleorApp;
} }
const AppListCardDescription: React.FC<AppListCardDescriptionProps> = ({ const AppListCardDescription: React.FC<AppListCardDescriptionProps> = ({

View file

@ -1,10 +1,10 @@
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types"; import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { useTheme } from "@saleor/macaw-ui"; import { useTheme } from "@saleor/macaw-ui";
import { Box, Text } from "@saleor/macaw-ui/next"; import { Box, Text } from "@saleor/macaw-ui/next";
import React from "react"; import React from "react";
interface AppListCardIntegrationsProps { interface AppListCardIntegrationsProps {
integrations: GetV2SaleorAppsResponse.SaleorApp["integrations"]; integrations: AppstoreApi.SaleorApp["integrations"];
} }
const AppListCardIntegrations: React.FC<AppListCardIntegrationsProps> = ({ const AppListCardIntegrations: React.FC<AppListCardIntegrationsProps> = ({

View file

@ -1,3 +1,4 @@
import { AppstoreApi } from "@dashboard/apps/appstore.types";
import * as context from "@dashboard/apps/context"; import * as context from "@dashboard/apps/context";
import { import {
comingSoonApp, comingSoonApp,
@ -5,7 +6,6 @@ import {
pendingAppInProgress, pendingAppInProgress,
releasedApp, releasedApp,
} from "@dashboard/apps/fixtures"; } from "@dashboard/apps/fixtures";
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types";
import { appInstallationStatusMessages } from "@dashboard/apps/messages"; import { appInstallationStatusMessages } from "@dashboard/apps/messages";
import Wrapper from "@test/wrapper"; import Wrapper from "@test/wrapper";
import { render, screen, within } from "@testing-library/react"; import { render, screen, within } from "@testing-library/react";
@ -127,7 +127,7 @@ describe("Apps AppListRow", () => {
it("displays placeholder initial when no released app logo passed", () => { it("displays placeholder initial when no released app logo passed", () => {
// Arrange // Arrange
const app: GetV2SaleorAppsResponse.ReleasedSaleorApp = { const app: AppstoreApi.ReleasedSaleorApp = {
...releasedApp, ...releasedApp,
logo: { logo: {
...releasedApp.logo, ...releasedApp.logo,
@ -153,7 +153,7 @@ describe("Apps AppListRow", () => {
it("displays placeholder initial when no coming soon app logo passed", () => { it("displays placeholder initial when no coming soon app logo passed", () => {
// Arrange // Arrange
const app: GetV2SaleorAppsResponse.ComingSoonSaleorApp = { const app: AppstoreApi.ComingSoonSaleorApp = {
...comingSoonApp, ...comingSoonApp,
logo: { logo: {
...comingSoonApp.logo, ...comingSoonApp.logo,

View file

@ -1,9 +1,9 @@
// @ts-strict-ignore // @ts-strict-ignore
import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { useAppListContext } from "@dashboard/apps/context"; import { useAppListContext } from "@dashboard/apps/context";
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types";
import { import {
getAppDetails, getAppDetails,
resolveInstallationOfMarketplaceApp, resolveInstallationOfAppstoreApp,
} from "@dashboard/apps/utils"; } from "@dashboard/apps/utils";
import { AppInstallationFragment } from "@dashboard/graphql"; import { AppInstallationFragment } from "@dashboard/graphql";
import { Box } from "@saleor/macaw-ui/next"; import { Box } from "@saleor/macaw-ui/next";
@ -16,7 +16,7 @@ import AppListCardIntegrations from "./AppListCardIntegrations";
import AppListCardLinks from "./AppListCardLinks"; import AppListCardLinks from "./AppListCardLinks";
interface AppListRowProps { interface AppListRowProps {
appPair: GetV2SaleorAppsResponse.SaleorApp[]; appPair: AppstoreApi.SaleorApp[];
appInstallationList?: AppInstallationFragment[]; appInstallationList?: AppInstallationFragment[];
navigateToAppInstallPage?: (manifestUrl: string) => void; navigateToAppInstallPage?: (manifestUrl: string) => void;
navigateToGithubForkPage?: (githubForkUrl: string) => void; navigateToGithubForkPage?: (githubForkUrl: string) => void;
@ -34,11 +34,11 @@ const AppListRow: React.FC<AppListRowProps> = ({
const isSingleApp = appPair.length === 1; const isSingleApp = appPair.length === 1;
const appDetails = React.useCallback( const appDetails = React.useCallback(
(app: GetV2SaleorAppsResponse.SaleorApp) => (app: AppstoreApi.SaleorApp) =>
getAppDetails({ getAppDetails({
intl, intl,
app, app,
appInstallation: resolveInstallationOfMarketplaceApp( appInstallation: resolveInstallationOfAppstoreApp(
app, app,
appInstallationList, appInstallationList,
), ),

View file

@ -9,7 +9,7 @@ import {
PermissionEnum, PermissionEnum,
} from "@dashboard/graphql"; } from "@dashboard/graphql";
import { GetV2SaleorAppsResponse } from "./marketplace.types"; import { AppstoreApi } from "./appstore.types";
export const activeApp: AppListItemFragment = { export const activeApp: AppListItemFragment = {
__typename: "App", __typename: "App",
@ -166,7 +166,7 @@ export const appAvatar: AppAvatarFragment = {
__typename: "App", __typename: "App",
}; };
export const releasedApp: GetV2SaleorAppsResponse.ReleasedSaleorApp = { export const releasedApp: AppstoreApi.ReleasedSaleorApp = {
name: { name: {
en: "Test released app", en: "Test released app",
}, },
@ -212,7 +212,7 @@ export const releasedApp: GetV2SaleorAppsResponse.ReleasedSaleorApp = {
], ],
}; };
export const comingSoonApp: GetV2SaleorAppsResponse.ComingSoonSaleorApp = { export const comingSoonApp: AppstoreApi.ComingSoonSaleorApp = {
name: { name: {
en: "Test coming soon app", en: "Test coming soon app",
}, },

View file

@ -2,18 +2,18 @@
import { renderHook } from "@testing-library/react-hooks"; import { renderHook } from "@testing-library/react-hooks";
import { comingSoonApp, releasedApp } from "../fixtures"; import { comingSoonApp, releasedApp } from "../fixtures";
import useMarketplaceApps from "./useMarketplaceApps"; import useAppstoreApps from "./useAppstoreApps";
const mockApps = [releasedApp, comingSoonApp]; const mockApps = [releasedApp, comingSoonApp];
global.fetch = jest.fn(url => { global.fetch = jest.fn(url => {
if (url === "https://marketplace.com/apps") { if (url === "https://apps.saleor.io/apps") {
return Promise.resolve({ return Promise.resolve({
ok: true, ok: true,
json: jest.fn(() => Promise.resolve(mockApps)), json: jest.fn(() => Promise.resolve(mockApps)),
} as unknown as Response); } as unknown as Response);
} }
if (url === "https://marketplace.com/failing-apps-endpoint") { if (url === "https://apps.saleor.io/failing-apps-endpoint") {
return Promise.resolve({ return Promise.resolve({
ok: false, ok: false,
statusText: "API error", statusText: "API error",
@ -22,14 +22,14 @@ global.fetch = jest.fn(url => {
return Promise.reject(new Error("API is down")); return Promise.reject(new Error("API is down"));
}); });
describe("apps hooks useMarketplaceApps", () => { describe("apps hooks useAppstoreApps", () => {
it("should return apps when request to proper marketplace url returns apps", async () => { it("should return apps when request to proper appstore url returns apps", async () => {
// Arrange // Arrange
const marketplaceUrl = "https://marketplace.com/apps"; const appstoreUrl = "https://apps.saleor.io/apps";
// Act // Act
const { result, waitForNextUpdate } = renderHook(() => const { result, waitForNextUpdate } = renderHook(() =>
useMarketplaceApps(marketplaceUrl), useAppstoreApps(appstoreUrl),
); );
await waitForNextUpdate(); await waitForNextUpdate();
@ -37,13 +37,13 @@ describe("apps hooks useMarketplaceApps", () => {
expect(result.current).toEqual({ data: mockApps }); expect(result.current).toEqual({ data: mockApps });
}); });
it("should return error when request to proper marketplace url returns error", async () => { it("should return error when request to proper appstore url returns error", async () => {
// Arrange // Arrange
const marketplaceUrl = "https://marketplace.com/failing-apps-endpoint"; const appstoreUrl = "https://apps.saleor.io/failing-apps-endpoint";
// Act // Act
const { result, waitForNextUpdate } = renderHook(() => const { result, waitForNextUpdate } = renderHook(() =>
useMarketplaceApps(marketplaceUrl), useAppstoreApps(appstoreUrl),
); );
await waitForNextUpdate(); await waitForNextUpdate();
@ -51,13 +51,13 @@ describe("apps hooks useMarketplaceApps", () => {
expect(result.current).toEqual({ error: Error("API error") }); expect(result.current).toEqual({ error: Error("API error") });
}); });
it("should return error when request to wrong marketplace url fails", async () => { it("should return error when request to wrong appstore url fails", async () => {
// Arrange // Arrange
const marketplaceUrl = "https://wrong-marketplace.com"; const appstoreUrl = "https://wrong-appstore.com";
// Act // Act
const { result, waitForNextUpdate } = renderHook(() => const { result, waitForNextUpdate } = renderHook(() =>
useMarketplaceApps(marketplaceUrl), useAppstoreApps(appstoreUrl),
); );
await waitForNextUpdate(); await waitForNextUpdate();

View file

@ -1,27 +1,22 @@
import { GetV2SaleorAppsResponse } from "@dashboard/apps/marketplace.types"; import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { useEffect, useReducer, useRef } from "react"; import { useEffect, useReducer, useRef } from "react";
interface State { interface State {
data?: GetV2SaleorAppsResponse.SaleorApp[]; data?: AppstoreApi.SaleorApp[];
error?: Error; error?: Error;
} }
interface Cache { interface Cache {
[url: string]: GetV2SaleorAppsResponse.SaleorApp[]; [url: string]: AppstoreApi.SaleorApp[];
} }
// discriminated union type // discriminated union type
type Action = type Action =
| { type: "loading" } | { type: "loading" }
| { type: "fetched"; payload: GetV2SaleorAppsResponse.SaleorApp[] } | { type: "fetched"; payload: AppstoreApi.SaleorApp[] }
| { type: "error"; payload: Error }; | { type: "error"; payload: Error };
/** function useAppstoreApps(appstoreUrl?: string): State {
* Hook used to fetch apps list available under given marketplace url.
* @param marketplaceUrl - url from which fetch data with apps list
* @returns state object containing data with apps list or fetch error
*/
function useMarketplaceApps(marketplaceUrl?: string): State {
const cache = useRef<Cache>({}); const cache = useRef<Cache>({});
// Used to prevent state update if the component is unmounted // Used to prevent state update if the component is unmounted
@ -54,7 +49,7 @@ function useMarketplaceApps(marketplaceUrl?: string): State {
useEffect(() => { useEffect(() => {
// Do nothing if the url is not given // Do nothing if the url is not given
if (!marketplaceUrl) { if (!appstoreUrl) {
return; return;
} }
@ -64,20 +59,19 @@ function useMarketplaceApps(marketplaceUrl?: string): State {
dispatch({ type: "loading" }); dispatch({ type: "loading" });
// If a cache exists for this url, return it // If a cache exists for this url, return it
if (cache.current[marketplaceUrl]) { if (cache.current[appstoreUrl]) {
dispatch({ type: "fetched", payload: cache.current[marketplaceUrl] }); dispatch({ type: "fetched", payload: cache.current[appstoreUrl] });
return; return;
} }
try { try {
const response = await fetch(marketplaceUrl); const response = await fetch(appstoreUrl);
if (!response.ok) { if (!response.ok) {
throw new Error(response.statusText); throw new Error(response.statusText);
} }
const data = const data = (await response.json()) as AppstoreApi.SaleorApp[];
(await response.json()) as GetV2SaleorAppsResponse.SaleorApp[]; cache.current[appstoreUrl] = data;
cache.current[marketplaceUrl] = data;
if (cancelRequest.current) { if (cancelRequest.current) {
return; return;
} }
@ -100,9 +94,9 @@ function useMarketplaceApps(marketplaceUrl?: string): State {
cancelRequest.current = true; cancelRequest.current = true;
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [marketplaceUrl]); }, [appstoreUrl]);
return state; return state;
} }
export default useMarketplaceApps; export default useAppstoreApps;

1
src/apps/index.ts Normal file
View file

@ -0,0 +1 @@
export { AppsSectionRoot } from "./apps-routing";

View file

@ -1,16 +1,16 @@
import { AppstoreApi } from "@dashboard/apps/appstore.types";
import { AppInstallationFragment, JobStatusEnum } from "@dashboard/graphql"; import { AppInstallationFragment, JobStatusEnum } from "@dashboard/graphql";
import { intlMock } from "@test/intl"; import { intlMock } from "@test/intl";
import { appsInProgress, releasedApp } from "./fixtures"; import { appsInProgress, releasedApp } from "./fixtures";
import { GetV2SaleorAppsResponse } from "./marketplace.types"; import { getAppDetails, resolveInstallationOfAppstoreApp } from "./utils";
import { getAppDetails, resolveInstallationOfMarketplaceApp } from "./utils";
type AppDetails = ReturnType<typeof getAppDetails>; type AppDetails = ReturnType<typeof getAppDetails>;
describe("App utils app details", () => { describe("App utils app details", () => {
it("should return app details when required released app data passed", () => { it("should return app details when required released app data passed", () => {
// Arrange // Arrange
const app: GetV2SaleorAppsResponse.ReleasedSaleorApp = { const app: AppstoreApi.ReleasedSaleorApp = {
name: { name: {
en: "Test app", en: "Test app",
}, },
@ -68,7 +68,7 @@ describe("App utils app details", () => {
it("should return app details when required coming soon app data passed", () => { it("should return app details when required coming soon app data passed", () => {
// Arrange // Arrange
const app: GetV2SaleorAppsResponse.ComingSoonSaleorApp = { const app: AppstoreApi.ComingSoonSaleorApp = {
name: { name: {
en: "Test app", en: "Test app",
}, },
@ -109,7 +109,7 @@ describe("App utils app details", () => {
it("should return app details when required app pending installation data passed", () => { it("should return app details when required app pending installation data passed", () => {
// Arrange // Arrange
const app: GetV2SaleorAppsResponse.ReleasedSaleorApp = { const app: AppstoreApi.ReleasedSaleorApp = {
name: { name: {
en: "Test app", en: "Test app",
}, },
@ -176,7 +176,7 @@ describe("App utils app details", () => {
it("should return app details when required app failed installation data passed", () => { it("should return app details when required app failed installation data passed", () => {
// Arrange // Arrange
const app: GetV2SaleorAppsResponse.ReleasedSaleorApp = { const app: AppstoreApi.ReleasedSaleorApp = {
name: { name: {
en: "Test app", en: "Test app",
}, },
@ -259,7 +259,7 @@ describe("App utils app details", () => {
]; ];
// Act // Act
const installation = resolveInstallationOfMarketplaceApp( const installation = resolveInstallationOfAppstoreApp(
releasedApp, releasedApp,
appInstallationList, appInstallationList,
); );

View file

@ -3,40 +3,36 @@ import { getAppsConfig } from "@dashboard/config";
import { AppInstallationFragment, JobStatusEnum } from "@dashboard/graphql"; import { AppInstallationFragment, JobStatusEnum } from "@dashboard/graphql";
import { IntlShape } from "react-intl"; import { IntlShape } from "react-intl";
import { GetV2SaleorAppsResponse } from "./marketplace.types"; import { AppstoreApi } from "./appstore.types";
import { appsMessages } from "./messages"; import { appsMessages } from "./messages";
import { AppLink } from "./types"; import { AppLink } from "./types";
const getInstallableMarketplaceApps = ( const getInstallableAppstoreApps = (
marketplaceAppList?: GetV2SaleorAppsResponse.SaleorApp[], appstoreAppList?: AppstoreApi.SaleorApp[],
) => ) =>
marketplaceAppList?.filter( appstoreAppList?.filter(
app => "manifestUrl" in app || "githubForkUrl" in app, app => "manifestUrl" in app || "githubForkUrl" in app,
) as GetV2SaleorAppsResponse.ReleasedSaleorApp[] | undefined; ) as AppstoreApi.ReleasedSaleorApp[] | undefined;
const getComingSoonMarketplaceApps = ( const getComingSoonAppstoreApps = (appstoreAppList?: AppstoreApi.SaleorApp[]) =>
marketplaceAppList?: GetV2SaleorAppsResponse.SaleorApp[], appstoreAppList?.filter(
) =>
marketplaceAppList?.filter(
app => app =>
!("manifestUrl" in app) && !("manifestUrl" in app) &&
!("githubForkUrl" in app) && !("githubForkUrl" in app) &&
"releaseDate" in app, "releaseDate" in app,
) as GetV2SaleorAppsResponse.ComingSoonSaleorApp[] | undefined; ) as AppstoreApi.ComingSoonSaleorApp[] | undefined;
const getAppManifestUrl = ( const getAppManifestUrl = (appstoreApp: AppstoreApi.SaleorApp) => {
marketplaceApp: GetV2SaleorAppsResponse.SaleorApp, if ("manifestUrl" in appstoreApp) {
) => { return appstoreApp.manifestUrl;
if ("manifestUrl" in marketplaceApp) {
return marketplaceApp.manifestUrl;
} }
}; };
export const resolveInstallationOfMarketplaceApp = ( export const resolveInstallationOfAppstoreApp = (
marketplaceApp: GetV2SaleorAppsResponse.SaleorApp, appstoreApp: AppstoreApi.SaleorApp,
appInstallations?: AppInstallationFragment[], appInstallations?: AppInstallationFragment[],
) => { ) => {
const manifestUrl = getAppManifestUrl(marketplaceApp); const manifestUrl = getAppManifestUrl(appstoreApp);
if (manifestUrl) { if (manifestUrl) {
return appInstallations?.find( return appInstallations?.find(
@ -45,11 +41,11 @@ export const resolveInstallationOfMarketplaceApp = (
} }
}; };
export const getMarketplaceAppsLists = ( export const getAppstoreAppsLists = (
isMarketplaceAvailable: boolean, isAppstoreAvailable: boolean,
marketplaceAppList?: GetV2SaleorAppsResponse.SaleorApp[], appstoreAppList?: AppstoreApi.SaleorApp[],
) => { ) => {
if (!isMarketplaceAvailable) { if (!isAppstoreAvailable) {
return { return {
installableMarketplaceApps: [], installableMarketplaceApps: [],
comingSoonMarketplaceApps: [], comingSoonMarketplaceApps: [],
@ -57,9 +53,8 @@ export const getMarketplaceAppsLists = (
} }
return { return {
installableMarketplaceApps: installableMarketplaceApps: getInstallableAppstoreApps(appstoreAppList),
getInstallableMarketplaceApps(marketplaceAppList), comingSoonMarketplaceApps: getComingSoonAppstoreApps(appstoreAppList),
comingSoonMarketplaceApps: getComingSoonMarketplaceApps(marketplaceAppList),
}; };
}; };
@ -72,7 +67,7 @@ export const isAppInTunnel = (manifestUrl: string) =>
const prepareAppLinks = ( const prepareAppLinks = (
intl: IntlShape, intl: IntlShape,
app: GetV2SaleorAppsResponse.ReleasedSaleorApp, app: AppstoreApi.ReleasedSaleorApp,
): AppLink[] => [ ): AppLink[] => [
{ {
name: intl.formatMessage(appsMessages.repository), name: intl.formatMessage(appsMessages.repository),
@ -90,7 +85,7 @@ const prepareAppLinks = (
interface GetAppDetailsOpts { interface GetAppDetailsOpts {
intl: IntlShape; intl: IntlShape;
app: GetV2SaleorAppsResponse.SaleorApp; app: AppstoreApi.SaleorApp;
appInstallation?: AppInstallationFragment; appInstallation?: AppInstallationFragment;
navigateToAppInstallPage?: (url: string) => void; navigateToAppInstallPage?: (url: string) => void;
navigateToGithubForkPage?: (url?: string) => void; navigateToGithubForkPage?: (url?: string) => void;

View file

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

View file

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

View file

@ -18,13 +18,11 @@ import { RouteComponentProps } from "react-router-dom";
import { messages } from "./messages"; import { messages } from "./messages";
interface InstallAppCreateProps extends RouteComponentProps { interface Props extends RouteComponentProps {
params: AppInstallUrlQueryParams; params: AppInstallUrlQueryParams;
} }
export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({ export const AppInstallView: React.FC<Props> = ({ params }) => {
params,
}) => {
const [, setActiveInstallations] = useLocalStorage< const [, setActiveInstallations] = useLocalStorage<
Array<Record<"id" | "name", string>> Array<Record<"id" | "name", string>>
>("activeInstallations", []); >("activeInstallations", []);
@ -114,5 +112,3 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
</> </>
); );
}; };
export default InstallAppCreate;

View file

@ -0,0 +1 @@
export * from "./AppInstallView";

View file

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

View file

@ -2,7 +2,7 @@ import AppInProgressDeleteDialog from "@dashboard/apps/components/AppInProgressD
import AppListPage from "@dashboard/apps/components/AppListPage/AppListPage"; import AppListPage from "@dashboard/apps/components/AppListPage/AppListPage";
import { AppListContext, AppListContextValues } from "@dashboard/apps/context"; import { AppListContext, AppListContextValues } from "@dashboard/apps/context";
import useActiveAppsInstallations from "@dashboard/apps/hooks/useActiveAppsInstallations"; import useActiveAppsInstallations from "@dashboard/apps/hooks/useActiveAppsInstallations";
import useMarketplaceApps from "@dashboard/apps/hooks/useMarketplaceApps"; import useAppstoreApps from "@dashboard/apps/hooks/useAppstoreApps";
import { import {
AppListUrlDialog, AppListUrlDialog,
AppListUrlQueryParams, AppListUrlQueryParams,
@ -10,7 +10,7 @@ import {
} from "@dashboard/apps/urls"; } from "@dashboard/apps/urls";
import { import {
getAppInProgressName, getAppInProgressName,
getMarketplaceAppsLists, getAppstoreAppsLists,
} from "@dashboard/apps/utils"; } from "@dashboard/apps/utils";
import { getAppsConfig } from "@dashboard/config"; import { getAppsConfig } from "@dashboard/config";
import { import {
@ -36,11 +36,11 @@ import { useIntl } from "react-intl";
import { messages } from "./messages"; import { messages } from "./messages";
interface AppsListProps { interface Props {
params: AppListUrlQueryParams; params: AppListUrlQueryParams;
} }
export const AppsList: React.FC<AppsListProps> = ({ params }) => { export const AppListView: React.FC<Props> = ({ params }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl(); const intl = useIntl();
@ -139,12 +139,12 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
[navigate, openModal], [navigate, openModal],
); );
const { data: marketplaceAppList, error } = useMarketplaceApps( const { data: marketplaceAppList, error } = useAppstoreApps(
AppsConfig.marketplaceApiUri, AppsConfig.marketplaceApiUri,
); );
const { installableMarketplaceApps, comingSoonMarketplaceApps } = const { installableMarketplaceApps, comingSoonMarketplaceApps } =
getMarketplaceAppsLists(!!AppsConfig.marketplaceApiUri, marketplaceAppList); getAppstoreAppsLists(!!AppsConfig.marketplaceApiUri, marketplaceAppList);
const appsInstallations = appsInProgressData?.appsInstallations; const appsInstallations = appsInProgressData?.appsInstallations;
const installedApps = mapEdgesToItems(installedAppsData?.apps); const installedApps = mapEdgesToItems(installedAppsData?.apps);
@ -175,4 +175,3 @@ export const AppsList: React.FC<AppsListProps> = ({ params }) => {
</AppListContext.Provider> </AppListContext.Provider>
); );
}; };
export default AppsList;

View file

@ -0,0 +1 @@
export * from "./AppListView";

View file

@ -27,12 +27,12 @@ import {
} from "../../urls"; } from "../../urls";
import { messages } from "./messages"; import { messages } from "./messages";
interface AppDetailsProps { interface Props {
id: string; id: string;
params: AppDetailsUrlQueryParams; params: AppDetailsUrlQueryParams;
} }
export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => { export const AppManageView: React.FC<Props> = ({ id, params }) => {
const client = useApolloClient(); const client = useApolloClient();
const { data, loading, refetch } = useAppQuery({ const { data, loading, refetch } = useAppQuery({
displayLoader: true, displayLoader: true,
@ -161,5 +161,3 @@ export const AppDetails: React.FC<AppDetailsProps> = ({ id, params }) => {
</> </>
); );
}; };
export default AppDetails;

View file

@ -0,0 +1 @@
export * from "./AppManageView";

View file

@ -58,4 +58,3 @@ export const AppView: React.FC<AppProps> = ({ id }) => {
/> />
); );
}; };
export default AppView;

View file

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

4
src/apps/views/index.ts Normal file
View file

@ -0,0 +1,4 @@
export * from "./AppInstallView";
export * from "./AppView";
export * from "./AppListView";
export * from "./AppManageView";

View file

@ -16,7 +16,7 @@ import TagManager from "react-gtm-module";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { BrowserRouter, Route, Switch } from "react-router-dom"; import { BrowserRouter, Route, Switch } from "react-router-dom";
import AppsSection from "./apps"; import { AppsSectionRoot } from "./apps";
import { ExternalAppProvider } from "./apps/components/ExternalAppContext"; import { ExternalAppProvider } from "./apps/components/ExternalAppContext";
import { AppSections } from "./apps/urls"; import { AppSections } from "./apps/urls";
import AttributeSection from "./attributes"; import AttributeSection from "./attributes";
@ -286,7 +286,7 @@ const Routes: React.FC = () => {
<SectionRoute <SectionRoute
permissions={[PermissionEnum.MANAGE_APPS]} permissions={[PermissionEnum.MANAGE_APPS]}
path={AppSections.appsSection} path={AppSections.appsSection}
component={AppsSection} component={AppsSectionRoot}
/> />
<SectionRoute <SectionRoute
permissions={[PermissionEnum.MANAGE_PRODUCTS]} permissions={[PermissionEnum.MANAGE_PRODUCTS]}