Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/metrics : removed conversion, added date display option #537

Merged
merged 7 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/vendure-plugin-metrics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 1.6.0 (2024-11-19)

- Allow showing metrics of past X months instead of always past 12 months.
- Removed conversion metric, as it is can not be accurately calculated based on created orders alone
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix grammar in changelog entry

There's a grammar error in the conversion metric removal note.

-Removed conversion metric, as it is can not be accurately calculated based on created orders alone
+Removed conversion metric, as it cannot be accurately calculated based on created orders alone
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Removed conversion metric, as it is can not be accurately calculated based on created orders alone
- Removed conversion metric, as it cannot be accurately calculated based on created orders alone
🧰 Tools
🪛 LanguageTool

[grammar] ~4-~4: It appears that only one verb is needed.
Context: ...ths. - Removed conversion metric, as it is can not be accurately calculated based on c...

(THIS_IS_HAVE)

- Small improvement in query performance

# 1.5.0 (2024-11-03)

- Allow specifying per metric if it's filterable by variant ID's
Expand Down
29 changes: 16 additions & 13 deletions packages/vendure-plugin-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@

### [Official documentation here](https://pinelab-plugins.com/plugin/vendure-plugin-metrics)

A plugin to measure and visualize your shop's average order value (AOV),number of orders per
month or per week and number of items per product variant for the past 12 months (or weeks) per variants.
A plugin to visualize your shops most important metrics of the past year.

