Skip to main content

Create a user interface

Prerequisites

To proceed, make sure that you have followed the previous steps and should now have:

  • A new project created with the "empty template"
  • Your local development server is running and serving the app.
  • You have opened your project within the IDE of your choice.

Intro

Dynatrace App UIs are built with React and TypeScript. This tutorial doesn't explain all the needed concepts in detail. If you would like to learn more about these technologies, read React and TypeScript. The Strato design system provides a React component library, which you can use to create user interfaces for Dynatrace Apps without building every component from scratch. For more details, read the section about the Design System.

1. Set the stage

As a first step, change the generated code to match your requirements.

Go to the App.tsx file in src/app/, and change the text between <Heading> and </Heading> to Booking Overview and change the value of level to 1.

Remove the <Flex> and the <Paragraph> component and all unused imports; you don't need that for the app.

The local development server supports hot reload. Every change in the source code triggers a build of the app, and the browser window gets refreshed.

After this step, your file should look like the following:

src/app/App.tsx
import React from 'react';
import { AppHeader, AppName, Page, Heading } from '@dynatrace/strato-components-preview';

export const App = () => {
return (
<Page>
<Page.Header>
<AppHeader>
<AppName />
</AppHeader>
</Page.Header>
<Page.Main>
<Heading level={1}>Booking Overview</Heading>
</Page.Main>
</Page>
);
};

2. Create the Card component

You will create a generic Card component based on the SingleValue component to render the queried data from Dynatrace.

To create a new component, you have to create a new file Card.tsx. To follow best practices, create a components directory within src/app and place your file in it.

On the Dynatrace platform, functional components are used. This means a React component is more or less just a JavaScript function returning JSX code, which is rendered in your UI. Therefore, the skeleton of your new component looks like the following:

src/app/components/Card.tsx
import React from 'react';

export const Card = () => {
return <div></div>;
};

2.1. Add props to the Card component

To make a component dynamic, you can specify so-called props (short for properties). Props are arguments passed into React components. In this case, your need four props for the Card component:

  • value
  • chartLabel
  • chartSuffix
  • chartPrecision

Every React component receives a props object as a parameter that includes all the passed props as keys. You can find more information about this concept here.

As TypeScript is used in this app, create a CardProps interface to not only have type safety for all props passed into your component but also to get suggestions for props in your IDE. This makes the life of every developer way easier. The interface definition is placed after the imports within the Card.tsx file.

src/app/components/Card.tsx
interface CardProps {
value: number;
chartLabel: string;
chartSuffix: string;
chartPrecision: number;
}

As soon as you've defined an interface, assign that interface to your props objects. Additionally, use the destructuring assignment syntax in this example, which is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables:

src/app/components/Card.tsx
export const Card = ({ value, chartLabel, chartSuffix, chartPrecision }: CardProps) => {
return <div>{value}</div>;
};

2.2. Adding SingleValue to Card Component

The next step is to use the SingleValue component from the design system to render the data passed to the Card component via props. The first step is to import the component by adding the following line after the React import. Also import the Flex component, which will be used for styling later:

src/app/components/Card.tsx
import { SingleValue, Flex } from '@dynatrace/strato-components-preview';

You can configure the SingleValue component by passing different props to it. Use the props of the Card component to pass them to the SingleValue.

Add some conditional rendering logic to your component to only render the chart whenever the value is a valid number. You may embed expressions in JSX by wrapping them in curly braces. This includes the JavaScript logical && operator.

Finally, wrap everything in a Flex component, which will be used to style our Card component in the next step.

The Card component would then look like this:

src/app/components/Card.tsx
export const Card = ({ value, chartLabel, chartSuffix, chartPrecision }: CardProps) => {
return (
<Flex>
{Number.isFinite(value) && (
<SingleValue
data={value}
label={chartLabel}
formatterOptions={{
type: 'customUnit',
unit: chartSuffix,
precision: chartPrecision,
}}
/>
)}
</Flex>
);
};

2.3. Render Card component in App.tsx

In the first step, you need to import the Card component in the App.tsx file by adding the following line to the imports:

src/app/App.tsx
import { Card } from './components/Card';

To render the Card component, add it to the return statement of the App component. To try out if the component is rendering the passed props correctly, try to pass some dummy values to the Card component.

Your App.tsx file would now look like the following:

src/app/App.tsx
import React from 'react';
import { AppHeader, AppName, Page, Heading } from '@dynatrace/strato-components-preview';
import { Card } from './components/Card';

export const App = () => {
return (
<Page>
<Page.Header>
<AppHeader>
<AppName />
</AppHeader>
</Page.Header>
<Page.Main>
<Heading level={1}>Booking Overview</Heading>
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} />
</Page.Main>
</Page>
);
};

export default App;

2.4. Style the Card component

At this point, you have data displayed on your Card, but it's not yet looking like a Card. That's why adding some styling is the next step.

Strato provides CSS Flex and CSS Grid declarative components to allow easy layouting. The style prop on the Flex component will enable us to apply all kinds of CSS styling.

