Skip to content
This repository has been archived by the owner on Dec 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2188 from iFixit/piwik--add-user-privilege-tracking
Browse files Browse the repository at this point in the history
Piwik: Add Category Data To Some Ecommerce Events
  • Loading branch information
danielcliu-ifixit authored Feb 1, 2024
2 parents c82a875 + 29516ff commit d886dec
Show file tree
Hide file tree
Showing 17 changed files with 92 additions and 7 deletions.
1 change: 1 addition & 0 deletions frontend/helpers/product-preview-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function createCartLineItem(
imageSrc: productPreview.image?.url ?? '',
price: productPreview.price,
compareAtPrice: productPreview.compareAtPrice,
categories: productPreview.categories,
};
}

Expand Down
9 changes: 8 additions & 1 deletion frontend/models/components/product-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
} from '@config/env';
import { filterFalsyItems } from '@helpers/application-helpers';
import { printZodError } from '@helpers/zod-helpers';
import { getItemCodeFromSku, isLifetimeWarranty } from '@ifixit/helpers';
import {
getItemCodeFromSku,
isLifetimeWarranty,
getCategoriesFromTags,
} from '@ifixit/helpers';
import type { ProductPreviewFieldsFragment } from '@lib/shopify-storefront-sdk';
import algoliasearch from 'algoliasearch/lite';
import { z } from 'zod';
Expand Down Expand Up @@ -47,6 +51,7 @@ export const ProductPreviewSchema = z.object({
quantityAvailable: z.number().nullable(),
enabled: z.boolean().nullable().optional(),
shopifyVariantId: z.string().nullable(),
categories: z.array(z.string()).nullable(),
});

export function productPreviewFromAlgoliaHit(hit: any): ProductPreview | null {
Expand Down Expand Up @@ -76,6 +81,7 @@ export function productPreviewFromAlgoliaHit(hit: any): ProductPreview | null {
hasLifetimeWarranty: product.lifetime_warranty ?? false,
quantityAvailable: product.quantity_available ?? null,
shopifyVariantId: null,
categories: null,
};
}

Expand Down Expand Up @@ -115,6 +121,7 @@ export function productPreviewFromShopify(
quantityAvailable: fields.quantityAvailable ?? null,
enabled: fields.enabled?.value === 'true',
shopifyVariantId: fields.id,
categories: getCategoriesFromTags(fields.product.tags) || null,
};
}

