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: Skip contact owner even if it matches attribute routing #17015

Merged
merged 22 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e5e8a12
Initial commit
hariombalhara Sep 24, 2024
03c0650
routingForm to Booking a particular team member working
hariombalhara Sep 24, 2024
7c8b452
Happy path working
hariombalhara Sep 25, 2024
fb3b263
Fixes
hariombalhara Sep 26, 2024
8a367da
Merge remote-tracking branch 'origin/main' into routing-forms-team-ev…
hariombalhara Sep 27, 2024
4bfb2b7
Fix router query params forwarding
hariombalhara Sep 27, 2024
73c9945
Merge remote-tracking branch 'origin/main' into routing-forms-team-ev…
hariombalhara Oct 4, 2024
0c15120
Add basicConfig within app
hariombalhara Oct 4, 2024
9aed6f2
Tests
hariombalhara Oct 5, 2024
6b473e7
More tests
hariombalhara Oct 7, 2024
bffc946
Update packages/app-store/routing-forms/components/SingleForm.tsx
hariombalhara Oct 8, 2024
6c2b77d
Merge remote-tracking branch 'origin/main' into routing-forms-team-ev…
hariombalhara Oct 8, 2024
6559bc3
Merge remote-tracking branch 'origin/routing-forms-team-event-members…
hariombalhara Oct 8, 2024
dcbfabc
WIP
hariombalhara Oct 8, 2024
6fd9751
Merge remote-tracking branch 'origin/main' into skip-contact-owner-ev…
hariombalhara Oct 9, 2024
f06be8c
Merge remote-tracking branch 'origin/main' into skip-contact-owner-ev…
hariombalhara Oct 9, 2024
489e1b2
Fix lint issues
hariombalhara Oct 9, 2024
be53242
Fix tests -TS and add new logic
hariombalhara Oct 9, 2024
5135fdd
Add getEventTypeRedirectUrl tests
hariombalhara Oct 9, 2024
b7f73c9
Remove booking title changes code
hariombalhara Oct 9, 2024
36e21fb
More changes
hariombalhara Oct 9, 2024
c216dda
Add tests for getRoutedUsers
hariombalhara Oct 9, 2024
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
7 changes: 6 additions & 1 deletion packages/app-store/routing-forms/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@
- [ ] getAttributes
- [ ] Querying Logic Test
- [ ] getRoutedUsers tests
- [ ] getAvailableSlots
- [ ] should use routedTeamMemberIds in availability query

### Documentation

### Documentation / Tooltip
- [ ] Document well that the option label in Routing Form Field and Attribute Option label must be same to connect them.
- Due to the connection requirement b/w Attribute Option and Field Option, we use label(lowercased) to match attributes instead of attribute slug
- [ ] Fixed hosts of the event will be included through attribute routing as well. They aren't tested for attribute routing logic.

### V2.0
- [ ] Fallback for when no team member matches the criteria.
- Fallback will be attributes query builder that would match a different set of users. Though the booking will use the team members assigned to the event type, it might be better to be able to identify such a scenario and use a different set of users. It also makes it easy to identify when the fallback scenario happens.
- [ ] cal.routedTeamMembersIds query param - Could possible become a big payload and possibly break the URL limit. We could work on a short-lived row in a table that would hold that info and we pass the id of that row only in query param. handleNewBooking can then retrieve the routedTeamMembersIds from that short-lived row and delete the entry after successfully creating a booking.
- [ ] Better ability to test with contact owner from Routing Form Preview itself(if possible). Right now, we need to test the entire booking flow to verify that.


## TODO - Attributes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, it, expect } from "vitest";

import { CAL_URL } from "@calcom/lib/constants";

import { getAbsoluteEventTypeRedirectUrl } from "../getEventTypeRedirectUrl";

