Skip to content

Commit

Permalink
Custom Views context hook adjustment (#3292)
Browse files Browse the repository at this point in the history
* refactor(application-shell): cutom views context hook adjustment

* refactor(application-shell): allow custom view shell to receive a custom apollo client

* refactor(application-shell-connectors): change custom view context property name

* refactor(application-shell): allow more props in custom view tests renderer

* refactor(application-shell): make custom view tests renderer options optional

* refactor(application-config): introduce helper to calculate custom views resource accesses

* chore: add changesets

* refactor(application-config): rename new formatters

* chore: update changeset

Co-authored-by: Nicola Molinari <[email protected]>

---------

Co-authored-by: Nicola Molinari <[email protected]>
  • Loading branch information
CarlosCortizasCT and emmenko authored Nov 14, 2023
1 parent 828189d commit 5de8c88
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-clouds-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools-frontend/application-config': minor
---

Add new helper functions `computeCustomViewPermissionsKeys` and `computeCustomViewResourceAccesses` to compute the permission names for Custom Views.
5 changes: 5 additions & 0 deletions .changeset/late-masks-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools-applications/merchant-center-custom-view-template-starter-typescript': minor
---

Use the `useCustomViewContext` hook to fetch context data in the channels view example.
5 changes: 5 additions & 0 deletions .changeset/long-insects-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools-frontend/application-shell': minor
---

Extend the `renderCustomView` test utility to allow more parameters to control how Custom Views are rendered in the testing context.
5 changes: 5 additions & 0 deletions .changeset/nervous-candles-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@commercetools-frontend/application-shell-connectors': minor
---

Extend the `userCustomViewContext` so it not only returns the context specific to Custom View cofiguration but also to the underlying application context (user, project, etc.).
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useIntl } from 'react-intl';
import { useApplicationContext } from '@commercetools-frontend/application-shell-connectors';
import { useCustomViewContext } from '@commercetools-frontend/application-shell-connectors';
import { NO_VALUE_FALLBACK } from '@commercetools-frontend/constants';
import {
usePaginationState,
Expand Down Expand Up @@ -28,10 +28,12 @@ const columns = [

const Channels = () => {
const intl = useIntl();
const user = useApplicationContext((context) => context.user);
const dataLocale = useApplicationContext((context) => context.dataLocale);
const projectLanguages = useApplicationContext(
(context) => context.project?.languages
const { user, dataLocale, projectLanguages } = useCustomViewContext(
(context) => ({
user: context.user,
dataLocale: context.dataLocale,
projectLanguages: context.project?.languages,
})
);
const { page, perPage } = usePaginationState();
const tableSorting = useDataTableSortingState({ key: 'key', order: 'asc' });
Expand Down
46 changes: 45 additions & 1 deletion packages/application-config/src/formatters.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH } from '@commercetools-frontend/constants';
import { entryPointUriPathToPermissionKeys } from './formatters';
import {
computeCustomViewPermissionsKeys,
computeCustomViewResourceAccesses,
entryPointUriPathToPermissionKeys,
} from './formatters';

describe.each`
entryPointUriPath | formattedResourceAccessKey
Expand Down Expand Up @@ -77,3 +81,43 @@ describe.each`
});
}
);

describe.each`
permissionGroupNames | formattedResourceAccessKeyAdditionalName
${undefined} | ${''}
${['books']} | ${'Books'}
${['the-books']} | ${'TheBooks'}
${['the-movies']} | ${'TheMovies'}
`(
'computing Custom Views resource accesses',
({ permissionGroupNames, formattedResourceAccessKeyAdditionalName }) => {
it(`should format correctly`, () => {
expect(computeCustomViewResourceAccesses(permissionGroupNames)).toEqual({
view: `view`,
manage: `manage`,
[`view${formattedResourceAccessKeyAdditionalName}`]: `view${formattedResourceAccessKeyAdditionalName}`,
[`manage${formattedResourceAccessKeyAdditionalName}`]: `manage${formattedResourceAccessKeyAdditionalName}`,
});
});
}
);

describe.each`
permissionGroupNames | formattedResourceAccessKeyAdditionalName
${undefined} | ${''}
${['books']} | ${'Books'}
${['the-books']} | ${'TheBooks'}
${['the-movies']} | ${'TheMovies'}
`(
'computing Custom Views permissions keys',
({ permissionGroupNames, formattedResourceAccessKeyAdditionalName }) => {
it(`should format correctly`, () => {
expect(computeCustomViewPermissionsKeys(permissionGroupNames)).toEqual({
View: `View`,
Manage: `Manage`,
[`View${formattedResourceAccessKeyAdditionalName}`]: `View${formattedResourceAccessKeyAdditionalName}`,
[`Manage${formattedResourceAccessKeyAdditionalName}`]: `Manage${formattedResourceAccessKeyAdditionalName}`,
});
});
}
);
24 changes: 24 additions & 0 deletions packages/application-config/src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ type TImplicitCustomApplicationResourceAccesses<
string
>;

type TImplicitCustomViewResourceAccesses<
PermissionGroupName extends string = ''
> = TImplicitCustomApplicationResourceAccesses<PermissionGroupName>;

type TImplicitCustomApplicationPermissionKeys<
PermissionGroupName extends string = ''
> = Record<
Expand Down Expand Up @@ -118,6 +122,24 @@ function entryPointUriPathToResourceAccesses<
};
}

function computeCustomViewResourceAccesses<PermissionGroupName extends string>(
permissionGroupNames?: PermissionGroupName[]
): TImplicitCustomViewResourceAccesses<PermissionGroupName> {
return entryPointUriPathToResourceAccesses(
CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH,
permissionGroupNames || []
);
}

function computeCustomViewPermissionsKeys<PermissionGroupName extends string>(
permissionGroupNames?: PermissionGroupName[]
): TImplicitCustomApplicationPermissionKeys<PermissionGroupName> {
return entryPointUriPathToPermissionKeys(
CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH,
permissionGroupNames || []
);
}

