Fix datetime formatter, set date range, add padding

This commit is contained in:
rijkvanzanten
2021-06-18 11:41:36 -04:00
parent fc791b7c0d
commit e79c028455
5 changed files with 273 additions and 75 deletions

View File

@@ -11,7 +11,7 @@ export const i18n = createI18n({
messages: {
'en-US': enUSBase,
},
dateTimeFormats: dateFormats,
datetimeFormats: dateFormats,
silentTranslationWarn: true,
});

View File

@@ -144,6 +144,7 @@ boolean: Boolean
date: Date
date_range: Date Range
datetime: DateTime
precision: Precision
decimal: Decimal
float: Float
integer: Integer

View File

@@ -160,6 +160,46 @@ export default definePanel({
default_value: 2,
},
},
{
field: 'precision',
type: 'string',
name: '$t:precision',
meta: {
interface: 'select-dropdown',
width: 'half',
options: {
choices: [
{
text: 'Second',
value: 'second',
},
{
text: 'Minute',
value: 'minute',
},
{
text: 'Hour',
value: 'hour',
},
{
text: 'Day',
value: 'day',
},
{
text: 'Month',
value: 'month',
},
{
text: 'Year',
value: 'year',
},
],
},
},
schema: {
default_value: 'hour',
},
},
{
field: 'color',
name: '$t:color',

View File

@@ -6,6 +6,8 @@
import { defineComponent, PropType, ref, watch, onMounted, onUnmounted } from 'vue';
import api from '@/api';
import ApexCharts from 'apexcharts';
import { adjustDate } from '@/utils/adjust-date';
import { useI18n } from 'vue-i18n';
type TimeSeriesOptions = {
collection: string;
@@ -15,6 +17,7 @@ type TimeSeriesOptions = {
range: string; // 1 week, etc
color: string;
decimals: number;
precision: 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
};
export default defineComponent({
@@ -29,17 +32,130 @@ export default defineComponent({
},
},
setup(props) {
const { d } = useI18n();
const metrics = ref<Record<string, any>[]>([]);
const loading = ref(false);
const error = ref();
const chartEl = ref();
const chart = ref<ApexCharts>();
watch(() => props.options, fetchData, { deep: true });
watch(
() => props.options,
() => {
fetchData();
chart.value?.destroy();
setupChart();
},
{ deep: true }
);
fetchData();
onMounted(() => {
onMounted(setupChart);
onUnmounted(() => {
chart.value?.destroy();
});
return { chartEl, metrics, loading, error };
async function fetchData() {
loading.value = true;
try {
const results = await api.get(`/items/${props.options.collection}`, {
params: {
group: getGroups(),
aggregate: {
[props.options.function]: [props.options.valueField],
},
filter: {
_and: [
{
[props.options.dateField]: {
_gte: `$NOW(-${props.options.range || '1 week'})`,
},
},
{
[props.options.dateField]: {
_lte: `$NOW`,
},
},
],
},
limit: -1,
},
});
metrics.value = results.data.data;
chart.value?.updateSeries([
{
name: props.options.collection,
data: metrics.value.map((metric) => ({
x: toISO(metric),
y: Number(
Number(metric[`${props.options.valueField}_${props.options.function}`]).toFixed(
props.options.decimals ?? 0
)
),
})),
},
]);
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
function toISO(metric: Record<string, any>) {
const year = metric[`${props.options.dateField}_year`];
const month = padZero(metric[`${props.options.dateField}_month`] ?? 1);
const day = padZero(metric[`${props.options.dateField}_day`] ?? 1);
const hour = padZero(metric[`${props.options.dateField}_hour`] ?? 0);
const minute = padZero(metric[`${props.options.dateField}_minute`] ?? 0);
const second = padZero(metric[`${props.options.dateField}_second`] ?? 0);
return `${year}-${month}-${day}T${hour}:${minute}:${second}`;
function padZero(value: number) {
return String(value).padStart(2, '0');
}
}
function getGroups() {
let groups: string[] = [];
switch (props.options.precision || 'hour') {
case 'year':
groups = ['year'];
break;
case 'month':
groups = ['year', 'month'];
break;
case 'day':
groups = ['year', 'month', 'day'];
break;
case 'hour':
groups = ['year', 'month', 'day', 'hour'];
break;
case 'minute':
groups = ['year', 'month', 'day', 'hour', 'minute'];
break;
case 'second':
groups = ['year', 'month', 'day', 'hour', 'minute', 'second'];
break;
default:
groups = ['year', 'month', 'day', 'hour'];
break;
}
return groups.map((datePart) => `${datePart}(${props.options.dateField})`);
}
}
function setupChart() {
chart.value = new ApexCharts(chartEl.value, {
colors: [props.options.color ? props.options.color : 'var(--primary)'],
chart: {
@@ -82,6 +198,12 @@ export default defineComponent({
],
},
},
grid: {
padding: {
top: -20,
bottom: 8,
},
},
dataLabels: {
enabled: false,
},
@@ -89,6 +211,12 @@ export default defineComponent({
marker: {
show: false,
},
x: {
show: true,
formatter(date: number) {
return d(new Date(date), 'long');
},
},
},
xaxis: {
type: 'datetime',
@@ -101,82 +229,12 @@ export default defineComponent({
axisBorder: {
show: false,
},
range: new Date().getTime() - adjustDate(new Date(), `-${props.options.range}`)!.getTime(),
max: new Date().getTime(),
},
});
chart.value.render();
});
onUnmounted(() => {
chart.value?.destroy();
});
return { chartEl, metrics, loading, error };
async function fetchData() {
loading.value = true;
try {
const results = await api.get(`/items/${props.options.collection}`, {
params: {
group: [
`year(${props.options.dateField})`,
`month(${props.options.dateField})`,
`day(${props.options.dateField})`,
`hour(${props.options.dateField})`,
],
aggregate: {
[props.options.function]: [props.options.valueField],
},
filter: {
_and: [
{
[props.options.dateField]: {
_gte: `$NOW(-${props.options.range || '1 week'})`,
},
},
{
[props.options.dateField]: {
_lte: `$NOW`,
},
},
],
},
},
});
metrics.value = results.data.data;
chart.value?.updateOptions({
xaxis: {
categories: metrics.value.map((metric) => {
const year = metric[`${props.options.dateField}_year`];
const month = metric[`${props.options.dateField}_month`];
const day = metric[`${props.options.dateField}_day`];
const hour = metric[`${props.options.dateField}_hour`];
return `${year}-${month}-${day}T${hour}:00:00`;
}),
},
});
chart.value?.updateSeries([
{
name: props.options.collection,
data: metrics.value.map((metric) =>
Number(
Number(metric[`${props.options.valueField}_${props.options.function}`]).toFixed(
props.options.decimals ?? 0
)
)
),
},
]);
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
},
});

View File

@@ -0,0 +1,99 @@
/**
* @TODO this has to go in a shared package. Another one copy pasted from the API
*/
import {
addYears,
subWeeks,
subYears,
addWeeks,
subMonths,
addMonths,
subDays,
addDays,
subHours,
addHours,
subMinutes,
addMinutes,
subSeconds,
addSeconds,
addMilliseconds,
subMilliseconds,
} from 'date-fns';
import { clone } from 'lodash';
/**
* Adjust a given date by a given change in duration. The adjustment value uses the exact same syntax
* and logic as Vercel's `ms`.
*
* The conversion is lifted straight from `ms`.
*/
export function adjustDate(date: Date, adjustment: string): Date | undefined {
date = clone(date);
const subtract = adjustment.startsWith('-');
if (subtract || adjustment.startsWith('+')) {
adjustment = adjustment.substring(1);
}
const match =
/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|months?|mth|mo|years?|yrs?|y)?$/i.exec(
adjustment
);
if (!match) {
return;
}
const amount = parseFloat(match[1]);
const type = (match[2] || 'days').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return subtract ? subYears(date, amount) : addYears(date, amount);
case 'months':
case 'month':
case 'mth':
case 'mo':
return subtract ? subMonths(date, amount) : addMonths(date, amount);
case 'weeks':
case 'week':
case 'w':
return subtract ? subWeeks(date, amount) : addWeeks(date, amount);
case 'days':
case 'day':
case 'd':
return subtract ? subDays(date, amount) : addDays(date, amount);
case 'hours':
case 'hour':
case 'hrs':
case 'hr':
case 'h':
return subtract ? subHours(date, amount) : addHours(date, amount);
case 'minutes':
case 'minute':
case 'mins':
case 'min':
case 'm':
return subtract ? subMinutes(date, amount) : addMinutes(date, amount);
case 'seconds':
case 'second':
case 'secs':
case 'sec':
case 's':
return subtract ? subSeconds(date, amount) : addSeconds(date, amount);
case 'milliseconds':
case 'millisecond':
case 'msecs':
case 'msec':
case 'ms':
return subtract ? subMilliseconds(date, amount) : addMilliseconds(date, amount);
default:
return undefined;
}
}