Expand Down
4 changes: 3 additions & 1 deletion frontend/models/product/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { filterFalsyItems } from '@helpers/application-helpers';
import { parseItemcode } from '@ifixit/helpers';
import { parseItemcode, getCategoriesFromTags } from '@ifixit/helpers';
import type { FindProductQuery as ShopifyFindProductQuery } from '@lib/shopify-storefront-sdk';
import type { FindProductQuery as StrapiFindProductQuery } from '@lib/strapi-sdk';
import {
Expand Down Expand Up @@ -84,6 +84,7 @@ export const ProductSchema = z.object({
enabledDomains: z.array(ProductEnabledDomainSchema).nullable(),
vendor: z.string().nullable(),
crossSellVariants: z.array(ProductPreviewSchema),
categories: z.array(z.string()),
sections: z.array(ProductSectionSchema),
});

Expand Down Expand Up @@ -185,6 +186,7 @@ export async function getProduct({
),
vendor: shopifyProduct.vendor ?? null,
crossSellVariants: getAllCrossSellProductVariant(shopifyProduct),
categories: getCategoriesFromTags(shopifyProduct.tags),
sections,
};
}
Expand Down
1 change: 1 addition & 0 deletions frontend/templates/product/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default function ProductTemplate({ product }: ProductTemplateProps) {
item_variant: getVariantIdFromVariantURI(selectedVariant.id),
price: selectedVariant.price.amount,
quantity: selectedVariant.quantityAvailable,
categories: product.categories,
},
],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ function useOptimisticAddToCart(
imageSrc: selectedVariant.image?.url || product.images[0]?.url,
price: userPrice.price,
compareAtPrice: userPrice.compareAtPrice,
categories: product.categories,
},
});
onOpen(event, true);
Expand All @@ -169,6 +170,7 @@ function useOptimisticAddToCart(
product.images,
userPrice.price,
userPrice.compareAtPrice,
product.categories,
onOpen,
]);
return [addToCart.isLoading, optimisticAddToCart] as const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ export function ProductOverviewSection({
item_variant: getVariantIdFromVariantURI(newVariant.id),
price: newVariant.price.amount,
quantity: newVariant.quantityAvailable,
categories: product.categories,
},
],
});
}
},
[onVariantChange, product.variants]
[onVariantChange, product.variants, product.categories]
);
const isForSale = useIsProductForSale(product);
return (
Expand Down
1 change: 1 addition & 0 deletions frontend/tests/jest/__mocks__/products/battery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export const mockedBatteryProduct: Product = {
productcode: '390042',
vendor: '',
crossSellVariants: [],
categories: [],
sections: [
{
type: 'ProductOverview',
Expand Down
5 changes: 5 additions & 0 deletions frontend/tests/jest/__mocks__/products/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export const mockedProduct: Product = {
hasLifetimeWarranty: true,
enabled: true,
shopifyVariantId: '32965720473690',
categories: [],
},
{
id: 'gid://shopify/ProductVariant/32965720178778',
Expand Down Expand Up @@ -380,6 +381,7 @@ export const mockedProduct: Product = {
isPro: false,
enabled: true,
shopifyVariantId: '32965720178778',
categories: [],
},
{
id: 'gid://shopify/ProductVariant/39333786746970',
Expand Down Expand Up @@ -427,6 +429,7 @@ export const mockedProduct: Product = {
isPro: false,
enabled: true,
shopifyVariantId: '39333786746970',
categories: [],
},
{
id: 'gid://shopify/ProductVariant/39333786583130',
Expand Down Expand Up @@ -474,8 +477,10 @@ export const mockedProduct: Product = {
isPro: false,
enabled: true,
shopifyVariantId: '39333786583130',
categories: [],
},
],
categories: [],
sections: [
{
type: 'ProductOverview',
Expand Down
1 change: 1 addition & 0 deletions frontend/tests/jest/__mocks__/products/part.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export const mockedPartProduct: Product = {
productcode: '457000',
vendor: '',
crossSellVariants: [],
categories: [],
sections: [
{
type: 'ProductOverview',
Expand Down
2 changes: 2 additions & 0 deletions frontend/tests/jest/__mocks__/products/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ export const mockedToolProduct: Product = {
isPro: false,
enabled: true,
shopifyVariantId: '39333789794394',
categories: [],
},
],
categories: [],
sections: [
{
type: 'ProductOverview',
Expand Down
22 changes: 18 additions & 4 deletions packages/analytics/google/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ export function setupMinimumGA4(
}

export function trackGA4ViewItem(event: AnalyticsItemsEvent) {
gtag('event', 'view_item', event);
gtag('event', 'view_item', formatEvent(event));
}

export function trackGA4ViewCart(event: AnalyticsItemsEvent) {
gtag('event', 'view_cart', event);
gtag('event', 'view_cart', formatEvent(event));
}

export function trackGA4AddToCart(event: AnalyticsItemsEvent) {
gtag('event', 'add_to_cart', event);
gtag('event', 'add_to_cart', formatEvent(event));
}

export function trackGA4RemoveFromCart(event: AnalyticsItemsEvent) {
gtag('event', 'remove_from_cart', event);
gtag('event', 'remove_from_cart', formatEvent(event));
}

export function trackGA4ViewItemList(event: GTagViewItemsListEvent) {
Expand Down Expand Up @@ -89,3 +89,17 @@ export function useGACustomDimensions(): GACustomDimensions {
'no-language-found',
};
}

function formatEvent(event: AnalyticsItemsEvent) {
const { items, ...rest } = event;
const formatedItems = items.map((item) => {
const { categories, ...rest } = item;
return {
...rest,
};
});
return {
...rest,
items: formatedItems,
};
}
3 changes: 3 additions & 0 deletions packages/analytics/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export type AnalyticsItem = {
item_variant: string | null;
quantity: number | null | undefined;
price: number;
categories?: string[];
};

export type AnalyticsItemsEvent = {
items: AnalyticsItem[];
value: number;
Expand Down Expand Up @@ -76,6 +78,7 @@ export function convertCartLineItemsToAnalyticsItem(
item_variant: getVariantIdFromEncodedVariantURI(item.shopifyVariantId),
quantity: item.quantity,
price: Number(item.price.amount),
categories: item.categories ?? [],
};
});
}
1 change: 1 addition & 0 deletions packages/analytics/piwik/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,6 @@ function formatProduct(item: AnalyticsItem) {
price: item.price.toString(),
quantity: item.quantity,
variant: item.item_variant,
category: item.categories,
};
}
17 changes: 17 additions & 0 deletions packages/cart-sdk/hooks/use-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function createCart(input: APICart): Cart {
maxToAdd: product.maxToAdd,
price,
compareAtPrice,
categories: getCategoriesFromAPICategories(product.categoryAttributes),
};
return item;
});
Expand Down Expand Up @@ -119,6 +120,9 @@ function createCart(input: APICart): Cart {
? parsePriceTiers(apiProduct.price_tiers)
: null,
handle: apiProduct.handle,
categories: getCategoriesFromAPICategories(
apiProduct.categoryAttributes
),
};
})
),
Expand All @@ -136,3 +140,16 @@ const parsePriceTiers = (
return acc;
}, {} as Record<string, Money>);
};

const getCategoriesFromAPICategories = (apiCategories: {
is_tool: boolean;
item_category: string;
part_subcategory: string;
}): string[] => {
const firstCategory = apiCategories.is_tool ? 'Tool' : 'Part';
return [
firstCategory,
apiCategories.item_category,
apiCategories.part_subcategory,
];
};
12 changes: 12 additions & 0 deletions packages/cart-sdk/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface CartLineItem {
maxToAdd?: number;
price: Money;
compareAtPrice?: Money | null;
categories?: string[] | null;
}

export interface APICart {
Expand Down Expand Up @@ -60,6 +61,7 @@ export interface CrossSellProduct {
compareAtPrice?: Money | null;
proPricesByTier?: Record<string, Money> | null;
handle: string;
categories: string[];
}

interface APICrossSellProduct {
Expand All @@ -75,6 +77,11 @@ interface APICrossSellProduct {
subPrice: string | null;
url: string | null;
variant_id: number;
categoryAttributes: {
is_tool: boolean;
item_category: string;
part_subcategory: string;
};
}

interface APIPriceItem {
Expand All @@ -96,6 +103,11 @@ export interface APICartProduct {
subTotal: string;
subTotalStr: string;
variant: string;
categoryAttributes: {
is_tool: boolean;
item_category: string;
part_subcategory: string;
};
}

export interface MiniCartProduct {
Expand Down
14 changes: 14 additions & 0 deletions packages/helpers/product-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,17 @@ export function getProductIdFromGlobalId(globalProductId: string) {
export function getProductVariantURI(variantId: string | number): string {
return `gid://shopify/ProductVariant/${variantId}`;
}

export function getCategoriesFromTags(tags: string[]): string[] {
const tagsObj: Record<string, string> = {};
tags.forEach((tag) => {
const [name, val] = tag.split(/=|:/);
tagsObj[name] = val;
});
const categories = [
'Tool' in tagsObj ? 'Tool' : 'Part' in tagsObj ? 'Part' : 'N/A',
tagsObj['Item Category'],
tagsObj['Part SubCategory'],
];
return categories.map((category) => category ?? 'N/A');
}
1 change: 1 addition & 0 deletions packages/ui/cart/drawer/CrossSellItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function CrossSellItem({ item, ...otherProps }: CrossSellItemProps) {
imageSrc: item.imageSrc,
price: userPrice.price,
compareAtPrice: userPrice.compareAtPrice,
categories: item.categories,
},
});
}, [userPrice.price, userPrice.compareAtPrice, item]);
Expand Down

0 comments on commit d886dec

Please sign in to comment.