Skip to content

Instantly share code, notes, and snippets.

@gordonturner
Last active August 6, 2023 07:25
Show Gist options
  • Save gordonturner/039abb1b6bdfcd071fcfa98953b92f2e to your computer and use it in GitHub Desktop.
Save gordonturner/039abb1b6bdfcd071fcfa98953b92f2e to your computer and use it in GitHub Desktop.
Custom Grafana Plugin
import React, { useEffect } from 'react';
import { PanelProps } from '@grafana/data';
import { stylesFactory } from '@grafana/ui';
import { css, cx, injectGlobal } from 'emotion';
import glyphiconWoff from './fonts/glyphicons-halflings-regular.woff';
import { DEFAULT_API_URL, DEFAULT_API_KEY } from './constants';
import { ListlistData, ListlistItem, ListlistOptions } from './types';
import { DateTime } from 'luxon';
import axios, { AxiosRequestConfig } from 'axios';
interface Props extends PanelProps<ListlistOptions> {}
export const ListlistPanel: React.FC<Props> = ({ options, data, width, height }) => {
const styles = getStyles();
const [listlistData, updateListlistData] = React.useState<ListlistData>();
// HACK: Trick useEffect() by incrementing a counter on a timer.
const [count, setCount] = React.useState(0);
// Logging when this is called by Grafana.
console.log("Grafana called...");
// setCount(count + 1);
/**
* This function manages the async call to the API.
*
* curl 'https://prod.gordonturner.com/listlist-web/api/2' \
* -H 'api-key: XXXX'
*/
const requestData = async () => {
// Setup the authentication header
const requestOptions: AxiosRequestConfig = {
method: 'GET',
headers: {
'api-key': options.apiKey ? options.apiKey : DEFAULT_API_KEY,
},
};
const response = axios.get(options.apiUrl ? options.apiUrl : DEFAULT_API_URL, requestOptions);
const data = (await response).data;
console.log('Data updated:');
console.log(data);
if (data.list === undefined || data.list.length == 0) {
// Show a 'No Active Items' message
const item: ListlistItem = {
id: 1,
state: 'NONE',
name: 'No Active Items ',
sortOrder: 1,
createDate: '2222-01-01T00:00:00.000+0000',
dueDate: '2222-01-01T00:00:00.000+0000',
completedDate: '2222-01-01T00:00:00.000+0000',
};
const listlistDataNoActiveItems: ListlistData = {
list: [item]
};
updateListlistData(listlistDataNoActiveItems);
} else {
updateListlistData(data);
}
};
/**
* Accepts a function that contains imperative, possibly effectful code.
*
* Mutations, subscriptions, timers, logging, and other side effects are not allowed
* inside the main body of a function component (referred to as React’s render phase).
* Doing so will lead to confusing bugs and inconsistencies in the UI.
*
* Instead, use useEffect. The function passed to useEffect will run after the render
* is committed to the screen. Think of effects as an escape hatch from React’s purely
* functional world into the imperative world.
*
* - Reference:
* https://reactjs.org/docs/hooks-reference.html#useeffect
*
* In this useEffect hook, an empty array [] is the second argument so the code inside
* useEffect will run only once when the component is mounted. This is functionally
* similar to componentDidMount lifecycle method in react hooks.
*/
useEffect(() => {
const requestDataAsync = async () => {
await requestData();
};
requestDataAsync();
setTimeout(() => {
setCount(count + 1);
console.log(count);
}, 30000);
}, [count]);
return (
<div
className={cx(
styles.wrapper,
css`
width: ${width}px;
height: ${height}px;
`
)}
>
<h1 className={styles.listName}>{listlistData?.name}</h1>
<ul id="start-page-todo--todo" className={styles.listGroup}>
{listlistData?.list?.map(listlist => (
<li className={styles.listGroupItem} key={listlist.id}>
<span
className={[
styles.glyphicon,
getGlyphiconIconClass(listlist.state),
getGlyphiconColorClass(listlist.createDate),
].join(' ')}
></span>
<span className={styles.activeListItemName}>{listlist.name}</span>
</li>
))}
</ul>
</div>
);
};
/*
* Add font for the check icons.
*/
injectGlobal`
@font-face {
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
src: url('${glyphiconWoff}')
format('woff');
}`;
const getStyles = stylesFactory(() => {
return {
wrapper: css`
position: relative;
`,
svg: css`
position: absolute;
top: 0;
left: 0;
`,
textBox: css`
position: absolute;
bottom: 0;
left: 0;
padding: 10px;
`,
listName: css`
margin-left: 10%;
`,
listGroup: css`
list-style: none;
margin-left: 2%;
`,
listGroupItem: css`
border-radius: 0 !important;
font-size: 25px;
line-height: 1.5;
margin-left: 50px;
`,
glyphicon: css`
display: inline-block;
margin-top: 3px;
vertical-align: text-top;
position: relative;
top: 3px;
display: inline-block;
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: normal;
line-height: 1;
font-family: 'Glyphicons Halflings';
`,
glyphiconColorNormal: css`
color: inherit;
`,
glyphiconColorGreen: css`
color: green;
`,
glyphiconColorOrange: css`
color: orange;
`,
glyphiconColorRed: css`
color: red;
`,
glyphiconChecked: css`
:before {
content: '\\e067';
}
`,
glyphiconUnchecked: css`
:before {
content: '\\e157';
}
`,
glyphiconNoActiveItems: css`
:before {
content: '\\e162';
}
`,
activeListItemName: css`
display: inline-block;
vertical-align: text-top;
width: 89%;
margin-left: 10px;
margin-top: 4px;
margin-bottom: 5px;
`,
};
});
/*
* Function that will return the appropriate class for create date.
*/
function getGlyphiconColorClass(createDate: string) {
let diffDate: number = DateTime.local().toMillis() - DateTime.fromISO(createDate).toMillis();
// console.log(createDate);
// console.log(diffDate);
if (diffDate < 0) {
// console.log('No Active Items');
return getStyles().glyphiconColorNormal;
} else if (diffDate < 604800000) {
// console.log('less then a week, green');
return getStyles().glyphiconColorGreen;
} else if (diffDate > 604800000 && diffDate < 1814400000) {
// console.log('more then a week, less then 3, orange');
return getStyles().glyphiconColorOrange;
} else {
// console.log('more then 3 weeks, red');
return getStyles().glyphiconColorRed;
}
}
/*
* Function that will return the appropriate class for icon.
*/
function getGlyphiconIconClass(state: string) {
if ('ACTIVE' == state) {
// console.log('ACTIVE, setting icon to unchecked');
return getStyles().glyphiconUnchecked;
} else if ('COMPLETED' == state) {
// console.log('COMPLETED, setting icon to checked');
return getStyles().glyphiconChecked;
} else {
// console.log('Empty, setting no icon');
return getStyles().glyphiconNoActiveItems;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment