# Make a Scene... with Grafana 10 + qryn

**Grafana Scenes** is a new front-end library that enables developers to create dashboard-like experiences — such as querying and transformations, dynamic panel rendering, and time ranges — directly within Grafana application plugins. 

[**Scenes**](https://grafana.com/blog/2023/10/31/create-a-logs-app-plugin-with-grafana-scenes-and-grafana-loki) are collections of objects representing different UI components, such as data, time ranges, variables, layout, and visualizations. Let's see how we can use them to build a **Log-centric** Scene using a **qryn** *Loki compatible data source.*

### Let's make a Scene

Let's begin our journey by exploring the **Apps** item in the left-side menu.

The boilerplate Scenes app comes with three routes: **Page with tabs**, **Page with drilldown**, and **Hello world**. We’re going to focus on the **Home** route located in `src/pages/Home.tsx` and rendered as the default page of our new app plugin.

The most important objects in a **Grafana Scene** are:

* `SceneApp`: Responsible for top-level pages routing.
    
* `scene.Component`: Used in render functions to render your `SceneApp`.
    

Let's begin by customizing the title and subtitle of our new **Log Scenes App**

![A screenshot of the title and subtitle for the Logs Scenes App](https://grafana.com/media/blog/scenes-loki/logs-scenes-app-title-and-subtitle.png align="left")

We will include a verification for the existence of a qryn **Loki data source** and make sure to show a notification if it is missing. This will help avoid unforeseen errors and confirm that our setup is prepared for developing our new application.

```javascript
import { DataSourceInstanceSettings } from '@grafana/data';

function hasLoggingDataSources(dataSources: Record<string, DataSourceInstanceSettings>) {
 	return Object.entries(dataSources).some(([_, ds]) => ds.type === 'loki');
}
```

Once done, proceed updating your `HomePage` component.

```javascript
export const HomePage = () => {
	const scene = useMemo(() => getScene(), []);
	const hasDataSources = useMemo(() => hasLoggingDataSources(config.datasources), []);
   
	return (
		<>
			{!hasDataSources && (
			<Alert title={`Missing logging data sources`}>
					This plugin requires a Loki data source. Please add and configure a Loki data source to your Grafana instance.
			</Alert>
			)}  	
			<scene.Component model={scene} />
		</>
	);
};
```

If your local environment is configured correctly, you should not see any alerts.

### **Scenes for a Loki app**

In this step, our focus will be on the file located in `src/pages/Home/scenes.tsx`.

In this file, we export the `getBasicScene()` function which will be used in the SceneAppPage on the Home page. To start, we need to consider the importance of time. Every request we make requires time, and for this application, we need a specific time interval to retrieve the data stored in qryn within that range.

The `SceneTimeRange` component manages the selection of time for query requests, and it is used in the `SceneTimePicker` control. In our example, we will create an instance that represents the time between now and one hour before. However, this range can be customized according to your needs. You don't have to worry about the fixed value, as we will allow the user to customize their selection.

```javascript
import { SceneTimeRange } from '@grafana/scenes';

const timeRange = new SceneTimeRange({
	from: 'now-1h',
	to: 'now',
  });
```

User input is our next goal. To achieve this, we will utilize various controls that generate variables. Each variable will have a specific name and value. The `DataSourceVariable` enables the user to choose a data source from the ones configured in this Grafana instance. Once a data source is selected, a variable is generated. This variable possesses a designated name and can be utilized in queries and other components.

```javascript
import { DataSourceVariable } from '@grafana/scenes';

const dsHandler = new DataSourceVariable({
	label: 'Data source',
	name: 'ds', // being $ds the name of the variable holding UID value of the selected data source
	pluginId: 'loki'
  });
```

`QueryVariable` enables you to showcase the outcomes of a query-based collection of values, like metric names or server names, in a dropdown menu. In this instance, we will request our qryn datasource to return the names of the stream selectors.

```javascript
import { QueryVariable } from '@grafana/scenes';

const streamHandler = new QueryVariable({
	label: 'Source stream',
	name: 'stream_name', // $stream_name will hold the selected stream
	datasource: {
  		type: 'loki',
  		uid: '$ds' // here the value of $ds selected in the DataSourceVariable will be interpolated.
	},
	query: 'label_names()',
  });
```

* `TextBoxVariable` is an input to enter free text. We will use it to select the value of the selected stream.
    

```javascript
import { TextBoxVariable } from '@grafana/scenes';
const streamValueHandler = new TextBoxVariable({
	label: 'Stream value',
	name: 'stream_value', // $stream_value will hold the user input
  });
```

We currently have time and user input stored in our variables. Moving forward, we will utilize both the time and user input to construct queries. This is where the scene component comes into play. The `SceneQueryRunner` will retrieve data from the qryn Loki data source and deliver the results to a visualization or multiple visualizations.

Each query is represented as a JSON object, containing a reference ID (`refid`) and an expression that specifies the query to be executed.

```javascript
import { SceneQueryRunner } from '@grafana/scenes';

const queryRunner = new SceneQueryRunner({
	datasource: {
  		type: 'loki',
  		uid: '$ds' // here the value of $ds selected in the DataSourceVariable will be interpolated.
	},
	queries: [
		{
			refId: 'A',
			expr: 'your query here',
		},
	],
});
```

### Visualizing Data

The `PanelBuilders` API provides support for building visualization objects for the supported visualization types, such as `Stat`, `TimeSeries`, and `Logs`.

#### **1\. Stat panel**

We will begin by creating a stat visualization. Stats display a single prominent value along with an optional graph sparkline. You have the ability to customize the background or value color using thresholds or overrides. To achieve this, we will utilize a `QueryRunner` and a `PanelBuilder`.

To gather the necessary data, we will employ a qryn metric query to visualize the rate at which these logs occur within the specified time frame.

```javascript
import { SceneQueryRunner, PanelBuilders } from '@grafana/scenes';
import { BigValueGraphMode } from '@grafana/schema';

const statQueryRunner = new SceneQueryRunner({
	datasource: {
		type: 'loki',
		uid: '$ds'
	},
	queries: [
		{
			refId: 'A',
			expr: 'sum(rate({$stream_name="$stream_value"} [$__auto]))',
		},
	],
});

const statPanel = PanelBuilders.stat()
	.setTitle('Logs rate / second')
	.setData(statQueryRunner)
	.setOption('graphMode', BigValueGraphMode.None)
	.setOption('reduceOptions', {
 		values: false,
 		calcs: ['mean'],
 		fields: '',
	});
```

Like we mentioned earlier, variables are going to be replaced with user input:

`sum(rate({$stream_name="$stream_value"} [$__interval]))`

Here's our Panel's logic explained as a sequence: 

* Request a `Stat` from `PanelBuilder`.
    
* Provide a title.
    
* Tell it where to get the data.
    
* Tell it that we don’t want to see the sparklines, just the big number.
    
* Provide some customizations around how to treat the data. 
    

In order to display a single numerical value on the stat panel, we need to customize it to calculate the mean of the provided values. As metric queries return time series, we can use this customization to see the average of all values in this particular example.

#### **2\. Time series**

The second visualization we’re going to use is a `TimeSeries` panel, because we want to see how data changes over a period of time.

```javascript
import { SceneQueryRunner, PanelBuilders } from '@grafana/scenes';

const timeSeriesQueryRunner = new SceneQueryRunner({
	datasource: {
		type: 'loki',
		uid: '$ds',
	},
	queries: [
		{
			refId: 'B',
			expr: 'count_over_time({$stream_name="$stream_value"} [$__auto])',
		},
	],
});

  const timeSeriesPanel = PanelBuilders
	.timeseries()
	.setTitle('Logs over time')
	.setData(timeSeriesQueryRunner);
```

This one is simpler, and works out of the box without customizations.

#### **3\. Logs panel**

For our logs panel we will use a qryn [log query](https://grafana.com/docs/loki/latest/query/log_queries/?pg=blog&plcmt=body-txt&src=li&mdm=social&camp=blog) + a log visualization widget.

```javascript
import { SceneQueryRunner, PanelBuilders } from '@grafana/scenes';

const logsQueryRunner = new SceneQueryRunner({
	datasource: {
		type: 'loki',
		uid: '$ds',
	},
	queries: [
		{
			refId: 'A',
			expr: '{$stream_name="$stream_value"}',
			maxLines: 20, // Use up to 5000
		},
	],
});

  const logsPanel = PanelBuilders.logs()
	.setTitle('Logs')
	.setData(logsQueryRunner);
```

### **Arrange the scenes**

To organize the scenes, we will follow these steps:

First, we have gathered all the necessary building blocks for our application. Now, in the final stage, we need to pass these objects to the Scenes library and determine how they should be organized in the UI. To achieve this, we will utilize a grid layout.

This layout is the default behavior of dashboards in Grafana, and it provides a similar experience for our scenes. Within the grid layout, we will incorporate three SceneGridItem components. In order to pass the variables to our scene, we will encapsulate them within a SceneVariableSet.

Lastly, we will arrange the scene in a way that grants the user access to certain controls. These controls include:

* `VariableValueSelectors` to modify the variable controls.
    
* `SceneControlsSpacer` to add a little bit of air between objects.
    
* `SceneTimePicker` to customize the time selection.
    

```javascript
export function getBasicScene() {
	// Everything before goes over here
	return new EmbeddedScene({
		$timeRange: timeRange,
		$variables: new SceneVariableSet({
			variables: [dsHandler, streamHandler, streamValueHandler],
		}),
		body: new SceneGridLayout({
			children: [
				new SceneGridItem({
					height: 8,
					width: 8,
					x: 0,
					y: 0,
					body: statPanel.build(),
				}),
				new SceneGridItem({
					height: 8,
					width: 16,
					x: 8,
					y: 0,
					body: timeSeriesPanel.build(),
				}),
				new SceneGridItem({
					height: 8,
					width: 24,
					x: 0,
					y: 4,
					body: logsPanel.build(),
				})
			],
		}),
		controls: [
			new VariableValueSelectors({}),
			new SceneControlsSpacer(),
			new SceneTimePicker({ isOnCanvas: true }),
		],
	});
}
```

If you followed our tutorial, the results should look similar to the following example:

![A screenshot of the Logs Scenes App](https://grafana.com/media/blog/scenes-loki/logs-scenes-app-screenshot.png align="left")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">don’t forget to enter a value for the stream value input for the qryn source</div>
</div>

## **Conclusion**

Now we know how to develop a Grafana plugin and how to use the Scenes library to constructing interactive interfaces with visualizations using ***qryn's*** *Loki and LogQL compatible APIs.* This experiment further confirms our vision: being **polyglot** rocks!

That's right! Since **qryn** is a *drop-in compatible LGTM alternative*, any guide or tutorial designed and created for **Grafana Loki**, **Mimir or Tempo** will work out of the box when used with **qryn** and [**qryn.cloud**](https://qryn.cloud) using our custom made APIs built on top of fast and scalable OLAP databases such as *ClickHouse, DuckDB and InfluxDB.*

*Ready to try?* Deploy [qryn](https://qryn.dev) OSS locally or try our [qryn.cloud](https://qryn.cloud) managed service 👇

[![](https://user-images.githubusercontent.com/1423657/218818279-3efff74f-0191-498a-bdc4-f2650c9d3b49.gif align="center")](https://qryn.dev)
