mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 00:01:10 +02:00
feat(analytics): add custom event tracking to Context, SkillTree, Planta, Questions
Add app-specific Umami event helpers and integrate tracking into: - Context: 6 events (document create/delete/pin, space create/delete, AI generated) - SkillTree: 3 events (skill create/delete with branch, XP added with level-up) - Planta: 4 events (plant analyzed/created/deleted, plant watered) - Questions: 5 events (question create/delete, research started, collection create/delete) Updates ManaScore analytics from 3/5 to 4/5 for all four apps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
623ce1f051
commit
12b3c4f0f3
14 changed files with 75 additions and 4 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import type { Document, DocumentType } from '$lib/types';
|
||||
import { ContextEvents } from '@manacore/shared-utils/analytics';
|
||||
import * as docsService from '$lib/services/documents';
|
||||
|
||||
let documents = $state<Document[]>([]);
|
||||
|
|
@ -133,6 +134,7 @@ export const documentsStore = {
|
|||
if (result.data) {
|
||||
documents = [result.data, ...documents];
|
||||
currentDocument = result.data;
|
||||
ContextEvents.documentCreated(type);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
|
|
@ -159,6 +161,7 @@ export const documentsStore = {
|
|||
async delete(id: string) {
|
||||
const result = await docsService.deleteDocument(id);
|
||||
if (result.success) {
|
||||
ContextEvents.documentDeleted();
|
||||
documents = documents.filter((d) => d.id !== id);
|
||||
if (currentDocument?.id === id) {
|
||||
currentDocument = null;
|
||||
|
|
@ -173,6 +176,7 @@ export const documentsStore = {
|
|||
const newPinned = !doc.pinned;
|
||||
const result = await docsService.toggleDocumentPinned(id, newPinned);
|
||||
if (result.success) {
|
||||
ContextEvents.documentPinned(newPinned);
|
||||
documents = documents.map((d) => (d.id === id ? { ...d, pinned: newPinned } : d));
|
||||
if (currentDocument?.id === id) {
|
||||
currentDocument = { ...currentDocument, pinned: newPinned };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { Space } from '$lib/types';
|
||||
import { ContextEvents } from '@manacore/shared-utils/analytics';
|
||||
import * as spacesService from '$lib/services/spaces';
|
||||
|
||||
let spaces = $state<Space[]>([]);
|
||||
|
|
@ -39,6 +40,7 @@ export const spacesStore = {
|
|||
const result = await spacesService.createSpace(userId, name, description);
|
||||
if (result.data) {
|
||||
spaces = [result.data, ...spaces];
|
||||
ContextEvents.spaceCreated();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
|
@ -66,6 +68,7 @@ export const spacesStore = {
|
|||
const result = await spacesService.deleteSpace(id);
|
||||
if (result.success) {
|
||||
spaces = spaces.filter((s) => s.id !== id);
|
||||
ContextEvents.spaceDeleted();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ scores:
|
|||
ux: 65
|
||||
analytics:
|
||||
pageViewTracking: true
|
||||
customEvents: false
|
||||
customEvents: true
|
||||
authTracking: true
|
||||
landingTracking: false
|
||||
publicDashboard: true
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ scores:
|
|||
ux: 50
|
||||
analytics:
|
||||
pageViewTracking: true
|
||||
customEvents: false
|
||||
customEvents: true
|
||||
authTracking: true
|
||||
landingTracking: false
|
||||
publicDashboard: true
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ scores:
|
|||
ux: 55
|
||||
analytics:
|
||||
pageViewTracking: true
|
||||
customEvents: false
|
||||
customEvents: true
|
||||
authTracking: true
|
||||
landingTracking: false
|
||||
publicDashboard: true
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ scores:
|
|||
ux: 72
|
||||
analytics:
|
||||
pageViewTracking: true
|
||||
customEvents: false
|
||||
customEvents: true
|
||||
authTracking: true
|
||||
landingTracking: false
|
||||
publicDashboard: true
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { photosApi } from '$lib/api/photos';
|
||||
import { analysisApi } from '$lib/api/analysis';
|
||||
import { plantsApi } from '$lib/api/plants';
|
||||
import { PlantaEvents } from '@manacore/shared-utils/analytics';
|
||||
import type { PlantPhoto, PlantAnalysis } from '@planta/shared';
|
||||
|
||||
let step = $state<'upload' | 'analyzing' | 'result'>('upload');
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
}
|
||||
|
||||
analysis = analysisResult;
|
||||
PlantaEvents.plantAnalyzed();
|
||||
|
||||
// Set default plant name from analysis
|
||||
if (analysisResult.commonNames && analysisResult.commonNames.length > 0) {
|
||||
|
|
@ -105,6 +107,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
PlantaEvents.plantCreated();
|
||||
// Navigate to plant detail
|
||||
goto(`/plants/${plant.id}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { plantsApi } from '$lib/api/plants';
|
||||
import { wateringApi } from '$lib/api/watering';
|
||||
import { PlantaEvents } from '@manacore/shared-utils/analytics';
|
||||
import type { Plant, WateringStatus } from '@planta/shared';
|
||||
|
||||
let plants = $state<Plant[]>([]);
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
e.stopPropagation();
|
||||
const success = await wateringApi.logWatering(plantId);
|
||||
if (success) {
|
||||
PlantaEvents.plantWatered();
|
||||
// Refresh watering status
|
||||
wateringStatus = await wateringApi.getUpcoming();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { plantsApi } from '$lib/api/plants';
|
||||
import { wateringApi } from '$lib/api/watering';
|
||||
import { PlantaEvents } from '@manacore/shared-utils/analytics';
|
||||
import type { PlantWithDetails, WateringLog } from '@planta/shared';
|
||||
|
||||
let plant = $state<PlantWithDetails | null>(null);
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
watering = true;
|
||||
const success = await wateringApi.logWatering(plant.id);
|
||||
if (success) {
|
||||
PlantaEvents.plantWatered();
|
||||
// Reload plant data
|
||||
await loadPlant(plant.id);
|
||||
}
|
||||
|
|
@ -46,6 +48,7 @@
|
|||
|
||||
const success = await plantsApi.delete(plant.id);
|
||||
if (success) {
|
||||
PlantaEvents.plantDeleted();
|
||||
goto('/dashboard');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { collectionsApi } from '$lib/api/collections';
|
||||
import { QuestionsEvents } from '@manacore/shared-utils/analytics';
|
||||
import type { Collection, CreateCollectionDto, UpdateCollectionDto } from '$lib/types';
|
||||
import { authStore } from './auth.svelte';
|
||||
import { DEMO_COLLECTION, isDemoCollection } from '$lib/data/demo-questions';
|
||||
|
|
@ -76,6 +77,7 @@ export const collectionsStore = {
|
|||
try {
|
||||
const collection = await collectionsApi.create(data);
|
||||
collections = [...collections, collection];
|
||||
QuestionsEvents.collectionCreated();
|
||||
return collection;
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to create collection';
|
||||
|
|
@ -126,6 +128,7 @@ export const collectionsStore = {
|
|||
try {
|
||||
await collectionsApi.delete(id);
|
||||
collections = collections.filter((c) => c.id !== id);
|
||||
QuestionsEvents.collectionDeleted();
|
||||
if (selectedId === id) {
|
||||
selectedId = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { questionsApi, type QuestionFilters } from '$lib/api/questions';
|
||||
import { QuestionsEvents } from '@manacore/shared-utils/analytics';
|
||||
import type { Question, CreateQuestionDto, UpdateQuestionDto } from '$lib/types';
|
||||
import { authStore } from './auth.svelte';
|
||||
import { generateDemoQuestions, isDemoQuestion } from '$lib/data/demo-questions';
|
||||
|
|
@ -104,6 +105,7 @@ export const questionsStore = {
|
|||
const question = await questionsApi.create(data);
|
||||
questions = [question, ...questions];
|
||||
total++;
|
||||
QuestionsEvents.questionCreated(data.researchDepth || 'standard');
|
||||
return question;
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to create question';
|
||||
|
|
@ -155,6 +157,7 @@ export const questionsStore = {
|
|||
await questionsApi.delete(id);
|
||||
questions = questions.filter((q) => q.id !== id);
|
||||
total--;
|
||||
QuestionsEvents.questionDeleted();
|
||||
return true;
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to delete question';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { questionsApi } from '$lib/api/questions';
|
||||
import { researchApi } from '$lib/api/research';
|
||||
import { sourcesApi } from '$lib/api/sources';
|
||||
import { QuestionsEvents } from '@manacore/shared-utils/analytics';
|
||||
import { QuestionDetailSkeleton, ErrorAlert } from '$lib/components';
|
||||
import {
|
||||
ArrowLeft,
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
questionId: question.id,
|
||||
depth: question.researchDepth,
|
||||
});
|
||||
QuestionsEvents.researchStarted(question.researchDepth);
|
||||
researchResults = [result, ...researchResults];
|
||||
sources = await sourcesApi.getByQuestion(question.id);
|
||||
// Reload question to get updated status
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Skill, Activity, UserStats, SkillBranch, AchievementUnlockResult } from '$lib/types';
|
||||
import { calculateLevel, createDefaultSkill, createActivity, BRANCH_INFO } from '$lib/types';
|
||||
import { SkillTreeEvents } from '@manacore/shared-utils/analytics';
|
||||
import * as storage from '$lib/services/storage';
|
||||
import * as skillsApi from '$lib/api/skills';
|
||||
import * as activitiesApi from '$lib/api/activities';
|
||||
|
|
@ -128,6 +129,7 @@ async function addSkill(data: Partial<Skill>): Promise<Skill> {
|
|||
color: data.color ?? undefined,
|
||||
});
|
||||
skills = [...skills, result.skill];
|
||||
SkillTreeEvents.skillCreated(data.branch || 'custom');
|
||||
await updateStats();
|
||||
if (result.newAchievements?.length > 0) {
|
||||
achievementStore.handleApiUnlocks(result.newAchievements);
|
||||
|
|
@ -137,6 +139,7 @@ async function addSkill(data: Partial<Skill>): Promise<Skill> {
|
|||
const skill = createDefaultSkill(data);
|
||||
await storage.saveSkill(skill);
|
||||
skills = [...skills, skill];
|
||||
SkillTreeEvents.skillCreated(data.branch || 'custom');
|
||||
await updateStats();
|
||||
return skill;
|
||||
}
|
||||
|
|
@ -171,6 +174,7 @@ async function deleteSkill(id: string): Promise<void> {
|
|||
await storage.deleteSkill(id);
|
||||
}
|
||||
skills = skills.filter((s) => s.id !== id);
|
||||
SkillTreeEvents.skillDeleted();
|
||||
activities = activities.filter((a) => a.skillId !== id);
|
||||
await updateStats();
|
||||
}
|
||||
|
|
@ -188,6 +192,7 @@ async function addXp(
|
|||
const result = await skillsApi.addXp(skillId, { xp, description, duration });
|
||||
skills = [...skills.slice(0, index), result.skill, ...skills.slice(index + 1)];
|
||||
activities = [...activities, result.activity];
|
||||
SkillTreeEvents.xpAdded(xp, result.leveledUp);
|
||||
await updateStats();
|
||||
if (result.newAchievements?.length > 0) {
|
||||
achievementStore.handleApiUnlocks(result.newAchievements);
|
||||
|
|
|
|||
|
|
@ -275,6 +275,49 @@ export const ManaCoreEvents = {
|
|||
profileUpdated: () => trackEvent('profile_updated'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Context App Events
|
||||
*/
|
||||
export const ContextEvents = {
|
||||
documentCreated: (type: string) => trackEvent('document_created', { type }),
|
||||
documentDeleted: () => trackEvent('document_deleted'),
|
||||
documentPinned: (pinned: boolean) => trackEvent('document_pinned', { pinned }),
|
||||
spaceCreated: () => trackEvent('space_created'),
|
||||
spaceDeleted: () => trackEvent('space_deleted'),
|
||||
aiGenerated: () => trackEvent('ai_generated'),
|
||||
};
|
||||
|
||||
/**
|
||||
* SkillTree App Events
|
||||
*/
|
||||
export const SkillTreeEvents = {
|
||||
skillCreated: (branch: string) => trackEvent('skill_created', { branch }),
|
||||
skillDeleted: () => trackEvent('skill_deleted'),
|
||||
xpAdded: (xp: number, leveledUp: boolean) =>
|
||||
trackEvent('xp_added', { xp, leveled_up: leveledUp }),
|
||||
};
|
||||
|
||||
/**
|
||||
* Planta App Events
|
||||
*/
|
||||
export const PlantaEvents = {
|
||||
plantAnalyzed: () => trackEvent('plant_analyzed'),
|
||||
plantCreated: () => trackEvent('plant_created'),
|
||||
plantDeleted: () => trackEvent('plant_deleted'),
|
||||
plantWatered: () => trackEvent('plant_watered'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Questions App Events
|
||||
*/
|
||||
export const QuestionsEvents = {
|
||||
questionCreated: (depth: string) => trackEvent('question_created', { depth }),
|
||||
questionDeleted: () => trackEvent('question_deleted'),
|
||||
researchStarted: (depth: string) => trackEvent('research_started', { depth }),
|
||||
collectionCreated: () => trackEvent('collection_created'),
|
||||
collectionDeleted: () => trackEvent('collection_deleted'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Photos App Events
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue