📊 Data & UI — Module 4

Querying the Grail

Hands-on

Querying the Grail

Grail is Dynatrace's unified data lake — metrics, logs, traces, entities, events, all in one place. DQL (Dynatrace Query Language) is how you talk to it. In this module, you'll query real data and display it in your app.

The useDql Hook

One hook, one query, live data:

import { useDql } from "@dynatrace-sdk/react-hooks";

export const HostList = () => {
  const { data, error, isLoading } = useDql({
    query: `fetch dt.entity.host
      | fields entity.name, cpuCores, osType
      | sort entity.name asc
      | limit 20`
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

The hook handles authentication, polling, and caching automatically. Your app.config.json must include the right scopes — storage:entities:read for entity queries, storage:metrics:read for timeseries.

DQL Essentials

Entity Queries (fetch)

// All hosts
fetch dt.entity.host
| fields entity.name, cpuCores, osType, state

// Filter by name
fetch dt.entity.host
| filter contains(entity.name, "prod")
| fields entity.name, cpuCores

// With relationships
fetch dt.entity.host
| expand runs_on = runs[dt.entity.process_group]
| fields entity.name, runs_on

Metric Queries (timeseries)

// CPU usage per host, last 2 hours
timeseries avg(dt.host.cpu.usage), by:{dt.entity.host}, from:-2h

// Memory usage with 5-minute buckets
timeseries avg(dt.host.memory.usage), by:{dt.entity.host}, from:-1h, interval:5m

// Multiple metrics
timeseries cpu=avg(dt.host.cpu.usage), mem=avg(dt.host.memory.usage),
  by:{dt.entity.host}, from:-2h

Key rules:

  • fetch is for entities and logs — returns rows of data
  • timeseries is for metrics — returns time-bucketed aggregations
  • timeseries requires an aggregation: avg(), sum(), min(), max(), count()
  • by:{} groups results by dimensions (replaces classic :splitBy())

Displaying Data in a Table

Strato's DataTable is the standard way to show tabular data:

import { useDql } from "@dynatrace-sdk/react-hooks";
import { DataTable, convertToColumns } from "@dynatrace/strato-components/tables";
import { Flex, TitleBar } from "@dynatrace/strato-components/layouts";

export const HostList = () => {
  const { data, isLoading } = useDql({
    query: `fetch dt.entity.host
      | fields entity.name, cpuCores, osType, state
      | sort entity.name asc
      | limit 50`
  });

  const columns = data ? convertToColumns(data) : [];

  return (
    <Flex flexDirection="column" gap={16} padding={32}>
      <TitleBar>
        <TitleBar.Title>Hosts</TitleBar.Title>
      </TitleBar>
      <DataTable
        data={data?.records ?? []}
        columns={columns}
        loading={isLoading}
      />
    </Flex>
  );
};

convertToColumns auto-generates column definitions from the DQL result schema. For custom columns, define them manually:

const columns = [
  { accessor: "entity.name", header: "Host Name" },
  { accessor: "cpuCores", header: "CPU Cores" },
  { accessor: "osType", header: "OS" },
];

Required Scopes

Add these to your app.config.json scopes array:

[
  { "name": "storage:entities:read", "comment": "Fetch entities via DQL" },
  { "name": "storage:metrics:read", "comment": "Query metrics via DQL timeseries" },
  { "name": "storage:buckets:read", "comment": "Access Grail data buckets" }
]

Missing scopes = empty results or permission errors. Always check scopes first when debugging.

What's Next

In Module 5, we explore the Strato component arsenal — buttons, chips, progress circles, and more. Your data deserves better than a raw table.