Fabric workload development kit frontend (preview)
This Microsoft Fabric workload development sample repository serves as a guide for integrating a custom UX Workload with Microsoft Fabric. This project enables developers to seamlessly integrate their own UI components and behaviors into Fabric's runtime environment, enabling rapid experimentation and customization. Developers can use the Fabric development kit's framework to build workloads and create custom capabilities that extend the Fabric experience. The Fabric platform is designed to be interoperable with Independent Software Vendor (ISV) capabilities. For example, the item editor allows creating a native, consistent user experience by embedding ISV’s frontend in the context of a Fabric workspace item.
The UX Workload Frontend is a standard web app (React) that incorporates our workload client SDK, a standard npm package, to enable its functionality.
The ISV hosts and runs it inside a sandboxed <iframe>
in the Fabric portal. It presents ISV-specific UI experiences such as an item editor.
The SDK provides all the necessary interfaces, APIs, and bootstrap functions required to transform a regular web app into a Micro Frontend web app that operates seamlessly within the Fabric portal.
The SDK provides a sample UI with the following features:
- Showcases the usage of most of the available SDK calls
- Demonstrates an example of the Fluent UI-based extensible Ribbon that matches the look and feel of Fabric
- Allows easy customization
- Allows you to observe changes in Fabric in real time, while in Fabric Development Mode
Prerequisites
UX Workload web app
This package is built on top of the Fluent UI and is designed for React.
UX Workload Frontend Manifest
The UX Workload Frontend Manifest is a JSON resource that the ISV provides. It contains essential information about the workload, such as the URL of the workload web app, and various UI details like the display name of the ISV item and associated icons. It also enables the ISV to customize what happens when users interact with their items in the Fabric portal.
In this package, the manifest files are located here, under the Package folder: Frontend Manifest files and a detailed description can be found in the frontend manifest files.
Step 1: Enable Workloads Development Feature in Fabric
The tenant administrator has to enable the workloads development feature in the Admin Portal. It can be enabled for the entire organization or for specific groups within the organization by enabling the tenant switch Capacity admins can develop additional workloads.
Step 2: Set up the frontend
To set up the front end of the sample project, follow these steps:
Verify that
Node.js
andnpm
are installed and that thenpm
version is at least 9 (If not, install latestNode.js
andnpm
)Clone the repository: Clone the repository found here: https://go.microsoft.com/fwlink/?linkid=2272254
This is the package directory layout, with a description of the essential components and resources:
- Package - the location of the workload package: Frontend resources, including manifests and assets.
- src - Workload code:
- index.ts - main initialization file,
bootstrap
theindex.worker
andindex.ui
iFrames - detailed below - App.tsx - routing of paths to pages, for example -
/sample-workload-editor
is routed to theSampleWorkloadEditor
function undercomponents
- assets - location for images(
jpg
,jpeg
,png
), that can be referenced in the Manifest and be shown in the UI. For example,assets/github.jpg
is set in the manifest as the Product's icon. - components - location of the actual UI code - the Editor view, and other views that are used by the sample (Ribbon, Authentication page, Panel, etc.)
- controller - the Controller that calls the SDK APIs
- models - the contracts and models used by the UI itself and for communication with the boilerplate's backend
- index.ts - main initialization file,
- tools -
webpack.config.js
- configuration of the local Node.js server- Web configuration and manifest reader/processor
- validation -
validateSchema.js
- validation of product and item json files schema. It is configured to run onnpm start
.
Install
Under the repository folder, go to
Frontend
and run npm install<repository folder>\Frontend> npm install
Start server
<repository folder>\Frontend> npm start
This command starts a local Node.js server (using
webpack
), that Microsoft Fabric connects to when in Development Mode.Refer to the localhost server notes for port details that appear after it starts. The current port is
60006
. After the localhost server starts, opening the URL:127.0.0.1:60006/manifests
fetches the aggregated manifest created from the 'Frontend/Package' folder. Open it to verify that the server is up and running.Modifying source files triggers a reload of contents in Fabric through
webpack
, if it's already connected. However, typically, you would still need to refresh the page.If you change files under the 'Frontend/Package' folder, you should "npm start" again.
Run In Fabric, enable the Fabric Developer mode setting, to allow Fabric to access your localhost server. Go to Developer Settings --> Fabric Developer Mode and refresh the page. This setting is persisted in the current browser.
Example of usage
To run a typical Hello World test scenario:
Start the local server and enable Dev Mode. The menu at the left bottom corner should show the new Sample Workload:
Select the Sample Workload and navigate the user to the Sample workload Home page. The upper section presents the Create Experience:
Select the Sample Workload card to open the Sample Workload's UI within Fabric:
Explore the various controls to see Fabric's WorkloadClient API (SDK) capabilities:
- Open Notifications and Dialogs
- Navigate to pages
- Get theme and Workload settings
- Execute Actions
Most of the available SDK functionality is either wired to the button actions, or registered as callbacks. The results are usually a notification or a message box showing that an API was invoked.
For example:
- The Execute Action calls the
action.execute()
API with an action named sample.Action. The action's functionality is to show a notification. - Select Save on the Ribbon to call the
dialog.open()
API, which opens a dialog where a user provides a name and saves the item in Fabric (this dialog is further explored in the CRUD section). - Get Theme Settings button shows a list of Fabric's theme configurations (via the
theme.get()
API).
The Sample Workload UI is hosted in a Fabric sandboxed iframe
that we can see when we examine the page's DOM:
Note
The sandboxed iframe allows the following attributes: allow-same-origin, allow-scripts. For more information on the meaning of sandbox and different attributes, refer to MDN web docs
Step 3: Dive into the code
bootstrap()
Before bootstrapping, check the path to see if you need to close the window. This step is needed for authentication API.
const redirectUriPath = '/close';
const url = new URL(window.location.href);
if (url.pathname?.startsWith(redirectUriPath)) {
window.close();
}
Every Fabric Workload app needs to support being loaded in two modes:
- UI mode: Am app in UI mode is loaded in visible iFrames and listens for its own route changes to render corresponding UI components, including pages, panels, dialogs, and so on.
- Worker mode: An app in worker mode runs in an invisible iFrame, which is primarily used to receive commands sent from the outside world and respond to them.
@ms-fabric/workload-client
provides abootstrap()
method to simplify the initialization steps. The bootstrap() method internally detects whether the current app is loaded in UI mode or worker mode, and then calls the appropriate initialization method (initializeUI or initializeWorker). After the initialization is complete, bootstrap() notifies Fabric micro-frontend framework of the initialization success or failure.
bootstrap({
initializeWorker: (params) =>
import('./index.worker').then(({ initialize }) => initialize(params)),
initializeUI: (params) =>
import('./index.ui').then(({ initialize }) => initialize(params)),
});
index.worker
This is the main onAction
registration. It handles events sent from the Fabric host, which are themselves triggered by executed actions.
These actions can be sent either by the workload itself to Fabric, and then called-back into the onAction
handler, or they can be initiated by the Fabric host. For example, when clicking on Create Sample Item - Frontend Only, Fabric triggers the action 'open.createSampleWorkloadFrontendOnly', and the onAction handler initiates the opening of the workload main UI page, as seen in the following code.
The current workspace objectId
is passed into the frontend-only experience as well.
workloadClient.action.onAction((message) => {
switch (message.action) {
/**
* This opens the Frontend-only experience, allowing to experiment with the UI without the need for CRUD operations.
* This experience still allows saving the item, if the Backend is connected and registered
*/
case 'open.createSampleWorkloadFrontendOnly':
const { workspaceObjectId: workspaceObjectId1 } = data as ItemCreateContext;
return workloadClient.page.open({
extensionName: 'Org.WorkloadSample',
route: {
path: `/sample-workload-frontend-only/${workspaceObjectId1}`,
},
});
// ... elided for brevity...
default:
throw new Error('Unknown action received');
}
});
index.ui
The initialize()
function renders the React DOM where the App
function is embedded. To invoke the API calls, pass the workloadClient
SDK handle, which is used throughout the code.
The FluentProvider
class ensures style consistency across the various FluentUI controls.
ReactDOM.render(
<FluentProvider theme={fabricLightTheme}>
<App
history={history}
workloadClient={workloadClient}
/>
</FluentProvider>,
document.querySelector("#root")
);
Development flow
App
routes the code into theSampleWorkloadEditor
, which is a function returning aReact.JSX.Element
.- The function contains the UI structure (the Ribbon and the controls on the page - buttons, input fields, etc.).
- Information collected from the user is stored via React's
useState()
hook. - Handlers of the UI controls call the SampleWorkloadController functions and pass the relevant state variables.
- To support the CRUD operations, the state of the created/loaded item is stored in
artifactItem
along withworkspaceObjectId
and a sample implementation of payload variables.
An example with notification.open()
API:
state:
const [apiNotificationTitle, setNotificationTitle] = useState<string>(''); const [apiNotificationMessage, setNotificationMessage] = useState<string>('');
UI:
Title:
<Field label="Title" validationMessage={notificationValidationMessage} orientation="horizontal" className="field"> <Input size="small" placeholder="Notification Title" onChange={e => setNotificationTitle(e.target.value)} /> </Field>
Send Button:
<Button icon={<AlertOn24Regular />} appearance="primary" onClick={() => onCallNotification()} > Send Notification </Button>
Handler:
function onCallNotification() { ... elided for brevity callNotificationOpen(apiNotificationTitle, apiNotificationMessage, undefined, undefined, workloadClient, setNotificationId); };
Controller:
export async function callNotificationOpen( title: string, message: string, type: NotificationType = NotificationType.Success, duration: NotificationToastDuration = NotificationToastDuration.Medium, workloadClient: WorkloadClientAPI, setNotificationId?: Dispatch<SetStateAction<string>>) { const result = await workloadClient.notification.open({ notificationType: type, title, duration, message }); if (type == NotificationType.Success && setNotificationId) { setNotificationId(result?.notificationId); } }
CRUD operations
While a frontend-only development scenario is easily supported, the full end-to-end developer experience requires saving, reading, and editing existing workload items. The Back-end implementation guide describes in detail how to set up and use the backend side.
Once the backend is up and running, and the Org.WorkloadSample.SampleWorkloadItem
type is registered in Fabric, you can perform CRUD operations on this type.
The following operations are exposed via ItemCrud API.
CREATE
A sample call to create
, as implemented when saving the workload item for the first time:
const params: CreateItemParams = {
workspaceObjectId,
payload: { artifactType, displayName, description, workloadPayload: JSON.stringify(workloadPayload), payloadContentType: "InlineJson", }
};
const result: CreateItemResult = await workloadClient.ItemCrud.createItem(params);
Our sample implementation stores the created item inside the artifactItem
.
The item is created under the currently selected workspace. Therefore the workspace must be assigned to the capacity that is configured by the backend configuration, as detailed in the backend docs.
Any attempt to create an item under a noncompatible workspace fails.
The
onCreateFabricItem
callback in the backend blocks the CREATE call - a failure there causes the operation to fail and no item is created in Fabric. See backend's debugging/TSG documentation.Currently, a saved item doesn't automatically appear in the workspace. A page refresh is needed.
GET
When you select an existing Sample Workload item in the workspace view, Fabric navigates to the route that is defined in the Frontend manifest, under artifacts
-->editor
-->path
:
"artifacts": [
{
"name": "Org.WorkloadSample.SampleWorkloadItem",
"editor": {
"extension": "Org.WorkloadSample",
"path": "/sample-workload-editor"
},
When you invoke itemCrud.getItem
, data is loaded from Fabric's backend, along with data from the Workload backend, and is loaded into the artifactItem
object of the opened GUI.
UPDATE
Updated an existing item with itemCrud.updateItem
. The Workload payload itself is updated by the Workload backend, while in Fabric only the item's lastModifiedTime
is changed.
DELETE
Call the delete operation either from Fabric's Workspace view (as a general action available for all items), or via an explicit call from the Workload to itemCrud.deleteItem
.
Both calls go through the Workload backend's onDeleteItem
callback.
Authentication
In the sample workload editor, there's a section that lets you navigate to the authentication section. Before you use authentication API, configure a Microsoft Entra app Microsoft Entra ID. Additionally, ensure that your env.dev file is configured accurately. For more details refer to: Configure the workload local manifest and acquire a token for your application
Step 4: Debug
To see the Worker and UI iFrames, open the Source tab of the browser's DevTools (F12).
You can place a breakpoint both in the Worker iFrame and see the main switch
on the incoming Action. You can also debug the UI iFrame, for example, the code inside SampleWorkloadEditor
.
Fluent UI controls
UX Workloads use Fluent UI controls for visual consistency with Fabric and ease of development. The Sample Workload provides examples of how to use the most common controls. More information can be found on the Fluent UI page.
Frontend Manifest customization
The frontend manifest describes the frontend aspects of the workload - product appearance, names, visual assets, available actions, and more. It's the main point of contact between Fabric and the workload. For our sample workload, the manifest is loaded into Fabric in Developer mode, and its various sections, definitions and examples of the manifest are shown in the frontend manifest files. Changes to the manifest's entries, the wiring of different actions and updating of visual assets are seen in real time after a page refresh.
Client SDK - supported APIs
The Client SDK documentation is located here: @ms-fabric/workload-client package.
The following APIs are supported:
- notification.open
- notification.hide
- panel.open
- panel.close
- action.onAction
- action.execute
- navigation.navigate
- navigation.onNavigate
- navigation.onBeforeNavigateAway
- navigation.onAfterNavigateAway
- page.open
- dialog.openDialog
- dialog.openMessageBox
- dialog.close
- theme.get
- theme.onChange
- settings.get
- settings.onChange
- errorHandling.openErrorDialog
- errorHandling.handleRequestFailure
- itemCrud.createItem
- itemCrud.getItem
- itemCrud.updateItem
- itemCrud.deleteItem
- itemSchedule.runItemJob
- itemSchedule.cancelItemJob
- itemRecentRuns.open