![image](https://user-images.githubusercontent.com/6604455/236404288-e55c37ba-9508-43e6-a54c-2eb7b3cd36ee.png)
![image](https://raw.githubusercontent.com/Pinelab-studio/pinelab-vendure-plugins/96ed9d15e7a2908e0620a8a1e92b1d8c9fe381a4/docs-website/public/plugin-images/metrics.png)

## Getting started

1. Configure the plugin in `vendure-config.ts`:

```ts
import { MetricsPlugin, AverageOrderValueMetric, SalesPerProductMetric } from "@pinelab/vendure-plugin-metrics";
import { MetricsPlugin } from "@pinelab/vendure-plugin-metrics";

plugins: [
...
MetricsPlugin.init({
metrics: [
new AverageOrderValueMetric(),
new SalesPerProductMetric()
]
// Consider displaying fewer months for shops with a lot of orders
displayPastMonths: 13
}),
AdminUiPlugin.init({
port: 3002,
Expand All @@ -34,15 +31,17 @@ plugins: [
]
```

2. Start your Vendure server and login as administrator
3. You should now be able to add the widget `metrics` on your dashboard.
2. Rebuild your Admin UI
3. Start your Vendure server and login as administrator
4. You should now be able to add the widget `metrics` on your dashboard.

Metric results are cached in memory to prevent heavy database queries every time a user opens its dashboard.

### Default built-in Metrics
### Built-in Metrics

1. Average Order Value (AOV): The average of `order.totalWithTax` of the orders per week/month
2. Sales per product: The number of items sold. When no variants are selected, this metric counts the total nr of items per order.
1. Revenue (per product): The total revenue per month, or the revenue generated by specific variants if a variant is selected.
2. Average Order Value (AOV): The average of `order.totalWithTax` of the orders per week/month
3. Units sold: The number of units sold for the selected variant(s).

# Custom Metrics

Expand All @@ -67,6 +66,10 @@ import {
export class AverageOrderLineValue implements MetricStrategy<OrderLine> {
readonly metricType: AdvancedMetricType = AdvancedMetricType.Currency;
readonly code = 'average-orderline-value';
/**
* Determines if this metric allows filtering by variants
*/
readonly allowProductSelection = true;

getTitle(ctx: RequestContext): string {
return `Average Order Line Value`;
Expand Down
2 changes: 1 addition & 1 deletion packages/vendure-plugin-metrics/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pinelab/vendure-plugin-metrics",
"version": "1.5.0",
"version": "1.6.0",
"description": "Vendure plugin measuring and visualizing e-commerce metrics",
"author": "Martijn van de Brug <[email protected]>",
"homepage": "https://pinelab-plugins.com/",
Expand Down
14 changes: 8 additions & 6 deletions packages/vendure-plugin-metrics/src/api/metrics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ export class MetricsService {
);
const today = endOfDay(new Date());
// Use start of month, because we'd like to see the full results of last years same month
const oneYearAgo = startOfMonth(sub(today, { years: 1 }));
const startDate = startOfMonth(
sub(today, { months: this.pluginOptions.displayPastMonths })
);
Comment on lines +51 to +53
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for displayPastMonths

Ensure that this.pluginOptions.displayPastMonths is a positive integer to prevent unexpected behavior when calculating startDate.

// For each metric strategy
return Promise.all(
this.metricStrategies.map(async (metricStrategy) => {
const cacheKeyObject = {
code: metricStrategy.code,
from: today.toDateString(),
to: oneYearAgo.toDateString(),
from: startDate.toDateString(),
to: today.toDateString(),
channel: ctx.channel.token,
variantIds: [] as string[],
variantIds: [] as string[], // Set below if input is given
};
if (metricStrategy.allowProductSelection) {
// Only use variantIds for cache key if the strategy allows filtering by variants
Expand All @@ -78,14 +80,14 @@ export class MetricsService {
const allEntities = await metricStrategy.loadEntities(
ctx,
new Injector(this.moduleRef),
oneYearAgo,
startDate,
today,
variants
);
const entitiesPerMonth = this.splitEntitiesInMonths(
metricStrategy,
allEntities,
oneYearAgo,
startDate,
today
);
// Calculate datapoints per 'name', because we could be dealing with a multi line chart
Expand Down
101 changes: 0 additions & 101 deletions packages/vendure-plugin-metrics/src/api/metrics/conversion.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ export class UnitsSoldMetric implements MetricStrategy<OrderLine> {
.getRepository(ctx, OrderLine)
.createQueryBuilder('orderLine')
.leftJoin('orderLine.order', 'order')
.addSelect(['order.id', 'order.orderPlacedAt'])
.select([
'order.id',
'order.orderPlacedAt',
'orderLine.id',
'orderLine.quantity',
])
.leftJoin('order.channels', 'channel')
.where(`channel.id=:channelId`, { channelId: ctx.channelId })
.andWhere('order.orderPlacedAt BETWEEN :fromDate AND :toDate', {
Expand Down
1 change: 0 additions & 1 deletion packages/vendure-plugin-metrics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ export * from './ui/generated/graphql';
export * from './api/metrics/average-order-value';
export * from './api/metrics/revenue-per-product';
export * from './api/metrics/units-sold-metric';
export * from './api/metrics/conversion';
18 changes: 14 additions & 4 deletions packages/vendure-plugin-metrics/src/metrics.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ import { PLUGIN_INIT_OPTIONS } from './constants';
import { RevenuePerProduct } from './api/metrics/revenue-per-product';
import { AverageOrderValueMetric } from './api/metrics/average-order-value';
import { UnitsSoldMetric } from './api/metrics/units-sold-metric';
import { ConversionMetric } from './api/metrics/conversion';

export interface MetricsPluginOptions {
/**
* The enabled metrics shown in the widget.
*/
metrics: MetricStrategy<any>[];
/**
* The number of past months to display in the metrics widget.
* If your shop has a lot of orders, consider using only the last 3 months for example.
*/
displayPastMonths: number;
}

@VendurePlugin({
Expand All @@ -31,14 +38,17 @@ export class MetricsPlugin {
static options: MetricsPluginOptions = {
metrics: [
new RevenuePerProduct(),
new ConversionMetric(),
new AverageOrderValueMetric(),
new UnitsSoldMetric(),
],
displayPastMonths: 13,
};

static init(options: MetricsPluginOptions): typeof MetricsPlugin {
this.options = options;
static init(options: Partial<MetricsPluginOptions>): typeof MetricsPlugin {
this.options = {
...this.options,
...options,
};
Comment on lines +47 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix use of 'this' in static context

Using 'this' in static methods can be confusing. Consider using the class name directly.

Apply this fix:

  static init(options: Partial<MetricsPluginOptions>): typeof MetricsPlugin {
-    this.options = {
-      ...this.options,
+    MetricsPlugin.options = {
+      ...MetricsPlugin.options,
      ...options,
    };
    return MetricsPlugin;
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static init(options: Partial<MetricsPluginOptions>): typeof MetricsPlugin {
this.options = {
...this.options,
...options,
};
static init(options: Partial<MetricsPluginOptions>): typeof MetricsPlugin {
MetricsPlugin.options = {
...MetricsPlugin.options,
...options,
};
return MetricsPlugin;
}
🧰 Tools
🪛 Biome

[error] 48-48: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 49-49: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

return MetricsPlugin;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/vendure-plugin-metrics/test/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import { addItem, createSettledOrder } from '../../test/src/shop-utils';
paymentMethodHandlers: [testPaymentMethod],
},
plugins: [
MetricsPlugin,
MetricsPlugin.init({
displayPastMonths: 19,
}),
DefaultSearchPlugin,
AdminUiPlugin.init({
port: 3002,
Expand Down
48 changes: 21 additions & 27 deletions packages/vendure-plugin-metrics/test/metrics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Metrics', () => {
await expect(promise).rejects.toThrow('authorized');
});

it('Fetches metrics for past 13 months', async () => {
it('Fetches metrics for past 14 months', async () => {
await adminClient.asSuperAdmin();
const { advancedMetricSummaries } =
await adminClient.query<AdvancedMetricSummariesQuery>(GET_METRICS);
Expand All @@ -84,23 +84,17 @@ describe('Metrics', () => {
const salesPerProduct = advancedMetricSummaries.find(
(m) => m.code === 'units-sold'
)!;
const conversion = advancedMetricSummaries.find(
(m) => m.code === 'conversion'
)!;
expect(advancedMetricSummaries.length).toEqual(4);
expect(averageOrderValue.series[0].values.length).toEqual(13);
expect(averageOrderValue.labels.length).toEqual(13);
expect(advancedMetricSummaries.length).toEqual(3);
expect(averageOrderValue.series[0].values.length).toEqual(14);
expect(averageOrderValue.labels.length).toEqual(14);
// All orders are 4102 without tax, so that the AOV
expect(averageOrderValue.series[0].values[12]).toEqual(4102);
expect(revenuePerProduct.series[0].values.length).toEqual(13);
expect(revenuePerProduct.labels.length).toEqual(13);
expect(averageOrderValue.series[0].values[13]).toEqual(4102);
expect(revenuePerProduct.series[0].values.length).toEqual(14);
expect(revenuePerProduct.labels.length).toEqual(14);
// All orders are 4102 without tax, and we placed 3 orders
expect(revenuePerProduct.series[0].values[12]).toEqual(3 * 4102); //12306
expect(salesPerProduct.series[0].values.length).toEqual(13);
expect(salesPerProduct.labels.length).toEqual(13);
expect(conversion.series[0].values.length).toEqual(13);
expect(conversion.labels.length).toEqual(13);
expect(conversion.series[0].values[12]).toEqual(100);
expect(revenuePerProduct.series[0].values[13]).toEqual(3 * 4102); //12306
expect(salesPerProduct.series[0].values.length).toEqual(14);
expect(salesPerProduct.labels.length).toEqual(14);
});

it('Fetches metrics for specific variant', async () => {
Expand All @@ -109,29 +103,29 @@ describe('Metrics', () => {
await adminClient.query<AdvancedMetricSummariesQuery>(GET_METRICS, {
input: { variantIds: [1, 2] },
});
expect(advancedMetricSummaries.length).toEqual(4);
expect(advancedMetricSummaries.length).toEqual(3);
const revenuePerProduct = advancedMetricSummaries.find(
(m) => m.code === 'revenue-per-product'
)!;
// For revenue per product we expect 2 series: one for each variant
expect(revenuePerProduct.series[0].values.length).toEqual(13);
expect(revenuePerProduct.series[1].values.length).toEqual(13);
expect(revenuePerProduct.labels.length).toEqual(13);
expect(revenuePerProduct.series[0].values.length).toEqual(14);
expect(revenuePerProduct.series[1].values.length).toEqual(14);
expect(revenuePerProduct.labels.length).toEqual(14);
// Expect the first series (variant 1), to have 389700 revenue in last month
expect(revenuePerProduct.series[0].values[12]).toEqual(3897);
expect(revenuePerProduct.series[0].values[13]).toEqual(3897);
// Expect the first series (variant 2), to have 839400 revenue in last month
expect(revenuePerProduct.series[1].values[12]).toEqual(8394);
expect(revenuePerProduct.series[1].values[13]).toEqual(8394);
const salesPerProduct = advancedMetricSummaries.find(
(m) => m.code === 'units-sold'
)!;
// For sales per product we expect 2 series: one for each variant
expect(salesPerProduct.series[0].values.length).toEqual(13);
expect(salesPerProduct.series[1].values.length).toEqual(13);
expect(salesPerProduct.labels.length).toEqual(13);
expect(salesPerProduct.series[0].values.length).toEqual(14);
expect(salesPerProduct.series[1].values.length).toEqual(14);
expect(salesPerProduct.labels.length).toEqual(14);
// Expect the first series (variant 1), to have 3 revenue in last month
expect(salesPerProduct.series[0].values[12]).toEqual(3);
expect(salesPerProduct.series[0].values[13]).toEqual(3);
// Expect the first series (variant 2), to have 6 revenue in last month
expect(salesPerProduct.series[1].values[12]).toEqual(6);
expect(salesPerProduct.series[1].values[13]).toEqual(6);
});

if (process.env.TEST_ADMIN_UI) {
Expand Down
Loading