This commit is contained in:
Emily
2025-03-14 16:40:50 +01:00
parent d1b3e997c1
commit afda29997d
11 changed files with 247 additions and 30 deletions

View File

@@ -68,21 +68,47 @@ const avgBouncingRate = computed(() => {
return avg.toFixed(2) + ' %';
})
function weightedAverage(data: number[]): number {
if (data.length === 0) return 0;
// Compute median
const sortedData = [...data].sort((a, b) => a - b);
const middle = Math.floor(sortedData.length / 2);
const median = sortedData.length % 2 === 0
? (sortedData[middle - 1] + sortedData[middle]) / 2
: sortedData[middle];
// Define a threshold (e.g., 3 times the median) to filter out extreme values
const threshold = median * 3;
const filteredData = data.filter(num => num <= threshold);
if (filteredData.length === 0) return median; // Fallback to median if all are removed
// Compute weights based on inverse absolute deviation from median
const weights = filteredData.map(num => 1 / (1 + Math.abs(num - median)));
// Compute weighted sum and sum of weights
const weightedSum = filteredData.reduce((sum, num, i) => sum + num * weights[i], 0);
const sumOfWeights = weights.reduce((sum, weight) => sum + weight, 0);
return weightedSum / sumOfWeights;
}
const avgSessionDuration = computed(() => {
if (!sessionsDurationData.data.value) return '0.00 %'
const counts = sessionsDurationData.data.value.data
.filter(e => e > 0)
// .filter(e => e > 0)
.reduce((a, e) => e + a, 0);
const avg = counts / (Math.max(sessionsDurationData.data.value.data.filter(e => e > 0).length, 1)) / 5;
const avg = weightedAverage(sessionsDurationData.data.value.data);
// counts / (Math.max(sessionsDurationData.data.value.data.length, 1));
let hours = 0;
let minutes = 0;
let seconds = 0;
seconds += avg * 60;
while (seconds > 60) { seconds -= 60; minutes += 1; }
while (minutes > 60) { minutes -= 60; hours += 1; }
while (seconds >= 60) { seconds -= 60; minutes += 1; }
while (minutes >= 60) { minutes -= 60; hours += 1; }
return `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds.toFixed()}s`
});

View File

@@ -33,9 +33,9 @@ const guestProjectList = computed(() => {
return guestProjectsRequest.data.value;
})
const refreshProjectsList = () => {
projectsRequest.refresh();
guestProjectsRequest.refresh();
const refreshProjectsList = async () => {
await projectsRequest.refresh();
await guestProjectsRequest.refresh();
}
const activeProjectId = ref<string | undefined>();

View File

@@ -40,8 +40,10 @@ async function createProject() {
await actions.refreshProjectsList();
const newActiveProjectId = projectList.value?.[projectList.value?.length - 1]._id.toString();
if (newActiveProjectId) {
await actions.setActiveProject(newActiveProjectId);
console.log('Set active project', newActiveProjectId);
}
setPageLayout('dashboard');

View File

@@ -8,7 +8,7 @@ export default defineEventHandler(async event => {
const { name } = await readBody(event);
if (name.trim()) return setResponseStatus(event, 400, 'name is required');
if (name.trim().length == 0) return setResponseStatus(event, 400, 'name is required');
if (name.trim().length < 2) return setResponseStatus(event, 400, 'name too short');
if (name.trim().length > 32) return setResponseStatus(event, 400, 'name too long');

View File

@@ -20,7 +20,7 @@ export default defineEventHandler(async event => {
}
const link = `http://127.0.0.1:3000/accept_invite?project_id=${project_id.toString()}`;
const link = `https://dashboard.litlyx.com/accept_invite?project_id=${project_id.toString()}`;
if (!targetUser) {

View File

@@ -42,7 +42,7 @@ export default defineEventHandler(async event => {
})
for (const member of members) {
const userMember = await UserModel.findById(member.user_id);
const userMember = member.user_id ? await UserModel.findById(member.user_id) : await UserModel.findOne({ email: member.email });
if (!userMember) continue;
const permission: TPermission = {

View File

@@ -24,6 +24,28 @@ export default defineEventHandler(async event => {
customProjection: {
count: { $divide: ["$duration", "$count"] }
},
customQueries: [
{
index: 1,
query: {
"$lookup": {
"from": "visits",
"localField": "session",
"foreignField": "session",
"as": "visits",
"pipeline": [{ "$limit": 1 }]
}
},
},
{
index: 2,
query: {
"$match": {
"visits.0": { "$exists": true }
}
}
}
]
});
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to);
return timelineFilledMerged;

View File

@@ -20,7 +20,8 @@ export type AdvancedTimelineAggregationOptions = TimelineAggregationOptions & {
customGroup?: Record<string, any>,
customProjection?: Record<string, any>,
customIdGroup?: Record<string, any>,
customAfterMatch?: Record<string, any>
customAfterMatch?: Record<string, any>,
customQueries?: { index: number, query: Record<string, any> }[]
}
export async function executeAdvancedTimelineAggregation<T = {}>(options: AdvancedTimelineAggregationOptions) {
@@ -29,6 +30,7 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
options.customGroup = options.customGroup || {};
options.customProjection = options.customProjection || {};
options.customIdGroup = options.customIdGroup || {};
options.customQueries = options.customQueries || [];
const { dateFromParts, granularity } = DateService.getGranularityData(options.slice, '$tmpDate');
if (!dateFromParts) throw Error('Slice is probably not correct');
@@ -103,6 +105,9 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
}
] as any[];
for (const customQuery of options.customQueries) {
aggregation.splice(customQuery.index, 0, customQuery.query);
}
if (options.customAfterMatch) aggregation.splice(1, 0, options.customAfterMatch);