feat(dashboard,medusa): Promotion Campaign fixes (#7337)

* chore(medusa): strict zod versions in workspace

* feat(dashboard): add campaign create to promotion UI

* wip

* fix(medusa): Missing middlewares export (#7289)

* fix(docblock-generator): fix how type names created from Zod objects are inferred (#7292)

* feat(api-ref): show schema of a tag (#7297)

* feat: Add support for sendgrid and logger notification providers (#7290)

* feat: Add support for sendgrid and logger notification providers

* fix: changes based on PR review

* chore: add action to automatically label docs (#7284)

* chore: add action to automatically label docs

* removes the paths param

* docs: preparations for preview (#7267)

* configured base paths + added development banner

* fix typelist site url

* added navbar and sidebar badges

* configure algolia filters

* remove AI assistant

* remove unused imports

* change navbar text and badge

* lint fixes

* fix build error

* add to api reference rewrites

* fix build error

* fix build errors in user-guide

* fix feedback component

* add parent title to pagination

* added breadcrumbs component

* remove user-guide links

* resolve todos

* fix details about authentication

* change documentation title

* lint content

* chore: fix bug with form reset

* chore: address reviews

* chore: fix specs

* chore: loads of FE fixes + BE adds

* chore: add more polishes + reorg files

* chore: fixes to promotions modal

* chore: cleanup

* chore: cleanup

* chore: fix build

* chore: fkix cart spec

* chore: fix module tests

* chore: fix moar tests

* wip

* chore: templates + fixes + migrate currency

* chore: fix build, add validation for max_quantity

* chore: allow removing campaigns

* chore: fix specs

* chore: scope campaigns based on currency

* remove console logs

* chore: add translations + update keys

* chore: move over filesfrom v2 to routes

* chore(dashboard): Delete old translation files (#7423)

* feat(dashboard,admin-sdk,admin-shared,admin-vite-plugin): Add support for UI extensions (#7383)

* intial work

* update lock

* add routes and fix HMR of configs

* cleanup

* rm imports

* rm debug from plugin

* address feedback

* address feedback

* temp skip specs

---------

Co-authored-by: Adrien de Peretti <adrien.deperetti@gmail.com>
Co-authored-by: Shahed Nasser <shahednasser@gmail.com>
Co-authored-by: Stevche Radevski <sradevski@live.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
Co-authored-by: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com>
This commit is contained in:
72 files changed
+5380 -3473

No files matched your search

@@ -5,13 +5,13 @@ export const defaultCampaignsData = [
id: "campaign-id-1",
name: "campaign 1",
description: "test description",
currency: "USD",
campaign_identifier: "test-1",
starts_at: new Date("01/01/2023"),
ends_at: new Date("01/01/2024"),
budget: {
type: CampaignBudgetType.SPEND,
limit: 1000,
currency_code: "USD",
used: 0,
},
},
@@ -19,13 +19,13 @@ export const defaultCampaignsData = [
id: "campaign-id-2",
name: "campaign 1",
description: "test description",
currency: "USD",
campaign_identifier: "test-2",
starts_at: new Date("01/01/2023"),
ends_at: new Date("01/01/2024"),
budget: {
type: CampaignBudgetType.USAGE,
limit: 1000,
currency_code: "USD",
used: 0,
},
},
@@ -1,14 +1,29 @@
import { CreatePromotionDTO } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils"
export const defaultPromotionsData = [
export const defaultPromotionsData: CreatePromotionDTO[] = [
{
id: "promotion-id-1",
code: "PROMOTION_1",
type: PromotionType.STANDARD,
application_method: {
currency_code: "USD",
target_type: "items",
type: "fixed",
allocation: "across",
value: 1000,
},
},
{
id: "promotion-id-2",
code: "PROMOTION_2",
type: PromotionType.STANDARD,
application_method: {
currency_code: "USD",
target_type: "items",
type: "fixed",
allocation: "across",
value: 1000,
},
},
]
@@ -1,4 +1,9 @@
import { CreatePromotionDTO } from "@medusajs/types"
import {
CreatePromotionDTO,
IPromotionModuleService,
PromotionDTO,
} from "@medusajs/types"
import { isPresent } from "@medusajs/utils"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { Promotion } from "@models"
import { defaultPromotionsData } from "./data"
@@ -21,3 +26,47 @@ export async function createPromotions(
return promotions
}
export async function createDefaultPromotions(
service: IPromotionModuleService,
promotionsData: Partial<CreatePromotionDTO>[] = defaultPromotionsData
): Promise<Promotion[]> {
const promotions: Promotion[] = []
for (let promotionData of promotionsData) {
let promotion = await createDefaultPromotion(service, promotionData)
promotions.push(promotion)
}
return promotions
}
export async function createDefaultPromotion(
service: IPromotionModuleService,
data: Partial<CreatePromotionDTO>
): Promise<PromotionDTO> {
const { application_method = {}, campaign = {}, ...promotion } = data
return await service.create({
code: "PROMOTION_TEST",
type: "standard",
campaign_id: "campaign-id-1",
...promotion,
application_method: {
currency_code: "USD",
target_type: "items",
type: "fixed",
allocation: "across",
value: 1000,
...application_method,
},
campaign: isPresent(campaign)
? {
campaign_identifier: "campaign-identifier",
name: "new campaign",
...campaign,
}
: undefined,
})
}
@@ -1,6 +1,7 @@
import { Modules } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { CampaignBudgetType } from "../../../../../../core/utils/src/promotion/index"
import { createCampaigns } from "../../../__fixtures__/campaigns"
import { createPromotions } from "../../../__fixtures__/promotion"
@@ -27,7 +28,7 @@ moduleIntegrationTestRunner({
id: "campaign-id-1",
name: "campaign 1",
description: "test description",
currency: "USD",
campaign_identifier: "test-1",
starts_at: expect.any(Date),
ends_at: expect.any(Date),
@@ -40,7 +41,7 @@ moduleIntegrationTestRunner({
id: "campaign-id-2",
name: "campaign 1",
description: "test description",
currency: "USD",
campaign_identifier: "test-2",
starts_at: expect.any(Date),
ends_at: expect.any(Date),
@@ -92,6 +93,26 @@ moduleIntegrationTestRunner({
)
})
it("should throw an error when required budget params are not met", async () => {
const error = await service
.createCampaigns([
{
name: "test",
campaign_identifier: "test",
budget: {
limit: 1000,
type: "spend",
used: 10,
},
},
])
.catch((e) => e)
expect(error.message).toContain(
"Campaign Budget type is a required field"
)
})
it("should create a basic campaign successfully", async () => {
const startsAt = new Date("01/01/2024")
const endsAt = new Date("01/01/2025")
@@ -174,7 +195,6 @@ moduleIntegrationTestRunner({
{
id: "campaign-id-1",
description: "test description 1",
currency: "EUR",
campaign_identifier: "new",
starts_at: new Date("01/01/2024"),
ends_at: new Date("01/01/2025"),
@@ -184,7 +204,6 @@ moduleIntegrationTestRunner({
expect(updatedCampaign).toEqual(
expect.objectContaining({
description: "test description 1",
currency: "EUR",
campaign_identifier: "new",
starts_at: new Date("01/01/2024"),
ends_at: new Date("01/01/2025"),
@@ -214,6 +233,37 @@ moduleIntegrationTestRunner({
})
)
})
it("should create a campaign budget if not present successfully", async () => {
await createCampaigns(MikroOrmWrapper.forkManager(), [
{
id: "campaign-id-new",
name: "campaign 1",
description: "test description",
campaign_identifier: "test-1",
} as any,
])
const [updatedCampaign] = await service.updateCampaigns([
{
id: "campaign-id-new",
budget: {
type: CampaignBudgetType.SPEND,
limit: 100,
used: 100,
},
},
])
expect(updatedCampaign).toEqual(
expect.objectContaining({
budget: expect.objectContaining({
limit: 100,
used: 100,
}),
})
)
})
})
describe("retrieveCampaign", () => {
@@ -2,6 +2,7 @@ import { Modules } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { createCampaigns } from "../../../__fixtures__/campaigns"
import { createDefaultPromotion } from "../../../__fixtures__/promotion"
jest.setTimeout(30000)
@@ -18,11 +19,7 @@ moduleIntegrationTestRunner({
describe("registerUsage", () => {
it("should register usage for type spend", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_SPEND",
type: "standard",
campaign_id: "campaign-id-1",
})
const createdPromotion = await createDefaultPromotion(service, {})
await service.registerUsage([
{
@@ -53,9 +50,7 @@ moduleIntegrationTestRunner({
})
it("should register usage for type usage", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_USAGE",
type: "standard",
const createdPromotion = await createDefaultPromotion(service, {
campaign_id: "campaign-id-2",
})
@@ -103,9 +98,7 @@ moduleIntegrationTestRunner({
})
it("should not register usage when limit is exceed for type usage", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_USAGE",
type: "standard",
const createdPromotion = await createDefaultPromotion(service, {
campaign_id: "campaign-id-2",
})
@@ -144,11 +137,7 @@ moduleIntegrationTestRunner({
})
it("should not register usage above limit when exceeded for type spend", async () => {
const createdPromotion = await service.create({
code: "TEST_PROMO_SPEND",
type: "standard",
campaign_id: "campaign-id-1",
})
const createdPromotion = await createDefaultPromotion(service, {})
await service.updateCampaigns({
id: "campaign-id-1",
@@ -1,58 +0,0 @@
import { PromotionType } from "@medusajs/utils"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { createPromotions } from "../../../__fixtures__/promotion"
import { IPromotionModuleService } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PROMOTION,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IPromotionModuleService>) => {
describe("Promotion Service", () => {
beforeEach(async () => {
await createPromotions(MikroOrmWrapper.forkManager())
})
describe("create", () => {
it("should throw an error when required params are not passed", async () => {
const error = await service
.create([
{
type: PromotionType.STANDARD,
} as any,
])
.catch((e) => e)
expect(error.message).toContain(
"Value for Promotion.code is required, 'undefined' found"
)
})
it("should create a promotion successfully", async () => {
await service.create([
{
code: "PROMOTION_TEST",
type: PromotionType.STANDARD,
},
])
const [promotion] = await service.list({
code: ["PROMOTION_TEST"],
})
expect(promotion).toEqual(
expect.objectContaining({
code: "PROMOTION_TEST",
is_automatic: false,
type: "standard",
})
)
})
})
})
},
})