feat: Add an analytics module and local and posthog providers (#12505)

* feat: Add an analytics module and local and posthog providers

* fix: Add tests and wire up in missing places

* fix: Address feedback and add missing module typing

* fix: Address feedback and add missing module typing

---------

Co-authored-by: Adrien de Peretti <adrien.deperetti@gmail.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Stevche Radevski
2025-05-19 19:57:13 +02:00
committed by GitHub
co-authored by Adrien de Peretti Oli Juhl
parent 52bd9f9a53
commit b9a51e217d
49 changed files with 1009 additions and 0 deletions
@@ -1,6 +1,7 @@
import { Link } from "@medusajs/modules-sdk"
import {
ConfigModule,
IAnalyticsModuleService,
IApiKeyModuleService,
IAuthModuleService,
ICacheService,
@@ -48,6 +49,7 @@ declare module "@medusajs/types" {
[ContainerRegistrationKeys.REMOTE_QUERY]: RemoteQueryFunction
[ContainerRegistrationKeys.QUERY]: Omit<RemoteQueryFunction, symbol>
[ContainerRegistrationKeys.LOGGER]: Logger
[Modules.ANALYTICS]: IAnalyticsModuleService
[Modules.AUTH]: IAuthModuleService
[Modules.CACHE]: ICacheService
[Modules.CART]: ICartModuleService
@@ -0,0 +1,4 @@
export * from "./mutations"
export * from "./service"
export * from "./provider"
export * from "./providers"
@@ -0,0 +1,39 @@
export interface TrackAnalyticsEventDTO {
/**
* The event name
*/
event: string
/**
* The actor of the event, if there is any
*/
actor_id?: string
/**
* The group that the event is for, such as an organization or team.
* The "type" defines the name of the group (eg. "organization"), and the "id" is the id of the group.
*/
group?: {
type?: string
id?: string
}
/**
* The properties of the event. The format and content depends on the provider.
*/
properties?: Record<string, any>
}
export interface IdentifyActorDTO {
actor_id: string
properties?: Record<string, any>
}
export interface IdentifyGroupDTO {
group: {
type: string
id: string
}
// When identifying a group, the actor can potentially be passed as well as metadata.
actor_id?: string
properties?: Record<string, any>
}
// Either actor_id or group must be provided. Depending on the provided identifier, the properties will be set for the actor or group.
export type IdentifyAnalyticsEventDTO = IdentifyActorDTO | IdentifyGroupDTO
@@ -0,0 +1,33 @@
import { IdentifyAnalyticsEventDTO, TrackAnalyticsEventDTO } from "./mutations"
export type ProviderTrackAnalyticsEventDTO = TrackAnalyticsEventDTO
export type ProviderIdentifyAnalyticsEventDTO = IdentifyAnalyticsEventDTO
export interface IAnalyticsProvider {
/**
* This method is used to track an event in the analytics provider
*
* @param {ProviderTrackAnalyticsEventDTO} data - The data for the event.
* @returns {Promise<void>} Resolves when the event is tracked successfully.
*
*/
track(data: ProviderTrackAnalyticsEventDTO): Promise<void>
/**
* This method is used to identify an actor or group in the analytics provider
*
* @param {ProviderIdentifyAnalyticsEventDTO} data - The data for the actor or group..
* @returns {Promise<void>} Resolves when the event is tracked successfully.
*
*/
identify(data: ProviderIdentifyAnalyticsEventDTO): Promise<void>
/**
* This method is used to shutdown the analytics provider, and flush all data before shutting down.
*
* @returns {Promise<void>} Resolves when the provider is shutdown successfully.
*
*/
shutdown?(): Promise<void>
}
@@ -0,0 +1,2 @@
export * from "./posthog"
export * from "./local"
@@ -0,0 +1 @@
export interface LocalAnalyticsServiceOptions {}
@@ -0,0 +1,10 @@
export interface PosthogAnalyticsServiceOptions {
/**
* The key for the posthog events
*/
posthogEventsKey: string
/**
* The endpoint for the posthog server
*/
posthogHost: string
}
@@ -0,0 +1,45 @@
import { IModuleService } from "../modules-sdk"
import { IdentifyAnalyticsEventDTO, TrackAnalyticsEventDTO } from "./mutations"
import { IAnalyticsProvider } from "./provider"
export interface IAnalyticsModuleService extends IModuleService {
/**
* Returns a reference to the analytics provider in use
*/
getProvider(): IAnalyticsProvider
/**
* This method tracks an event in the analytics provider
*
* @param {TrackAnalyticsEventDTO} data - The data for the event.
* @returns {Promise<void>} Resolves when the event is tracked successfully.
*
*
* @example
* await analyticsModuleService.track({
* event: "product_viewed",
* properties: {
* product_id: "123",
* product_name: "Product Name"
* }
* })
*/
track(data: TrackAnalyticsEventDTO): Promise<void>
/**
* This method identifies an actor or group in the analytics provider
*
* @param {IdentifyAnalyticsEventDTO} data - The data for the actor or group.
* @returns {Promise<void>} Resolves when the actor or group is identified successfully.
*
*
* @example
* await analyticsModuleService.identify({
* actor_id: "123",
* properties: {
* name: "John Doe"
* }
* })
*/
identify(data: IdentifyAnalyticsEventDTO): Promise<void>
}
+1
View File
@@ -1,4 +1,5 @@
export * as AdminTypes from "./admin"
export * as AnalyticsTypes from "./analytics"
export * as ApiKeyTypes from "./api-key"
export * as AuthTypes from "./auth"
export * as CacheTypes from "./cache"
+1
View File
@@ -1,5 +1,6 @@
export * from "./address"
export * from "./admin"
export * from "./analytics"
export * from "./api-key"
export * from "./auth"
export * from "./bundles"
@@ -0,0 +1,121 @@
import {
IAnalyticsProvider,
ProviderIdentifyAnalyticsEventDTO,
ProviderTrackAnalyticsEventDTO,
} from "@medusajs/types"
/**
* ### constructor
*
* The constructor allows you to access resources from the module's container using the first parameter,
* and the module's options using the second parameter.
*
* If you're creating a client or establishing a connection with a third-party service, do it in the constructor.
*
* #### Example
*
* ```ts
* import { Logger } from "@medusajs/framework/types"
* import { AbstractAnalyticsProviderService } from "@medusajs/framework/utils"
*
* type InjectedDependencies = {
* logger: Logger
* }
*
* type Options = {
* apiKey: string
* }
*
* class MyAnalyticsProviderService extends AbstractAnalyticsProviderService {
* protected logger_: Logger
* protected options_: Options
* static identifier = "my-analytics"
* // assuming you're initializing a client
* protected client
*
* constructor (
* { logger }: InjectedDependencies,
* options: Options
* ) {
* super()
*
* this.logger_ = logger
* this.options_ = options
*
* // assuming you're initializing a client
* this.client = new Client(options)
* }
* }
*
* export default MyAnalyticsProviderService
* ```
*/
export class AbstractAnalyticsProviderService implements IAnalyticsProvider {
/**
* Each analytics provider has a unique ID used to identify it. The provider's ID
* will be stored as `aly_{identifier}_{id}`, where `{id}` is the provider's `id`
* property in the `medusa-config.ts`.
*
* @example
* class MyAnalyticsProviderService extends AbstractAnalyticsProviderService {
* static identifier = "my-analytics"
* // ...
* }
*/
static identifier: string
/**
* @ignore
*/
getIdentifier() {
return (this.constructor as any).identifier
}
/**
* This method tracks an event using your provider's semantics
*
* This method will be used when tracking events to third-party providers.
*
* @param {ProviderTrackAnalyticsEventDTO} data - The data for the event.
* @returns {Promise<void>} Resolves when the event is tracked successfully.
*
* @example
* class MyAnalyticsProviderService extends AbstractAnalyticsProviderService {
* // ...
* async track(
* data: ProviderTrackAnalyticsEventDTO
* ): Promise<void> {
* // track event to third-party provider
* // or using custom logic
* // for example:
* this.client.track(data)
* }
* }
*/
async track(data: ProviderTrackAnalyticsEventDTO): Promise<void> {
throw Error("track must be overridden by the child class")
}
/**
* This method identifies an actor or group in the analytics provider
*
* @param {ProviderIdentifyAnalyticsEventDTO} data - The data for the actor or group.
* @returns {Promise<void>} Resolves when the actor or group is identified successfully.
*
* @example
* class MyAnalyticsProviderService extends AbstractAnalyticsProviderService {
* // ...
* async identify(
* data: ProviderIdentifyAnalyticsEventDTO
* ): Promise<void> {
* // identify actor or group in the analytics provider
* // or using custom logic
* // for example:
* this.client.identify(data)
* }
* }
*/
async identify(data: ProviderIdentifyAnalyticsEventDTO): Promise<void> {
throw Error("identify must be overridden by the child class")
}
}
@@ -0,0 +1 @@
export * from "./abstract-analytics-provider"
+1
View File
@@ -1,4 +1,5 @@
export * from "./api-key"
export * from "./analytics"
export * from "./auth"
export * from "./bundles"
export * from "./common"
@@ -1,4 +1,5 @@
export const Modules = {
ANALYTICS: "analytics",
AUTH: "auth",
CACHE: "cache",
CART: "cart",
@@ -28,6 +29,7 @@ export const Modules = {
} as const
export const MODULE_PACKAGE_NAMES = {
[Modules.ANALYTICS]: "@medusajs/medusa/analytics",
[Modules.AUTH]: "@medusajs/medusa/auth",
[Modules.CACHE]: "@medusajs/medusa/cache-inmemory",
[Modules.CART]: "@medusajs/medusa/cart",