describe("getAbsoluteEventTypeRedirectUrl", () => {
const defaultForm = {
team: null,
nonOrgUsername: null,
nonOrgTeamslug: null,
userOrigin: "https://user.cal.com",
teamOrigin: "https://team.cal.com",
};

const defaultParams = {
eventTypeRedirectUrl: "user/event",
form: defaultForm,
allURLSearchParams: new URLSearchParams(),
isEmbed: false,
};

it("should return CAL_URL for non-migrated user", () => {
const result = getAbsoluteEventTypeRedirectUrl({
...defaultParams,
eventTypeRedirectUrl: "user/event",
form: { ...defaultForm, nonOrgUsername: "user" },
});
expect(result).toBe(`${CAL_URL}/user/event?`);
});

it("should return user origin for migrated user", () => {
const result = getAbsoluteEventTypeRedirectUrl(defaultParams);
expect(result).toBe("https://user.cal.com/user/event?");
});

it("should return CAL_URL for non-migrated team", () => {
const result = getAbsoluteEventTypeRedirectUrl({
...defaultParams,
eventTypeRedirectUrl: "team/team1/event",
form: { ...defaultForm, nonOrgTeamslug: "team1" },
});
expect(result).toBe(`${CAL_URL}/team/team1/event?`);
});

it("should return team origin for migrated team", () => {
const result = getAbsoluteEventTypeRedirectUrl({
...defaultParams,
eventTypeRedirectUrl: "team/team1/event",
});
expect(result).toBe("https://team.cal.com/team/team1/event?");
});

it("should append URL search params", () => {
const result = getAbsoluteEventTypeRedirectUrl({
...defaultParams,
allURLSearchParams: new URLSearchParams("foo=bar&baz=qux"),
});
expect(result).toBe("https://user.cal.com/user/event?foo=bar&baz=qux");
});

it("should append /embed for embedded views", () => {
const result = getAbsoluteEventTypeRedirectUrl({
...defaultParams,
allURLSearchParams: new URLSearchParams("foo=bar&baz=qux"),
isEmbed: true,
});
expect(result).toBe("https://user.cal.com/user/event/embed?foo=bar&baz=qux");
});

it("should throw an error if invalid team event redirect URL is provided", () => {
expect(() =>
getAbsoluteEventTypeRedirectUrl({
...defaultParams,
eventTypeRedirectUrl: "team/",
})
).toThrow("eventTypeRedirectUrl must have username or teamSlug");
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it, afterEach, vi } from "vitest";

import { jsonLogicToPrisma } from "../../jsonLogicToPrisma";
import { jsonLogicToPrisma } from "../jsonLogicToPrisma";

afterEach(() => {
vi.resetAllMocks();
Expand Down
6 changes: 3 additions & 3 deletions packages/app-store/routing-forms/components/SingleForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF

function testRouting() {
const route = findMatchingRoute({ form, response });

if (route?.action?.type === "eventTypeRedirectUrl") {
setEventTypeUrl(
enrichedWithUserProfileForm
Expand All @@ -271,9 +271,9 @@ function SingleForm({ form, appUrl, Page, enrichedWithUserProfileForm }: SingleF
: ""
);
}

setChosenRoute(route || null);

if (!route) return;

findTeamMembersMatchingAttributeLogicMutation.mutate({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
// This is taken from "react-awesome-query-builder/lib/config/basic";
import type {
Operators as RAQBOperators,
Conjunction as RAQBConjunction,
Widget as RAQBWidget,
Type as RAQBType,
Settings as RAQBSettings,
Operator as RAQBOperator,
} from "react-awesome-query-builder";

export type Conjunction = Omit<RAQBConjunction, "formatConj" | "sqlFormatConj" | "spelFormatConj" | "mongoConj">;
export type Conjunction = Omit<
RAQBConjunction,
"formatConj" | "sqlFormatConj" | "spelFormatConj" | "mongoConj"
>;
export type Conjunctions = Record<string, Conjunction>;
export type Operator = RAQBOperator & {
_jsonLogicIsExclamationOp?: boolean;
}
};
export type Operators = Record<string, Operator>;
export type WidgetWithoutFactory = Omit<RAQBWidget, "factory"> & {
type: string;
Expand Down Expand Up @@ -450,4 +452,4 @@ export default {
widgets,
types,
settings,
};
};
4 changes: 3 additions & 1 deletion packages/app-store/routing-forms/getEventTypeRedirectUrl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CAL_URL } from "@calcom/lib/constants";
import { Ensure } from "@calcom/types/utils";
import type { Ensure } from "@calcom/types/utils";

function getUserAndEventTypeSlug(eventTypeRedirectUrl: string) {
if (eventTypeRedirectUrl.startsWith("/")) {
Expand All @@ -14,6 +14,8 @@ function getUserAndEventTypeSlug(eventTypeRedirectUrl: string) {
}

/**
* @param eventTypeRedirectUrl - The event path without a starting slash
*
* Handles the following cases
* 1. A team form where the team isn't a sub-team
* 1.1 A team form where team isn't a sub-team and the user is migrated. i.e. User has been migrated but not the team
Expand Down
5 changes: 4 additions & 1 deletion packages/app-store/routing-forms/lib/InitialConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { FormFieldsBaseConfig, AttributesBaseConfig } from "../components/react-awesome-query-builder/config/config";
import {
FormFieldsBaseConfig,
AttributesBaseConfig,
} from "../components/react-awesome-query-builder/config/config";

export const FormFieldsInitialConfig = FormFieldsBaseConfig;
export const AttributesInitialConfig = AttributesBaseConfig;
10 changes: 8 additions & 2 deletions packages/app-store/routing-forms/lib/evaluateRaqbLogic.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"use client";

import { Utils as QbUtils, type JsonTree } from "react-awesome-query-builder";
import jsonLogic from "./jsonLogic";

import { safeStringify } from "@calcom/lib/safeStringify";

import jsonLogic from "./jsonLogic";

export const enum RaqbLogicResult {
MATCH = "MATCH",
NO_MATCH = "NO_MATCH",
Expand Down Expand Up @@ -30,7 +33,10 @@ export const evaluateRaqbLogic = ({
if (beStrictWithEmptyLogic && queryValue.children1 && Object.keys(queryValue.children1).length > 0) {
throw new Error("Couldn't build the logic from the query value");
}
console.log("No logic found", safeStringify({ queryValue, queryBuilderConfigFields: queryBuilderConfig.fields }));
console.log(
"No logic found",
safeStringify({ queryValue, queryBuilderConfigFields: queryBuilderConfig.fields })
);
// If no logic is provided, then consider it a match
return RaqbLogicResult.LOGIC_NOT_FOUND_SO_MATCHED;
}
Expand Down
9 changes: 5 additions & 4 deletions packages/app-store/routing-forms/lib/getAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";

import type { Attribute } from "../types/types";

async function getAttributeToUserWithMembershipAndAttributesForTeam({ teamId }: { teamId: number }) {
let log = logger.getSubLogger({ prefix: ["getAttributeToUserWithMembershipAndAttributes"] });
const log = logger.getSubLogger({ prefix: ["getAttributeToUserWithMembershipAndAttributes"] });

const whereClauseForAttributesAssignedToMembersOfTeam = {
member: {
Expand Down Expand Up @@ -52,9 +53,9 @@ async function getAttributeToUserWithMembershipAndAttributesForTeam({ teamId }:
}

async function getAttributesAssignedToMembersOfTeam({ teamId }: { teamId: number }) {
let log = logger.getSubLogger({ prefix: ["getAttributeToUserWithMembershipAndAttributes"] });
const log = logger.getSubLogger({ prefix: ["getAttributeToUserWithMembershipAndAttributes"] });

const whereClauseForAttributesAssignedToMembersOfTeam = {
const whereClauseForAttributesAssignedToMembersOfTeam = {
options: {
some: {
assignedUsers: {
Expand All @@ -72,7 +73,7 @@ async function getAttributesAssignedToMembersOfTeam({ teamId }: { teamId: number
},
},
},
}
};

log.debug(
safeStringify({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AttributeType } from "@calcom/prisma/enums";

import type { RoutingForm, Attribute } from "../types/types";
import { FieldTypes, RoutingFormFieldType } from "./FieldTypes";
import { AttributesInitialConfig, FormFieldsInitialConfig } from "./InitialConfig";
Expand Down
5 changes: 3 additions & 2 deletions packages/app-store/routing-forms/lib/getSerializableForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { App_RoutingForms_Form } from "@prisma/client";
import type { z } from "zod";

import { entityPrismaWhereClause } from "@calcom/lib/entityPermissionUtils";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { RoutingFormSettings } from "@calcom/prisma/zod-utils";

import type { SerializableForm, SerializableFormTeamMembers } from "../types/types";
Expand All @@ -11,8 +13,7 @@ import getConnectedForms from "./getConnectedForms";
import isRouter from "./isRouter";
import isRouterLinkedField from "./isRouterLinkedField";
import { getFieldWithOptions } from "./selectOptions";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";

const log = logger.getSubLogger({ prefix: ["getSerializableForm"] });
/**
* Doesn't have deleted fields by default
Expand Down
4 changes: 2 additions & 2 deletions packages/app-store/routing-forms/lib/processRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type { z } from "zod";

import type { FormResponse, Route, SerializableForm } from "../types/types";
import type { zodNonRouterRoute } from "../zod";
import { evaluateRaqbLogic, RaqbLogicResult } from "./evaluateRaqbLogic";
import { getQueryBuilderConfigForFormFields } from "./getQueryBuilderConfig";
import { isFallbackRoute } from "./isFallbackRoute";
import isRouter from "./isRouter";
import { evaluateRaqbLogic, RaqbLogicResult } from "./evaluateRaqbLogic";

export function findMatchingRoute({
form,
Expand Down Expand Up @@ -54,7 +54,7 @@ export function findMatchingRoute({
queryBuilderConfig,
data: responseValues,
});

if (result === RaqbLogicResult.MATCH || result === RaqbLogicResult.LOGIC_NOT_FOUND_SO_MATCHED) {
chosenRoute = route;
break;
Expand Down
4 changes: 4 additions & 0 deletions packages/app-store/routing-forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"json-logic-js": "^2.0.2",
"react-awesome-query-builder": "^5.1.2"
},
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix"
},
"devDependencies": {
Comment on lines +14 to 18
Copy link
Member Author

Choose a reason for hiding this comment

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

Allows to be able to run the lint on routing-forms directly

"@calcom/types": "*",
"@types/json-logic-js": "^1.2.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import SingleForm, {
getServerSidePropsForSingleFormView as getServerSideProps,
} from "../../components/SingleForm";
import type {FormFieldsBaseConfig} from "../../components/react-awesome-query-builder/config/config";
import "../../components/react-awesome-query-builder/styles.css";
import type { JsonLogicQuery } from "../../jsonLogicToPrisma";
import { getQueryBuilderConfigForFormFields,type FormFieldsQueryBuilderConfigWithRaqbFields } from "../../lib/getQueryBuilderConfig";
import {
getQueryBuilderConfigForFormFields,
type FormFieldsQueryBuilderConfigWithRaqbFields,
} from "../../lib/getQueryBuilderConfig";

export { getServerSideProps };


const Result = ({ formId, jsonLogicQuery }: { formId: string; jsonLogicQuery: JsonLogicQuery | null }) => {
const { t } = useLocale();

Expand Down Expand Up @@ -167,7 +168,7 @@ const Reporter = ({ form }: { form: inferSSRProps<typeof getServerSideProps>["fo
return (
<div className="cal-query-builder">
<Query
{...config as unknown as Config}
{...(config as unknown as Config)}
value={query.state.tree}
onChange={(immutableTree, config) => {
onChange(immutableTree, config as unknown as FormFieldsQueryBuilderConfigWithRaqbFields);
Expand Down
Loading
Loading