fix: between operator for attribute matching (#21031)
* update json logic for raqb between operator * nit * add tests * e2e tests * after merge: updated tests due to latest changes in routingforms v3 --------- Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
This commit is contained in:
@@ -119,5 +119,41 @@ describe("Query Builder Config", () => {
|
||||
it("should have maxNesting set to 1 in settings", () => {
|
||||
assertMaxNesting(AttributesBaseConfig, 1);
|
||||
});
|
||||
|
||||
it("should provide jsonlogic for between operator", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const jsonLogic = AttributesBaseConfig.operators.between.jsonLogic(
|
||||
{ var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" },
|
||||
"between",
|
||||
["1", "5"]
|
||||
);
|
||||
|
||||
// Verifies that it is using >= and <= in implementation and integer parsed
|
||||
expect(jsonLogic).toEqual({
|
||||
and: [
|
||||
{ ">=": [{ var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" }, 1] },
|
||||
{ "<=": [{ var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" }, 5] },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should provide jsonlogic for not_between operator", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const jsonLogic = AttributesBaseConfig.operators.not_between.jsonLogic(
|
||||
{ var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" },
|
||||
"not_between",
|
||||
["1", "5"]
|
||||
);
|
||||
|
||||
// Verifies that it is using < and > in implementation and integer parsed
|
||||
expect(jsonLogic).toEqual({
|
||||
or: [
|
||||
{ "<": [{ var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" }, 1] },
|
||||
{ ">": [{ var: "89ee81ae-953c-409b-aacc-700e1ce5ae20" }, 5] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+14
-1
@@ -117,7 +117,13 @@ const operators: Operators = {
|
||||
cardinality: 2,
|
||||
valueLabels: ["Value from", "Value to"],
|
||||
reversedOp: "not_between",
|
||||
jsonLogic: "<=",
|
||||
jsonLogic: (field: any, op: any, vals: [any, any]) => {
|
||||
const min = parseInt(vals[0], 10);
|
||||
const max = parseInt(vals[1], 10);
|
||||
return {
|
||||
and: [{ ">=": [field, min] }, { "<=": [field, max] }],
|
||||
};
|
||||
},
|
||||
},
|
||||
not_between: {
|
||||
isNotOp: true,
|
||||
@@ -126,6 +132,13 @@ const operators: Operators = {
|
||||
cardinality: 2,
|
||||
valueLabels: ["Value from", "Value to"],
|
||||
reversedOp: "between",
|
||||
jsonLogic: (field: any, op: any, vals: [any, any]) => {
|
||||
const min = parseInt(vals[0], 10);
|
||||
const max = parseInt(vals[1], 10);
|
||||
return {
|
||||
or: [{ "<": [field, min] }, { ">": [field, max] }],
|
||||
};
|
||||
},
|
||||
},
|
||||
is_empty: {
|
||||
label: "Is empty",
|
||||
|
||||
@@ -58,6 +58,13 @@ async function selectFirstValueForAttributeValue({
|
||||
});
|
||||
}
|
||||
|
||||
async function selectOperatorOption({ fromLocator, option }: { fromLocator: Locator; option: number }) {
|
||||
await selectOptionUsingLocator({
|
||||
locator: fromLocator,
|
||||
option,
|
||||
});
|
||||
}
|
||||
|
||||
async function addAttributeRoutingRule(page: Page) {
|
||||
// TODO: Use a better selector maybe?
|
||||
await page.locator('text="Add rule"').nth(1).click();
|
||||
@@ -75,6 +82,25 @@ async function addAttributeRoutingRule(page: Page) {
|
||||
});
|
||||
}
|
||||
|
||||
async function addAttributeRoutingRuleWithOperator(page: Page, valueFrom: string, valueTo: string) {
|
||||
await page.locator('text="Add rule"').nth(1).click();
|
||||
const attributeQueryBuilder = page.locator(".group-container").nth(1);
|
||||
const attributeSelectorFirstRule = attributeQueryBuilder.locator(".rule--field").nth(0);
|
||||
await selectFirstAttributeOption({
|
||||
fromLocator: attributeSelectorFirstRule,
|
||||
});
|
||||
|
||||
const attributeOperatorSelector = attributeQueryBuilder.locator(".rule--operator").nth(0);
|
||||
await selectOperatorOption({
|
||||
fromLocator: attributeOperatorSelector,
|
||||
option: 7,
|
||||
});
|
||||
|
||||
const attributeValueSelector = attributeQueryBuilder.locator(".rule--value").nth(0);
|
||||
await attributeValueSelector.locator("input").nth(0).fill(valueFrom);
|
||||
await attributeValueSelector.locator("input").nth(1).fill(valueTo);
|
||||
}
|
||||
|
||||
async function selectFirstEventRedirectOption(page: Page) {
|
||||
await selectOption({
|
||||
selector: {
|
||||
@@ -694,6 +720,171 @@ test.describe("Routing Forms", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Form with Attribute Routing with Between operator - Not Matching - Team Form", () => {
|
||||
test.beforeEach(async ({ page, users }) => {
|
||||
const userFixture = await users.create(
|
||||
{ username: "routing-forms" },
|
||||
{
|
||||
hasTeam: true,
|
||||
isOrg: true,
|
||||
hasSubteam: true,
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
}
|
||||
);
|
||||
|
||||
const orgMembership = await userFixture.getOrgMembership();
|
||||
|
||||
const createdAttribute = await prisma.attribute.create({
|
||||
data: {
|
||||
teamId: orgMembership.teamId,
|
||||
type: AttributeType.NUMBER,
|
||||
name: "Company Size",
|
||||
slug: `company-size-orgId-${orgMembership.teamId}`,
|
||||
options: {
|
||||
create: [
|
||||
{
|
||||
slug: "10",
|
||||
value: "10",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
include: {
|
||||
options: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.attributeToUser.create({
|
||||
data: {
|
||||
member: {
|
||||
connect: {
|
||||
userId_teamId: {
|
||||
userId: userFixture.id,
|
||||
teamId: orgMembership.teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
attributeOption: {
|
||||
connect: {
|
||||
id: createdAttribute.options[0].id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await userFixture.apiLogin();
|
||||
});
|
||||
|
||||
test.afterEach(async ({ users }) => {
|
||||
// This also delete forms on cascade
|
||||
await users.deleteAll();
|
||||
});
|
||||
|
||||
test("should not match any member if between operator values are input such that", async ({ page }) => {
|
||||
await addForm(page, {
|
||||
forTeam: true,
|
||||
});
|
||||
|
||||
await page.locator('[data-testid="toggle-group-item-route-builder"]').nth(1).click();
|
||||
await page.waitForURL("/routing/route-builder/**");
|
||||
await addNewRoute(page);
|
||||
// This would select Round Robin event that we created above
|
||||
await selectFirstEventRedirectOption(page);
|
||||
await addAttributeRoutingRuleWithOperator(page, "1", "5");
|
||||
await saveCurrentForm(page);
|
||||
|
||||
// asserting there is no match as attribute value for the member is 10, while input is between 1 and 5
|
||||
await (async function testPreviewWhereThereIsNoMatch() {
|
||||
await page.click('[data-testid="preview-button"]');
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
await expect(page.locator('[data-testid="attribute-logic-matched"]')).toHaveText("No");
|
||||
await expect(page.locator('[data-testid="attribute-logic-fallback-matched"]')).toHaveText("Yes");
|
||||
await page.click('[data-testid="close-results-button"]');
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Form with Attribute Routing with Between operator - Matching - Team Form", () => {
|
||||
test.beforeEach(async ({ page, users }) => {
|
||||
const userFixture = await users.create(
|
||||
{ username: "routing-forms" },
|
||||
{
|
||||
hasTeam: true,
|
||||
isOrg: true,
|
||||
hasSubteam: true,
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
}
|
||||
);
|
||||
|
||||
const orgMembership = await userFixture.getOrgMembership();
|
||||
|
||||
const createdAttribute = await prisma.attribute.create({
|
||||
data: {
|
||||
teamId: orgMembership.teamId,
|
||||
type: AttributeType.NUMBER,
|
||||
name: "Company Size",
|
||||
slug: `company-size-orgId-${orgMembership.teamId}`,
|
||||
options: {
|
||||
create: [
|
||||
{
|
||||
slug: "3",
|
||||
value: "3",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
include: {
|
||||
options: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.attributeToUser.create({
|
||||
data: {
|
||||
member: {
|
||||
connect: {
|
||||
userId_teamId: {
|
||||
userId: userFixture.id,
|
||||
teamId: orgMembership.teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
attributeOption: {
|
||||
connect: {
|
||||
id: createdAttribute.options[0].id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await userFixture.apiLogin();
|
||||
});
|
||||
|
||||
test.afterEach(async ({ users }) => {
|
||||
// This also delete forms on cascade
|
||||
await users.deleteAll();
|
||||
});
|
||||
|
||||
test("should match the member if between operator values are input such that", async ({ page }) => {
|
||||
await addForm(page, {
|
||||
forTeam: true,
|
||||
});
|
||||
|
||||
await page.locator('[data-testid="toggle-group-item-route-builder"]').nth(1).click();
|
||||
await page.waitForURL("/routing/route-builder/**");
|
||||
await addNewRoute(page);
|
||||
// This would select Round Robin event that we created above
|
||||
await selectFirstEventRedirectOption(page);
|
||||
await addAttributeRoutingRuleWithOperator(page, "1", "5");
|
||||
await saveCurrentForm(page);
|
||||
|
||||
// asserting there is a match as attribute value for the member is 3 that is between 1 and 5
|
||||
await (async function testPreviewWhereThereIsMatch() {
|
||||
await page.click('[data-testid="preview-button"]');
|
||||
await page.click('[data-testid="submit-button"]');
|
||||
await page.waitForSelector("text=@example.com");
|
||||
await page.click('[data-testid="close-results-button"]');
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Team Routing Form", () => {
|
||||
test.afterEach(async ({ users }) => {
|
||||
await users.deleteAll();
|
||||
|
||||
Reference in New Issue
Block a user