Skip to main content

App Registry

This service allows apps to register themselves with the platform.

Routes

The primary way apps display UI is via routed pages. To do so performantly, we must lazy-load the registered routed pages. This requires a little more ceremony around how we register routes and how the route module is structured.

Register a Route

A route can be registered with registerRoutes. Registration can only be done from a TypeScript file. A route can be responsible for 1-N paths. An app may be structured such that each path has its own page/component it resolves to. In this case, each registerRoutes call will only have one path. Other apps may have multiple paths point to a single component that handles additional routing internally. Path duplicates - checked against all registered apps - will throw an error at runtime.

entry.ts

import { registerRoutes } from '@rhapsody/app-registry'

registerRoutes({
paths: ['/example/dashboard'],
bridgeLoader: () => import('./routesBridge'),
})

The bridge module requires a render export. render is a function that will display the route's UI when called. While it's not required, it is recommended that this file be a TypeScript file as well.

routesBridge.tsx

import { ExampleDashboard } from './Dashboard/Dashboard'

export const render() {
return <ExampleDashboard />
}

Path Requirements

All paths must:

  • Start with a forward slash
  • Not end with a forward slash
  • Only contain alphanumeric characters, forward slashes, dashes, underscores, and colons

Paths may include:

  • Parameters prefixed with a colon, e.g. /example/:id/detail

Path best practices include:

  • Using dashes (-) instead of underscores (_) in paths
  • Camel-casing parameter names, e.g. /user/:userId

Advanced: Get Routes

caution

This operation should only be done by the platform in the nav shell.

Routes can be retrieved from the registry using getAllRegisteredRoutes.

import { Switch } from 'react-router-dom'
import { getAllRegisteredRoutes } from '@rhapsody/app-registry'

export function Routes() {
const [routes] = useState(getAllRegisteredRoutes)

return (
<Switch>
{routes.map((route) => {
// ...
})}
</Switch>
)
}

Redirects

Apps can signal the platform that a URL needs to redirect from one to another.

Register a Redirect

A redirect can be registered with registerRedirect. It takes an object, or array of objects, with a from and to field. Any duplicates in the from field with any other registered redirect's from value as well as any registered route paths will throw an error at runtime. Both from and to fields should adhere to the route path requirements.

entry.ts

import { registerRedirect } from '@rhapsody/app-registry'

registerRedirect({
from: '/example/old',
to: '/example/new',
})
// You can also register an array of redirects as well
registerRedirect([
{ from: '/example/team-dashboard', to: '/example/dashboard/team' },
{ from: '/example/user-dashboard', to: '/example/dashboard/user' },
])

Advanced: Get Redirects

caution

This operation should only be done by the platform in the nav shell.

Routes can be retrieved from the registry using getAllRegisteredRedirects.

import { Switch } from 'react-router-dom'
import { getAllRegisteredRedirects } from '@rhapsody/app-registry'

export function Redirects() {
const [redirects] = useState(getAllRegisteredRedirects)

return (
<Switch>
{redirects.map((redirect) => {
// ...
})}
</Switch>
)
}

Settings Sections

Apps can inject settings pages into the platform by registering settings sections. To do so performantly, we must lazy-load the settings section pages. This requires a little more ceremony around how we register settings sections and how a settings page module is structured.

Register a Settings Section

A settings section can be registered with registerSettingsSection. Registration can only be done from a TypeScript file. A settings section will be shown as a menu item in the settings sidebar as well as render 1-N main and additional pages. If a settings section renders more than one main page, navigation tabs will automatically be shown for each main page. Page paths for main and additional pages are built up from path fragments. For instance, a section with the path fragment /example and a page with the path fragment /tab-1 will have the full path of /settings/example/tab-1. All path fragment fields must adhere to the route path requirements. Full path duplicates - checked against other settings sections, registered routes, and registered redirects - will throw an error at runtime. One can also optionally specify an integer to affect the order of the section menu item (lower numbers display higher in the list) and whether to include additional pages as a part of a given settings section. The latter is primarily for full-screen settings pages.

entry.ts

import { registerSettingsSection } from '@rhapsody/app-registry'

registerSettingsSection({
title: 'Example',
category: 'personal',
pathFragment: '/example',
mainPages: [
{
title: 'Tab 1',
pathFragment: '/tab-1',
bridgeLoader: () => import('./firstTabBridgeModule'),
},
{
title: 'Tab 2',
pathFragment: '/tab-2',
bridgeLoader: () => import('./secondTabBridgeModule'),
},
],
additionalPages: [
{
title: 'Full Page',
pathFragment: '/full-page',
bridgeLoader: () => import('./fullScreenBridgeModule'),
},
],
order: -1,
})

The bridge module requires a render export. render is a function that will display each page's UI when called. While it's not required, it is recommended that this file be a TypeScript file as well.

