-
Notifications
You must be signed in to change notification settings - Fork 8k
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: Salesforce RR skip based on a user on a lookup field on an account #17526
Changes from 4 commits
feb63fd
90ada06
0e407eb
03583d4
dd859b0
071cf74
89191c6
20fcf6c
df3f4d5
b604055
ebcc41d
93e358b
868f302
2076d23
968d0b3
778382b
54a724a
cd03f79
74d72e9
7218477
f058483
8fbc654
def526d
2c72ee8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import dynamic from "next/dynamic"; | ||
|
||
export const routingFormAppComponents = { | ||
salesforce: dynamic(() => import("../salesforce/components/RoutingFormOptions")), | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also any metadata associated with any apps enabled for routing forms. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { routingFormOptions as salesforce_routing_form_schema } from "../salesforce/zod"; | ||
|
||
export const routingFormAppDataSchemas = { | ||
salesforce: salesforce_routing_form_schema, | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This component is used to dynamically render app specific options on the route builder. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import type { Route, AttributeRoutingConfig } from "../types/types"; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export default function DynamicAppComponent<T extends Record<string, React.ComponentType<any>>>(props: { | ||
componentMap: T; | ||
slug: string; | ||
appData: any; | ||
route: Route; | ||
setAttributeRoutingConfig: (id: string, attributeRoutingConfig: Partial<AttributeRoutingConfig>) => void; | ||
wrapperClassName?: string; | ||
}) { | ||
const { componentMap, slug, wrapperClassName, appData, route, setAttributeRoutingConfig, ...rest } = props; | ||
|
||
// There can be apps with no matching component | ||
if (!componentMap[slug]) return null; | ||
|
||
const Component = componentMap[slug]; | ||
|
||
return ( | ||
<div className={wrapperClassName || ""}> | ||
<Component | ||
appData={appData} | ||
route={route} | ||
setAttributeRoutingConfig={setAttributeRoutingConfig} | ||
{...rest} | ||
/> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// This is a list of apps which have functionality in routing forms | ||
export const enabledAppSlugs = ["salesforce"]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import { areTheySiblingEntitites } from "@calcom/lib/entityPermissionUtils"; | |
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
import type { App_RoutingForms_Form } from "@calcom/prisma/client"; | ||
import { SchedulingType } from "@calcom/prisma/client"; | ||
import { EventTypeAppMetadataSchema } from "@calcom/prisma/zod-utils"; | ||
import type { RouterOutputs } from "@calcom/trpc/react"; | ||
import { trpc } from "@calcom/trpc/react"; | ||
import type { inferSSRProps } from "@calcom/types/inferSSRProps"; | ||
|
@@ -26,13 +27,16 @@ import { | |
Switch, | ||
} from "@calcom/ui"; | ||
|
||
import { routingFormAppComponents } from "../../appComponents"; | ||
import DynamicAppComponent from "../../components/DynamicAppComponent"; | ||
import type { RoutingFormWithResponseCount } from "../../components/SingleForm"; | ||
import SingleForm, { | ||
getServerSidePropsForSingleFormView as getServerSideProps, | ||
} from "../../components/SingleForm"; | ||
import "../../components/react-awesome-query-builder/styles.css"; | ||
import { RoutingPages } from "../../lib/RoutingPages"; | ||
import { createFallbackRoute } from "../../lib/createFallbackRoute"; | ||
import { enabledAppSlugs } from "../../lib/enabledApps"; | ||
import { | ||
getQueryBuilderConfigForFormFields, | ||
getQueryBuilderConfigForAttributes, | ||
|
@@ -41,31 +45,22 @@ import { | |
} from "../../lib/getQueryBuilderConfig"; | ||
import isRouter from "../../lib/isRouter"; | ||
import type { SerializableForm } from "../../types/types"; | ||
import type { GlobalRoute, LocalRoute, SerializableRoute, Attribute } from "../../types/types"; | ||
import type { | ||
GlobalRoute, | ||
LocalRoute, | ||
SerializableRoute, | ||
Attribute, | ||
EditFormRoute, | ||
LocalRouteWithRaqbStates, | ||
AttributeRoutingConfig, | ||
} from "../../types/types"; | ||
import { RouteActionType } from "../../zod"; | ||
|
||
type FormFieldsQueryBuilderState = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move types to the types file so they can be used in other components |
||
tree: ImmutableTree; | ||
config: FormFieldsQueryBuilderConfigWithRaqbFields; | ||
}; | ||
|
||
type AttributesQueryBuilderState = { | ||
tree: ImmutableTree; | ||
config: AttributesQueryBuilderConfigWithRaqbFields; | ||
}; | ||
|
||
type LocalRouteWithRaqbStates = LocalRoute & { | ||
formFieldsQueryBuilderState: FormFieldsQueryBuilderState; | ||
attributesQueryBuilderState: AttributesQueryBuilderState | null; | ||
fallbackAttributesQueryBuilderState: AttributesQueryBuilderState | null; | ||
}; | ||
|
||
type EventTypesByGroup = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]; | ||
|
||
type Form = inferSSRProps<typeof getServerSideProps>["form"]; | ||
|
||
type Route = LocalRouteWithRaqbStates | GlobalRoute; | ||
type SetRoute = (id: string, route: Partial<Route>) => void; | ||
type SetRoute = (id: string, route: Partial<EditFormRoute>) => void; | ||
|
||
const RoundRobinContactOwnerOverrideSwitch = ({ | ||
route, | ||
|
@@ -96,7 +91,6 @@ const RoundRobinContactOwnerOverrideSwitch = ({ | |
|
||
type AttributesQueryValue = NonNullable<LocalRoute["attributesQueryValue"]>; | ||
type FormFieldsQueryValue = LocalRoute["queryValue"]; | ||
type AttributeRoutingConfig = NonNullable<LocalRoute["attributeRoutingConfig"]>; | ||
|
||
/** | ||
* We need eventTypeId in every redirect url action now for Rerouting to work smoothly. | ||
|
@@ -107,7 +101,7 @@ function useEnsureEventTypeIdInRedirectUrlAction({ | |
eventOptions, | ||
setRoute, | ||
}: { | ||
route: Route; | ||
route: EditFormRoute; | ||
eventOptions: { label: string; value: string; eventTypeId: number }[]; | ||
setRoute: SetRoute; | ||
}) { | ||
|
@@ -134,7 +128,7 @@ function useEnsureEventTypeIdInRedirectUrlAction({ | |
}, [eventOptions, setRoute, route.id, (route as unknown as any).action?.value]); | ||
} | ||
|
||
const hasRules = (route: Route) => { | ||
const hasRules = (route: EditFormRoute) => { | ||
if (isRouter(route)) return false; | ||
route.queryValue.children1 && Object.keys(route.queryValue.children1).length; | ||
}; | ||
|
@@ -169,9 +163,14 @@ const buildEventsData = ({ | |
}: { | ||
eventTypesByGroup: EventTypesByGroup | undefined; | ||
form: Form; | ||
route: Route; | ||
route: EditFormRoute; | ||
}) => { | ||
const eventOptions: { label: string; value: string; eventTypeId: number }[] = []; | ||
const eventOptions: { | ||
label: string; | ||
value: string; | ||
eventTypeId: number; | ||
eventTypeAppMetadata: Record<string, any>; | ||
}[] = []; | ||
const eventTypesMap = new Map< | ||
number, | ||
{ | ||
|
@@ -196,16 +195,34 @@ const buildEventsData = ({ | |
const isRouteAlreadyInUse = isRouter(route) ? false : uniqueSlug === route.action.value; | ||
|
||
// If Event is already in use, we let it be so as to not break the existing setup | ||
|
||
// Pass app data that works with routing forms | ||
const eventTypeAppMetadataParse = EventTypeAppMetadataSchema.safeParse(eventType.metadata?.apps); | ||
const eventTypeAppMetadata: Record<string, any> = {}; | ||
if (eventTypeAppMetadataParse.success) { | ||
const appMetadata = eventTypeAppMetadataParse.data; | ||
|
||
if (appMetadata) { | ||
for (const appSlug of Object.keys(appMetadata)) { | ||
if (enabledAppSlugs.includes(appSlug)) { | ||
eventTypeAppMetadata[appSlug] = appMetadata[appSlug as keyof typeof appMetadata]; | ||
} | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it is in draft but still leaving code feedback. Let's abstract it out |
||
|
||
if (!isRouteAlreadyInUse && !eventTypeValidInContext) { | ||
return; | ||
} | ||
eventTypesMap.set(eventType.id, { | ||
eventTypeAppMetadata, | ||
schedulingType: eventType.schedulingType, | ||
}); | ||
eventOptions.push({ | ||
label: uniqueSlug, | ||
value: uniqueSlug, | ||
eventTypeId: eventType.id, | ||
eventTypeAppMetadata, | ||
}); | ||
}); | ||
}); | ||
|
@@ -230,13 +247,13 @@ const Route = ({ | |
eventTypesByGroup, | ||
}: { | ||
form: Form; | ||
route: Route; | ||
routes: Route[]; | ||
route: EditFormRoute; | ||
routes: EditFormRoute[]; | ||
setRoute: SetRoute; | ||
setAttributeRoutingConfig: (id: string, attributeRoutingConfig: Partial<AttributeRoutingConfig>) => void; | ||
formFieldsQueryBuilderConfig: FormFieldsQueryBuilderConfigWithRaqbFields; | ||
attributesQueryBuilderConfig: AttributesQueryBuilderConfigWithRaqbFields | null; | ||
setRoutes: React.Dispatch<React.SetStateAction<Route[]>>; | ||
setRoutes: React.Dispatch<React.SetStateAction<EditFormRoute[]>>; | ||
fieldIdentifiers: string[]; | ||
moveUp?: { fn: () => void; check: () => boolean } | null; | ||
moveDown?: { fn: () => void; check: () => boolean } | null; | ||
|
@@ -271,7 +288,7 @@ const Route = ({ | |
}); | ||
|
||
const onChangeFormFieldsQuery = ( | ||
route: Route, | ||
route: EditFormRoute, | ||
immutableTree: ImmutableTree, | ||
config: FormFieldsQueryBuilderConfigWithRaqbFields | ||
) => { | ||
|
@@ -283,7 +300,7 @@ const Route = ({ | |
}; | ||
|
||
const onChangeTeamMembersQuery = ( | ||
route: Route, | ||
route: EditFormRoute, | ||
immutableTree: ImmutableTree, | ||
config: AttributesQueryBuilderConfigWithRaqbFields | ||
) => { | ||
|
@@ -295,7 +312,7 @@ const Route = ({ | |
}; | ||
|
||
const onChangeFallbackTeamMembersQuery = ( | ||
route: Route, | ||
route: EditFormRoute, | ||
immutableTree: ImmutableTree, | ||
config: AttributesQueryBuilderConfigWithRaqbFields | ||
) => { | ||
|
@@ -404,12 +421,25 @@ const Route = ({ | |
and use only the Team Members that match the following criteria (matches all by default) | ||
</span> | ||
|
||
{isRoundRobinEventSelectedForRedirect ? ( | ||
{eventTypeRedirectUrlSelectedOption?.eventTypeAppMetadata && | ||
"salesforce" in eventTypeRedirectUrlSelectedOption.eventTypeAppMetadata ? ( | ||
<div className="mt-4 px-2.5"> | ||
<DynamicAppComponent | ||
componentMap={routingFormAppComponents} | ||
slug="salesforce" | ||
appData={eventTypeRedirectUrlSelectedOption?.eventTypeAppMetadata["salesforce"]} | ||
route={route} | ||
setAttributeRoutingConfig={setAttributeRoutingConfig} | ||
/> | ||
</div> | ||
) : null} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: Make this more dynamic and not hard code "salesforce" here |
||
|
||
{/* {isRoundRobinEventSelectedForRedirect ? ( | ||
<RoundRobinContactOwnerOverrideSwitch | ||
route={route} | ||
setAttributeRoutingConfig={setAttributeRoutingConfig} | ||
/> | ||
) : null} | ||
) : null} */} | ||
|
||
<div className="mt-2"> | ||
{route.attributesQueryBuilderState && attributesQueryBuilderConfig && ( | ||
|
@@ -625,7 +655,7 @@ const deserializeRoute = ({ | |
route: Exclude<SerializableRoute, GlobalRoute>; | ||
formFieldsQueryBuilderConfig: FormFieldsQueryBuilderConfigWithRaqbFields; | ||
attributesQueryBuilderConfig: AttributesQueryBuilderConfigWithRaqbFields | null; | ||
}): Route => { | ||
}): EditFormRoute => { | ||
const attributesQueryBuilderState = | ||
route.attributesQueryValue && attributesQueryBuilderConfig | ||
? buildState({ | ||
|
@@ -707,7 +737,7 @@ function useRoutes({ | |
return newRoutes; | ||
}); | ||
|
||
function getRoutesToSave(routes: Route[]) { | ||
function getRoutesToSave(routes: EditFormRoute[]) { | ||
return routes.map((route) => { | ||
if (isRouter(route)) { | ||
return route; | ||
|
@@ -868,7 +898,7 @@ const Routes = ({ | |
}); | ||
} | ||
|
||
const setRoute = (id: string, route: Partial<Route>) => { | ||
const setRoute = (id: string, route: Partial<EditFormRoute>) => { | ||
const index = routes.findIndex((route) => route.id === id); | ||
const existingRoute = routes[index]; | ||
const newRoutes = [...routes]; | ||
|
@@ -990,7 +1020,7 @@ const Routes = ({ | |
id: routerId, | ||
name: option.name, | ||
description: option.description, | ||
} as Route, | ||
} as EditFormRoute, | ||
]); | ||
} | ||
}} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with the components that are rendered on the routing form route builder.