Add some styles via the style prop on the Flex component we added in the previous step. You can specify any CSS properties via the style prop. In this example, use Strato design tokens and static values. Using the provided design tokens allows you to have a common look and feel for all Dynatrace Apps.

To use Colors, Borders, and BoxShadows from the Design System install the Design Tokens by running:

npm install @dynatrace/strato-design-tokens

As soon as the installation succeeds, add the following imports to your component:

src/app/components/Card.tsx
import { Borders, BoxShadows, Colors } from '@dynatrace/strato-design-tokens';

Start by adding the following CSS properties to the Flex component to make it look like a Card.

src/app/components/Card.tsx
<Flex
style={{
border: Colors.Border.Neutral.Default,
borderRadius: Borders.Radius.Container.Subdued,
background: Colors.Background.Surface.Default,
boxShadow: BoxShadows.Surface.Raised.Rest,
}}
>

</Flex>

Additionally, you need some more CSS properties to define the size of the card and the positioning of the content in the card. Therefore, add width, height, alignItems and justifyContent. After that, the Flex component will look like the following:

src/app/components/Card.tsx
<Flex
style={{
border: Colors.Border.Neutral.Default,
borderRadius: Borders.Radius.Container.Subdued,
background: Colors.Background.Surface.Default,
boxShadow: BoxShadows.Surface.Raised.Rest,
width: '400px',
height: '120px',
alignItems: 'center',
justifyContent: 'center',
}}
>

</Flex>

2.5. Add a skeleton to the Card

To indicate whether the data is still loading, add a Skeleton to your Card component. The Skeleton component from the design system is the easiest way to achieve this. Instead of displaying the SingleValueChart, a Skeleton should be displayed when the data is still loading. As the Card is only responsible for rendering data and has no information about the data fetching itself, you've to extend your CardProps with an isLoading prop, which will be passed to the component from the outside.

This means, that both the CardProps interface and the function interface have to be adapted like the following:

src/app/components/Card.tsx
interface CardProps {
value: number;
chartLabel: string;
chartSuffix: string;
chartPrecision: number;
isLoading: boolean;
}

export const Card = ({
value,
chartLabel,
chartSuffix,
chartPrecision,
isLoading,
}: CardProps) => {
...
}

As this changed the API of your Card component make sure to add the isLoading prop to the Card component in your App.tsx file. It would look like the following then:

src/app/App.tsx
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />

Back in Card.tsx, you can now add the Skeleton before the SingleValue component and render it only when needed. Start with adding it to the imports:

src/app/components/Card.tsx
import { Skeleton } from '@dynatrace/strato-components-preview';

Now you have to add it to your component before the SingleValue. And don't forget to update the SingleValue condition as well to hide it while it's loading.

src/app/components/Card.tsx
<Flex
style={{
border: Colors.Border.Neutral.Default,
borderRadius: Borders.Radius.Container.Subdued,
background: Colors.Background.Surface.Default,
boxShadow: BoxShadows.Surface.Raised.Rest,
width: '400px',
height: '120px',
alignItems: 'center',
justifyContent: 'center',
}}
>
{isLoading && <Skeleton />}
{!isLoading && Number.isFinite(value) && (
<SingleValue
data={value}
label={chartLabel}
formatterOptions={{
type: 'customUnit',
unit: chartSuffix,
precision: chartPrecision,
}}
/>
)}
</Flex>

3. Render multiple Cards in your app

Within the last section of this page, render multiple instances of the Card component in your app. In the final state of your app, six Cards will be shown in a Grid.

Let's start with adding the Grid component from the design system to your imports.

src/app/App.tsx
import { AppHeader, AppName, Grid, Page } from '@dynatrace/strato-components-preview';

As the goal is to have two columns, you can specify this via the gridTemplateColumns prop. Now wrap your already existing Card component into a Grid and add more instances of Card:

src/app/App.tsx
<Grid gap={32} gridTemplateColumns={'1fr 1fr'}>
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
</Grid>

To have all the content nicely centered, wrap all the content within Page.Main into a Flex component. Again, first import Flex from the design system:

src/app/App.tsx
import { AppHeader, AppName, Page, Heading, Grid, Flex } from '@dynatrace/strato-components-preview';

The content within Page.Main will look like this now:

src/app/App.tsx
<Flex gap={32} flexDirection="column" alignItems={'center'}>
<Heading level={1}>Booking Overview</Heading>
<Grid gap={32} gridTemplateColumns={'1fr 1fr'}>
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
<Card value={42} chartLabel="This is a label" chartSuffix="Awesome Suffix" chartPrecision={0} isLoading={false} />
</Grid>
</Flex>

Check grid or flex on MDN Web docs for further information on Grid and Flex and their behavior.

Checkpoint

Click here to see how your project should look like at this point.

Summary and next steps

This section of the tutorial explained how you can build a basic UI on the Dynatrace platform with the help of Strato. In the next section, you'll learn how to fetch Dynatrace data and display it in your UI.

Still have questions?
Find answers in the Dynatrace Community