firstTabBridgeModule.tsx

import { ExampleSettings } from './ExampleSettings/ExampleSettings'

export const render() {
return <ExampleSettings />
}

Advanced: Get Settings Sections

caution

This operation should only be done by the platform in the nav shell.

Settings sections can be retrieved from the registry using getAllRegisteredSettingsSection.

import { Switch } from 'react-router-dom'
import {
getAllRegisteredSettingsSection,
normalizeSettingsPaths,
} from '@rhapsody/app-registry'

const getRoutesFromSettingsSections = () =>
normalizeSettingsPaths(getAllRegisteredSettingsSection())

export function SettingsRoutes() {
const [settingsRoutes] = useState(getRoutesFromSettingsSections)

return (
<Switch>
{settingsRoutes.map((settingsRoute) => {
// ...
})}
</Switch>
)
}

Top Nav Icons

One of the ways apps can add themselves to the platform is via nav icons in the top-right corner of the nav shell. To do so performantly, we must lazy-load the registered icons. This requires a little more ceremony around how we register icons and how the icon module is structured.

Register a Nav Icon

A top nav icon can be registered with registerNavIcon. Registration can only be done from a TypeScript file. When registering a nav icon, one must provide a unique namespaced string ID for the icon as well as a dynamic import for the bridge module, which we'll discuss in a second. One can also optionally specify an integer to affect the order of the icons (lower numbers display further left) and whether to display the icon when in a "minimized"/"mobile" view.

entry.ts

import { registerNavIcon } from '@rhapsody/app-registry'

registerNavIcon({
id: 'topNavIcon.app.messenger',
bridgeLoader: () => import('./messenger'),
order: -5,
displayWhenMinimized: true,
})

The bridge module must have two exports: render and shouldRender. render is a function that will display the icon when called. shouldRender is either an async function that resolves to a boolean indicating whether the icon should be rendered or a static value of true if the icon should always be rendered. While it's not required, it is recommended that this file be a TypeScript file as well.

messenger.ts

import { TopNavRenderArgs } from '@rhapsody/app-registry'

// Function to render the icon. The platform provides an appropriately sized
// `Skeleton` component passed in as `skeletonComponent`. One also has access
// to the options used to register the icon, such as `id`, with the exception
// of `bridgeLoader`.
export function render({ skeletonComponent: Skeleton }: TopNavRenderArgs) {
const { data, isLoading } = useFetchMessages()

if (isLoading) return <Skeleton />

return <div>Number of messages: {data.length}</div>
}

// Async function to detemine whether to render the icon or not. It's passed
// the same argument as `render`. This can also be a static value of `true`
// if the icon should always render, e.g. `export const shouldRender = true`.
export async function shouldRender(_renderArgs: TopNavRenderArgs) {
const settings = await getMessengerSettings()
return settings.isEnabled
}

Advanced: Get Nav Icons

caution

This operation should only be done by the platform in the nav shell.

Nav icons can be retrieved from the registry using getAllRegisteredNavIcons. The nav icons are returned pre-sorted by order from lowest to highest.

import { getAllRegisteredNavIcons } from '@rhapsody/app-registry'

export function NavIcons() {
const [icons] = useState(getAllRegisteredNavIcons)

return (
<ul>
{icons.map((icon) => {
// ...
})}
</ul>
)
}

Tasks

One of the ways apps can add themselves to the platform is via tasks that are invoked when the application boots. To do so performantly, we must lazy-load the tasks. This requires a little more ceremony around how we register tasks and how the task module is structured.

Register a Task

A task can be registered with registerTask. Registration can only be done from a TypeScript file. When registering a task, one must provide a unique namespaced string ID for the task as well as a dynamic import for the bridge module, which we'll discuss in a second.

entry.ts

import { registerTask } from '@rhapsody/app-registry'

registerTask({
id: 'task.app.dialer',
bridgeLoader: () => import('./taskBridge'),
})

The bridge module must have one exports: executeTask. executeTask is a function that invokes the logic required for the current task. While it's not required, it is recommended that this file be a TypeScript file as well.

taskBridge.ts

import { subscribeToEvent } from '@rhapsody/events'

// Function to execute the task's business logic. The `taskBridge.ts` file
// should not be side effectful. All work should begin when `executeTask` is run.
export function executeTask() {
subscribeToEvent(
'event.app.dialer.pane.toggleVisibility',
() => {
// Do something
},
{ name: 'Dialer Task' }
)
}

Advanced: Get Tasks

caution

This operation should only be done by the platform.

Tasks can be retrieved from the registry using getAllRegisteredTasks.

import { getAllRegisteredTasks } from '@rhapsody/app-registry'

function loadAllTasks() {
const tasks = getAllRegisteredTasks()
tasks.forEach(async () => {
const { executeTask } = await task.bridgeLoader()
executeTask()
})
}

Global Components

One of the ways apps can add themselves to the platform is via global components (most commonly a modal) that will mount when the application boots. To do so performantly, we must lazy-load the global components. This requires a little more ceremony around how we register global components and how the global component module is structured.

Register a Global Component

A global component can be registered with registerGlobalComponent. Registration can only be done from a TypeScript file. When registering a global component, one must provide a unique namespaced string ID for the global component as well as a dynamic import for the bridge module, which we'll discuss in a second.

entry.ts

import { registerGlobalComponent } from '@rhapsody/app-registry'

registerGlobalComponent({
id: 'globalComponent.app.dialer',
bridgeLoader: () => import('./globalComponentBridge'),
})

The bridge module must have two exports: render and shouldRender. render is a function that will display the global component when called. shouldRender is either an async function that resolves to a boolean indicating whether the global component should be rendered or a static value of true if the global component should always be rendered. While it's not required, it is recommended that this file be a TypeScript file as well.

globalComponentBridge.tsx

import { ExampleModal } from './ExampleModal/ExampleModal'
import { getExampleModalSettings } from './api'

// Function to render the global component.
export function render() {
return <ExampleModal />
}

// Async function to detemine whether to render the global component or not.
// This can also be a static value of `true` if the global component should
// always render, e.g. `export const shouldRender = true`.
export async function shouldRender() {
const settings = await getExampleModalSettings()
return settings.isEnabled
}

Advanced: Get Global Components

caution

This operation should only be done by the platform.

Global components can be retrieved from the registry using getAllRegisteredGlobalComponents.

import { getAllRegisteredGlobalComponents } from '@rhapsody/app-registry'

export function GlobalComponents() {
const [globalComponents] = useState(getAllRegisteredGlobalComponents)

return (
<>
{globalComponents.map((globalComponent) => {
// ...
})}
</>
)
}

Banners

One of the ways apps can add themselves to the platform is via banners that show at the top of the screen. To do so performantly, we must lazy-load the banners. This requires a little more ceremony around how we register banners and how the banner module is structured.

Register a Banner

A banner can be registered with registerBanner. Registration can only be done from a TypeScript file. When registering a banner, one must provide a unique namespaced string ID for the banner as well as a dynamic import for the bridge module, which we'll discuss in a second.

entry.ts

import { registerBanner } from '@rhapsody/app-registry'

registerBanner({
id: 'banner.app.crm',
bridgeLoader: () => import('./bannerBridge'),
})

The bridge module must have two exports: render and shouldRender. render is a function that will display the banner when called. shouldRender is either an async function that resolves to a boolean indicating whether the banner should be rendered or a static value of true if the banner should always be rendered. While it's not required, it is recommended that this file be a TypeScript file as well.

bannerBridge.tsx

import { ExampleBanner } from './ExampleBanner/ExampleBanner'
import { getExampleBannerSettings } from './api'

// Function to render the banner.
export function render() {
return <ExampleBanner />
}

// Async function to detemine whether to render the banner or not.
// This can also be a static value of `true` if the banner should
// always render, e.g. `export const shouldRender = true`.
export async function shouldRender() {
const settings = await getExampleBannerSettings()
return settings.isEnabled
}

Advanced: Get Banners

caution

This operation should only be done by the platform.

Banners can be retrieved from the registry using getAllRegisteredBanners.

import { getAllRegisteredBanners } from '@rhapsody/app-registry'

export function Banners() {
const [banners] = useState(getAllRegisteredBanners)

return (
<>
{banners.map((globalComponent) => {
// ...
})}
</>
)
}

Dashboard Panels

A Dashboard is a view of multiple panels with information and data that allows you to monitor scheduled actions, meetings, cadences, tasks, etc. The Dashboard is designed to display multiple visualizations that work together on a single screen. It offers a comprehensive view of your data and provides key insights for at-a-glance decision-making. The panels are the items inside it and with this library, you can render each one of those individually

Register a Dashboard Panel

A dashboard panel can be registered with registerDashboardPanel. Registration can only be done from a TypeScript file. When registering a dashboard panel, one must provide a unique namespaced string ID for the dashboard panel as well as a dynamic import for the bridge module, which we'll discuss in a second. You must also specify a dashboard ID, title, and name for the dashboard panel.

entry.ts

import { registerDashboardPanel } from '@rhapsody/app-registry'

registerDashboardPanel({
id: 'dashboardPanel.app.actionsForecastPanel',
bridgeLoader: () => import('./dashboardPanelLinkBridge'),
dashboardId: 'home',
name: 'actions-forecasting',
title: 'Scheduled Actions',
})

The bridge module must have two exports: render and shouldRender. render is a function that will display the dashboard panel's UI when called. shouldRender is either an async function that resolves to a boolean indicating whether the dashboard panel should be rendered or a static value of true if the dashboard panel should always be rendered. While it's not required, it is recommended that this file be a TypeScript file as well.

dashboardPanelLinkBridge.tsx

import React from 'react'
import { hasPermission } from '@rhapsody/permissions'
import { ActionsForecastingPanel } from '../ActionsForecastingPanel'

export function render() {
return <ActionsForecastingPanel />
}

export const shouldRender = async () => {
return hasPermission('view_team_actions_forecast')
}

Advanced: Get Specific Dashboard Panels

Dashboard panels can be retrieved specifically for a dashboard from the registry using getAllRegisteredDashboardPanels. The dashboard panels are returned without any sort of order.

import { getAllRegisteredDashboardPanels } from '@rhapsody/app-registry'

export function SidebarMenu() {
const [dashboardPanels] = useState(() =>
getAllRegisteredDashboardPanels('home')
)

return (
<ul>
{dashboardPanels.map((dashboardPanel) => {
// ...
})}
</ul>
)
}

Compose Panes

One of the ways apps can add themselves to the platform is via compose panes that show along the bottom of the viewport. To do so performantly, we must lazy-load the compose panes. This requires a little more ceremony around how we register compose panes and how the compose pane module is structured.

Register a Compose Pane

A compose pane can be registered with registerComposePane. Registration can only be done from a TypeScript file. When registering a compose pane, one must provide a unique namespaced string ID for the compose pane as well as a dynamic import for the bridge module, which we'll discuss in a second.

entry.ts

import { registerComposePane } from '@rhapsody/app-registry'

registerComposePane({
id: 'composePane.app.dialer',
bridgeLoader: () => import('./composePaneBridge'),
})

The bridge module must have two exports: render and shouldRender. render is a function that will display the compose pane when called. shouldRender is either an async function that resolves to a boolean indicating whether the compose pane should be rendered or a static value of true if the compose pane should always be rendered. While it's not required, it is recommended that this file be a TypeScript file as well.

composePaneBridge.tsx

import { ExamplePane } from './ExamplePane/ExamplePane'
import { getExamplePaneSettings } from './api'

// Function to render the compose pane.
export function render() {
return <ExamplePane />
}

// Async function to detemine whether to render the compose pane or not.
// This can also be a static value of `true` if the compose pane should
// always render, e.g. `export const shouldRender = true`.
export async function shouldRender() {
const settings = await getExamplePaneSettings()
return settings.isEnabled
}

Advanced: Get Compose Panes

caution

This operation should only be done by the platform.

Compose panes can be retrieved from the registry using getAllRegisteredComposePanes.

import { getAllRegisteredComposePanes } from '@rhapsody/app-registry'

export function GlobalComponents() {
const [composePanes] = useState(getAllRegisteredComposePanes)

return (
<>
{composePanes.map((composePane) => {
// ...
})}
</>
)
}

Unstable: Singletons

One of the ways apps can add themselves to the platform is via singletons, one-off components that need to be displayed in the nav shell. To do so performantly, we must lazy-load the singletons. This requires a little more ceremony around how we register singletons and how the singleton module is structured.

Register a Singleton

A singleton can be registered with registerSingleton. Registration can only be done from a TypeScript file. When registering a singleton, one must provide an ID for the singleton as well as a dynamic import for the bridge module, which we'll discuss in a second.

entry.ts

import { registerSingleton } from '@rhapsody/app-registry'

registerSingleton({
id: 'globalnav.workflow',
bridgeLoader: () => import('./sidebarWorkflowBridge'),
})

The bridge module must have two exports: render and shouldRender. render is a function that will display the singleton when called. shouldRender is either an async function that resolves to a boolean indicating whether the singleton should be rendered or a static value of true if the singleton should always be rendered. While it's not required, it is recommended that this file be a TypeScript file as well.

sidebarWorkflowBridge.tsx

import { Workflow } from './Workflow/Workflow'
import { getWorkflowSettings } from './api'

// Function to render the singleton.
export function render() {
return <Workflow />
}

// Async function to detemine whether to render the singleton or not.
// This can also be a static value of `true` if the singleton should
// always render, e.g. `export const shouldRender = true`.
export async function shouldRender() {
const settings = await getWorkflowSettings()
return settings.isEnabled
}

Advanced: Get Singletons

caution

This operation should only be done by the platform.

Singletons can be retrieved from the registry by ID using getRegisteredSingletonById.

import { getRegisteredSingletonById } from '@rhapsody/app-registry'

export function GlobalComponents() {
const [workflow] = useState(() => getRegisteredSingletonById('globalnav.workflow'))

return (
// Render the workflow singleton
)
}