From 0b6b1695767e64894e32225ba8447ee65604eb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Mon, 11 Mar 2024 16:40:47 +0100 Subject: [PATCH 01/21] Allow user without app permission access apps view --- src/components/Sidebar/menu/hooks/useMenuStructure.tsx | 2 +- src/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Sidebar/menu/hooks/useMenuStructure.tsx b/src/components/Sidebar/menu/hooks/useMenuStructure.tsx index 8e98a5b1489..658a09f33ed 100644 --- a/src/components/Sidebar/menu/hooks/useMenuStructure.tsx +++ b/src/components/Sidebar/menu/hooks/useMenuStructure.tsx @@ -56,7 +56,7 @@ export function useMenuStructure() { const getAppSection = (): SidebarMenuItem => ({ icon: , label: intl.formatMessage(sectionNames.apps), - permissions: [PermissionEnum.MANAGE_APPS], + permissions: [], id: "apps", url: AppPaths.appListPath, type: "item", diff --git a/src/index.tsx b/src/index.tsx index 0f217414392..3407c709f13 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -287,7 +287,7 @@ const Routes: React.FC = () => { matchPermission="any" /> From e88d8e832b63fc7a660919344a05e95049bb43b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 12 Mar 2024 11:13:29 +0100 Subject: [PATCH 02/21] Use css subgrid instead of multiple map loops --- src/apps/components/AllAppList/AllAppList.tsx | 19 ++-- .../AppListRow/AppListCardActions.tsx | 13 +-- .../AppListRow/AppListCardDescription.tsx | 10 +-- .../AppListRow/AppListCardIntegrations.tsx | 6 -- .../AppListRow/AppListCardLinks.tsx | 14 +-- src/apps/components/AppListRow/AppListRow.tsx | 90 +++++++++---------- 6 files changed, 55 insertions(+), 97 deletions(-) diff --git a/src/apps/components/AllAppList/AllAppList.tsx b/src/apps/components/AllAppList/AllAppList.tsx index 636704746c5..9c349df91be 100644 --- a/src/apps/components/AllAppList/AllAppList.tsx +++ b/src/apps/components/AllAppList/AllAppList.tsx @@ -1,8 +1,7 @@ import { AppstoreApi } from "@dashboard/apps/appstore.types"; import { AppInstallationFragment } from "@dashboard/graphql"; import { Skeleton } from "@material-ui/lab"; -import { Box } from "@saleor/macaw-ui-next"; -import chunk from "lodash/chunk"; +import { Box, useTheme } from "@saleor/macaw-ui-next"; import React from "react"; import AppListRow from "../AppListRow"; @@ -20,18 +19,24 @@ const AllAppList: React.FC = ({ navigateToAppInstallPage, navigateToGithubForkPage, }) => { - const appsPairs = React.useMemo(() => chunk(appList, 2), [appList]); + const { themeValues } = useTheme(); if (!appList) { return ; } return ( - - {appsPairs.map(appPair => ( + + {appList.map(app => ( = ({ } return ( - + {githubForkHandler && ( )} - {installHandler && IS_CLOUD_INSTANCE && ( - - )} - {installHandler && !IS_CLOUD_INSTANCE && ( - - - - - - - - - {intl.formatMessage(messages.installationCloudOnly)} - - - )} + + + {installationPending && ( void; +} + +export const AppListCardInstallButton = ({ + installHandler, +}: AppListCardInstallButtonProps) => { + const intl = useIntl(); + const { user } = useUser(); + const hasManageAppsPermission = hasAnyPermissions( + [PermissionEnum.MANAGE_APPS], + user, + ); + + if (!hasManageAppsPermission) { + return ( + + + + + + + + + {intl.formatMessage(messages.installationPermissionRequired)} + + + ); + } + + if (installHandler && IS_CLOUD_INSTANCE) { + return ( + + ); + } + + if (installHandler && !IS_CLOUD_INSTANCE) { + return ( + + + + + + + + + {intl.formatMessage(messages.installationCloudOnly)} + + + ); + } +}; diff --git a/src/apps/components/AppListRow/AppListRow.test.tsx b/src/apps/components/AppListRow/AppListRow.test.tsx index 1fb9fa2f024..6b29675130a 100644 --- a/src/apps/components/AppListRow/AppListRow.test.tsx +++ b/src/apps/components/AppListRow/AppListRow.test.tsx @@ -32,9 +32,6 @@ jest.mock("@dashboard/config", () => { }; }); -const releasedAppPair = [releasedApp, releasedApp]; -const comingSoonAppPair = [comingSoonApp, comingSoonApp]; - describe("Apps AppListRow", () => { it("displays released app details when released app data passed", () => { // Arrange @@ -43,7 +40,7 @@ describe("Apps AppListRow", () => { ); render( - + , ); const name = screen.queryAllByText(releasedApp.name.en); @@ -75,7 +72,7 @@ describe("Apps AppListRow", () => { render( @@ -103,7 +100,7 @@ describe("Apps AppListRow", () => { ); render( - + , ); const name = screen.queryAllByText(comingSoonApp.name.en); @@ -136,7 +133,7 @@ describe("Apps AppListRow", () => { }; render( - + , ); const logo = screen.getAllByTestId("app-logo"); @@ -162,7 +159,7 @@ describe("Apps AppListRow", () => { }; render( - + , ); const logo = screen.getAllByTestId("app-logo"); @@ -182,7 +179,7 @@ describe("Apps AppListRow", () => { render( , @@ -201,7 +198,7 @@ describe("Apps AppListRow", () => { render( , @@ -228,7 +225,7 @@ describe("Apps AppListRow", () => { render( , diff --git a/src/apps/components/AppListRow/messages.ts b/src/apps/components/AppListRow/messages.ts index 99b34043791..d78d72171be 100644 --- a/src/apps/components/AppListRow/messages.ts +++ b/src/apps/components/AppListRow/messages.ts @@ -21,4 +21,9 @@ export const messages = defineMessages({ description: "description", id: "IEpmGQ", }, + installationPermissionRequired: { + defaultMessage: "You need permission to install apps", + id: "Xo4tPo", + description: "description", + }, }); diff --git a/src/apps/components/AppPage/AppPage.tsx b/src/apps/components/AppPage/AppPage.tsx index 2d8bc7d935c..e159f79d91e 100644 --- a/src/apps/components/AppPage/AppPage.tsx +++ b/src/apps/components/AppPage/AppPage.tsx @@ -1,10 +1,12 @@ +import { useUser } from "@dashboard/auth"; +import { hasAnyPermissions } from "@dashboard/auth/misc"; import { borderHeight, topBarHeight, } from "@dashboard/components/AppLayout/consts"; import { DetailPageLayout } from "@dashboard/components/Layouts"; import { APP_VERSION } from "@dashboard/config"; -import { AppQuery } from "@dashboard/graphql"; +import { AppQuery, PermissionEnum } from "@dashboard/graphql"; import useShop from "@dashboard/hooks/useShop"; import { Box } from "@saleor/macaw-ui-next"; import React from "react"; @@ -26,6 +28,12 @@ export const AppPage: React.FC = ({ refetch, }) => { const shop = useShop(); + const { user } = useUser(); + + const hasManageAppsPermission = hasAnyPermissions( + [PermissionEnum.MANAGE_APPS], + user, + ); /** * TODO Make some loading state @@ -43,6 +51,7 @@ export const AppPage: React.FC = ({ homepageUrl={data?.homepageUrl} author={data?.author} appLogoUrl={data?.brand?.logo.default} + showMangeAppButton={hasManageAppsPermission} /> = ({ appList, appInstallationList, }) => { - if (!appList || !appInstallationList) { + const { user } = useUser(); + const hasAppManagedPermissions = hasAnyPermissions( + [PermissionEnum.MANAGE_APPS], + user, + ); + + if (!appList || (hasAppManagedPermissions && !appInstallationList)) { return ; } diff --git a/src/apps/mutations.ts b/src/apps/mutations.ts index 598774d8919..b2120f5a6ac 100644 --- a/src/apps/mutations.ts +++ b/src/apps/mutations.ts @@ -1,7 +1,10 @@ import { gql } from "@apollo/client"; export const appCreateMutation = gql` - mutation AppCreate($input: AppInput!) { + mutation AppCreate( + $input: AppInput! + $hasManagedAppsPermission: Boolean! = true + ) { appCreate(input: $input) { authToken app { @@ -15,7 +18,7 @@ export const appCreateMutation = gql` `; export const appDeleteMutation = gql` - mutation AppDelete($id: ID!) { + mutation AppDelete($id: ID!, $hasManagedAppsPermission: Boolean! = true) { appDelete(id: $id) { app { ...App @@ -89,7 +92,11 @@ export const appRetryInstallMutation = gql` `; export const appUpdateMutation = gql` - mutation AppUpdate($id: ID!, $input: AppInput!) { + mutation AppUpdate( + $id: ID! + $input: AppInput! + $hasManagedAppsPermission: Boolean! = true + ) { appUpdate(id: $id, input: $input) { app { ...App diff --git a/src/apps/queries.ts b/src/apps/queries.ts index 8fbd58b715e..4bbc4ebc18a 100644 --- a/src/apps/queries.ts +++ b/src/apps/queries.ts @@ -42,7 +42,7 @@ export const appsInProgressList = gql` `; export const appDetails = gql` - query App($id: ID!) { + query App($id: ID!, $hasManagedAppsPermission: Boolean!) { app(id: $id) { ...App aboutApp diff --git a/src/apps/views/AppListView/AppListView.tsx b/src/apps/views/AppListView/AppListView.tsx index f5294bf87fd..554f926c269 100644 --- a/src/apps/views/AppListView/AppListView.tsx +++ b/src/apps/views/AppListView/AppListView.tsx @@ -12,12 +12,15 @@ import { getAppInProgressName, getAppstoreAppsLists, } from "@dashboard/apps/utils"; +import { useUser } from "@dashboard/auth"; +import { hasAnyPermissions } from "@dashboard/auth/misc"; import { getAppsConfig } from "@dashboard/config"; import { AppInstallationFragment, AppSortField, AppTypeEnum, OrderDirection, + PermissionEnum, useAppsInstallationsQuery, useAppsListQuery, } from "@dashboard/graphql"; @@ -44,6 +47,7 @@ export const AppListView: React.FC = ({ params }) => { const navigate = useNavigator(); const notify = useNotifier(); const intl = useIntl(); + const { user } = useUser(); const [openModal, closeModal] = createDialogActionHandlers< AppListUrlDialog, @@ -86,6 +90,7 @@ export const AppListView: React.FC = ({ params }) => { const { data: appsInProgressData, refetch: appsInProgressRefetch } = useAppsInstallationsQuery({ displayLoader: false, + skip: !hasAnyPermissions([PermissionEnum.MANAGE_APPS], user), }); const installedAppNotify = (name: string) => { diff --git a/src/apps/views/AppManageView/AppManageView.tsx b/src/apps/views/AppManageView/AppManageView.tsx index 59f122aae1e..e013cb87987 100644 --- a/src/apps/views/AppManageView/AppManageView.tsx +++ b/src/apps/views/AppManageView/AppManageView.tsx @@ -36,7 +36,7 @@ export const AppManageView: React.FC = ({ id, params }) => { const client = useApolloClient(); const { data, loading, refetch } = useAppQuery({ displayLoader: true, - variables: { id }, + variables: { id, hasManagedAppsPermission: true }, }); const appExists = data?.app !== null; diff --git a/src/apps/views/AppPermissionRequestView/AppPermissionRequestView.tsx b/src/apps/views/AppPermissionRequestView/AppPermissionRequestView.tsx index 8401d140695..67c9cf8bf0a 100644 --- a/src/apps/views/AppPermissionRequestView/AppPermissionRequestView.tsx +++ b/src/apps/views/AppPermissionRequestView/AppPermissionRequestView.tsx @@ -66,6 +66,7 @@ export const AppPermissionRequestView = () => { const { data } = useAppQuery({ variables: { id: appId, + hasManagedAppsPermission: true, }, }); const [updatePermissions, { loading }] = useAppUpdatePermissionsMutation(); diff --git a/src/apps/views/AppView/AppView.tsx b/src/apps/views/AppView/AppView.tsx index 8137762cc37..7ee2a0217f8 100644 --- a/src/apps/views/AppView/AppView.tsx +++ b/src/apps/views/AppView/AppView.tsx @@ -1,8 +1,10 @@ import AppPage from "@dashboard/apps/components/AppPage"; import { appMessages } from "@dashboard/apps/messages"; import { AppPaths, AppUrls } from "@dashboard/apps/urls"; +import { useUser } from "@dashboard/auth"; +import { hasAnyPermissions } from "@dashboard/auth/misc"; import NotFoundPage from "@dashboard/components/NotFoundPage"; -import { useAppQuery } from "@dashboard/graphql"; +import { PermissionEnum, useAppQuery } from "@dashboard/graphql"; import useNavigator from "@dashboard/hooks/useNavigator"; import useNotifier from "@dashboard/hooks/useNotifier"; import React, { useCallback } from "react"; @@ -15,9 +17,15 @@ interface AppProps { export const AppView: React.FC = ({ id }) => { const location = useLocation(); + const { user } = useUser(); + const hasManageAppsPermission = hasAnyPermissions( + [PermissionEnum.MANAGE_APPS], + user, + ); + const { data, refetch } = useAppQuery({ displayLoader: true, - variables: { id }, + variables: { id, hasManagedAppsPermission: hasManageAppsPermission }, }); const appExists = data?.app !== null; diff --git a/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx b/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx index 33ae8939cad..4d7da09324d 100644 --- a/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx +++ b/src/custom-apps/views/CustomAppDetails/CustomAppDetails.tsx @@ -67,7 +67,7 @@ export const CustomAppDetails: React.FC = ({ const { data, loading, refetch } = useAppQuery({ displayLoader: true, - variables: { id }, + variables: { id, hasManagedAppsPermission: true }, }); const [activateApp, activateAppResult] = useAppActivateMutation({ onCompleted: data => { diff --git a/src/custom-apps/views/CustomAppWebhookCreate.tsx b/src/custom-apps/views/CustomAppWebhookCreate.tsx index 879bc44124d..926a4d8448b 100644 --- a/src/custom-apps/views/CustomAppWebhookCreate.tsx +++ b/src/custom-apps/views/CustomAppWebhookCreate.tsx @@ -28,7 +28,9 @@ export const CustomAppWebhookCreate: React.FC = ({ const notify = useNotifier(); const intl = useIntl(); - const { data } = useAppQuery({ variables: { id: appId } }); + const { data } = useAppQuery({ + variables: { id: appId, hasManagedAppsPermission: true }, + }); const availableEvents = useAvailableEvents(); diff --git a/src/fragments/apps.ts b/src/fragments/apps.ts index da2a4616c41..c993035eb02 100644 --- a/src/fragments/apps.ts +++ b/src/fragments/apps.ts @@ -44,20 +44,20 @@ export const appFragment = gql` default(format: WEBP, size: 64) } } - privateMetadata { + privateMetadata @include(if: $hasManagedAppsPermission) { key value } - metadata { + metadata @include(if: $hasManagedAppsPermission) { key value } - tokens { + tokens @include(if: $hasManagedAppsPermission) { authToken id name } - webhooks { + webhooks @include(if: $hasManagedAppsPermission) { ...Webhook } } diff --git a/src/graphql/hooks.generated.ts b/src/graphql/hooks.generated.ts index 37da5e2f845..07735cc6a19 100644 --- a/src/graphql/hooks.generated.ts +++ b/src/graphql/hooks.generated.ts @@ -59,20 +59,20 @@ export const AppFragmentDoc = gql` default(format: WEBP, size: 64) } } - privateMetadata { + privateMetadata @include(if: $hasManagedAppsPermission) { key value } - metadata { + metadata @include(if: $hasManagedAppsPermission) { key value } - tokens { + tokens @include(if: $hasManagedAppsPermission) { authToken id name } - webhooks { + webhooks @include(if: $hasManagedAppsPermission) { ...Webhook } } @@ -3371,7 +3371,7 @@ export const WebhookDetailsFragmentDoc = gql` } ${WebhookFragmentDoc}`; export const AppCreateDocument = gql` - mutation AppCreate($input: AppInput!) { + mutation AppCreate($input: AppInput!, $hasManagedAppsPermission: Boolean! = true) { appCreate(input: $input) { authToken app { @@ -3400,6 +3400,7 @@ export type AppCreateMutationFn = Apollo.MutationFunction; export type AppCreateMutationOptions = Apollo.BaseMutationOptions; export const AppDeleteDocument = gql` - mutation AppDelete($id: ID!) { + mutation AppDelete($id: ID!, $hasManagedAppsPermission: Boolean! = true) { appDelete(id: $id) { app { ...App @@ -3439,6 +3440,7 @@ export type AppDeleteMutationFn = Apollo.MutationFunction; export type AppRetryInstallMutationOptions = Apollo.BaseMutationOptions; export const AppUpdateDocument = gql` - mutation AppUpdate($id: ID!, $input: AppInput!) { + mutation AppUpdate($id: ID!, $input: AppInput!, $hasManagedAppsPermission: Boolean! = true) { appUpdate(id: $id, input: $input) { app { ...App @@ -3647,6 +3649,7 @@ export type AppUpdateMutationFn = Apollo.MutationFunction; export type AppsInstallationsQueryResult = Apollo.QueryResult; export const AppDocument = gql` - query App($id: ID!) { + query App($id: ID!, $hasManagedAppsPermission: Boolean!) { app(id: $id) { ...App aboutApp @@ -3976,6 +3979,7 @@ export const AppDocument = gql` * const { data, loading, error } = useAppQuery({ * variables: { * id: // value for 'id' + * hasManagedAppsPermission: // value for 'hasManagedAppsPermission' * }, * }); */ diff --git a/src/graphql/types.generated.ts b/src/graphql/types.generated.ts index 4d66fb5e734..54e11cfd911 100644 --- a/src/graphql/types.generated.ts +++ b/src/graphql/types.generated.ts @@ -8861,17 +8861,19 @@ export enum WeightUnitsEnum { export type AppCreateMutationVariables = Exact<{ input: AppInput; + hasManagedAppsPermission?: Scalars['Boolean']; }>; -export type AppCreateMutation = { __typename: 'Mutation', appCreate: { __typename: 'AppCreate', authToken: string | null, app: { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null, errors: Array<{ __typename: 'AppError', field: string | null, message: string | null, code: AppErrorCode, permissions: Array | null }> } | null }; +export type AppCreateMutation = { __typename: 'Mutation', appCreate: { __typename: 'AppCreate', authToken: string | null, app: { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null, errors: Array<{ __typename: 'AppError', field: string | null, message: string | null, code: AppErrorCode, permissions: Array | null }> } | null }; export type AppDeleteMutationVariables = Exact<{ id: Scalars['ID']; + hasManagedAppsPermission?: Scalars['Boolean']; }>; -export type AppDeleteMutation = { __typename: 'Mutation', appDelete: { __typename: 'AppDelete', app: { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null, errors: Array<{ __typename: 'AppError', field: string | null, message: string | null, code: AppErrorCode, permissions: Array | null }> } | null }; +export type AppDeleteMutation = { __typename: 'Mutation', appDelete: { __typename: 'AppDelete', app: { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null, errors: Array<{ __typename: 'AppError', field: string | null, message: string | null, code: AppErrorCode, permissions: Array | null }> } | null }; export type AppDeleteFailedInstallationMutationVariables = Exact<{ id: Scalars['ID']; @@ -8904,10 +8906,11 @@ export type AppRetryInstallMutation = { __typename: 'Mutation', appRetryInstall: export type AppUpdateMutationVariables = Exact<{ id: Scalars['ID']; input: AppInput; + hasManagedAppsPermission?: Scalars['Boolean']; }>; -export type AppUpdateMutation = { __typename: 'Mutation', appUpdate: { __typename: 'AppUpdate', app: { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null, errors: Array<{ __typename: 'AppError', message: string | null, permissions: Array | null, field: string | null, code: AppErrorCode }> } | null }; +export type AppUpdateMutation = { __typename: 'Mutation', appUpdate: { __typename: 'AppUpdate', app: { __typename: 'App', id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null, errors: Array<{ __typename: 'AppError', message: string | null, permissions: Array | null, field: string | null, code: AppErrorCode }> } | null }; export type AppTokenCreateMutationVariables = Exact<{ input: AppTokenInput; @@ -8964,10 +8967,11 @@ export type AppsInstallationsQuery = { __typename: 'Query', appsInstallations: A export type AppQueryVariables = Exact<{ id: Scalars['ID']; + hasManagedAppsPermission: Scalars['Boolean']; }>; -export type AppQuery = { __typename: 'Query', app: { __typename: 'App', aboutApp: string | null, author: string | null, dataPrivacy: string | null, dataPrivacyUrl: string | null, id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null }; +export type AppQuery = { __typename: 'Query', app: { __typename: 'App', aboutApp: string | null, author: string | null, dataPrivacy: string | null, dataPrivacyUrl: string | null, id: string, name: string | null, created: any | null, isActive: boolean | null, type: AppTypeEnum | null, homepageUrl: string | null, appUrl: string | null, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null } | null }; export type ExtensionListQueryVariables = Exact<{ filter: AppExtensionFilterInput; @@ -9887,7 +9891,7 @@ export type AddressFragment = { __typename: 'Address', city: string, cityArea: s export type AppManifestFragment = { __typename: 'Manifest', identifier: string, version: string, about: string | null, name: string, appUrl: string | null, configurationUrl: string | null, tokenTargetUrl: string | null, dataPrivacy: string | null, dataPrivacyUrl: string | null, homepageUrl: string | null, supportUrl: string | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, brand: { __typename: 'AppManifestBrand', logo: { __typename: 'AppManifestBrandLogo', default: string } } | 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, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, 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, manifestUrl: string | null, configurationUrl: string | null, supportUrl: string | null, version: string | null, accessToken: string | null, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: 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 | null, isActive: boolean, app: { __typename: 'App', id: string, name: string | null } }> | null }; export type AppInstallationFragment = { __typename: 'AppInstallation', status: JobStatusEnum, message: string | null, appName: string, manifestUrl: string, id: string, brand: { __typename: 'AppBrand', logo: { __typename: 'AppBrandLogo', default: string } } | null }; From 90f502f4e7b7a41b3e22978be43c80a2d764a427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 19 Mar 2024 14:01:38 +0100 Subject: [PATCH 04/21] Allow user to see manage app view --- .../components/AppDetailsPage/PermissionsCard.tsx | 8 ++++++++ .../AppListRow/AppListCardInstallButton.tsx | 2 ++ src/apps/components/AppPage/AppPage.tsx | 12 ++---------- .../AppWebhooksDisplay/AppWebhooksDisplay.tsx | 10 ++++++++++ src/apps/views/AppManageView/AppManageView.tsx | 10 +++++++++- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/apps/components/AppDetailsPage/PermissionsCard.tsx b/src/apps/components/AppDetailsPage/PermissionsCard.tsx index 8aa64e4b1a2..21d765f319c 100644 --- a/src/apps/components/AppDetailsPage/PermissionsCard.tsx +++ b/src/apps/components/AppDetailsPage/PermissionsCard.tsx @@ -1,4 +1,6 @@ import { AppPermissionsDialog } from "@dashboard/apps/components/AppPermissionsDialog"; +import { useUser } from "@dashboard/auth"; +import { hasAnyPermissions } from "@dashboard/auth/misc"; import Skeleton from "@dashboard/components/Skeleton"; import { PermissionEnum } from "@dashboard/graphql"; import { Box, BoxProps, Button, Text } from "@saleor/macaw-ui-next"; @@ -25,10 +27,16 @@ export const PermissionsCard: React.FC = ({ const [editPermissionDialogOpen, setEditPermissionDialogOpen] = useState(false); const intl = useIntl(); + const { user } = useUser(); + const hasManageAppsPermission = hasAnyPermissions( + [PermissionEnum.MANAGE_APPS], + user, + ); const editPermissionsButton = ( )} diff --git a/src/apps/components/AppPage/message.ts b/src/apps/components/AppPage/message.ts new file mode 100644 index 00000000000..12c31f3f9c9 --- /dev/null +++ b/src/apps/components/AppPage/message.ts @@ -0,0 +1,14 @@ +import { defineMessages } from "react-intl"; + +export const messages = defineMessages({ + manageApp: { + id: "LwX0Ug", + defaultMessage: "Manage app", + description: "Button with Manage app label", + }, + appSettings: { + id: "S90DJO", + defaultMessage: "App settings", + description: "Button with app settings label", + }, +}); From 0253908367f4cb16111499711027f86bab50e71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 20 Mar 2024 21:14:19 +0100 Subject: [PATCH 10/21] Improve typing --- .../InstalledAppList/InstalledAppList.tsx | 2 +- .../components/InstalledAppList/utils.test.ts | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/apps/components/InstalledAppList/InstalledAppList.tsx b/src/apps/components/InstalledAppList/InstalledAppList.tsx index fea111d99b7..e49ea58e680 100644 --- a/src/apps/components/InstalledAppList/InstalledAppList.tsx +++ b/src/apps/components/InstalledAppList/InstalledAppList.tsx @@ -55,7 +55,7 @@ const InstalledAppList: React.FC = ({ } /> ))} - {appList.map(({ app, isExternal, logo }) => ( + {appList?.map(({ app, isExternal, logo }) => ( { const appList = undefined; const appInstallationList = [ { - appInstallation: null, + isExternal: false, }, ] as AppInstallation[]; @@ -61,8 +61,8 @@ describe("InstalledAppList utils", () => { it("should return false when has apps installations and app installed", () => { // Arrange - const appList = []; - const appInstallationList = []; + const appList: InstalledApp[] = []; + const appInstallationList: AppInstallation[] = []; // Act const isLoading = appsIsLoading({ @@ -82,7 +82,7 @@ describe("InstalledAppList utils", () => { it("should return true when has apps installed", () => { // Arrange const appList = undefined; - const appInstallationList = []; + const appInstallationList: AppInstallation[] = []; // Act const isLoading = appsIsLoading({ @@ -97,7 +97,7 @@ describe("InstalledAppList utils", () => { it("should return false when has apps installed", () => { // Arrange - const appList = []; + const appList: InstalledApp[] = []; const appInstallationList = undefined; // Act @@ -118,8 +118,8 @@ describe("InstalledAppList utils", () => { const hasManagedAppsPermission = true; it("should return true when has empty apps installed and empty app installations", () => { // Arrange - const appList = []; - const appInstallationList = []; + const appList: InstalledApp[] = []; + const appInstallationList: AppInstallation[] = []; // Act const showInstalledApps = hasEmptyAppList({ @@ -150,10 +150,10 @@ describe("InstalledAppList utils", () => { it("should return false when has apps installations and no app installed", () => { // Arrange - const appList = []; + const appList: InstalledApp[] = []; const appInstallationList = [ { - appInstallation: null, + isExternal: false, }, ] as AppInstallation[]; @@ -172,10 +172,10 @@ describe("InstalledAppList utils", () => { // Arrange const appList = [ { - app: null, + isExternal: false, }, ] as InstalledApp[]; - const appInstallationList = []; + const appInstallationList: AppInstallation[] = []; // Act const showInstalledApps = hasEmptyAppList({ @@ -194,7 +194,7 @@ describe("InstalledAppList utils", () => { it("should return true when has empty apps installed", () => { // Arrange - const appList = []; + const appList: InstalledApp[] = []; const appInstallationList = undefined; // Act @@ -228,10 +228,10 @@ describe("InstalledAppList utils", () => { // Arrange const appList = [ { - app: null, + isExternal: false, }, ] as InstalledApp[]; - const appInstallationList = []; + const appInstallationList: AppInstallation[] = []; // Act const showInstalledApps = hasEmptyAppList({ From b06808df6402d6845cd7f3bad70fd446f8167326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 20 Mar 2024 21:47:15 +0100 Subject: [PATCH 11/21] Extract messages --- locale/defaultMessages.json | 11 +++++++++++ src/intl.ts | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 11a3556a0de..29912d3f430 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -3879,6 +3879,9 @@ "context": "header", "string": "{webhookName} Details" }, + "ORQvOg": { + "string": "You don't have permission to perform this action" + }, "OTDo9I": { "context": "tab name", "string": "All staff members" @@ -4424,6 +4427,10 @@ "S8kqP9": { "string": "Conditions" }, + "S90DJO": { + "context": "Button with app settings label", + "string": "App settings" + }, "SBb6Ej": { "context": "select a warehouse to fulfill product from", "string": "Select warehouse..." @@ -5300,6 +5307,10 @@ "context": "product pricing, section header", "string": "Pricing" }, + "Xo4tPo": { + "context": "description", + "string": "You need permission to install apps" + }, "Xsh2Pa": { "context": "column picker search input placeholder", "string": "Search for columns" diff --git a/src/intl.ts b/src/intl.ts index 88c927b3955..7e5d740885e 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -411,6 +411,10 @@ export const buttonMessages = defineMessages({ id: "rbrahO", defaultMessage: "Close", }, + noPermission: { + id: "ORQvOg", + defaultMessage: "You don't have permission to perform this action", + }, }); export const sectionNames = defineMessages({ From df577a2755887c503ed2113ba4765de6420a159b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 20 Mar 2024 21:47:50 +0100 Subject: [PATCH 12/21] ButtonWithTooltip --- .../ButtonWithTooltip/ButtonWithTooltip.tsx | 26 +++++++++++++++++++ src/components/ButtonWithTooltip/index.ts | 1 + 2 files changed, 27 insertions(+) create mode 100644 src/components/ButtonWithTooltip/ButtonWithTooltip.tsx create mode 100644 src/components/ButtonWithTooltip/index.ts diff --git a/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx b/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx new file mode 100644 index 00000000000..79d7649203b --- /dev/null +++ b/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx @@ -0,0 +1,26 @@ +import { Button, ButtonProps, Tooltip } from "@saleor/macaw-ui-next"; +import React from "react"; + +interface ButtonWithTooltipProps extends ButtonProps { + tooltip?: string; + children: React.ReactNode; +} + +export const ButtonWithTooltip = ({ + tooltip, + children, + ...props +}: ButtonWithTooltipProps) => { + if (!tooltip) { + return ; + } + + return ( + + + + + {tooltip} + + ); +}; diff --git a/src/components/ButtonWithTooltip/index.ts b/src/components/ButtonWithTooltip/index.ts new file mode 100644 index 00000000000..b93f8f0c654 --- /dev/null +++ b/src/components/ButtonWithTooltip/index.ts @@ -0,0 +1 @@ +export * from "./ButtonWithTooltip"; From 1c8e5816c3e64b938cf4c700d0faff962f7043ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 20 Mar 2024 21:48:20 +0100 Subject: [PATCH 13/21] Refactor HeaderOptions, block action when no manage_apps --- .../AppDetailsPage/HeaderOptions.tsx | 36 ++++++++----- src/apps/components/AppDetailsPage/styles.ts | 52 ------------------- 2 files changed, 22 insertions(+), 66 deletions(-) delete mode 100644 src/apps/components/AppDetailsPage/styles.ts diff --git a/src/apps/components/AppDetailsPage/HeaderOptions.tsx b/src/apps/components/AppDetailsPage/HeaderOptions.tsx index 2b851ddb009..7cc9bad9bca 100644 --- a/src/apps/components/AppDetailsPage/HeaderOptions.tsx +++ b/src/apps/components/AppDetailsPage/HeaderOptions.tsx @@ -1,13 +1,13 @@ +import { ButtonWithTooltip } from "@dashboard/components/ButtonWithTooltip"; +import { useHasManagedAppsPermission } from "@dashboard/hooks/useHasManagedAppsPermission"; import { buttonMessages } from "@dashboard/intl"; -import { ButtonBase } from "@material-ui/core"; import { Box } from "@saleor/macaw-ui-next"; import React from "react"; import SVG from "react-inlinesvg"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import activateIcon from "../../../../assets/images/activate-icon.svg"; import deleteIcon from "../../../../assets/images/delete.svg"; -import { useStyles } from "./styles"; interface HeaderOptionsProps { isActive: boolean; @@ -22,7 +22,12 @@ const HeaderOptions: React.FC = ({ onAppDeactivateOpen, onAppDeleteOpen, }) => { - const classes = useStyles(); + const intl = useIntl(); + const { hasManagedAppsPermission } = useHasManagedAppsPermission(); + + const tooltipContent = !hasManagedAppsPermission + ? intl.formatMessage(buttonMessages.noPermission) + : undefined; return ( = ({ borderColor="default1" borderBottomWidth={1} > -
- + @@ -43,16 +49,18 @@ const HeaderOptions: React.FC = ({ ) : ( )} - - + + - -
+ +
); }; diff --git a/src/apps/components/AppDetailsPage/styles.ts b/src/apps/components/AppDetailsPage/styles.ts deleted file mode 100644 index 00d8164af71..00000000000 --- a/src/apps/components/AppDetailsPage/styles.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { makeStyles } from "@saleor/macaw-ui"; - -export const useStyles = makeStyles( - theme => ({ - appHeader: { - marginBottom: theme.spacing(3), - }, - appHeaderLinks: { - "& img": { - marginRight: theme.spacing(1), - }, - alignItems: "center", - display: "flex", - padding: theme.spacing(2, 0), - }, - headerLinkContainer: { - "& svg": { - marginRight: theme.spacing(), - }, - "& span": { - fontWeight: 500, - }, - alignItems: "center", - color: theme.palette.text.primary, - display: "flex", - fontSize: theme.spacing(2), - fontWeight: 500, - lineHeight: 1.2, - marginRight: theme.spacing(3), - padding: 0, - textTransform: "none", - }, - hr: { - border: "none", - borderTop: `1px solid ${theme.palette.divider}`, - height: 0, - marginBottom: 0, - marginTop: 0, - width: "100%", - }, - marketplaceContent: { - "& button": { - marginTop: theme.spacing(1), - }, - "&:last-child": { - padding: theme.spacing(2, 3, 2, 3), - }, - padding: theme.spacing(1), - }, - }), - { name: "AppDetailsPage" }, -); From 6ef1b192a054de5a02cf4b6efb7755812126cc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 20 Mar 2024 22:14:51 +0100 Subject: [PATCH 14/21] Improve buttons --- locale/defaultMessages.json | 4 -- .../AppDetailsPage/HeaderOptions.tsx | 10 ++- .../AppDetailsPage/PermissionsCard.tsx | 15 ++-- .../AppListRow/AppListCardInstallButton.tsx | 70 +++++++------------ src/apps/components/AppListRow/messages.ts | 5 -- .../ButtonWithTooltip/ButtonWithTooltip.tsx | 7 +- 6 files changed, 49 insertions(+), 62 deletions(-) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 29912d3f430..b971c15db2f 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -5307,10 +5307,6 @@ "context": "product pricing, section header", "string": "Pricing" }, - "Xo4tPo": { - "context": "description", - "string": "You need permission to install apps" - }, "Xsh2Pa": { "context": "column picker search input placeholder", "string": "Search for columns" diff --git a/src/apps/components/AppDetailsPage/HeaderOptions.tsx b/src/apps/components/AppDetailsPage/HeaderOptions.tsx index 7cc9bad9bca..c7c81aec6e2 100644 --- a/src/apps/components/AppDetailsPage/HeaderOptions.tsx +++ b/src/apps/components/AppDetailsPage/HeaderOptions.tsx @@ -36,14 +36,16 @@ const HeaderOptions: React.FC = ({ borderColor="default1" borderBottomWidth={1} > - + - + + + {isActive ? ( ) : ( @@ -57,7 +59,9 @@ const HeaderOptions: React.FC = ({ disabled={!hasManagedAppsPermission} onClick={onAppDeleteOpen} > - + + + diff --git a/src/apps/components/AppDetailsPage/PermissionsCard.tsx b/src/apps/components/AppDetailsPage/PermissionsCard.tsx index 2dd89c4086c..0866b6ddb51 100644 --- a/src/apps/components/AppDetailsPage/PermissionsCard.tsx +++ b/src/apps/components/AppDetailsPage/PermissionsCard.tsx @@ -1,8 +1,10 @@ import { AppPermissionsDialog } from "@dashboard/apps/components/AppPermissionsDialog"; +import { ButtonWithTooltip } from "@dashboard/components/ButtonWithTooltip"; import Skeleton from "@dashboard/components/Skeleton"; import { PermissionEnum } from "@dashboard/graphql"; import { useHasManagedAppsPermission } from "@dashboard/hooks/useHasManagedAppsPermission"; -import { Box, BoxProps, Button, Text } from "@saleor/macaw-ui-next"; +import { buttonMessages } from "@dashboard/intl"; +import { Box, BoxProps, Text } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -29,13 +31,18 @@ export const PermissionsCard: React.FC = ({ const { hasManagedAppsPermission } = useHasManagedAppsPermission(); const editPermissionsButton = ( - + ); const renderContent = () => { diff --git a/src/apps/components/AppListRow/AppListCardInstallButton.tsx b/src/apps/components/AppListRow/AppListCardInstallButton.tsx index 9f56bc36ece..d79a1d82028 100644 --- a/src/apps/components/AppListRow/AppListCardInstallButton.tsx +++ b/src/apps/components/AppListRow/AppListCardInstallButton.tsx @@ -1,7 +1,8 @@ +import { ButtonWithTooltip } from "@dashboard/components/ButtonWithTooltip"; import { IS_CLOUD_INSTANCE } from "@dashboard/config"; import { useHasManagedAppsPermission } from "@dashboard/hooks/useHasManagedAppsPermission"; import { buttonMessages } from "@dashboard/intl"; -import { Button, Tooltip } from "@saleor/macaw-ui-next"; +import { Button } from "@saleor/macaw-ui-next"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -17,29 +18,23 @@ export const AppListCardInstallButton = ({ const intl = useIntl(); const { hasManagedAppsPermission } = useHasManagedAppsPermission(); + if (!installHandler) { + return null; + } + if (!hasManagedAppsPermission) { return ( - - - - - - - - - {intl.formatMessage(messages.installationPermissionRequired)} - - + + + ); } - if (installHandler && IS_CLOUD_INSTANCE) { + if (IS_CLOUD_INSTANCE) { return ( - - - - - {intl.formatMessage(messages.installationCloudOnly)} - - - ); - } - - return null; + return ( + + + + + + ); }; diff --git a/src/apps/components/AppListRow/messages.ts b/src/apps/components/AppListRow/messages.ts index d78d72171be..99b34043791 100644 --- a/src/apps/components/AppListRow/messages.ts +++ b/src/apps/components/AppListRow/messages.ts @@ -21,9 +21,4 @@ export const messages = defineMessages({ description: "description", id: "IEpmGQ", }, - installationPermissionRequired: { - defaultMessage: "You need permission to install apps", - id: "Xo4tPo", - description: "description", - }, }); diff --git a/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx b/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx index 79d7649203b..58ea974cb44 100644 --- a/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx +++ b/src/components/ButtonWithTooltip/ButtonWithTooltip.tsx @@ -2,7 +2,7 @@ import { Button, ButtonProps, Tooltip } from "@saleor/macaw-ui-next"; import React from "react"; interface ButtonWithTooltipProps extends ButtonProps { - tooltip?: string; + tooltip?: React.ReactNode; children: React.ReactNode; } @@ -20,7 +20,10 @@ export const ButtonWithTooltip = ({ - {tooltip} + + + {tooltip} + ); }; From 4c0e96bbc8872b64179a1e822910b427d133fbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 20 Mar 2024 22:19:47 +0100 Subject: [PATCH 15/21] Add changset --- .changeset/thin-avocados-grow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thin-avocados-grow.md diff --git a/.changeset/thin-avocados-grow.md b/.changeset/thin-avocados-grow.md new file mode 100644 index 00000000000..82484f83639 --- /dev/null +++ b/.changeset/thin-avocados-grow.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Allow user without MANAGE_APPS to access to apps page From c6808436c1951b7a6b3044b1e3f8f98e137d5d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 21 Mar 2024 10:10:59 +0100 Subject: [PATCH 16/21] Adjust singlePermission tests --- playwright/tests/singlePermissions/channelsWebhooks.spec.ts | 4 ++-- playwright/tests/singlePermissions/contentPage.spec.ts | 4 ++-- playwright/tests/singlePermissions/customer.spec.ts | 2 +- playwright/tests/singlePermissions/discount.spec.ts | 4 ++-- playwright/tests/singlePermissions/orders.spec.ts | 4 ++-- playwright/tests/singlePermissions/plugins.spec.ts | 2 +- playwright/tests/singlePermissions/product.spec.ts | 6 +++--- playwright/tests/singlePermissions/productType.spec.ts | 2 +- .../tests/singlePermissions/settingsConfiguration.spec.ts | 2 +- playwright/tests/singlePermissions/shipping.spec.ts | 2 +- playwright/tests/singlePermissions/staffMembers.spec.ts | 4 ++-- playwright/tests/singlePermissions/translations.spec.ts | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/playwright/tests/singlePermissions/channelsWebhooks.spec.ts b/playwright/tests/singlePermissions/channelsWebhooks.spec.ts index 0f83e815a1d..482803863ff 100644 --- a/playwright/tests/singlePermissions/channelsWebhooks.spec.ts +++ b/playwright/tests/singlePermissions/channelsWebhooks.spec.ts @@ -16,7 +16,7 @@ test("TC: SALEOR_11 User should be able to navigate to channel list as a staff m await page.goto(URL_LIST.homePage); await mainMenuPage.openConfiguration(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); await configurationPage.openChannels(); await expect(channelPage.createChannelButton).toBeVisible(); @@ -30,7 +30,7 @@ test("TC: SALEOR_12 User should be able to navigate to webhooks and events as a const configurationPage = new ConfigurationPage(page); await page.goto(URL_LIST.configuration); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); await configurationPage.openWebhooksAndEvents(); await expect(webhooksEventsPage.createAppButton).toBeVisible(); }); diff --git a/playwright/tests/singlePermissions/contentPage.spec.ts b/playwright/tests/singlePermissions/contentPage.spec.ts index 8178b010a04..d74a42762d1 100644 --- a/playwright/tests/singlePermissions/contentPage.spec.ts +++ b/playwright/tests/singlePermissions/contentPage.spec.ts @@ -18,7 +18,7 @@ test("TC: SALEOR_14 User should be able to navigate to content list as a staff m await page.goto(URL_LIST.homePage); await mainMenuPage.openContent(); await expect(contentPage.createContentButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(3); + await mainMenuPage.expectMenuItemsCount(4); await basePage.expectGridToBeAttached(); }); test("TC: SALEOR_15 User should be able to navigate to page types list as a staff member using CONTENT aka PAGE permission @e2e", async ({ @@ -36,5 +36,5 @@ test("TC: SALEOR_15 User should be able to navigate to page types list as a staf await configurationPage.openPageTypes(); await expect(pageTypesPage.createPageTypeButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(3); + await mainMenuPage.expectMenuItemsCount(4); }); diff --git a/playwright/tests/singlePermissions/customer.spec.ts b/playwright/tests/singlePermissions/customer.spec.ts index feff873ec17..65e4306e0b2 100644 --- a/playwright/tests/singlePermissions/customer.spec.ts +++ b/playwright/tests/singlePermissions/customer.spec.ts @@ -17,5 +17,5 @@ test("TC: SALEOR_13 User should be able to navigate to customer list as a staff await mainMenuPage.openCustomers(); await expect(customersPage.createCustomerButton).toBeVisible(); await basePage.expectGridToBeAttached(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); }); diff --git a/playwright/tests/singlePermissions/discount.spec.ts b/playwright/tests/singlePermissions/discount.spec.ts index e217ba7be6b..95c9da29301 100644 --- a/playwright/tests/singlePermissions/discount.spec.ts +++ b/playwright/tests/singlePermissions/discount.spec.ts @@ -18,7 +18,7 @@ test("TC: SALEOR_6 User should be able to navigate to discount list as a staff m await mainMenuPage.openDiscounts(); await expect(discountsPage.createDiscountButton).toBeVisible(); await basePage.expectGridToBeAttached(); - await mainMenuPage.expectMenuItemsCount(3); + await mainMenuPage.expectMenuItemsCount(4); }); test("TC: SALEOR_7 User should be able to navigate to voucher list as a staff member using DISCOUNTS permission @e2e", async ({ @@ -32,5 +32,5 @@ test("TC: SALEOR_7 User should be able to navigate to voucher list as a staff me await mainMenuPage.openVouchers(); await expect(vouchersPage.createVoucherButton).toBeVisible(); await basePage.expectGridToBeAttached(); - await mainMenuPage.expectMenuItemsCount(3); + await mainMenuPage.expectMenuItemsCount(4); }); diff --git a/playwright/tests/singlePermissions/orders.spec.ts b/playwright/tests/singlePermissions/orders.spec.ts index 4142685fae8..12a58d6d543 100644 --- a/playwright/tests/singlePermissions/orders.spec.ts +++ b/playwright/tests/singlePermissions/orders.spec.ts @@ -18,7 +18,7 @@ test("TC: SALEOR_8 User should be able to navigate to order list as a staff memb await mainMenuPage.openOrders(); await expect(ordersPage.createOrderButton).toBeVisible(); await basePage.expectGridToBeAttached(); - await mainMenuPage.expectMenuItemsCount(3); + await mainMenuPage.expectMenuItemsCount(4); }); test("TC: SALEOR_9 User should be able to navigate to draft list as a staff member using ORDER permission @e2e", async ({ page, @@ -31,5 +31,5 @@ test("TC: SALEOR_9 User should be able to navigate to draft list as a staff memb await mainMenuPage.openDrafts(); await expect(draftOrdersPage.createDraftOrderButton).toBeVisible(); await basePage.expectGridToBeAttached(); - await mainMenuPage.expectMenuItemsCount(3); + await mainMenuPage.expectMenuItemsCount(4); }); diff --git a/playwright/tests/singlePermissions/plugins.spec.ts b/playwright/tests/singlePermissions/plugins.spec.ts index 42e48bae565..804ac33eee6 100644 --- a/playwright/tests/singlePermissions/plugins.spec.ts +++ b/playwright/tests/singlePermissions/plugins.spec.ts @@ -16,5 +16,5 @@ test("TC: SALEOR_16 User should be able to navigate to plugin list as a staff me await page.goto(URL_LIST.configuration); await configurationPage.openPlugins(); await expect(pluginsPage.pluginRow.first()).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); }); diff --git a/playwright/tests/singlePermissions/product.spec.ts b/playwright/tests/singlePermissions/product.spec.ts index 848884bbc90..a2114a1c6b2 100644 --- a/playwright/tests/singlePermissions/product.spec.ts +++ b/playwright/tests/singlePermissions/product.spec.ts @@ -18,7 +18,7 @@ test("TC: SALEOR_23 User should be able to navigate to product list as a staff m await page.goto(URL_LIST.homePage); await mainMenuPage.openProducts(); await expect(productPage.addProductButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(5); + await mainMenuPage.expectMenuItemsCount(6); await basePage.expectGridToBeAttached(); }); test("TC: SALEOR_24 User should be able to navigate to collections list as a staff member using PRODUCT permission @e2e", async ({ @@ -31,7 +31,7 @@ test("TC: SALEOR_24 User should be able to navigate to collections list as a sta await page.goto(URL_LIST.homePage); await mainMenuPage.openCollections(); await expect(collectionsPage.createCollectionButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(5); + await mainMenuPage.expectMenuItemsCount(6); await basePage.expectGridToBeAttached(); }); test("TC: SALEOR_25 User should be able to navigate to categories list as a staff member using PRODUCT permission @e2e", async ({ @@ -44,6 +44,6 @@ test("TC: SALEOR_25 User should be able to navigate to categories list as a staf await page.goto(URL_LIST.homePage); await mainMenuPage.openCategories(); await expect(categoriesPage.createCategoryButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(5); + await mainMenuPage.expectMenuItemsCount(6); await basePage.expectGridToBeAttached(); }); diff --git a/playwright/tests/singlePermissions/productType.spec.ts b/playwright/tests/singlePermissions/productType.spec.ts index b1a35b4c5ce..b69122953c9 100644 --- a/playwright/tests/singlePermissions/productType.spec.ts +++ b/playwright/tests/singlePermissions/productType.spec.ts @@ -16,5 +16,5 @@ test("TC: SALEOR_17 User should be able to navigate to product type list as a st await page.goto(URL_LIST.configuration); await configurationPage.openProductTypes(); await expect(productTypePage.addProductTypeButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); }); diff --git a/playwright/tests/singlePermissions/settingsConfiguration.spec.ts b/playwright/tests/singlePermissions/settingsConfiguration.spec.ts index 121aa400d35..ec30d4b6d46 100644 --- a/playwright/tests/singlePermissions/settingsConfiguration.spec.ts +++ b/playwright/tests/singlePermissions/settingsConfiguration.spec.ts @@ -17,5 +17,5 @@ test("TC: SALEOR_18 User should be able to navigate to configuration as a staff await mainMenuPage.openConfiguration(); await configurationPage.openSiteSettings(); await expect(siteSettingsPage.companyInfoSection).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); }); diff --git a/playwright/tests/singlePermissions/shipping.spec.ts b/playwright/tests/singlePermissions/shipping.spec.ts index 2988e9f5dec..6781edb933e 100644 --- a/playwright/tests/singlePermissions/shipping.spec.ts +++ b/playwright/tests/singlePermissions/shipping.spec.ts @@ -17,5 +17,5 @@ test("TC: SALEOR_21 User should be able to navigate to shipping zones page as a await page.waitForTimeout(8000); await configurationPage.openShippingMethods(); await expect(shippingMethodsPage.createShippingZoneButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); }); diff --git a/playwright/tests/singlePermissions/staffMembers.spec.ts b/playwright/tests/singlePermissions/staffMembers.spec.ts index cc22d7f9c6d..96c3d4fd55f 100644 --- a/playwright/tests/singlePermissions/staffMembers.spec.ts +++ b/playwright/tests/singlePermissions/staffMembers.spec.ts @@ -20,7 +20,7 @@ test("TC: SALEOR_19 User should be able to navigate to staff members list page a await page.goto(URL_LIST.configuration); await configurationPage.openStaffMembers(); await expect(staffMembersPage.inviteStaffMembersButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); await basePage.expectGridToBeAttached(); }); @@ -35,6 +35,6 @@ test("TC: SALEOR_20 User should be able to navigate to permission group list pag await page.goto(URL_LIST.configuration); await configurationPage.openPermissionGroups(); await expect(permissionGroupsPage.createPermissionGroupButton).toBeVisible(); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); await basePage.expectGridToBeAttached(); }); diff --git a/playwright/tests/singlePermissions/translations.spec.ts b/playwright/tests/singlePermissions/translations.spec.ts index 83054e565e7..61dfed0a0e4 100644 --- a/playwright/tests/singlePermissions/translations.spec.ts +++ b/playwright/tests/singlePermissions/translations.spec.ts @@ -14,5 +14,5 @@ test("TC: SALEOR_22 User should be able to navigate to translations page as a st await page.goto(URL_LIST.homePage); await mainMenuPage.openTranslations(); await expect(basePage.pageHeader).toHaveText("Languages"); - await mainMenuPage.expectMenuItemsCount(2); + await mainMenuPage.expectMenuItemsCount(3); }); From deb8f63fe6ec070e18838c3d5742f42b197c997f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 21 Mar 2024 10:28:05 +0100 Subject: [PATCH 17/21] Remove required hasManagedAppsPermission when default --- src/apps/mutations.ts | 6 +++--- src/apps/views/AppManageView/AppManageView.tsx | 2 +- src/graphql/hooks.generated.ts | 6 +++--- src/graphql/types.generated.ts | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/apps/mutations.ts b/src/apps/mutations.ts index b2120f5a6ac..39d16157777 100644 --- a/src/apps/mutations.ts +++ b/src/apps/mutations.ts @@ -3,7 +3,7 @@ import { gql } from "@apollo/client"; export const appCreateMutation = gql` mutation AppCreate( $input: AppInput! - $hasManagedAppsPermission: Boolean! = true + $hasManagedAppsPermission: Boolean = true ) { appCreate(input: $input) { authToken @@ -18,7 +18,7 @@ export const appCreateMutation = gql` `; export const appDeleteMutation = gql` - mutation AppDelete($id: ID!, $hasManagedAppsPermission: Boolean! = true) { + mutation AppDelete($id: ID!, $hasManagedAppsPermission: Boolean = true) { appDelete(id: $id) { app { ...App @@ -95,7 +95,7 @@ export const appUpdateMutation = gql` mutation AppUpdate( $id: ID! $input: AppInput! - $hasManagedAppsPermission: Boolean! = true + $hasManagedAppsPermission: Boolean = true ) { appUpdate(id: $id, input: $input) { app { diff --git a/src/apps/views/AppManageView/AppManageView.tsx b/src/apps/views/AppManageView/AppManageView.tsx index 77afeb6027e..28327eca3c6 100644 --- a/src/apps/views/AppManageView/AppManageView.tsx +++ b/src/apps/views/AppManageView/AppManageView.tsx @@ -123,7 +123,7 @@ export const AppManageView: React.FC = ({ id, params }) => { const handleActivateConfirm = () => activateApp(mutationOpts); const handleDeactivateConfirm = () => deactivateApp(mutationOpts); - const handleRemoveConfirm = () => deleteApp(mutationOpts); + const handleRemoveConfirm = () => deleteApp({ ...mutationOpts }); if (!appExists) { return ; diff --git a/src/graphql/hooks.generated.ts b/src/graphql/hooks.generated.ts index 07735cc6a19..fdfbcc2024e 100644 --- a/src/graphql/hooks.generated.ts +++ b/src/graphql/hooks.generated.ts @@ -3371,7 +3371,7 @@ export const WebhookDetailsFragmentDoc = gql` } ${WebhookFragmentDoc}`; export const AppCreateDocument = gql` - mutation AppCreate($input: AppInput!, $hasManagedAppsPermission: Boolean! = true) { + mutation AppCreate($input: AppInput!, $hasManagedAppsPermission: Boolean = true) { appCreate(input: $input) { authToken app { @@ -3412,7 +3412,7 @@ export type AppCreateMutationHookResult = ReturnType; export type AppCreateMutationOptions = Apollo.BaseMutationOptions; export const AppDeleteDocument = gql` - mutation AppDelete($id: ID!, $hasManagedAppsPermission: Boolean! = true) { + mutation AppDelete($id: ID!, $hasManagedAppsPermission: Boolean = true) { appDelete(id: $id) { app { ...App @@ -3614,7 +3614,7 @@ export type AppRetryInstallMutationHookResult = ReturnType; export type AppRetryInstallMutationOptions = Apollo.BaseMutationOptions; export const AppUpdateDocument = gql` - mutation AppUpdate($id: ID!, $input: AppInput!, $hasManagedAppsPermission: Boolean! = true) { + mutation AppUpdate($id: ID!, $input: AppInput!, $hasManagedAppsPermission: Boolean = true) { appUpdate(id: $id, input: $input) { app { ...App diff --git a/src/graphql/types.generated.ts b/src/graphql/types.generated.ts index 54e11cfd911..6dc67e35dd9 100644 --- a/src/graphql/types.generated.ts +++ b/src/graphql/types.generated.ts @@ -8861,7 +8861,7 @@ export enum WeightUnitsEnum { export type AppCreateMutationVariables = Exact<{ input: AppInput; - hasManagedAppsPermission?: Scalars['Boolean']; + hasManagedAppsPermission?: InputMaybe; }>; @@ -8869,7 +8869,7 @@ export type AppCreateMutation = { __typename: 'Mutation', appCreate: { __typenam export type AppDeleteMutationVariables = Exact<{ id: Scalars['ID']; - hasManagedAppsPermission?: Scalars['Boolean']; + hasManagedAppsPermission?: InputMaybe; }>; @@ -8906,7 +8906,7 @@ export type AppRetryInstallMutation = { __typename: 'Mutation', appRetryInstall: export type AppUpdateMutationVariables = Exact<{ id: Scalars['ID']; input: AppInput; - hasManagedAppsPermission?: Scalars['Boolean']; + hasManagedAppsPermission?: InputMaybe; }>; From 332979f349eb52b6e7681ce2ff8b288314368105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 21 Mar 2024 15:24:52 +0100 Subject: [PATCH 18/21] Improve action icons --- .../components/AppDetailsPage/HeaderOptions.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/apps/components/AppDetailsPage/HeaderOptions.tsx b/src/apps/components/AppDetailsPage/HeaderOptions.tsx index c7c81aec6e2..be0bcb3c9f2 100644 --- a/src/apps/components/AppDetailsPage/HeaderOptions.tsx +++ b/src/apps/components/AppDetailsPage/HeaderOptions.tsx @@ -42,10 +42,11 @@ const HeaderOptions: React.FC = ({ tooltip={tooltipContent} disabled={!hasManagedAppsPermission} onClick={isActive ? onAppDeactivateOpen : onAppActivateOpen} + display="flex" + alignItems="center" + gap={1} > - - - + {isActive ? ( ) : ( @@ -58,10 +59,11 @@ const HeaderOptions: React.FC = ({ tooltip={tooltipContent} disabled={!hasManagedAppsPermission} onClick={onAppDeleteOpen} + display="flex" + alignItems="center" + gap={1} > - - - + From 3a898fbd55492334b63cce0a0457f76bbee0feb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 21 Mar 2024 15:32:22 +0100 Subject: [PATCH 19/21] Improve naming and refactor AppListCardInstallButton --- .../components/AppListRow/AppListCardActions.tsx | 4 +++- .../AppListRow/AppListCardInstallButton.tsx | 4 ---- .../InstalledAppList/InstalledAppList.tsx | 4 ++-- .../components/InstalledAppList/utils.test.ts | 16 ++++++++-------- src/apps/components/InstalledAppList/utils.ts | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/apps/components/AppListRow/AppListCardActions.tsx b/src/apps/components/AppListRow/AppListCardActions.tsx index 829f31e8683..31f6d052e8b 100644 --- a/src/apps/components/AppListRow/AppListCardActions.tsx +++ b/src/apps/components/AppListRow/AppListCardActions.tsx @@ -50,7 +50,9 @@ const AppListCardActions: React.FC = ({ )} - + {installHandler && ( + + )} {installationPending && ( diff --git a/src/apps/components/AppListRow/AppListCardInstallButton.tsx b/src/apps/components/AppListRow/AppListCardInstallButton.tsx index d79a1d82028..584faa288b8 100644 --- a/src/apps/components/AppListRow/AppListCardInstallButton.tsx +++ b/src/apps/components/AppListRow/AppListCardInstallButton.tsx @@ -18,10 +18,6 @@ export const AppListCardInstallButton = ({ const intl = useIntl(); const { hasManagedAppsPermission } = useHasManagedAppsPermission(); - if (!installHandler) { - return null; - } - if (!hasManagedAppsPermission) { return ( = ({ const { hasManagedAppsPermission } = useHasManagedAppsPermission(); if ( - appsIsLoading({ appList, appInstallationList, hasManagedAppsPermission }) + appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission }) ) { return ; } diff --git a/src/apps/components/InstalledAppList/utils.test.ts b/src/apps/components/InstalledAppList/utils.test.ts index 9ce90a22b70..b1913e5f6fc 100644 --- a/src/apps/components/InstalledAppList/utils.test.ts +++ b/src/apps/components/InstalledAppList/utils.test.ts @@ -1,9 +1,9 @@ import { AppInstallation, InstalledApp } from "@dashboard/apps/types"; -import { appsIsLoading, hasEmptyAppList } from "./utils"; +import { appsAreLoading, hasEmptyAppList } from "./utils"; describe("InstalledAppList utils", () => { - describe("appsIsLoading", () => { + describe("appsAreLoading", () => { describe("has MANAGE_APPS permission", () => { const hasManagedAppsPermission = true; @@ -13,7 +13,7 @@ describe("InstalledAppList utils", () => { const appInstallationList = undefined; // Act - const isLoading = appsIsLoading({ + const isLoading = appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission, @@ -33,7 +33,7 @@ describe("InstalledAppList utils", () => { ] as AppInstallation[]; // Act - const isLoading = appsIsLoading({ + const isLoading = appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission, @@ -49,7 +49,7 @@ describe("InstalledAppList utils", () => { const appInstallationList = undefined; // Act - const isLoading = appsIsLoading({ + const isLoading = appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission, @@ -65,7 +65,7 @@ describe("InstalledAppList utils", () => { const appInstallationList: AppInstallation[] = []; // Act - const isLoading = appsIsLoading({ + const isLoading = appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission, @@ -85,7 +85,7 @@ describe("InstalledAppList utils", () => { const appInstallationList: AppInstallation[] = []; // Act - const isLoading = appsIsLoading({ + const isLoading = appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission, @@ -101,7 +101,7 @@ describe("InstalledAppList utils", () => { const appInstallationList = undefined; // Act - const isLoading = appsIsLoading({ + const isLoading = appsAreLoading({ appList, appInstallationList, hasManagedAppsPermission, diff --git a/src/apps/components/InstalledAppList/utils.ts b/src/apps/components/InstalledAppList/utils.ts index 8a5e3c2c4d9..65bc84a7566 100644 --- a/src/apps/components/InstalledAppList/utils.ts +++ b/src/apps/components/InstalledAppList/utils.ts @@ -1,6 +1,6 @@ import { AppInstallation, InstalledApp } from "@dashboard/apps/types"; -export function appsIsLoading({ +export function appsAreLoading({ appInstallationList, appList, hasManagedAppsPermission, From 83f496d662f143ee8fe674c1dd930b9747f17df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Mon, 25 Mar 2024 10:36:08 +0100 Subject: [PATCH 20/21] Improve changset --- .changeset/thin-avocados-grow.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/thin-avocados-grow.md b/.changeset/thin-avocados-grow.md index 82484f83639..05db5e03d2e 100644 --- a/.changeset/thin-avocados-grow.md +++ b/.changeset/thin-avocados-grow.md @@ -2,4 +2,5 @@ "saleor-dashboard": patch --- -Allow user without MANAGE_APPS to access to apps page +Allow all user to access to APPS tab without checking any permissions. User will be able to see installed app list and enter to each apps. +Each app will be responsible for checking user permissions. From 452e391f80f748cc9de20d7d9a0694379a674f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Tue, 26 Mar 2024 11:35:33 +0100 Subject: [PATCH 21/21] Improve tests --- src/apps/components/InstalledAppList/utils.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/apps/components/InstalledAppList/utils.test.ts b/src/apps/components/InstalledAppList/utils.test.ts index b1913e5f6fc..43436ec0703 100644 --- a/src/apps/components/InstalledAppList/utils.test.ts +++ b/src/apps/components/InstalledAppList/utils.test.ts @@ -9,7 +9,11 @@ describe("InstalledAppList utils", () => { it("should return true when has apps installed and no app installations", () => { // Arrange - const appList = undefined; + const appList = [ + { + isExternal: false, + }, + ] as InstalledApp[]; const appInstallationList = undefined; // Act @@ -59,7 +63,7 @@ describe("InstalledAppList utils", () => { expect(isLoading).toBe(true); }); - it("should return false when has apps installations and app installed", () => { + it("should return false when has apps installations and app installed", () => { // Arrange const appList: InstalledApp[] = []; const appInstallationList: AppInstallation[] = []; @@ -79,7 +83,7 @@ describe("InstalledAppList utils", () => { describe("has no MANAGE_APPS permission", () => { const hasManagedAppsPermission = false; - it("should return true when has apps installed", () => { + it("should return true when has apps installed and ignore app installation", () => { // Arrange const appList = undefined; const appInstallationList: AppInstallation[] = []; @@ -95,7 +99,7 @@ describe("InstalledAppList utils", () => { expect(isLoading).toBe(true); }); - it("should return false when has apps installed", () => { + it("should return false when has apps installed and ignore app installtion", () => { // Arrange const appList: InstalledApp[] = []; const appInstallationList = undefined;