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:
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:
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.
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:
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:
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:
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:
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:
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:
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.
<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:
<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:
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:
<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:
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.
<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.
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:
<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:
import { AppHeader, AppName, Page, Heading, Grid, Flex } from '@dynatrace/strato-components-preview';
The content within Page.Main
will look like this now:
<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.
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.