Hi Everyone, it's me again ;-)
I still see posts on a regular basis regarding help/request for an easy time-picker plugin. As I myself was once in a similar situation and found out there weren’t that many options. I started looking for other options within the possibilities that Grafana (+ plugins) offers and these are some solution I came up with.
So here is PART 02 to help you on your way!
–
** IMPORTANT >> Grafana 11 users please use this [ link ] << **
Certain code and notations from the instructions have been specifically updated for this version
–
Before we continue I would like to mention 3 things:
-
note 1: If you are NOT familiar with this topic yet I would strongly advise you to read my other post ‘PART 01 : (Make your own custom ‘Datepicker’ (Use/set your own timerange). Here is how to’ first!
To get a better understanding of how Grafana’s basic principle of changing the time range works, and how to set up and use of the ‘Dynamic Text’ plugin panel (which is mandatory for all solutions that are mentioned here).
-
note 2: I do would like to address this is not in any way an official plugin but more a guide with creative solutions to make your daily life with Grafana easier.
-
note 3: My previous post focused on a more styled time selector. In this topic, I’ll be using a more traditional picker/picklist-style timepicker, which is more compact and can accommodate a larger selection of data.
–
Now lets get to business
Below you will find 4 different use cases to copy/paste and implement for your own project.
-
Specify (static) timeranges manually
-
Single DAY Picker with range ([ from: today – to: past ‘x’ days ]) using relative ranges
-
Single WEEK Picker [ from: this week – to: past ‘x’ week ] using relative ranges
-
[ADVANCED] Custom Datepicker genereated using (influxDb) datasource + extra mapping
1. Specify (static) timeranges manually
This is the easiest way to make a custom timepicker.
All you have to do is copy my example from below and in the ‘CONTENT’ part specify your own 2 dates in the following line.
<option onclick="changeToSelectedRange('date_from_here','date_to_here')">Custom text here will appear in picker</option>
To add another item to the picklist, simply copy and paste the <option>
line from above below the last item in my example (ensure it’s placed above the </select>
tag at the bottom). Then, specify a different from
and to
range, along with a new custom text/date
. (note: Format of from
and to
dates are: YYYY-MM-DD
)
preview
Copy/paste in field ‘CONTENT’
Example 01 (Single Dates)
<select placeholder="SelectDate">
<option value="" style="display:none;">-- Select Date --</option>
<!-- change/add items below this line -->
<option onclick="changeToSelectedRange('2024-02-01','2024-02-01')">01-Feb-2024</option>
<option onclick="changeToSelectedRange('2024-02-02','2024-02-02')">02-Feb-2024</option>
<option onclick="changeToSelectedRange('2024-03-20','2024-03-20')">20-Mar-2024</option>
<option onclick="changeToSelectedRange('2024-03-21','2024-03-21')">21-Mar-2024</option>
<option onclick="changeToSelectedRange('2024-05-11','2024-05-11')">11-May-2024</option>
<option onclick="changeToSelectedRange('2024-05-15','2024-05-15')">15-May-2024</option>
<option onclick="changeToSelectedRange('2024-06-07','2024-06-07')">07-Jun-2024</option>
<option onclick="changeToSelectedRange('2024-06-30','2024-06-30')">30-Jun-2024</option>
<option onclick="changeToSelectedRange('2024-07-09','2024-07-09')">09-Jul-2024</option>
<option onclick="changeToSelectedRange('2024-08-25','2024-08-25')">25-Aug-2024</option>
<!-- change/add items above this line -->
</select>
OR
Example 02 (Ranges of different Months)
<br>
<select placeholder="SelectMonth">
<option value="" style="display:none;">-- Select Range --</option>
<!-- change/add items below this line -->
<option onclick="changeToSelectedRange('2023-01-01','2023-01-31')">January 2023</option>
<option onclick="changeToSelectedRange('2023-02-01','2023-02-28')">February 2023</option>
<option onclick="changeToSelectedRange('2023-03-01','2023-03-31')">March 2023</option>
<option onclick="changeToSelectedRange('2023-04-01','2023-04-30')">April 2023</option>
<option onclick="changeToSelectedRange('2023-05-01','2023-05-31')">May 2023</option>
<option onclick="changeToSelectedRange('2023-06-01','2023-06-30')">June 2023</option>
<option onclick="changeToSelectedRange('2023-07-01','2023-07-31')">July 2023</option>
<option onclick="changeToSelectedRange('2023-08-01','2023-08-31')">August 2023</option>
<option onclick="changeToSelectedRange('2023-09-01','2023-09-30')">September 2023</option>
<option onclick="changeToSelectedRange('2023-10-01','2023-10-31')">October 2023</option>
<option onclick="changeToSelectedRange('2023-11-01','2023-11-30')">November 2023</option>
<option onclick="changeToSelectedRange('2023-12-01','2023-12-31')">December 2023</option>
<option onclick="changeToSelectedRange('2024-01-01','2024-01-31')">January 2024</option>
<option onclick="changeToSelectedRange('2024-02-01','2024-02-28')">February 2024</option>
<option onclick="changeToSelectedRange('2024-03-01','2024-03-31')">March 2024</option>
<option onclick="changeToSelectedRange('2024-04-01','2024-04-30')">April 2024</option>
<option onclick="changeToSelectedRange('2024-05-01','2024-05-31')">May 2024</option>
<option onclick="changeToSelectedRange('2024-06-01','2024-06-30')">June 2024</option>
<option onclick="changeToSelectedRange('2024-07-01','2024-07-31')">July 2024</option>
<option onclick="changeToSelectedRange('2024-08-01','2024-08-31')">August 2024</option>
<!-- change/add items above this line -->
</select>
Copy/paste in field ‘JAVASCRIPT’
changeToSelectedRange = (range_start, range_end) => {
let timeStart = "T00:00:00"; //<<-- Change 'start' time here of the range
let timeEnd = "T23:59:59"; //<<-- Change 'end' time of the range
let rangeStart_merged = range_start + timeStart;
let rangeEnd_merged = range_end + timeEnd;
//Two lines below are responsible to change the actual timerange of the dashboard
timeSrv = angular.element('grafana-app').injector().get('timeSrv');
timeSrv.setTime({ from: rangeStart_merged, to: rangeEnd_merged });
return;
}
Copy/paste in field ‘CSS STYLING’
select {
height: 36px;
width: 425px;
font-size: 14px;
font-weight: 600;
padding-left: 20px;
border: 1.5px solid black;
border-radius: 2px;
background: #e4e4e4;
margin-left: 25px;
margin-top: 15px;
}
result preview: example 01 (single dates)
result preview : example 02 (ranges of different months)
2. Custom (SINGLE) DAY Picker using relative ranges
This is for users who primarily use the dashboard and data from the past several days and easily want to see data from 1 specific day in that range. Grafana has a list of predefined time ranges called ‘quick ranges,’ but these are limited and cannot be customized. This method allows you to create your own list of frequently used quick ranges.
For this example I will use range (start: -8 days back) TO (end: 'today')
As specified in the header this approach doesn’t use the actual dates like ‘2024-08-01’ (static date) but is using the relative range/time (-> Section > ‘Time units and relative ranges’ ) indication. So
- today is indicated as
'now/d'
(range would beFrom:
'now/d'
To:
'now/d'
) - yesterday is indicated as
'now-1d/d'
(range would beFrom:
'now-1d/d'
To:
'now-1d/d'
) - day before yesterday is indicated as ‘now-2d/d’
(range would beFrom:
'now-2d/d'
To:
'now-2d/d'
) - etc…
*note: because of relative time indication I added an extra ‘date’ field
to see what date you actual have selected above the picker
Copy/paste in field ‘CONTENT’
<p>
<table height="100px">
<tr valign="middle">
<td>
<div class="text01">Selected date:
<div class="text02" id="insertDate2"></div>
</div>
</td>
</tr>
<tr valign="middle">
<td>
<select placeholder="SelectDay">
<option value="" style="display:none;">-- Select Day --</option>
<!-- change/add items below this line -->
<option onclick="changeToSelectedDay_v2('now/d','now/d');getDateToday_v2(0);">today</option>
<option onclick="changeToSelectedDay_v2('now/d','now');getDateToday_v2(0);">today (so far)</option>
<option onclick="changeToSelectedDay_v2('now-1d/d','now-1d/d');getDateSelected_v2(1);">today -1d</option>
<option onclick="changeToSelectedDay_v2('now-2d/d','now-2d/d');getDateSelected_v2(2);">today -2d</option>
<option onclick="changeToSelectedDay_v2('now-3d/d','now-3d/d');getDateSelected_v2(3);">today -3d</option>
<option onclick="changeToSelectedDay_v2('now-4d/d','now-4d/d');getDateSelected_v2(4);">today -4d</option>
<option onclick="changeToSelectedDay_v2('now-5d/d','now-5d/d');getDateSelected_v2(5);">today -5d</option>
<option onclick="changeToSelectedDay_v2('now-6d/d','now-6d/d');getDateSelected_v2(6);">today -6d</option>
<option onclick="changeToSelectedDay_v2('now-7d/d','now-7d/d');getDateSelected_v2(7);">today -7d</option>
<option onclick="changeToSelectedDay_v2('now-8d/d','now-8d/d');getDateSelected_v2(8);">today -8d</option>
<!-- change/add items above this line -->
</select>
</td>
</tr>
</table>
Copy/paste in field ‘JAVASCRIPT’
//--------- Change GRAFANA date -------//
changeToSelectedDay_v2 = (date_start, date_end) => {
timeSrv = angular.element('grafana-app').injector().get('timeSrv');
timeSrv.setTime({ from: date_start, to: date_end });
return;
};
//--------- Show selected date in above custom datepicker -------//
getDateSelected_v2 = (x) => {
let date = new Date();
date.setDate(date.getDate() - x);
let year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
let month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date);
let day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
let weekday = new Intl.DateTimeFormat('en', { weekday: 'short' }).format(date);
let dateCustom = `${weekday}, ${day}-${month}-${year}`;
document.getElementById("insertDate2").innerHTML = dateCustom;
//alert(dateCustom);
}
Copy/paste in field ‘CSS STYLING’
.text01 {
font-size: 14px;
font-weight: 400;
padding-left: 5px;
padding-right: 15px;
margin-left: 5px;
}
.text02 {
font-size: 14px;
font-weight: 600;
}
select {
height: 36px;
width: 225px;
font-size: 14px;
font-weight: 600;
padding-left: 20px;
border: 1.5px solid black;
border-radius: 2px;
background: #e4e4e4;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
table {
margin-left: 5px;
width: 100px;
}
result preview : Custom (SINGLE) DAY Picker using relative ranges
3. Custom (SINGLE) WEEK Picker using relative ranges
This is for users who mainly use the dashboard and data from the past weeks and easily want to see/change data from 1 specific week ‘x’ in that range. Grafana has a list of predefined time ranges called ‘quick ranges,’ but these are limited and cannot be customized. This method allows you to create your own list of frequently used ranges.
For this example I will use range (start: -8 weeks back) TO (end: 'today')
As specified in the header this approach doesn’t use the actual dates like ‘2024-08-01’ (static date) but is using the relative range/time (-> Section > ‘Time units and relative ranges’ ) indication. So
- this week is indicated as ‘now/w’
(range would beFrom:
'now/w'
To:
'now/w'
) - previous week is indicated as ‘now-1w/w’
(range would beFrom:
'now-1w/w'
To:
'now-1w/w'
) - going 2 weeks back from today is indicated as ‘now-2w/w’
(range would beFrom:
'now-2w/w'
To:
'now-2w/w'
) - etc…
*note: because of relative time use/indication I added an extra ‘date’ field
to see what date/week you selected above the picker
Copy/paste in field ‘CONTENT’
<p>
<table height="100px">
<tr valign="middle">
<td>
<div class="text01">Selected date/week:
<div class="text02" id="insertWeek"></div>
<div class="text02" id="insertWeekNr"></div>
</div>
</td>
</tr>
<tr valign="middle">
<td>
<select name="pickerWeek" placeholder="SelectDay">
<option value="" style="display:none;">-- Select Week --</option>
<!-- change/add items below this line -->
<option onclick="changeToSelectedWeekV3('now/w','now/w');getDateToday_v3(0);getWeek(0);">this week</option>
<option onclick="changeToSelectedWeekV3('now/w','now');getDateToday_v3(0);getWeek(0);">this week (so far)</option>
<option onclick="changeToSelectedWeekV3('now-1w/w','now-1w/w');getDateSelected_v3(1);getWeek(1);">today -1w</option>
<option onclick="changeToSelectedWeekV3('now-2w/w','now-2w/w');getDateSelected_v3(2);getWeek(2);">today -2w</option>
<option onclick="changeToSelectedWeekV3('now-3w/w','now-3w/w');getDateSelected_v3(3);getWeek(3);">today -3w</option>
<option onclick="changeToSelectedWeekV3('now-4w/w','now-4w/w');getDateSelected_v3(4);getWeek(4);">today -4w</option>
<option onclick="changeToSelectedWeekV3('now-5w/w','now-5w/w');getDateSelected_v3(5);getWeek(5);">today -5w</option>
<option onclick="changeToSelectedWeekV3('now-6w/w','now-6w/w');getDateSelected_v3(6);getWeek(6);">today -6w</option>
<option onclick="changeToSelectedWeekV3('now-7w/w','now-7w/w');getDateSelected_v3(7);getWeek(7);">today -7w</option>
<option onclick="changeToSelectedWeekV3('now-8w/w','now-8w/w');getDateSelected_v3(8);getWeek(8);">today -8w</option>
<!-- change/add items above this line -->
</select>
</td>
</tr>
</table>
Copy/paste in field ‘JAVASCRIPT’
//--------- Change GRAFANA date -------//
changeToSelectedWeekV3 = (date_start, date_end) => {
timeSrv = angular.element('grafana-app').injector().get('timeSrv');
timeSrv.setTime({ from: date_start, to: date_end });
return;
};
//--------- Show selected date in above custom datepicker -------//
getDateSelected_v3 = (x) => {
let date = new Date(Date.now() - (6.048e+8 * x));
let year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
let month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date);
let day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
let weekday = new Intl.DateTimeFormat('en', { weekday: 'short' }).format(date);
let dateCustom = `${weekday}, ${day}-${month}-${year}`;
document.getElementById("insertWeek").innerHTML = dateCustom;
}
//--------- get week (Show selected date in above custom datepicker) -------//
getWeek = (y) => {
const d = new Date(Date.now() - (6.048e+8 * y));
let yearStart = new Date(d.getFullYear(), 0, 1);
let today = new Date(d.getFullYear(), d.getMonth(), d.getDate());
let dayOfYear = ((today - yearStart + 1) / 86400000);
let week = Math.ceil(dayOfYear / 7);
document.getElementById("insertWeekNr").innerHTML = "weekNr : " + week;
}
Copy/paste in field ‘CSS STYLING’
.text01 {
font-size: 14px;
font-weight: 400;
padding-left: 5px;
padding-right: 15px;
margin-left: 5px;
}
.text02 {
font-size: 14px;
font-weight: 600;
}
select {
height: 36px;
width: 225px;
font-size: 14px;
font-weight: 600;
padding-left: 20px;
border: 1.5px solid black;
border-radius: 2px;
background: #e4e4e4;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
table {
margin-left: 15px;
width: 100px;
}
result preview : Custom (SINGLE) WEEK Picker using relative ranges
4. [ADVANCED] Custom Datepicker genereated using (influxDb) datasource + extra mapping
The principle behind this method is as follow.
-
Create a dummy query using your own datasource (influxDb / MySQL) with timerange ‘x’ to ‘y’
-
The focus will be on the ‘_time’ column
(my example is based on dateTime:RFC3339 | YYYY-MM-DDThh:mm:ssZ | 2020-06-18T17:24:53Z** format). -
Mapping! Extract elements like (DAY (DD) - MONTH (MM) - YEAR (YYYY) - WEEKDAY - WEEKNR - ETC…) from ‘_time’ as a “string” and map them to seperate columns ( using strings.substring() function )
-
++ Additional benefits ++
Adding these extra mapped fields in general allows you to easily filter and sort by specific days of the week, week numbers, months, and more.
- Generate/create CUSTOM DATEPICKER with prefered data above
So how to achieve this?
- Start with a basic (dummy) query with 1 or 2 columns (a
'_time'
column is required) - Add ‘PART 01’ of my mapping code to your query
- Add ‘PART 02’ of my mapping code to your query
- Copy/paste in field ‘CONTENT’
- Copy/paste in field ‘JAVASCRIPT’
- Copy/paste in field ‘CSS STYLING’
–
01. Your query (should look something like this)
from(bucket: "your_bucket_here")
|> range(start: 2024-01-01T02:00:01Z , stop: now())
|> filter(fn: (r) => r["_measurement"] == "reservation_status")
|> group(columns: ["_measurement"])
|> unique()
|> aggregateWindow(every: 1d, fn: count, createEmpty: true, timeSrc: "_start")
|> yield(name: "with_mapping")
A few extra notes for your own query part
Im using a static timerange from date ‘x’ (start) untill ‘today’ (stop) so this works independantly from the Grafanatime picker. Change startdate ‘x’ to your own date with one of the options below:
For my example I will use option 1
- option 1
|> range(start: 2024-01-01T02:00:01Z , stop: now())
use static date/time- option 2
|> range(start: -8d, stop: now())
use relative amount of days back as starting point of range- option 3
|> range(start: -8w, stop: now())
use relative amount of weeks back as starting time of range
aggregate ‘_time’ by ‘1d’ in your query like example , so you get only 1 value for each date
02. COPY PART 01
// <--- [A] copy/paste this on top of your dummy query above from(bucket: "") line --
import "join"
import "strings"
import "array"
import "system"
import "date"
import "dict"
weekdayName = [0:"Sun",1:"Mon",2:"Tue",3:"Wed",4:"Thu",5:"Fri",6:"Sat"]
monthName = [1:"Jan",2:"Feb",3:"Mar",4:"Apr",5:"May",6:"Jun",7:"Jul",8:"Aug",9:"Sep",10:"Oct",11:"Nov",12:"Dec"]
// -- end of copy part [A] ---/>
03. COPY PART 02: Copy this below your own query, make sure its above the ‘yield’ field if you are using that
// <-- [B] copy this below your dummy query , make sure its above the 'yield' field --
|> map(fn: (r) => ({
_time: r._time,
weekDayNr: date.weekDay(t: r._time),
weekDayName: dict.get(dict: weekdayName, key:date.weekDay(t: r._time), default: ""),
weekNr: date.week(t: r._time),
weekNrCurrent: date.week(t: now()),
monthNr: date.month(t: r._time) ,
monthName: dict.get(dict: monthName, key:date.month(t: r._time), default: ""),
dateDate: "${strings.substring(end: 10, start: 8, v: string(v: r._time))}",
dateMonth: "${strings.substring(end: 7, start: 5, v: string(v: r._time))}",
dateYear: "${strings.substring(end: 4, start: 0, v: string(v: r._time))}",
date__full: "${strings.substring(end: 10, start: 0, v: string(v: r._time))}",
date__today: "${strings.substring(end: 4, start: 0, v: string(v: system.time()))}-${strings.substring(end: 7, start: 5, v: string(v: system.time()))}-${strings.substring(end: 10, start: 8, v: string(v: system.time()))}"
}))
// -- end of copy part [B] should be above 'yield' field ---/>
|> yield(name: "with_mapping")
Your full query should look something like this
// <-- copy part [01] --
import "join"
import "strings"
import "array"
import "system"
import "date"
import "dict"
weekdayName = [0:"Sun",1:"Mon",2:"Tue",3:"Wed",4:"Thu",5:"Fri",6:"Sat"]
monthName = [1:"Jan",2:"Feb",3:"Mar",4:"Apr",5:"May",6:"Jun",7:"Jul",8:"Aug",9:"Sep",10:"Oct",11:"Nov",12:"Dec"]
// -- end of copy part [01] ---/>
// <-- Your query here --
from(bucket: "your_dB_here")
|> range(start: 2024-01-01T02:00:01Z , stop: now())
|> filter(fn: (r) => r["_measurement"] == "reservation_status")
|> group(columns: ["_measurement"])
|> unique()
|> aggregateWindow(every: 1d, fn: count, createEmpty: true, timeSrc: "_start")
// <-- copy part [02] --
|> map(fn: (r) => ({
_time: r._time,
weekDayNr: date.weekDay(t: r._time),
weekDayName: dict.get(dict: weekdayName, key:date.weekDay(t: r._time), default: ""),
weekNr: date.week(t: r._time),
weekNrCurrent: date.week(t: now()),
monthNr: date.month(t: r._time) ,
monthName: dict.get(dict: monthName, key:date.month(t: r._time), default: ""),
dateDate: "${strings.substring(end: 10, start: 8, v: string(v: r._time))}",
dateMonth: "${strings.substring(end: 7, start: 5, v: string(v: r._time))}",
dateYear: "${strings.substring(end: 4, start: 0, v: string(v: r._time))}",
date__full: "${strings.substring(end: 10, start: 0, v: string(v: r._time))}",
date__today: "${strings.substring(end: 4, start: 0, v: string(v: system.time()))}-${strings.substring(end: 7, start: 5, v: string(v: system.time()))}-${strings.substring(end: 10, start: 8, v: string(v: system.time()))}"
}))
// -- end of copy part [02] ---/>
|> yield(name: "with_mapping")
So now you have a table with ‘x’ results from you query with additional mapped fields.
You can use this data in combination with ‘Dynamix Text’ plugin to generate a picklist.
04. Copy/paste in field ‘CONTENT’
<p><br>
<select placeholder="SelectDate">
<option name="" value="" style="display:none;">-- Select Date --</option>
{{#each data}}
{{#if (eq date__full date__today) }}
<option name="week{{weekNr}}" onclick="changeToToday('now/d','now/d','{{weekNr}}')">{{date__today}} - [ TODAY ]</option>
{{else}}
<option name="week{{weekNr}}" onclick="changeToSelectedDate('{{date__full}}','{{date__full}}','{{weekNr}}', this)">
{{date__full}} - {{weekDayName}} [ Week {{weekNr}} ]</option>
{{/if}}
{{/each}}
</select>
05. Copy/paste in field ‘JAVASCRIPT’
changeToToday = (date_start, date_end, weekNo) => {
let date_start1 = date_start + " 00:00:00";
let date_end1 = date_end + " 23:59:00";
timeSrv = angular.element('grafana-app').injector().get('timeSrv');
timeSrv.setTime({ from: date_start, to: date_end });
return;
}
changeToSelectedDate = (date_start, date_end, weekNo, that) => {
let date_start1 = date_start + " 00:00:00";
let date_end1 = date_end + " 23:59:00";
timeSrv = angular.element('grafana-app').injector().get('timeSrv');
timeSrv.setTime({ from: date_start1, to: date_end1 });
return;
}
06. Copy/paste in field ‘CSS STYLING’
select {
height: 36px;
width: auto;
font-size: 14px;
font-weight: 500;
padding-left: 20px;
border: 1.5px solid black;
border-radius: 2px;
background: #e4e4e4;
margin-left: 25px;
margin-top: 15px;
}
If done correctly, you’ve now created a picklist with ‘x’ number of dates based on your specified range. For this example I used the range |> range(start: 2024-01-01T02:00:01Z , stop: now())
. Your Grafana Dashboards timerange will change accordingly after selecting one of these dates with the picklist
result preview
ps. I assume this method can also be applied for MySQL (datasource) users to achieve the same but with slightly different query commands to extract/map data. If anyone knows how to do so send me a DM please and I will add that to this post.
Once again I hope this helps you on your way. For now I will leave other creative solutions for timepickers to you again.
See you at the next post!