mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
add cap to dates on slices
This commit is contained in:
@@ -6,7 +6,7 @@ const props = defineProps<{ title: string, sub?: string }>();
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LyxUiCard>
|
<LyxUiCard>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4 h-full">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex flex-col grow">
|
<div class="flex flex-col grow">
|
||||||
<div class="poppins font-semibold text-[1rem] md:text-[1.3rem] text-text">
|
<div class="poppins font-semibold text-[1rem] md:text-[1.3rem] text-text">
|
||||||
@@ -18,8 +18,7 @@ const props = defineProps<{ title: string, sub?: string }>();
|
|||||||
</div>
|
</div>
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="h-full">
|
||||||
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import type { ChartData, ChartOptions, TooltipModel } from 'chart.js';
|
|||||||
import { useLineChart, LineChart } from 'vue-chart-3';
|
import { useLineChart, LineChart } from 'vue-chart-3';
|
||||||
registerChartComponents();
|
registerChartComponents();
|
||||||
|
|
||||||
|
const errorData = ref<{ errored: boolean, text: string }>({
|
||||||
|
errored: false,
|
||||||
|
text: ''
|
||||||
|
})
|
||||||
|
|
||||||
const chartOptions = ref<ChartOptions<'line'>>({
|
const chartOptions = ref<ChartOptions<'line'>>({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@@ -130,6 +134,7 @@ function externalTooltipHandler(context: { chart: any, tooltip: TooltipModel<'li
|
|||||||
const selectLabels: { label: string, value: Slice }[] = [
|
const selectLabels: { label: string, value: Slice }[] = [
|
||||||
{ label: 'Hour', value: 'hour' },
|
{ label: 'Hour', value: 'hour' },
|
||||||
{ label: 'Day', value: 'day' },
|
{ label: 'Day', value: 'day' },
|
||||||
|
{ label: 'Month', value: 'month' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const selectedLabelIndex = ref<number>(1);
|
const selectedLabelIndex = ref<number>(1);
|
||||||
@@ -157,19 +162,35 @@ const body = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function onResponseError(e: any) {
|
||||||
|
console.log('ON RESPONSE ERROR')
|
||||||
|
errorData.value = { errored: true, text: e.response._data.message ?? 'Generic error' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResponse(e: any) {
|
||||||
|
console.log('ON RESPONSE')
|
||||||
|
if (e.response.status != 500) errorData.value = { errored: false, text: '' }
|
||||||
|
}
|
||||||
|
|
||||||
const visitsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/visits`, {
|
const visitsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/visits`, {
|
||||||
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
||||||
lazy: true, immediate: false
|
lazy: true, immediate: false,
|
||||||
|
onResponseError,
|
||||||
|
onResponse
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events`, {
|
const eventsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events`, {
|
||||||
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
||||||
lazy: true, immediate: false
|
lazy: true, immediate: false,
|
||||||
|
onResponseError,
|
||||||
|
onResponse
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessionsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/sessions`, {
|
const sessionsData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/sessions`, {
|
||||||
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
method: 'POST', ...signHeaders({ v2: 'true' }), body, transform: transformResponse,
|
||||||
lazy: true, immediate: false
|
lazy: true, immediate: false,
|
||||||
|
onResponseError,
|
||||||
|
onResponse
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -310,9 +331,14 @@ const inLiveDemo = isLiveDemo();
|
|||||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col items-end" v-if="readyToDisplay">
|
<div class="flex flex-col items-end" v-if="readyToDisplay && !errorData.errored">
|
||||||
<LineChart ref="lineChartRef" class="w-full h-full" v-bind="lineChartProps"> </LineChart>
|
<LineChart ref="lineChartRef" class="w-full h-full" v-bind="lineChartProps"> </LineChart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorData.errored" class="flex items-center justify-center py-8">
|
||||||
|
{{ errorData.text }}
|
||||||
|
</div>
|
||||||
|
|
||||||
</CardTitled>
|
</CardTitled>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const headers = computed(() => {
|
|||||||
'x-to': safeSnapshotDates.value.to,
|
'x-to': safeSnapshotDates.value.to,
|
||||||
'Authorization': authorizationHeaderComputed.value,
|
'Authorization': authorizationHeaderComputed.value,
|
||||||
'x-schema': 'events',
|
'x-schema': 'events',
|
||||||
'x-limit': "10",
|
'x-limit': "6",
|
||||||
'x-pid': activeProjectId.data.value || ''
|
'x-pid': activeProjectId.data.value || ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,26 +32,26 @@ function transformResponse(input: { _id: string, name: string, count: number }[]
|
|||||||
const parsedDatasets: any[] = [];
|
const parsedDatasets: any[] = [];
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
"#5655d0",
|
"#5655d0",
|
||||||
"#6bbbe3",
|
"#6bbbe3",
|
||||||
"#a6d5cb",
|
"#a6d5cb",
|
||||||
"#fae0b9",
|
"#fae0b9",
|
||||||
"#f28e8e",
|
"#f28e8e",
|
||||||
"#e3a7e4",
|
"#e3a7e4",
|
||||||
"#c4a8e1",
|
"#c4a8e1",
|
||||||
"#8cc1d8",
|
"#8cc1d8",
|
||||||
"#f9c2cd",
|
"#f9c2cd",
|
||||||
"#b4e3b2",
|
"#b4e3b2",
|
||||||
"#ffdfba",
|
"#ffdfba",
|
||||||
"#e9c3b5",
|
"#e9c3b5",
|
||||||
"#d5b8d6",
|
"#d5b8d6",
|
||||||
"#add7f6",
|
"#add7f6",
|
||||||
"#ffd1dc",
|
"#ffd1dc",
|
||||||
"#ffe7a1",
|
"#ffe7a1",
|
||||||
"#a8e6cf",
|
"#a8e6cf",
|
||||||
"#d4a5a5",
|
"#d4a5a5",
|
||||||
"#f3d6e4",
|
"#f3d6e4",
|
||||||
"#c3aed6"
|
"#c3aed6"
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i < fixed.allKeys.length; i++) {
|
for (let i = 0; i < fixed.allKeys.length; i++) {
|
||||||
@@ -74,8 +74,26 @@ function transformResponse(input: { _id: string, name: string, count: number }[]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorData = ref<{ errored: boolean, text: string }>({
|
||||||
|
errored: false,
|
||||||
|
text: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function onResponseError(e: any) {
|
||||||
|
console.log('ON RESPONSE ERROR')
|
||||||
|
errorData.value = { errored: true, text: e.response._data.message ?? 'Generic error' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function onResponse(e: any) {
|
||||||
|
console.log('ON RESPONSE')
|
||||||
|
if (e.response.status != 500) errorData.value = { errored: false, text: '' }
|
||||||
|
}
|
||||||
|
|
||||||
const eventsStackedData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events_stacked`, {
|
const eventsStackedData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events_stacked`, {
|
||||||
method: 'POST', body, lazy: true, immediate: false, transform: transformResponse, ...signHeaders()
|
method: 'POST', body, lazy: true, immediate: false, transform: transformResponse, ...signHeaders(),
|
||||||
|
onResponseError,
|
||||||
|
onResponse
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -86,13 +104,17 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="h-full">
|
||||||
<div v-if="eventsStackedData.pending.value" class="flex justify-center py-40">
|
<div v-if="eventsStackedData.pending.value" class="flex justify-center py-40">
|
||||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<AdvancedStackedBarChart v-if="!eventsStackedData.pending.value"
|
<AdvancedStackedBarChart v-if="!eventsStackedData.pending.value && !errorData.errored"
|
||||||
:datasets="eventsStackedData.data.value?.datasets || []"
|
:datasets="eventsStackedData.data.value?.datasets || []"
|
||||||
:labels="eventsStackedData.data.value?.labels || []">
|
:labels="eventsStackedData.data.value?.labels || []">
|
||||||
</AdvancedStackedBarChart>
|
</AdvancedStackedBarChart>
|
||||||
|
<div v-if="errorData.errored" class="flex items-center justify-center py-8 h-full">
|
||||||
|
{{ errorData.text }}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,15 +20,15 @@ const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProje
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full overflow-y-auto pb-20 p-6 gap-6 flex flex-col">
|
<div class="w-full h-full overflow-y-auto pb-20 p-6 gap-6 flex flex-col">
|
||||||
|
|
||||||
<div class="flex gap-6 flex-col xl:flex-row">
|
<div class="flex gap-6 flex-col xl:flex-row h-full">
|
||||||
|
|
||||||
<CardTitled :key="refreshKey" class="p-4 flex-[4] w-full" title="Events" sub="Events stacked bar chart.">
|
<CardTitled :key="refreshKey" class="p-4 flex-[4] w-full h-full" title="Events" sub="Events stacked bar chart.">
|
||||||
<template #header>
|
<template #header>
|
||||||
<SelectButton @changeIndex="eventsStackedSelectIndex = $event"
|
<SelectButton @changeIndex="eventsStackedSelectIndex = $event"
|
||||||
:currentIndex="eventsStackedSelectIndex" :options="selectLabelsEvents">
|
:currentIndex="eventsStackedSelectIndex" :options="selectLabelsEvents">
|
||||||
</SelectButton>
|
</SelectButton>
|
||||||
</template>
|
</template>
|
||||||
<div>
|
<div class="h-full">
|
||||||
<EventsStackedBarChart :slice="(selectLabelsEvents[eventsStackedSelectIndex].value as any)">
|
<EventsStackedBarChart :slice="(selectLabelsEvents[eventsStackedSelectIndex].value as any)">
|
||||||
</EventsStackedBarChart>
|
</EventsStackedBarChart>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PerformanceThing {
|
|
||||||
public min: number = Infinity;
|
|
||||||
public max: number = -Infinity;
|
|
||||||
private things: number[] = [];
|
|
||||||
private slice: number = 0;
|
|
||||||
constructor(public id: string, private maxThings: number) { }
|
|
||||||
start() { this.slice = performance.now(); }
|
|
||||||
stop() {
|
|
||||||
const time = performance.now() - this.slice;
|
|
||||||
if (time > this.max) this.max = time;
|
|
||||||
if (time < this.min) this.min = time;
|
|
||||||
this.things.push(time);
|
|
||||||
if (this.things.length > this.maxThings) {
|
|
||||||
this.things.shift();
|
|
||||||
}
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
avg() {
|
|
||||||
return this.things.reduce((a, e) => a + e, 0) / this.things.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
print() {
|
|
||||||
console.log(`${this.id} | Avg: ${this.avg().toFixed(0)} ms | Min: ${this.min.toFixed(0)} ms | Max: ${this.max.toFixed(0)} ms`)
|
|
||||||
}
|
|
||||||
get data() { return this.things; }
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PerformanceService {
|
|
||||||
static create(id: string, maxThings: number = 100) {
|
|
||||||
const thing = new PerformanceThing(id, maxThings);
|
|
||||||
return thing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,6 +29,17 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
|
|||||||
|
|
||||||
const { group, sort, fromParts } = DateService.getQueryDateRange(options.slice);
|
const { group, sort, fromParts } = DateService.getQueryDateRange(options.slice);
|
||||||
|
|
||||||
|
if (!sort) throw Error('Slice is probably not correct');
|
||||||
|
|
||||||
|
const dateDistDays = (new Date(options.to).getTime() - new Date(options.from).getTime()) / (1000 * 60 * 60 * 24)
|
||||||
|
// 15 Days
|
||||||
|
if (options.slice === 'hour' && (dateDistDays > 15)) throw Error('Date gap too big for this slice');
|
||||||
|
// 1 Year
|
||||||
|
if (options.slice === 'day' && (dateDistDays > 365)) throw Error('Date gap too big for this slice');
|
||||||
|
// 3 Years
|
||||||
|
if (options.slice === 'month' && (dateDistDays > 365 * 3)) throw Error('Date gap too big for this slice');
|
||||||
|
|
||||||
|
|
||||||
const aggregation = [
|
const aggregation = [
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ class DateService {
|
|||||||
|
|
||||||
getChartLabelFromISO(iso: string, locale: string, slice: Slice) {
|
getChartLabelFromISO(iso: string, locale: string, slice: Slice) {
|
||||||
const date = dayjs(iso).locale(locale);
|
const date = dayjs(iso).locale(locale);
|
||||||
if (slice === 'hour') return date.format('HH:mm')
|
if (slice === 'hour') return date.format('HH:mm');
|
||||||
if (slice === 'day') return date.format('DD/MM')
|
if (slice === 'day') return date.format('DD/MM');
|
||||||
|
if (slice === 'month') return date.format('MM MMMM');
|
||||||
|
if (slice === 'year') return date.format('YYYY');
|
||||||
return date.format();
|
return date.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user