From c922dfb6fecf7a13bc08df38b2b40d4ac613377f Mon Sep 17 00:00:00 2001 From: Wojciech Mista Date: Wed, 8 Jun 2022 08:44:28 +0200 Subject: [PATCH] Application Marketplace available in dashboard (#2054) * Add marketplace * Update messages * Extract styles * Update test env * Update config and rename uri * Update template * Trigger CI * Possible fix * Update deploy yml * Add marketplace to staging * Fix responsiveness * Trigger CI * Fix navigation tests (#2081) * fix navigate through shop * wait for progress bar not to exist * simplify code * add wait * Fix navigate through shop (#2076) * fix navigate through shop * wait for progress bar not to exist * simplify code * Refine App about section (#2056) * Trigger CI * Trigger CI * Trigger CI * Trigger CI * Fix app activation/deactivation * Hide configuration url and policy if theres none * Remove about and support links * Remove unused code * Update stories and tests * Update messages * Fix privacy policy * Fix activation and "open app" * Update tests Co-authored-by: Karolina Rakoczy --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/workflows/deploy-master-staging.yaml | 3 +- .github/workflows/test-env-deploy.yml | 11 ++- Dockerfile | 3 +- Dockerfile.dev | 1 + .../account/left-menu/left-menu-selectors.js | 7 ++ cypress/fixtures/permissions.js | 3 - cypress/fixtures/permissionsUsers.js | 2 +- cypress/integration/navigation.js | 44 ++++++++++- locale/defaultMessages.json | 16 +--- .../AppDetailsPage/AppDetailsPage.stories.tsx | 1 - .../AppDetailsPage/AppDetailsPage.tsx | 52 +++++-------- src/apps/components/AppFrame/AppFrame.tsx | 5 +- src/apps/components/AppPage/AppPage.tsx | 26 ------- src/apps/urls.ts | 3 - src/apps/views/AppDetails/AppDetails.tsx | 5 +- src/components/AppLayout/menuStructure.ts | 47 ++++++++++-- src/config.ts | 1 + src/index.tsx | 7 ++ src/intl.ts | 5 ++ src/marketplace/index.tsx | 38 ++++++++++ src/marketplace/styles.ts | 15 ++++ src/marketplace/urls.ts | 3 + .../__snapshots__/Stories.test.ts.snap | 75 ------------------- webpack.config.js | 1 + 25 files changed, 201 insertions(+), 174 deletions(-) create mode 100644 src/marketplace/index.tsx create mode 100644 src/marketplace/styles.ts create mode 100644 src/marketplace/urls.ts diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e681b1a3a..a66d76c93 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,3 +25,4 @@ greatly reduce the amount of work needed to review your work. --> Modify API_URI if you want test instance to use custom backend. CYPRESS_API_URI is optional, use when necessary. --> API_URI=https://automation-dashboard.staging.saleor.cloud/graphql/ +MARKETPLACE_URL=https://marketplace-gray.vercel.app/ diff --git a/.github/workflows/deploy-master-staging.yaml b/.github/workflows/deploy-master-staging.yaml index c89146b12..77ed23bc7 100644 --- a/.github/workflows/deploy-master-staging.yaml +++ b/.github/workflows/deploy-master-staging.yaml @@ -10,6 +10,7 @@ jobs: runs-on: ubuntu-20.04 env: API_URI: /graphql/ + MARKETPLACE_URL: "https://marketplace-gray.vercel.app/" APP_MOUNT_URI: /dashboard/ STATIC_URL: /dashboard/static/ SENTRY_ORG: saleor @@ -52,4 +53,4 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLOUD_DEPLOYMENTS_WEBHOOK_URL }} JOB_TITLE: "Dashboard deployment to ${{ env.ENVIRONMENT }}" run: | - python3 ./.github/workflows/notify/notify-slack.py \ No newline at end of file + python3 ./.github/workflows/notify/notify-slack.py diff --git a/.github/workflows/test-env-deploy.yml b/.github/workflows/test-env-deploy.yml index 31f750c4b..d9125ed91 100644 --- a/.github/workflows/test-env-deploy.yml +++ b/.github/workflows/test-env-deploy.yml @@ -57,11 +57,21 @@ jobs: pattern: (http|https)://[a-zA-Z0-9.-]+/graphql/? run: | echo "::set-output name=custom_api_uri::$(echo $pull_request_body | grep -Eo "$prefix$pattern" | sed s/$prefix// | head -n 1)" + - name: Get MARKETPLACE_URL + id: marketplace_url + # Search for MARKETPLACE_URL in PR description + env: + pull_request_body: ${{ github.event.pull_request.body }} + prefix: MARKETPLACE_URL= + pattern: (http|https)://[a-zA-Z0-9.-]+/? + run: | + echo "::set-output name=custom_marketplace_url::$(echo $pull_request_body | grep -Eo "$prefix$pattern" | sed s/$prefix// | head -n 1)" - name: Run build env: # Use custom API_URI or the default one API_URI: ${{ steps.api_uri.outputs.custom_api_uri || 'https://qa.staging.saleor.cloud/graphql/' }} + MARKETPLACE_URL: ${{ steps.marketplace_url.outputs.custom_marketplace_url }} APP_MOUNT_URI: / STATIC_URL: / IS_CLOUD_INSTANCE: true @@ -113,7 +123,6 @@ jobs: env_url: https://${{ steps.set-domain.outputs.domain }}/storybook/index.html deployment_id: ${{ steps.storybook-deployment.outputs.deployment_id }} - cypress-run-critical: needs: deploy runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile index ecf980ee6..45c2c3cec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,12 @@ RUN npm install COPY . . ARG APP_MOUNT_URI ARG API_URI +ARG MARKETPLACE_URL ARG STATIC_URL ENV API_URI ${API_URI:-http://localhost:8000/graphql/} ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/dashboard/} ENV STATIC_URL ${STATIC_URL:-/dashboard/} -RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build +RUN STATIC_URL=${STATIC_URL} API_URI=${API_URI} MARKETPLACE_URL=${MARKETPLACE_URL} APP_MOUNT_URI=${APP_MOUNT_URI} npm run build FROM nginx:stable WORKDIR /app diff --git a/Dockerfile.dev b/Dockerfile.dev index 3147c17dd..e2d65bc6c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -5,6 +5,7 @@ RUN npm install COPY . . ARG APP_MOUNT_URI ARG API_URI +ARG MARKETPLACE_URL ARG STATIC_URL ENV API_URI ${API_URI:-http://localhost:8000/graphql/} ENV APP_MOUNT_URI ${APP_MOUNT_URI:-/} diff --git a/cypress/elements/account/left-menu/left-menu-selectors.js b/cypress/elements/account/left-menu/left-menu-selectors.js index a6aa78a56..df7c06537 100644 --- a/cypress/elements/account/left-menu/left-menu-selectors.js +++ b/cypress/elements/account/left-menu/left-menu-selectors.js @@ -5,6 +5,7 @@ export const LEFT_MENU_SELECTORS = { home: "[data-test='menu-item-label'][data-test-id='home']", orders: "[data-test='menu-item-label'][data-test-id='orders']", discounts: "[data-test='menu-item-label'][data-test-id='discounts']", + appSection: "[data-test='menu-item-label'][data-test-id='apps_section']", app: "[data-test='menu-item-label'][data-test-id='apps']", translations: "[data-test='menu-item-label'][data-test-id='translations']", customers: "[data-test='menu-item-label'][data-test-id='customers']" @@ -22,3 +23,9 @@ export const CATALOG = { categories: "[data-test='submenu-item-label'][data-test-id='categories']", collections: "[data-test='submenu-item-label'][data-test-id='collections']" }; + +export const APP_MENU_SELECTORS = { + app: "[data-test='submenu-item-label'][data-test-id='apps']" +}; + +export const appCommonSelector = "[data-test-id*='apps']"; diff --git a/cypress/fixtures/permissions.js b/cypress/fixtures/permissions.js index 478433887..a90e1b56f 100644 --- a/cypress/fixtures/permissions.js +++ b/cypress/fixtures/permissions.js @@ -7,9 +7,6 @@ const configurationAsParent = { }; export const PERMISSIONS = { - app: { - permissionSelectors: [menuSelectors.LEFT_MENU_SELECTORS.app] - }, channel: { parent: configurationAsParent, permissionSelectors: [CONFIGURATION_SELECTORS.channels] diff --git a/cypress/fixtures/permissionsUsers.js b/cypress/fixtures/permissionsUsers.js index 68cb1b4f8..d5c4b6d77 100644 --- a/cypress/fixtures/permissionsUsers.js +++ b/cypress/fixtures/permissionsUsers.js @@ -9,7 +9,7 @@ export const PERMISSIONS_OPTIONS = { }, app: { user: ONE_PERMISSION_USERS.app, - permissions: [PERMISSIONS.app], + permissions: [], testCase: "TC: SALEOR_3402" }, channel: { diff --git a/cypress/integration/navigation.js b/cypress/integration/navigation.js index e22d8913c..30844a39a 100644 --- a/cypress/integration/navigation.js +++ b/cypress/integration/navigation.js @@ -1,17 +1,55 @@ /// /// +import { + APP_MENU_SELECTORS, + appCommonSelector, + LEFT_MENU_SELECTORS +} from "../elements/account/left-menu/left-menu-selectors"; import { PERMISSIONS_OPTIONS } from "../fixtures/permissionsUsers"; import filterTests from "../support/filterTests"; import * as permissionsSteps from "../support/pages/permissionsPage"; describe("As a staff user I want to navigate through shop using different permissions", () => { - Object.keys(PERMISSIONS_OPTIONS).forEach(key => { + const permissionsOptions = PERMISSIONS_OPTIONS; + + before(() => { + cy.loginUserViaRequest() + .visit("/") + .get(appCommonSelector) + .should("be.visible") + .get("body") + .then($body => { + // This will be deleted when Marketplace is released + // Consider this solution as temporary + + let appPermissions; + + if ($body.find(LEFT_MENU_SELECTORS.appSection).length) { + appPermissions = { + parent: { + parentMenuSelector: LEFT_MENU_SELECTORS.appSection, + parentSelectors: [APP_MENU_SELECTORS] + }, + permissionSelectors: [APP_MENU_SELECTORS.app] + }; + } else { + appPermissions = { + permissionSelectors: [LEFT_MENU_SELECTORS.app] + }; + } + + permissionsOptions.all.permissions.push(appPermissions); + permissionsOptions.app.permissions = [appPermissions]; + }); + }); + + Object.keys(permissionsOptions).forEach(key => { const tags = key === "all" ? ["critical", "all", "refactored"] : ["all", "refactored"]; filterTests({ definedTags: tags }, () => { - it(`should be able to navigate through shop as a staff member using ${key} permission. ${PERMISSIONS_OPTIONS[key].testCase}`, () => { - const permissionOption = PERMISSIONS_OPTIONS[key]; + it(`should be able to navigate through shop as a staff member using ${key} permission. ${permissionsOptions[key].testCase}`, () => { + const permissionOption = permissionsOptions[key]; const permissions = permissionOption.permissions; cy.clearSessionData(); permissionsSteps.navigateToAllAvailablePageAndCheckIfDisplayed( diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 32bbffc12..345c9468b 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1218,10 +1218,6 @@ "context": "WarehouseSettings public stock label", "string": "Public Stock" }, - "89PSdB": { - "context": "link", - "string": "Edit settings" - }, "8B8E+3": { "context": "navigator placeholder", "string": "Order Number" @@ -1929,6 +1925,10 @@ "context": "date time attribute type", "string": "Date Time" }, + "E+M17x": { + "context": "marketplace section name", + "string": "Marketplace" + }, "E22x4H": { "context": "no card defuned alert message", "string": "You haven’t defined a gift card product!" @@ -5700,10 +5700,6 @@ "hX5PAb": { "string": "No results found" }, - "hdcGSJ": { - "context": "button", - "string": "Support/FAQ" - }, "hkENym": { "string": "Customer" }, @@ -6204,10 +6200,6 @@ "llBnr+": { "string": "Warehouse Name" }, - "llC1q8": { - "context": "button", - "string": "App home page" - }, "lm9NSK": { "context": "password reset, button", "string": "Send an email with reset link" diff --git a/src/apps/components/AppDetailsPage/AppDetailsPage.stories.tsx b/src/apps/components/AppDetailsPage/AppDetailsPage.stories.tsx index 78ae94664..644e6faf4 100644 --- a/src/apps/components/AppDetailsPage/AppDetailsPage.stories.tsx +++ b/src/apps/components/AppDetailsPage/AppDetailsPage.stories.tsx @@ -9,7 +9,6 @@ const props: AppDetailsPageProps = { data: appDetails, loading: false, navigateToApp: () => undefined, - navigateToAppSettings: () => undefined, onAppActivateOpen: () => undefined, onAppDeactivateOpen: () => undefined }; diff --git a/src/apps/components/AppDetailsPage/AppDetailsPage.tsx b/src/apps/components/AppDetailsPage/AppDetailsPage.tsx index 2324b6869..7c25ec3bb 100644 --- a/src/apps/components/AppDetailsPage/AppDetailsPage.tsx +++ b/src/apps/components/AppDetailsPage/AppDetailsPage.tsx @@ -16,7 +16,6 @@ import { FormattedMessage, useIntl } from "react-intl"; import ReactMarkdown from "react-markdown"; import activateIcon from "../../../../assets/images/activate-icon.svg"; -import settingsIcon from "../../../../assets/images/settings-icon.svg"; import supportIcon from "../../../../assets/images/support-icon.svg"; import { useStyles } from "../../styles"; import DeactivatedText from "../DeactivatedText"; @@ -25,7 +24,6 @@ export interface AppDetailsPageProps { loading: boolean; data: AppQuery["app"]; navigateToApp: () => void; - navigateToAppSettings: () => void; onAppActivateOpen: () => void; onAppDeactivateOpen: () => void; } @@ -34,7 +32,6 @@ export const AppDetailsPage: React.FC = ({ data, loading, navigateToApp, - navigateToAppSettings, onAppActivateOpen, onAppDeactivateOpen }) => { @@ -76,20 +73,6 @@ export const AppDetailsPage: React.FC = ({ description="link" /> - {data.configurationUrl && ( - - - - - )} = ({ - - - - {!loading ? ( - <> - {data?.dataPrivacy} + {(loading || data?.dataPrivacyUrl) && ( + + + + {!loading ? ( = ({ description="app privacy policy link" /> - - ) : ( - - )} - - + ) : ( + + )} + + + )} ); diff --git a/src/apps/components/AppFrame/AppFrame.tsx b/src/apps/components/AppFrame/AppFrame.tsx index a74a02109..acf50dd79 100644 --- a/src/apps/components/AppFrame/AppFrame.tsx +++ b/src/apps/components/AppFrame/AppFrame.tsx @@ -1,5 +1,6 @@ import useShop from "@saleor/hooks/useShop"; import { useTheme } from "@saleor/macaw-ui"; +import clsx from "clsx"; import React from "react"; import urlJoin from "url-join"; @@ -10,6 +11,7 @@ interface Props { src: string; appToken: string; appId: string; + className?: string; onLoad?(): void; onError?(): void; } @@ -20,6 +22,7 @@ export const AppFrame: React.FC = ({ src, appToken, appId, + className, onLoad, onError }) => { @@ -55,7 +58,7 @@ export const AppFrame: React.FC = ({ src={urlJoin(src, `?domain=${shop.domain.host}&id=${appId}`)} onError={onError} onLoad={handleLoad} - className={classes.iframe} + className={clsx(classes.iframe, className)} sandbox="allow-same-origin allow-forms allow-scripts" /> ); diff --git a/src/apps/components/AppPage/AppPage.tsx b/src/apps/components/AppPage/AppPage.tsx index 09754dfd5..9394178fe 100644 --- a/src/apps/components/AppPage/AppPage.tsx +++ b/src/apps/components/AppPage/AppPage.tsx @@ -70,32 +70,6 @@ export const AppPage: React.FC = ({ description="button" /> - - diff --git a/src/apps/urls.ts b/src/apps/urls.ts index ca7d3292a..6ad3ea2fc 100644 --- a/src/apps/urls.ts +++ b/src/apps/urls.ts @@ -55,9 +55,6 @@ export const appInstallUrl = appInstallPath; export const appDetailsUrl = (id: string, params?: AppDetailsUrlQueryParams) => appDetailsPath(encodeURIComponent(id)) + "?" + stringifyQs(params); -export const appSettingsUrl = (id: string, params?: AppDetailsUrlQueryParams) => - appSettingsPath(encodeURIComponent(id)) + "?" + stringifyQs(params); - export const appUrl = (id: string, params?: AppDetailsUrlQueryParams) => appPath(encodeURIComponent(id)) + "?" + stringifyQs(params); export const appDeepUrl = ( diff --git a/src/apps/views/AppDetails/AppDetails.tsx b/src/apps/views/AppDetails/AppDetails.tsx index 98ea54fa3..38fd4eba5 100644 --- a/src/apps/views/AppDetails/AppDetails.tsx +++ b/src/apps/views/AppDetails/AppDetails.tsx @@ -16,9 +16,9 @@ import AppActivateDialog from "../../components/AppActivateDialog"; import AppDeactivateDialog from "../../components/AppDeactivateDialog"; import AppDetailsPage from "../../components/AppDetailsPage"; import { + appDetailsUrl, AppDetailsUrlDialog, AppDetailsUrlQueryParams, - appSettingsUrl, appsListPath, appUrl } from "../../urls"; @@ -88,7 +88,7 @@ export const AppDetails: React.FC = ({ id, params }) => { const [openModal, closeModal] = createDialogActionHandlers< AppDetailsUrlDialog, AppDetailsUrlQueryParams - >(navigate, params => appUrl(id, params), params); + >(navigate, params => appDetailsUrl(id, params), params); const handleActivateConfirm = () => { activateApp(mutationOpts); @@ -121,7 +121,6 @@ export const AppDetails: React.FC = ({ id, params }) => { data={data?.app} loading={loading} navigateToApp={() => navigate(appUrl(id))} - navigateToAppSettings={() => navigate(appSettingsUrl(id))} onAppActivateOpen={() => openModal("app-activate")} onAppDeactivateOpen={() => openModal("app-deactivate")} /> diff --git a/src/components/AppLayout/menuStructure.ts b/src/components/AppLayout/menuStructure.ts index 80562e547..a2dc79102 100644 --- a/src/components/AppLayout/menuStructure.ts +++ b/src/components/AppLayout/menuStructure.ts @@ -11,12 +11,14 @@ import { extensionMountPoints, useExtensions } from "@saleor/apps/useExtensions"; +import { MARKETPLACE_URL } from "@saleor/config"; import { configurationMenuUrl } from "@saleor/configuration"; import { getConfigMenuItemsPermissions } from "@saleor/configuration/utils"; import { giftCardListUrl } from "@saleor/giftCards/urls"; import { PermissionEnum, UserFragment } from "@saleor/graphql"; import { commonMessages, sectionNames } from "@saleor/intl"; import { SidebarMenuItem } from "@saleor/macaw-ui"; +import { marketplaceUrl } from "@saleor/marketplace/urls"; import { pageListPath } from "@saleor/pages/urls"; import { IntlShape } from "react-intl"; @@ -55,6 +57,42 @@ function useMenuStructure( label: intl.formatMessage(sectionNames.appExtensions) }; + // This will be deleted when Marketplace is released + // Consider this solution as temporary + const getAppSection = () => { + if (MARKETPLACE_URL) { + return { + ariaLabel: "apps_section", + iconSrc: appsIcon, + label: intl.formatMessage(sectionNames.apps), + permissions: [PermissionEnum.MANAGE_APPS], + id: "apps_section", + children: [ + { + label: intl.formatMessage(sectionNames.apps), + id: "apps", + url: appsListPath + }, + { + ariaLabel: "marketplace", + label: intl.formatMessage(sectionNames.marketplace), + id: "marketplace", + url: marketplaceUrl + } + ] + }; + } + + return { + ariaLabel: "apps", + iconSrc: appsIcon, + label: intl.formatMessage(sectionNames.apps), + permissions: [PermissionEnum.MANAGE_APPS], + id: "apps", + url: appsListPath + }; + }; + const menuItems: FilterableMenuItem[] = [ { ariaLabel: "home", @@ -202,14 +240,7 @@ function useMenuStructure( id: "pages", url: pageListPath }, - { - ariaLabel: "apps", - iconSrc: appsIcon, - label: intl.formatMessage(sectionNames.apps), - permissions: [PermissionEnum.MANAGE_APPS], - id: "apps", - url: appsListPath - }, + getAppSection(), { ariaLabel: "translations", children: extensions.NAVIGATION_TRANSLATIONS.length > 0 && [ diff --git a/src/config.ts b/src/config.ts index 876dfcfbe..9b0de7d8c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -8,6 +8,7 @@ export const APP_DEFAULT_URI = "/"; export const API_URI = process.env.API_URI; export const SW_INTERVAL = parseInt(process.env.SW_INTERVAL, 10); export const IS_CLOUD_INSTANCE = process.env.IS_CLOUD_INSTANCE === "true"; +export const MARKETPLACE_URL = process.env.MARKETPLACE_URL; export const DEFAULT_INITIAL_SEARCH_DATA: SearchVariables = { after: null, diff --git a/src/index.tsx b/src/index.tsx index 2f2e89389..9e63e48f5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -47,6 +47,8 @@ import { giftCardsSectionUrlName } from "./giftCards/urls"; import { apolloClient, saleorClient } from "./graphql/client"; import HomePage from "./home"; import { commonMessages } from "./intl"; +import MarketplaceSection from "./marketplace"; +import { marketplaceUrl } from "./marketplace/urls"; import NavigationSection from "./navigation"; import { navigationSection } from "./navigation/urls"; import { NotFound } from "./NotFound"; @@ -250,6 +252,11 @@ const Routes: React.FC = () => { path={appsSection} component={AppsSection} /> + { + const classes = useStyles(); + const intl = useIntl(); + const navigate = useNavigator(); + + if (!MARKETPLACE_URL) { + return navigate("/")} />; + } + + return ( + <> + + + + + + ); +}; + +export default Component; diff --git a/src/marketplace/styles.ts b/src/marketplace/styles.ts new file mode 100644 index 000000000..024af05c0 --- /dev/null +++ b/src/marketplace/styles.ts @@ -0,0 +1,15 @@ +import { makeStyles } from "@saleor/macaw-ui"; + +const useStyles = makeStyles( + () => ({ + iframe: { + height: "100vh", + position: "sticky" + } + }), + { + name: "marketplaceStyles" + } +); + +export { useStyles }; diff --git a/src/marketplace/urls.ts b/src/marketplace/urls.ts new file mode 100644 index 000000000..b190437c6 --- /dev/null +++ b/src/marketplace/urls.ts @@ -0,0 +1,3 @@ +const marketplaceSection = "/marketplace/"; + +export const marketplaceUrl = marketplaceSection; diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 72ba092b5..93471c493 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -24699,34 +24699,6 @@ exports[`Storyshots Views / Apps / App default 1`] = ` About - - - App home page - - - - - Support/FAQ - -
- - - - App home page - - - - - Support/FAQ - -