-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #518 from Pinelab-studio/feat/campaign-tracker
Campaign Tracker Plugin
- Loading branch information
Showing
31 changed files
with
2,045 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# 0.0.1 (2024-10-21) | ||
|
||
- Initial setup of this plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# Vendure Campaign Tracker plugin | ||
|
||
### [Official documentation here](https://pinelab-plugins.com/plugin/vendure-plugin-campaign-tracker) | ||
|
||
Vendure plugin to track revenue per campaign, so that you can compare different campaigns from different sources. | ||
To track campaigns, your storefront should send the unique campaign code to Vendure on a page visit: | ||
|
||
- Pass a campaign code in the url, e.g. `my-website.com?ref=summer-sale-ad`. This URL is then included in your ads or email campaigns. | ||
- Or, set a fixed campaign code for a landing page. For example, all visits to page `/sale-landing` will get the campaign code `sale-landing` | ||
|
||
## Getting started | ||
|
||
Add the plugin to your `vendure-config.ts` | ||
|
||
```ts | ||
import { CampaignTrackerPlugin, LastInteractionAttribution } from '@pinelab/vendure-plugin-campaign-tracker'; | ||
|
||
... | ||
plugins: [ | ||
CampaignTrackerPlugin.init({ | ||
// Pick an attribution model. Choose from `LastInteractionAttribution`, `FirstInteractionAttribution`, `LinearAttribution` | ||
// Or, implement your own by implementing the AttributionModel interface | ||
attributionModel: new LastInteractionAttribution() | ||
}), | ||
AdminUiPlugin.init({ | ||
port: 3002, | ||
route: 'admin', | ||
app: compileUiExtensions({ | ||
outputPath: path.join(__dirname, '__admin-ui'), | ||
extensions: [ | ||
CampaignTrackerPlugin.ui, | ||
... // your other plugin UI extensions | ||
], | ||
}), | ||
}), | ||
... // your other plugins | ||
] | ||
``` | ||
|
||
1. Run a database migration. | ||
2. Rebuild the admin UI | ||
3. Start Vendure, and navigate to 'Campaign' (below Promotions) | ||
4. Create a campaign, e.g. `my-first-campaign`. | ||
5. Make sure that every page on your storefront includes the following code: | ||
|
||
```ts | ||
const url = new URL(window.location.href); | ||
const params = new URLSearchParams(url.search); | ||
const ref = params.get('ref'); | ||
|
||
const activeOrder = await yourGraphqlClient.query( | ||
gql` | ||
mutation addCampaignToOrder($campaignCode: String!) { | ||
addCampaignToOrder(campaignCode: $campaignCode) { | ||
id | ||
code | ||
total | ||
} | ||
} | ||
`, | ||
{ campaignCode: ref } | ||
); | ||
``` | ||
|
||
This will add any visits to your website with `?ref=my-first-campaign` campaign to the order. This mutation will create a new active order if none exists yet. | ||
|
||
If `my-first-campaign` doesn't exists as campaign in Vendure, the call is ignored and no active order is returned (or created). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
schema: 'src/api/api-extensions.ts' | ||
documents: 'src/ui/queries.ts' | ||
generates: | ||
./src/ui/generated/graphql.ts: | ||
plugins: | ||
- typescript | ||
- typescript-operations | ||
config: | ||
avoidOptionals: false | ||
scalars: | ||
DateTime: Date | ||
ID: number | string |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module.exports = (async () => { | ||
const { default: parentConfig } = await import('../../eslint-base.config.js'); | ||
return [ | ||
...parentConfig, | ||
{ | ||
languageOptions: { | ||
parserOptions: { | ||
project: './tsconfig.json', | ||
}, | ||
}, | ||
}, | ||
]; | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "@pinelab/vendure-plugin-campaign-tracker", | ||
"version": "0.0.1", | ||
"description": "Compare different campaign and ads sources with server side revenue tracking.", | ||
"author": "Martijn van de Brug <[email protected]>", | ||
"homepage": "https://pinelab-plugins.com/", | ||
"repository": "https://github.com/Pinelab-studio/pinelab-vendure-plugins", | ||
"license": "MIT", | ||
"private": false, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"README.md", | ||
"CHANGELOG.md" | ||
], | ||
"scripts": { | ||
"build": "rimraf dist && yarn generate && tsc && copyfiles -u 1 'src/ui/**/*' dist/", | ||
"start": "yarn ts-node test/dev-server.ts", | ||
"generate": "graphql-codegen", | ||
"test": "vitest run", | ||
"lint": "eslint ." | ||
}, | ||
"dependencies": { | ||
"catch-unknown": "^2.0.0" | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
packages/vendure-plugin-campaign-tracker/src/api/api-extensions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import gql from 'graphql-tag'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Only used by graphql codegen | ||
const _scalars = gql` | ||
scalar DateTime | ||
scalar Money | ||
scalar PaginatedList | ||
scalar StringOperators | ||
type Order { | ||
id: ID | ||
code: String | ||
total: Money | ||
} | ||
enum SortOrder { | ||
ASC | ||
DESC | ||
} | ||
`; | ||
|
||
const commonApiExtensions = gql` | ||
type Campaign { | ||
id: ID! | ||
createdAt: DateTime! | ||
updatedAt: DateTime! | ||
code: String! | ||
name: String! | ||
metricsUpdatedAt: DateTime | ||
revenueLast7days: Money | ||
revenueLast30days: Money | ||
revenueLast365days: Money | ||
} | ||
`; | ||
|
||
export const shopApiExtensions = gql` | ||
${commonApiExtensions} | ||
extend type Mutation { | ||
""" | ||
Add a campaign code to the current order. | ||
Creates a new active order if none exists. | ||
""" | ||
addCampaignToOrder(campaignCode: String!): Order | ||
} | ||
`; | ||
|
||
export const adminApiExtensions = gql` | ||
${commonApiExtensions} | ||
type CampaignList { | ||
items: [Campaign!]! | ||
totalItems: Int! | ||
} | ||
input CampaignInput { | ||
code: String! | ||
name: String! | ||
} | ||
input CampaignSortParameter { | ||
createdAt: SortOrder | ||
updatedAt: SortOrder | ||
code: SortOrder | ||
name: SortOrder | ||
revenueLast7days: SortOrder | ||
revenueLast30days: SortOrder | ||
revenueLast365days: SortOrder | ||
} | ||
input CampaignFilterParameter { | ||
code: StringOperators | ||
name: StringOperators | ||
} | ||
input CampaignListOptions { | ||
skip: Int | ||
take: Int | ||
sort: CampaignSortParameter | ||
filter: CampaignFilterParameter | ||
} | ||
extend type Mutation { | ||
createCampaign(input: CampaignInput!): Campaign! | ||
updateCampaign(id: ID!, input: CampaignInput!): Campaign! | ||
deleteCampaign(id: ID!): Boolean! | ||
} | ||
extend type Query { | ||
campaigns(options: CampaignListOptions): CampaignList! | ||
} | ||
`; |
61 changes: 61 additions & 0 deletions
61
packages/vendure-plugin-campaign-tracker/src/api/campaign-tracker-admin.resolver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; | ||
import { Permission } from '@vendure/common/lib/generated-types'; | ||
import { ID } from '@vendure/common/lib/shared-types'; | ||
import { Allow, Ctx, RequestContext, Transaction } from '@vendure/core'; | ||
import { CampaignTrackerService } from '../services/campaign-tracker.service'; | ||
import { | ||
Campaign, | ||
CampaignList, | ||
MutationCreateCampaignArgs, | ||
MutationDeleteCampaignArgs, | ||
MutationUpdateCampaignArgs, | ||
QueryCampaignsArgs, | ||
} from '../ui/generated/graphql'; | ||
|
||
@Resolver() | ||
export class CampaignTrackerAdminResolver { | ||
constructor(private campaignTrackerService: CampaignTrackerService) {} | ||
|
||
@Query() | ||
@Allow(Permission.SuperAdmin) | ||
async campaigns( | ||
@Ctx() ctx: RequestContext, | ||
@Args() { options }: QueryCampaignsArgs | ||
): Promise<CampaignList> { | ||
return await this.campaignTrackerService.getCampaigns( | ||
ctx, | ||
options ?? undefined | ||
); | ||
} | ||
|
||
@Mutation() | ||
@Transaction() | ||
@Allow(Permission.SuperAdmin) | ||
async createCampaign( | ||
@Ctx() ctx: RequestContext, | ||
@Args() { input }: MutationCreateCampaignArgs | ||
): Promise<Campaign> { | ||
return await this.campaignTrackerService.createCampaign(ctx, input); | ||
} | ||
|
||
@Mutation() | ||
@Transaction() | ||
@Allow(Permission.SuperAdmin) | ||
async updateCampaign( | ||
@Ctx() ctx: RequestContext, | ||
@Args() { id, input }: MutationUpdateCampaignArgs | ||
): Promise<Campaign> { | ||
return await this.campaignTrackerService.updateCampaign(ctx, id, input); | ||
} | ||
|
||
@Mutation() | ||
@Transaction() | ||
@Allow(Permission.SuperAdmin) | ||
async deleteCampaign( | ||
@Ctx() ctx: RequestContext, | ||
@Args() { id }: MutationDeleteCampaignArgs | ||
): Promise<boolean> { | ||
await this.campaignTrackerService.deleteCampaign(ctx, id); | ||
return true; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
packages/vendure-plugin-campaign-tracker/src/api/campaign-tracker-shop.resolver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Args, Mutation, Resolver } from '@nestjs/graphql'; | ||
import { | ||
Allow, | ||
Ctx, | ||
Order, | ||
Permission, | ||
RequestContext, | ||
Transaction, | ||
} from '@vendure/core'; | ||
import { CampaignTrackerService } from '../services/campaign-tracker.service'; | ||
import { MutationAddCampaignToOrderArgs } from '../ui/generated/graphql'; | ||
|
||
@Resolver() | ||
export class CampaignTrackerShopResolver { | ||
constructor(private campaignTrackerService: CampaignTrackerService) {} | ||
|
||
@Mutation() | ||
@Transaction() | ||
@Allow(Permission.UpdateOrder, Permission.Owner) | ||
async addCampaignToOrder( | ||
@Ctx() ctx: RequestContext, | ||
@Args() { campaignCode }: MutationAddCampaignToOrderArgs | ||
): Promise<Order | undefined> { | ||
return await this.campaignTrackerService.addCampaignToOrder( | ||
ctx, | ||
campaignCode | ||
); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
packages/vendure-plugin-campaign-tracker/src/campaign-tracker.plugin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core'; | ||
import { AdminUiExtension } from '@vendure/ui-devkit/compiler'; | ||
|
||
import { adminApiExtensions, shopApiExtensions } from './api/api-extensions'; | ||
import { CampaignTrackerAdminResolver } from './api/campaign-tracker-admin.resolver'; | ||
import { CampaignTrackerShopResolver } from './api/campaign-tracker-shop.resolver'; | ||
import { CAMPAIGN_TRACKER_PLUGIN_OPTIONS } from './constants'; | ||
import { Campaign } from './entities/campaign.entity'; | ||
import { OrderCampaign } from './entities/order-campaign.entity'; | ||
import { LastInteractionAttribution } from './services/attribution-models'; | ||
import { CampaignTrackerService } from './services/campaign-tracker.service'; | ||
import { CampaignTrackerOptions } from './types'; | ||
import path from 'path'; | ||
|
||
@VendurePlugin({ | ||
imports: [PluginCommonModule], | ||
providers: [ | ||
{ | ||
provide: CAMPAIGN_TRACKER_PLUGIN_OPTIONS, | ||
useFactory: () => CampaignTrackerPlugin.options, | ||
}, | ||
CampaignTrackerService, | ||
], | ||
configuration: (config) => { | ||
return config; | ||
}, | ||
compatibility: '>=3.0.0', | ||
adminApiExtensions: { | ||
schema: adminApiExtensions, | ||
resolvers: [CampaignTrackerAdminResolver], | ||
}, | ||
shopApiExtensions: { | ||
schema: shopApiExtensions, | ||
resolvers: [CampaignTrackerShopResolver], | ||
}, | ||
entities: [Campaign, OrderCampaign], | ||
}) | ||
export class CampaignTrackerPlugin { | ||
static options: CampaignTrackerOptions = { | ||
attributionModel: new LastInteractionAttribution(), | ||
}; | ||
|
||
static init( | ||
options: Partial<CampaignTrackerOptions> | ||
): Type<CampaignTrackerPlugin> { | ||
this.options = { | ||
...this.options, | ||
...options, | ||
}; | ||
return CampaignTrackerPlugin; | ||
} | ||
|
||
static ui: AdminUiExtension = { | ||
id: 'campaign-tracker', | ||
extensionPath: path.join(__dirname, 'ui'), | ||
routes: [{ route: 'campaigns', filePath: 'routes.ts' }], | ||
providers: ['providers.ts'], | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const CAMPAIGN_TRACKER_PLUGIN_OPTIONS = Symbol( | ||
'CAMPAIGN_TRACKER_PLUGIN_OPTIONS' | ||
); | ||
export const loggerCtx = 'CampaignTrackerPlugin'; |
Oops, something went wrong.