function entryPointUriPathToPermissionKeys(
entryPointUriPath: string
): TImplicitCustomApplicationPermissionKeys<''>;
Expand Down Expand Up @@ -149,4 +171,6 @@ export {
entryPointUriPathToPermissionKeys,
formatEntryPointUriPathToResourceAccessKey,
formatPermissionGroupNameToResourceAccessKey,
computeCustomViewResourceAccesses,
computeCustomViewPermissionsKeys,
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { createContext, useContext, useMemo, type ReactNode } from 'react';
import type { CustomViewData } from '@commercetools-frontend/constants';
import {
type TApplicationContext,
useApplicationContext,
} from '../application-context';

export type TCustomViewContext = {
hostUrl: string;
config: CustomViewData;
customViewConfig: CustomViewData;
};

export type TMergedContext = TApplicationContext<{}> & TCustomViewContext;

export type TCustomViewContextProviderProps = {
hostUrl: string;
customViewConfig: CustomViewData;
Expand All @@ -18,7 +24,7 @@ const CustomViewContextProvider = (props: TCustomViewContextProviderProps) => {
const contextValue = useMemo<TCustomViewContext>(
() => ({
hostUrl: props.hostUrl,
config: props.customViewConfig,
customViewConfig: props.customViewConfig,
}),
[props.hostUrl, props.customViewConfig]
);
Expand All @@ -29,23 +35,24 @@ const CustomViewContextProvider = (props: TCustomViewContextProviderProps) => {

// Use function overloading to declare two possible signatures with two
// distict return types, based on the selector function argument.
function useCustomViewContextHook(): TCustomViewContext;
function useCustomViewContextHook(): TMergedContext;
function useCustomViewContextHook<SelectedContext>(
selector: (context: TCustomViewContext) => SelectedContext
selector: (context: TMergedContext) => SelectedContext
): SelectedContext;

// Then implement the function. Typescript will pick the appropriate signature
// based on the function arguments.
function useCustomViewContextHook<SelectedContext>(
selector?: (context: TCustomViewContext) => SelectedContext
selector?: (context: TMergedContext) => SelectedContext
) {
const context = useContext(Context);
// Because of the way the CustomViewShell configures the Context.Provider,
// we ensure that, when we read from the context, we always get actual
// context object and not the initial value.
// Therefore, we can safely cast the value to be out `TCustomViewContext` type.
const customViewContext = context as TCustomViewContext;
return selector ? selector(customViewContext) : customViewContext;
const customViewContext = useContext(Context) as TCustomViewContext;
const applicationContext = useApplicationContext();
const mergedContext = { ...applicationContext, ...customViewContext };
return selector ? selector(mergedContext) : mergedContext;
}

// This is a workaround to trick babel/rollup to correctly export the function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
StrictMode,
type ReactNode,
} from 'react';
import { ApolloClient, type NormalizedCacheObject } from '@apollo/client';
import { PageUnauthorized } from '@commercetools-frontend/application-components';
import { CustomViewContextProvider } from '@commercetools-frontend/application-shell-connectors';
import {
Expand Down Expand Up @@ -44,6 +45,7 @@ type THostEventData = {
};

type TCustomViewShellProps = {
apolloClient?: ApolloClient<NormalizedCacheObject>;
applicationMessages: TAsyncLocaleDataProps['applicationMessages'];
disableDevHost?: boolean;
enableReactStrictMode?: boolean;
Expand Down Expand Up @@ -134,6 +136,7 @@ function CustomViewShell(props: TCustomViewShellProps) {
<ApplicationShellProvider
environment={window.app}
applicationMessages={props.applicationMessages}
apolloClient={props.apolloClient}
>
{({ isAuthenticated }) => {
if (isAuthenticated) {
Expand Down
22 changes: 20 additions & 2 deletions packages/application-shell/src/test-utils/custom-views-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { ReactNode } from 'react';
import { ApolloClient, type NormalizedCacheObject } from '@apollo/client';
import type { RenderResult } from '@testing-library/react';
import { CustomViewContextProvider } from '@commercetools-frontend/application-shell-connectors';
import type { CustomViewData } from '@commercetools-frontend/constants';
import {
CustomViewContextProvider,
type TProviderProps,
} from '@commercetools-frontend/application-shell-connectors';
import {
CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH,
type CustomViewData,
} from '@commercetools-frontend/constants';
import { TCustomViewType } from '../types/generated/settings';
import { renderApp } from './test-utils';

Expand All @@ -23,8 +30,12 @@ const testCustomViewData: CustomViewData = {
type TRenderCustomViewParams = {
locale: string;
projectKey?: string;
projectAllAppliedPermissions?: { name: string; value: boolean }[];
customViewHostUrl?: string;
customViewConfig?: Partial<CustomViewData>;
apolloClient?: ApolloClient<NormalizedCacheObject>;
environment?: Partial<TProviderProps<{}>['environment']>;
user?: Partial<TProviderProps<{}>['user']>;
children: ReactNode;
};

Expand All @@ -42,10 +53,17 @@ export const renderCustomView = (
{props.children}
</CustomViewContextProvider>,
{
apolloClient: props.apolloClient,
locale: props.locale,
project: {
key: props.projectKey,
allAppliedPermissions: props.projectAllAppliedPermissions || [],
},
environment: {
...(props.environment || {}),
entryPointUriPath: CUSTOM_VIEW_HOST_ENTRY_POINT_URI_PATH,
},
user: props.user,
}
);
};

2 comments on commit 5de8c88

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for merchant-center-application-kit ready!

✅ Preview
https://merchant-center-application-gv158habh-commercetools.vercel.app

Built with commit 5de8c88.
This pull request is being automatically deployed with vercel-action

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for application-kit-custom-views ready!

✅ Preview
https://application-kit-custom-views-ga4u32414-commercetools.vercel.app

Built with commit 5de8c88.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.