Set of components and utilities to serve as the customization framework.
Installation
@commercetools-frontend/application-shell packagenpm --save install @commercetools-frontend/application-shell
Additionally, install the peer dependencies (if not present)
npm --save install @apollo/client react react-dom react-intl react-redux react-router-dom redux @testing-library/react @testing-library/react-hooks
Components
ApplicationShell
This is the main component that contains all the general logic to render a Custom Application.
ApplicationShell component is expected to be rendered as the top-level component of your application.Usage
children of <ApplicationShell> instead of the render prop.<ApplicationShell> to pre-configure the application entry point routes. In addition to that, the entry point route is protected by the basic View permission check. This means that a user without permissions of your Custom Application won't be able to access the Custom Application route.import { ApplicationShell } from '@commercetools-frontend/application-shell';
const loadMessages = async (locale) => {
// ...
};
const AsyncApplicationRoutes = React.lazy(() =>
import('../../routes' /* webpackChunkName: "channels" */)
);
const EntryPoint = () => (
<ApplicationShell environment={window.app} applicationMessages={loadMessages}>
<AsyncApplicationRoutes />
</ApplicationShell>
);
export default EntryPoint;
Properties
applicationMessages
This is either an object containing all the translated messages, grouped by locale
{ en: { Welcome: "Welcome" }, de: { Welcome: "Wilkommen" } }or a function that returns a Promise that resolves to such an object.
The function is called with a
locale parameter. See Import translations.environment
The application runtime environment, which is exposed in
window.app. See Runtime configuration.children
Instead of using the
render prop, render your application component as children of <ApplicationShell>.By doing so, the
<ApplicationShell> pre-configures the main application routes according to the entryPointUriPath defined in the custom-application-config.json.This is an opt-int behavior as a replacement of the
render prop, to simplify the entry point setup.render
The render function is called when the
<ApplicationShell> is ready to render the actual application. This is the case when the required data (user, project) has been fetched and the application context has been initialized.It's recommended to use the
children prop to benefit from a simpler setup.apolloClient
An optional instance of ApolloClient to be used instead of the default one. This is usually the case when you need to configure the Apollo cache. See
createApolloClient.CustomViewShell
CustomViewShell component must be rendered as the top-level component of your Custom View.Usage
children of <CustomViewShell>.<CustomViewShell> pre-configures the Custom View entry point routes. In addition, the entry point route is protected by the basic View permission check. This means that a user without permission from your Custom View won't be able to access the Custom View route.You aren't required to use routes in your Custom View. You can use them if you need to have different views. For example, a Custom View with a listing page and details page.
import { CustomViewShell } from '@commercetools-frontend/application-shell';
const loadMessages = async (locale) => {
// ...
};
const AsyncApplicationRoutes = React.lazy(() =>
import('../../routes' /* webpackChunkName: "channels" */)
);
const EntryPoint = () => (
<CustomViewShell applicationMessages={loadMessages}>
<AsyncApplicationRoutes />
</CustomViewShell>
);
export default EntryPoint;
Properties
applicationMessages
This is either an object containing all the translated messages, grouped by locale
{ en: { Welcome: "Welcome" }, de: { Welcome: "Willkommen" } }or a function that returns a Promise that resolves to such an object.
The function is called with a
locale parameter. See Import translations.children
Instead of using the
render prop, render your application component as children of <CustomViewShell>.By doing so, the
<CustomViewShell> pre-configures the main application routes according to the entryPointUriPath defined in the custom-application-config.json.This is an opt-int behavior as a replacement of the
render prop, to simplify the entry point setup.apolloClient
An optional instance of ApolloClient to be used instead of the default one. This is usually the case when you need to configure the Apollo cache. See
createApolloClient.ApplicationPageTitle
21.15.0 onwards.<title>.Usage
We recommend using this component on pages with a human-readable resource identifier, for example, a Product name on the product details page.
import { ApplicationPageTitle } from '@commercetools-frontend/application-shell';
<ApplicationPageTitle additionalParts={['Red shoes']} />;
// Red shoes - products - my-shop - Merchant Center
<ApplicationPageTitle> component is used multiple times, the last one rendered will overwrite the previous ones.Properties
additionalParts
A list of parts to be prepended to the default page title, separated by
-.Hooks
useMcQuery
useMcQuery properly types the context object, which is always used to define the GraphQL target. See Data fetching.useMcLazyQuery
useMcLazyQuery properly types the context object, which is always used to define the GraphQL target. See Data fetching.useMcMutation
useMcMutation properly types the context object, which is always used to define the GraphQL target. See Data fetching.Utilities
setupGlobalErrorListener
entry-point file.For Custom Applications
For Custom Views
import {
setupGlobalErrorListener,
ApplicationShell,
} from '@commercetools-frontend/application-shell';
setupGlobalErrorListener();
const EntryPoint = () => {
return (
<ApplicationShell
apolloClient={apolloClient}
// ...other props
/>
);
};
createApolloClient
Creates a new instance of the Apollo Client. Use this to extend certain functionalities of the preconfigured Apollo Client.
import { createApolloClient } from '@commercetools-frontend/application-shell';
createApolloClient({
// ...
});
Available options are:
-
cache(optional): Configuration of the Apollo cache in relation to the data requirements of your customization. -
restLink(optional): Instance of the Apollo REST link.This feature is available from version21.10.0onwards.Theapollo-link-restand its related dependencies are not included in the@commercetools-frontend/application-shellpackage and must be installed separately.When configuring the REST link, we recommend setting theuriusing the getMcApiUrl() utility function.
src/apollo-client.js, and define the configuration there.import { createApolloClient } from '@commercetools-frontend/application-shell';
const configureApollo = () =>
createApolloClient({
cache: {
// ...
},
});
export default configureApollo;
<ApplicationShell> or <CustomViewShell>.For Custom Applications
For Custom Views
import { ApplicationShell } from '@commercetools-frontend/application-shell';
import configureApolloClient from '../../apollo-client';
const apolloClient = configureApolloClient();
const EntryPoint = () => {
return (
<ApplicationShell
apolloClient={apolloClient}
// ...other props
/>
);
};
Furthermore, in your tests you also need to create a new instance of your custom Apollo Client and pass it to the test utils.
For Custom Applications
For Custom Views
import { renderAppWithRedux } from '@commercetools-frontend/application-shell/test-utils';
import configureApolloClient from '../../apollo-client';
renderAppWithRedux({
apolloClient: configureApolloClient(),
// ...
});
createApolloContextForProxyForwardTo
context object with all the required options for using the /forward-to endpoint. See Integrate with your own API.For Custom Applications
For Custom Views
import {
createApolloContextForProxyForwardTo,
useMcQuery,
} from '@commercetools-frontend/application-shell';
import { useApplicationContext } from '@commercetools-frontend/application-shell-connectors';
const useExternalApiFetcher = () => {
// Assuming that the `custom-application-config` file contains the custom value:
// `{ additionalEnv: { externalApiUrl: 'https://my-custom-app.com/graphql'} }`
const externalApiUrl = useApplicationContext(
(context) => context.environment.externalApiUrl
);
const { loading, data, error } = useMcQuery(MyQuery, {
context: createApolloContextForProxyForwardTo({
// The URL to your external API
uri: externalApiUrl,
// Provide custom HTTP headers (optional)
headers: {
'x-foo': 'bar',
},
// Set `"X-Forward-To-Audience-Policy"` header in the request with provided value (optional)
audiencePolicy: 'forward-url-full-path',
// Set `"X-Forward-To-Claims": "permissions"` header in the request (optional)
includeUserPermissions: true,
}),
});
return {
loading,
data,
error,
};
};
Available options are:
uri(required): The URL of the external API to forward the request to.headers(optional): Additional HTTP headers to be included in the request to the external API.audiencePolicy(optional): See configure the audience policy.includeUserPermissions(optional): See configure custom claims.version(optional): See versioning.
executeHttpClientRequest
21.10.0 onwards.- Defining the required/recommended HTTP headers for the Merchant Center API.
- Automatically renewing the token to access a particular API.
import { executeHttpClientRequest } from '@commercetools-frontend/application-shell';
THttpClientFetcher to execute the request and an optional object THttpClientConfig for the HTTP request configuration.The callback function
THttpClientFetcher is passed one argument with the configured request options THttpClientOptions that you would use to configure the HTTP request for your HTTP client.type THttpClientOptions = {
credentials: 'include';
/**
* The HTTP headers included by default are:
* - Accept
* - Authorization (only in development)
* - X-Application-Id
* - X-Correlation-Id
* - X-Project-Key
* - X-User-Agent
*/
headers: THeaders;
};
type TFetcherResponse<Data> = {
/**
* The parsed response from the server.
*/
data: Data;
/**
* The HTTP status code from the server response.
*/
statusCode: number;
/**
* Implement a function to access the HTTP headers from the server response.
*/
getHeader: (headerName: string) => string | null;
};
type THttpClientFetcher<Data> = (
options: THttpClientOptions
) => Promise<THttpClientFetcherResponse<Data>>;
async function executeHttpClientRequest<Data>(
fetcher: THttpClientFetcher<Data>,
config?: THttpClientConfig
): Promise<Data>;
THttpClientConfig object accepts the following options:-
userAgent(optional): A custom user agent to identify the HTTP client.
We recommend to use the@commercetools/http-user-agentpackage.import createHttpUserAgent from '@commercetools/http-user-agent'; const userAgent = createHttpUserAgent({ name: 'fetch-client', version: '2.6.0', libraryName: window.app.applicationName, contactEmail: 'support@my-company.com', }); -
headers(optional): Additional HTTP headers to be included in the request. The provided recommended headers won't be overwritten. -
forwardToConfig(optional): Configuration for using the/proxy/forward-toendpoint to connect to an external API.uri(required): The URL of the external API to forward the request to.headers(optional): Additional HTTP headers to be included in the request to the external API.audiencePolicy(optional): See configure the audience policy.includeUserPermissions(optional): See configure custom claims.version(recommended): See versioning.
You can see some examples of integrating this with different HTTP clients:
getMcApiUrl
import { getMcApiUrl } from '@commercetools-frontend/application-shell';
const mcApiUrl = getMcApiUrl();
// https://mc-api.<region>.commercetools.com
buildApiUrl
import { buildApiUrl } from '@commercetools-frontend/application-shell';
const apiEndpoint = buildApiUrl('/proxy/ctp/channels');
// https://mc-api.<region>.commercetools.com/proxy/ctp/channels
Test utils
The package provides a separate entry point with utilities for testing customizations.
import /**/ '@commercetools-frontend/application-shell/test-utils';
test-utils simulate the components-under-test as if it was rendered by the <ApplicationShell> or <CustomViewShell> and provide the necessary setup to fully test a customization. This includes things like Apollo, React Intl, React Router, etc.renderApp
render method of React Testing Library. All the basic setup for testing is included here.Usage
import {
renderApp,
screen,
} from '@commercetools-frontend/application-shell/test-utils';
describe('rendering', () => {
it('should render the authenticated users first name', async () => {
renderApp(<FirstName />, {
user: {
firstName: 'Leonard',
},
});
await screen.findByText('First name: Leonard');
});
});
Options
locale
Determines the UI language and number format. Is used to configure
<IntlProvider>. Only core messages will be available during tests, no matter the locale. The locale can be a full IETF language tag, although the Merchant Center is currently only available in a limited set of languages.dataLocale
Sets the locale which is used to display
LocalizedStrings.mocks
Allows mocking requests made with Apollo.
mocks is forwarded as the mocks argument to MockedProvider. If mocks is not provided or is an empty array, the Apollo MockedProvider is not used. This is an opt-in functionality, as the default behavior is to mock requests using Mock Service Worker.apolloClient
Pass a custom instance of Apollo client, useful when your Custom Application has some custom cache policies. You can use the exported function
createApolloClient of @commercetools-frontend/application-shell.route
The route the user is on, like
/test-project/products. Defaults to /.disableAutomaticEntryPointRoutes
Pass
true if you are using the render prop for the <ApplicationShell> instead of the children prop.history
By default a memory-history is generated which has the provided
route set as its initial history entry. It's possible to pass a custom history as well. In that case, we recommend using the factory function createEnhancedHistory from the @commercetools-frontend/browser-history package, as it contains the enhanced location with the parsed query object.adapter
flags
An object whose keys are feature-toggle keys and whose values are their toggle state. Use this to test your component with different feature toggle combinations. Example:
{ betaUserProfile: true }.environment
Allows to set the
applicationContext.environment. The passed object gets merged with the tests default environment. Pass null to completely remove the environment, which renders the ui as if no environment was given.user
Allows to set the
applicationContext.user. The passed object gets merged with the test's default user. Pass null to completely remove the user, which renders the ui as if no user was authenticated.project
Allows to set the
applicationContext.project. The passed object gets merged with the tests default project. Pass null to completely remove the project which renders the ui outside of a project context.Return values
renderApp returns the same Result object of React Testing Library, with the addition of the following properties:history
The history created by
renderApp which is passed to the router. It can be used to simulate location changes and so on.user
The
user object used to configure <ApplicationContextProvider>, so the result of merging the default user with options.user. Note that this is not the same as applicationContext.user. Can be undefined when no user is authenticated (when options.user was null).project
The
project object used to configure <ApplicationContextProvider>, so the result of merging the default project with options.project. Note that this is not the same as applicationContext.project. Can be undefined when no project was set (when options.project was null).environment
The
environment object used to configure <ApplicationContextProvider>, so the result of merging the default environment with options.environment. Note that this is not the same as applicationContext.environment. Can be undefined when no environment was set (when options.environment was null).renderAppWithRedux
renderApp method with the additional support of Redux. This is only useful if your components-under-test relies on Redux, for example when dispatching notifications.Usage
import {
renderAppWithRedux,
screen,
} from '@commercetools-frontend/application-shell/test-utils';
describe('rendering', () => {
it('should render the authenticated users first name', async () => {
renderAppWithRedux(<FirstName />, {
user: {
firstName: 'Leonard',
},
});
await screen.findByText('First name: Leonard');
});
});
Options
renderApp.
It is not possible to pass either storeState or sdkMocks together with store.store
A custom redux store.
storeState
Pass an initial state to the default Redux store.
sdkMocks
Allows mocking requests made with
@commercetools-frontend/sdk (Redux). The sdkMocks is forwarded as mocks to the SDK test-utils.renderCustomView
render method of the React Testing Library. All the basic setup for testing is included here.Usage
import {
renderCustomView,
screen,
} from '@commercetools-frontend/application-shell/test-utils';
describe('rendering', () => {
it('should render the authenticated users first name', async () => {
renderCustomView({
user: {
firstName: 'Leonard',
},
children: <FirstName />,
});
await screen.findByText('First name: Leonard');
});
});
Options
locale
Determines the UI language and number format. It is used to configure
<IntlProvider>. Only core messages will be available during tests, no matter the locale. The locale can be a full IETF language tag, although the Merchant Center is currently only available in a limited set of languages.projectKey
Sets the Custom View context
projectKey. The passed key gets merged with the tests default project.projectAllAppliedPermissions
Sets the default project
allAppliedPermissions property. The passed array will replace the default project allAppliedPermissions.customViewHostUrl
Defines the URL the Custom View receives as part of the emulation of it being rendered in a Merchant Center built-in application.
customViewConfig
The configuration object used to configure the
<CustomViewContextProvider>, so the result of merging the default Custom View configuration with options.customViewConfig.apolloClient
Pass a custom instance of Apollo client, useful when your Custom View has some custom cache policies. You can use the exported function
createApolloClient of @commercetools-frontend/application-shell.environment
Sets the
customViewContext.environment. The passed object gets merged with the tests default environment. Pass null to completely remove the environment, which renders the ui as if no environment was given.user
Sets the
customViewContext.user. The passed object gets merged with the test's default user. Pass null to completely remove the user, which renders the ui as if no user was authenticated.Return values
renderCustomView returns the Result object of React Testing Library, with the addition of the following properties:history
The history created by
renderApp which is passed to the router. It can be used to simulate location changes and so on.user
The
user object used to configure <CustomViewContextProvider>, so the result of merging the default user with options.user. Note that this is not the same as customViewContext.user. Can be undefined when no user is authenticated (when options.user was null).project
The
project object used to configure <CustomViewContextProvider>, so the result of merging the default project with options.project. Note that this is not the same as customViewContext.project. Can be undefined when no project was set (when options.project was null).environment
The
environment object used to configure <CustomViewContextProvider>, so the result of merging the default environment with options.environment. Note that this is not the same as customViewContext.environment. Can be undefined when no environment was set (when options.environment was null).mapNotificationToComponent
Pass a function to map a notification to a custom component.
mapResourceAccessToAppliedPermissions
{
project: {
allAppliedPermissions: mapResourceAccessToAppliedPermissions([
PERMISSIONS.View,
]),
},
}
denormalizePermissions
Helper function to map user permissions defined as objects to a list of applied resource permissions.
{
project: {
allAppliedPermissions: denormalizePermissions({
canViewCustomChannels: true,
}),
},
}