Skip to content

Commit 73bcf68

Browse files
authored
Merge pull request #129 from openobserve/dev
Dev
2 parents e1f62cd + a9b91cb commit 73bcf68

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

src/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
// import type { Core } from '@strapi/strapi';
1+
import type { Core } from '@strapi/strapi';
2+
import {
3+
validateVariantChanges,
4+
validateGoalType,
5+
} from "./utils/validateVariantChange";
26

37
export default {
48
/**
@@ -7,7 +11,21 @@ export default {
711
*
812
* This gives you an opportunity to extend code.
913
*/
10-
register(/* { strapi }: { strapi: Core.Strapi } */) {},
14+
register( { strapi }: { strapi: Core.Strapi } ) {
15+
// Registering a custom service
16+
strapi.documents.use(async (context, next) => {
17+
const { uid, action, params } = context;
18+
// Only apply to your content type
19+
if (uid === "api::ab-experiment.ab-experiment") {
20+
// Intercept create & update operations
21+
if (["create", "update"].includes(action)) {
22+
validateVariantChanges(params);
23+
validateGoalType(params);
24+
}
25+
}
26+
return next();
27+
});
28+
},
1129

1230
/**
1331
* An asynchronous bootstrap function that runs before

src/utils/validateVariantChange.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { errors } from "@strapi/utils";
2+
const { ValidationError } = errors;
3+
4+
// Type definitions
5+
interface Change {
6+
type: "text" | "style" | "html" | "image";
7+
selector?: string;
8+
value?: string;
9+
style?: string;
10+
html?: string;
11+
image?: any;
12+
}
13+
14+
interface Variant {
15+
changes?: Change[];
16+
}
17+
18+
interface Goal {
19+
type: "Time On Page" | "Scroll Depth" | "Click" | "Page View";
20+
value?: string | number;
21+
}
22+
23+
interface ValidationData {
24+
data?: {
25+
variants?: Variant[];
26+
targetingRules?: {
27+
goal?: Goal;
28+
};
29+
};
30+
}
31+
32+
// Type for params from Strapi middleware
33+
type StrapiParams = Record<string, any>;
34+
35+
// Mapping of change types to their required fields
36+
const CHANGE_TYPE_REQUIREMENTS: Record<
37+
string,
38+
{ field: string; label: string }
39+
> = {
40+
text: { field: "value", label: "Value" },
41+
style: { field: "style", label: "Style" },
42+
html: { field: "html", label: "HTML" },
43+
image: { field: "image", label: "Image" },
44+
};
45+
46+
// Goal types that require a value
47+
const GOAL_TYPES_REQUIRING_VALUE = ["Time On Page", "Scroll Depth"];
48+
49+
export function validateVariantChanges(data: StrapiParams): void {
50+
const variants = data?.data?.variants;
51+
52+
if (!Array.isArray(variants) || variants.length === 0) {
53+
return;
54+
}
55+
56+
variants.forEach((variant, variantIndex) => {
57+
const changes = variant?.changes;
58+
59+
if (!Array.isArray(changes)) {
60+
return;
61+
}
62+
63+
changes.forEach((change, changeIndex) => {
64+
const { type, selector } = change;
65+
66+
// Validate selector is present
67+
if (!selector || selector.trim() === "") {
68+
throw new ValidationError(
69+
`AB Experiment Validation Error: Selector is required for Variant ${variantIndex + 1}, Change ${changeIndex + 1}`
70+
);
71+
}
72+
73+
// Validate type-specific required fields
74+
const requirement = CHANGE_TYPE_REQUIREMENTS[type];
75+
if (requirement) {
76+
const fieldValue = change[requirement.field as keyof Change];
77+
78+
if (
79+
!fieldValue ||
80+
(typeof fieldValue === "string" && fieldValue.trim() === "")
81+
) {
82+
throw new ValidationError(
83+
`AB Experiment Validation Error: ${requirement.label} is required for change type "${type}" in Variant ${variantIndex + 1}, Change ${changeIndex + 1}`
84+
);
85+
}
86+
}
87+
});
88+
});
89+
}
90+
91+
export function validateGoalType(data: StrapiParams): void {
92+
const goal = data?.data?.targetingRules?.goal;
93+
94+
if (!goal || !goal.type) {
95+
return;
96+
}
97+
98+
const { type, value } = goal;
99+
100+
// Check if this goal type requires a value
101+
if (GOAL_TYPES_REQUIRING_VALUE.includes(type)) {
102+
if (!value || (typeof value === "string" && value.trim() === "")) {
103+
throw new ValidationError(
104+
`AB Experiment Validation Error: Goal value is required for goal type "${type}"`
105+
);
106+
}
107+
108+
// Additional validation for numeric values
109+
if (type === "Scroll Depth") {
110+
const numericValue =
111+
typeof value === "string" ? parseFloat(value) : value;
112+
if (isNaN(numericValue) || numericValue < 0 || numericValue > 100) {
113+
throw new ValidationError(
114+
`AB Experiment Validation Error: Scroll Depth value must be a number between 0 and 100 (received: ${value})`
115+
);
116+
}
117+
}
118+
119+
if (type === "Time On Page") {
120+
const numericValue =
121+
typeof value === "string" ? parseFloat(value) : value;
122+
if (isNaN(numericValue) || numericValue <= 0) {
123+
throw new ValidationError(
124+
`AB Experiment Validation Error: Time On Page value must be a positive number (received: ${value})`
125+
);
126+
}
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)