Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various fixes #8

Merged
merged 7 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .config/webpack/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ const config = async (env): Promise<Configuration> => {

module: {
rules: [
{
test: /\.js$/,
enforce: "pre",
use: ["source-map-loader"],
},
{
exclude: /(node_modules)/,
test: /\.[tj]sx?$/,
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ services:
ports:
- 4000:3000/tcp
volumes:
- ./dist:/var/lib/grafana/plugins/cubismgrafana-panel
- ./dist:/var/lib/grafana/plugins/ekacnet-cubismgrafana-panel
- ./provisioning:/etc/grafana/provisioning
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cubism-grafana-panel",
"version": "0.0.2",
"version": "0.0.3",
"description": "A panel to display cubism like graph in grafana",
"scripts": {
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",
Expand Down Expand Up @@ -50,6 +50,7 @@
"replace-in-file-webpack-plugin": "^1.0.6",
"sass": "^1.63.2",
"sass-loader": "^13.3.1",
"source-map-loader": "^5.0.0",
"style-loader": "^3.3.3",
"swc-loader": "^0.2.3",
"ts-node": "^10.9.1",
Expand Down
55 changes: 27 additions & 28 deletions src/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
<!-- This README file is going to be the one displayed on the Grafana.com website for your plugin. Uncomment and replace the content here before publishing.
# Cubism graph

Remove any remaining comments before publishing as these may be displayed on Grafana.com -->
A panel to display [cubism](https://square.github.io/cubism) like graph in grafana.

# Cubism-Grafana-Panel
## Overview / Introduction
Cubism is a way of representing information dense graphs in a limited amount of spaces, let's imagine you want to display the volume of 5xx errors that you are facing for each of the regions of your cloud provider.

The goto way is to do a regular graph with one serie per region, but if the volume are different across region you might be missing out something happening on lower volume region.

Cubism graph solve that by giving each serie its own horizon and by representing the increase of the metric by a darker color, if all of a sudden the region A is getting much darker you know right away that something is happening there.

Something similar to this graph:

![cubism graph](https://raw.githubusercontent.com/ekacnet/grafanacubism-panel/main/src/img/screenshot.png)


## Requirements

Grafana 10.3 is required to install this plugin.

## Getting Started

A panel to display cubism like graph in grafana
1. Create a new visualization widget
2. Create the query that you want to display
3. On the right hand side click on time series and search for Cubism graph

<!-- To help maximize the impact of your README and improve usability for users, we propose the following loose structure:
And you should get a cubism graph.

## Contributing
Look at the [grafanacubism-panel](https://github.com/ekacnet/grafanacubism-panel) project on Github to see how you can help
<!--

**BEFORE YOU BEGIN**
- Ensure all links are absolute URLs so that they will work when the README is displayed within Grafana and Grafana.com
- Be inspired ✨
- [grafana-polystat-panel](https://github.com/grafana/grafana-polystat-panel)
- [volkovlabs-variable-panel](https://github.com/volkovlabs/volkovlabs-variable-panel)

**ADD SOME BADGES**

Expand All @@ -30,22 +47,4 @@ Full example: ![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?lo

Consider other [badges](https://shields.io/badges) as you feel appropriate for your project.

## Overview / Introduction
Provide one or more paragraphs as an introduction to your plugin to help users understand why they should use it.

Consider including screenshots:
- in [plugin.json](https://grafana.com/developers/plugin-tools/reference-plugin-json#info) include them as relative links.
- in the README ensure they are absolute URLs.

## Requirements
List any requirements or dependencies they may need to run the plugin.

## Getting Started
Provide a quick start on how to configure and use the plugin.

## Documentation
If your project has dedicated documentation available for users, provide links here. For help in following Grafana's style recommendations for technical documentation, refer to our [Writer's Toolkit](https://grafana.com/docs/writers-toolkit/).

## Contributing
Do you want folks to contribute to the plugin or provide feedback through specific means? If so, tell them how!
-->
18 changes: 14 additions & 4 deletions src/components/SimplePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const D3Graph: React.FC<{

const renderD3 = React.useCallback(
(wrapperDiv: HTMLDivElement | null) => {
if (!wrapperDiv|| data.series.length === 0) {
if (!wrapperDiv || data.series.length === 0) {
return;
}
const anHour = 60 * 60 * 1000;
Expand All @@ -108,17 +108,28 @@ export const D3Graph: React.FC<{
let cubismData = data.series.map(function (series, seriesIndex) {
return convertDataToCubism(series, seriesIndex, cubismTimestamps, context);
});
cubismData = cubismData.filter(function (el) {
if (el !== null) {
return el;
}
});

wrapperDiv.innerHTML = '';
wrapperDiv.className = stylesGetter.wrapper;

if (cubismData.length === 0) {
wrapperDiv.innerHTML = 'The series contained no data, check your query';
return;
}
const outerDiv = d3.create('div');
outerDiv.node()!.className = stylesGetter.d3outer;
outerDiv.node()!.className = stylesGetter.d3outer;
// size seems to be more the nubmer of pix
// steps seems to be how often things things change in microseconds ?
// it also control the range (ie. given the number of pixel and that a
// pixel reprensent 1e3 milliseconds, the range is 1e3 * size seconds)
context.size(size).step(step);
// @ts-ignore
context.stop();

const innnerDiv = d3.create('div');
const axisDiv = d3.create('div');
Expand Down Expand Up @@ -158,7 +169,6 @@ export const D3Graph: React.FC<{
context.axis().ticks(scale, count).orient(dataValue).render(d3.select(this));
});


// create the horizon
const h = innnerDiv
.selectAll('.horizon')
Expand All @@ -175,7 +185,7 @@ export const D3Graph: React.FC<{
.attr('id', 'rule');
context.rule().render(ruleDiv);
// extent is the vertical range for the values for a given horinzon
if (options.automaticExtents) {
if (options.automaticExtents || options.extentMin === undefined || options.extentMax === undefined) {
context.horizon().render(h);
} else {
context.horizon().extent([options.extentMin, options.extentMax]).render(h);
Expand Down
57 changes: 34 additions & 23 deletions src/cubism_utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from 'lodash';
import { DataFrame } from '@grafana/data';
import { DataFrame, getFieldDisplayName } from '@grafana/data';

export function upSampleData(dataPoints: number[], dataPointsTS: number[], pointIndex: number) {
return function (ts: number, tsIndex: number) {
Expand All @@ -20,6 +20,7 @@ export function upSampleData(dataPoints: number[], dataPointsTS: number[], point
}

export function downSampleData(timestamps: number[], dataAndTS: number[][], override: { summaryType: string }) {
let val = 0;
return function (ts: number, tsIndex: number) {
let nextTs: null | number = null;
if (tsIndex + 1 < timestamps.length) {
Expand All @@ -33,15 +34,20 @@ export function downSampleData(timestamps: number[], dataAndTS: number[][], over
return point[1];
});

if (values.length === 0) {
return val;
}

if (override.summaryType === 'sum') {
return sumValues(values);
val = sumValues(values);
} else if (override.summaryType === 'min') {
return minValue(values);
val = minValue(values);
} else if (override.summaryType === 'max') {
return maxValue(values);
val = maxValue(values);
} else {
return averageValues(values);
val = averageValues(values);
}
return val;
};
}

Expand Down Expand Up @@ -71,22 +77,27 @@ export function minValue(values: number[]) {
}

export function convertDataToCubism(series: DataFrame, seriesIndex: number, timestamps: number[], context: any) {
return context.metric(function (start: number, stop: number, step: number, callback: any) {
let dataPoints: number[] = series.fields[1].values;
let dataPointsTS: number[] = series.fields[0].values;
let values: number[] = [];
if (timestamps.length === dataPoints.length) {
values = dataPoints.map(function (point: number) {
return point;
});
} else if (timestamps.length > dataPoints.length) {
let pointIndex = 0;
values = _.chain(timestamps).map(upSampleData(dataPoints, dataPointsTS, pointIndex)).value();
} else {
let override = { summaryType: 'avg' };
let dataAndTS = dataPointsTS.map((item, index) => [item, dataPoints[index]]);
values = _.chain(timestamps).map(downSampleData(timestamps, dataAndTS, override)).value();
}
callback(null, values);
}, series.fields[1].name);
if (series.length > 0) {
let name = getFieldDisplayName(series.fields[1], series);
return context.metric(function (start: number, stop: number, step: number, callback: any) {
let dataPoints: number[] = series.fields[1].values;
let dataPointsTS: number[] = series.fields[0].values;
let values: number[] = [];
if (timestamps.length === dataPoints.length) {
values = dataPoints.map(function (point: number) {
return point;
});
} else if (timestamps.length > dataPoints.length) {
let pointIndex = 0;
values = _.chain(timestamps).map(upSampleData(dataPoints, dataPointsTS, pointIndex)).value();
} else {
let override = { summaryType: 'avg' };
let dataAndTS = dataPointsTS.map((item, index) => [item, dataPoints[index]]);
values = _.chain(timestamps).map(downSampleData(timestamps, dataAndTS, override)).value();
}
callback(null, values);
}, name);
} else {
return null;
}
}
2 changes: 1 addition & 1 deletion src/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json",
"type": "panel",
"name": "Cubism-Grafana-Panel",
"name": "Cubism Graph",
"id": "ekacnet-cubismgrafana-panel",
"info": {
"keywords": ["panel"],
Expand Down
8 changes: 8 additions & 0 deletions validate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

PLUGIN_ID=$(grep '"id"' < src/plugin.json | sed -E 's/.*"id" *: *"(.*)".*/\1/')
rm -rf ${PLUGIN_ID}
rm -rf ${PLUGIN_ID}.zip
cp -r dist "${PLUGIN_ID}"
zip -qr "${PLUGIN_ID}.zip" "${PLUGIN_ID}"
npx @grafana/plugin-validator@latest -sourceCodeUri file://. "${PLUGIN_ID}.zip"
Loading