Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f8d2f2b38 | ||
|
|
c3731d9f17 | ||
|
|
6e4b2d8886 | ||
|
|
cf13c06c57 | ||
|
|
e309e72be3 | ||
|
|
b4d804de7c | ||
|
|
f501ef3269 | ||
|
|
79f1074f93 | ||
|
|
48aa6bf489 | ||
|
|
8ed2aacd9e | ||
|
|
dfc5d914fc | ||
|
|
0a11c45065 | ||
|
|
4ae5de5a3a |
@@ -0,0 +1,187 @@
|
|||||||
|
language: en-US
|
||||||
|
tone_instructions: ''
|
||||||
|
early_access: false
|
||||||
|
enable_free_tier: true
|
||||||
|
reviews:
|
||||||
|
profile: chill
|
||||||
|
request_changes_workflow: false
|
||||||
|
high_level_summary: false
|
||||||
|
high_level_summary_placeholder: '@coderabbitai summary'
|
||||||
|
high_level_summary_in_walkthrough: false
|
||||||
|
auto_title_placeholder: '@coderabbitai'
|
||||||
|
auto_title_instructions: ''
|
||||||
|
review_status: true
|
||||||
|
commit_status: true
|
||||||
|
fail_commit_status: false
|
||||||
|
collapse_walkthrough: false
|
||||||
|
changed_files_summary: false
|
||||||
|
sequence_diagrams: false
|
||||||
|
estimate_code_review_effort: false
|
||||||
|
assess_linked_issues: true
|
||||||
|
related_issues: false
|
||||||
|
related_prs: true
|
||||||
|
suggested_labels: false
|
||||||
|
auto_apply_labels: false
|
||||||
|
suggested_reviewers: false
|
||||||
|
auto_assign_reviewers: false
|
||||||
|
poem: false
|
||||||
|
labeling_instructions: []
|
||||||
|
path_filters:
|
||||||
|
- '!**/node_modules/**'
|
||||||
|
- '!**/dist/**'
|
||||||
|
- '!**/build/**'
|
||||||
|
- '!**/.next/**'
|
||||||
|
path_instructions:
|
||||||
|
- path: "**/*.{ts,tsx,js,jsx}"
|
||||||
|
instructions: "Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module."
|
||||||
|
abort_on_close: true
|
||||||
|
disable_cache: false
|
||||||
|
auto_review:
|
||||||
|
enabled: true
|
||||||
|
auto_incremental_review: true
|
||||||
|
ignore_title_keywords: []
|
||||||
|
labels: []
|
||||||
|
drafts: false
|
||||||
|
base_branches: []
|
||||||
|
finishing_touches:
|
||||||
|
docstrings:
|
||||||
|
enabled: true
|
||||||
|
unit_tests:
|
||||||
|
enabled: true
|
||||||
|
pre_merge_checks:
|
||||||
|
docstrings:
|
||||||
|
mode: warning
|
||||||
|
threshold: 80
|
||||||
|
title:
|
||||||
|
mode: warning
|
||||||
|
requirements: ''
|
||||||
|
description:
|
||||||
|
mode: warning
|
||||||
|
issue_assessment:
|
||||||
|
mode: warning
|
||||||
|
tools:
|
||||||
|
ast-grep:
|
||||||
|
rule_dirs: []
|
||||||
|
util_dirs: []
|
||||||
|
essential_rules: true
|
||||||
|
packages: []
|
||||||
|
shellcheck:
|
||||||
|
enabled: true
|
||||||
|
ruff:
|
||||||
|
enabled: true
|
||||||
|
markdownlint:
|
||||||
|
enabled: true
|
||||||
|
github-checks:
|
||||||
|
enabled: true
|
||||||
|
timeout_ms: 180000
|
||||||
|
languagetool:
|
||||||
|
enabled: true
|
||||||
|
enabled_rules: []
|
||||||
|
disabled_rules: []
|
||||||
|
enabled_categories: []
|
||||||
|
disabled_categories: []
|
||||||
|
enabled_only: false
|
||||||
|
level: default
|
||||||
|
biome:
|
||||||
|
enabled: true
|
||||||
|
hadolint:
|
||||||
|
enabled: true
|
||||||
|
swiftlint:
|
||||||
|
enabled: true
|
||||||
|
phpstan:
|
||||||
|
enabled: true
|
||||||
|
level: default
|
||||||
|
phpmd:
|
||||||
|
enabled: true
|
||||||
|
phpcs:
|
||||||
|
enabled: true
|
||||||
|
golangci-lint:
|
||||||
|
enabled: true
|
||||||
|
yamllint:
|
||||||
|
enabled: true
|
||||||
|
gitleaks:
|
||||||
|
enabled: true
|
||||||
|
checkov:
|
||||||
|
enabled: true
|
||||||
|
detekt:
|
||||||
|
enabled: true
|
||||||
|
eslint:
|
||||||
|
enabled: true
|
||||||
|
flake8:
|
||||||
|
enabled: true
|
||||||
|
rubocop:
|
||||||
|
enabled: true
|
||||||
|
buf:
|
||||||
|
enabled: true
|
||||||
|
regal:
|
||||||
|
enabled: true
|
||||||
|
actionlint:
|
||||||
|
enabled: true
|
||||||
|
pmd:
|
||||||
|
enabled: true
|
||||||
|
cppcheck:
|
||||||
|
enabled: true
|
||||||
|
semgrep:
|
||||||
|
enabled: true
|
||||||
|
circleci:
|
||||||
|
enabled: true
|
||||||
|
clippy:
|
||||||
|
enabled: true
|
||||||
|
sqlfluff:
|
||||||
|
enabled: true
|
||||||
|
prismaLint:
|
||||||
|
enabled: true
|
||||||
|
pylint:
|
||||||
|
enabled: true
|
||||||
|
oxc:
|
||||||
|
enabled: true
|
||||||
|
shopifyThemeCheck:
|
||||||
|
enabled: true
|
||||||
|
luacheck:
|
||||||
|
enabled: true
|
||||||
|
brakeman:
|
||||||
|
enabled: true
|
||||||
|
dotenvLint:
|
||||||
|
enabled: true
|
||||||
|
htmlhint:
|
||||||
|
enabled: true
|
||||||
|
checkmake:
|
||||||
|
enabled: true
|
||||||
|
osvScanner:
|
||||||
|
enabled: true
|
||||||
|
chat:
|
||||||
|
art: true
|
||||||
|
auto_reply: true
|
||||||
|
integrations:
|
||||||
|
jira:
|
||||||
|
usage: auto
|
||||||
|
linear:
|
||||||
|
usage: auto
|
||||||
|
knowledge_base:
|
||||||
|
opt_out: false
|
||||||
|
web_search:
|
||||||
|
enabled: true
|
||||||
|
code_guidelines:
|
||||||
|
enabled: true
|
||||||
|
filePatterns: []
|
||||||
|
learnings:
|
||||||
|
scope: auto
|
||||||
|
issues:
|
||||||
|
scope: auto
|
||||||
|
jira:
|
||||||
|
usage: auto
|
||||||
|
project_keys: []
|
||||||
|
linear:
|
||||||
|
usage: auto
|
||||||
|
team_keys: []
|
||||||
|
pull_requests:
|
||||||
|
scope: auto
|
||||||
|
mcp:
|
||||||
|
usage: auto
|
||||||
|
disabled_servers: []
|
||||||
|
code_generation:
|
||||||
|
docstrings:
|
||||||
|
language: en-US
|
||||||
|
path_instructions: []
|
||||||
|
unit_tests:
|
||||||
|
path_instructions: []
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"functions": {
|
|
||||||
"pages/api/slots/*.ts": {
|
|
||||||
"memory": 512
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,42 +3,48 @@
|
|||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
if (process.env.NODE_ENV === "production") {
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN_CLIENT,
|
Sentry.init({
|
||||||
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN_CLIENT,
|
||||||
|
|
||||||
sampleRate: parseFloat(process.env.SENTRY_SAMPLE_RATE ?? "1.0") || 1.0,
|
sampleRate: parseFloat(process.env.SENTRY_SAMPLE_RATE ?? "1.0") || 1.0,
|
||||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||||
tracesSampleRate: parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE ?? "0.0") || 0.0,
|
tracesSampleRate: parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE ?? "0.0") || 0.0,
|
||||||
|
|
||||||
// Define how likely Replay events are sampled.
|
// Define how likely Replay events are sampled.
|
||||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||||
// in development and sample at a lower rate in production
|
// in development and sample at a lower rate in production
|
||||||
// replaysSessionSampleRate: parseFloat(process.env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE ?? "0.0") || 0.0,
|
// replaysSessionSampleRate: parseFloat(process.env.SENTRY_REPLAYS_SESSION_SAMPLE_RATE ?? "0.0") || 0.0,
|
||||||
|
|
||||||
// Define how likely Replay events are sampled when an error occurs.
|
// Define how likely Replay events are sampled when an error occurs.
|
||||||
// replaysOnErrorSampleRate: parseFloat(process.env.SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE ?? "0.0") || 0.0,
|
// replaysOnErrorSampleRate: parseFloat(process.env.SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE ?? "0.0") || 0.0,
|
||||||
|
|
||||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||||
debug: !!process.env.SENTRY_DEBUG,
|
debug: !!process.env.SENTRY_DEBUG,
|
||||||
beforeSend(event) {
|
beforeSend(event) {
|
||||||
if (
|
if (
|
||||||
event.exception?.values?.some(
|
event.exception?.values?.some(
|
||||||
(e) =>
|
(e) =>
|
||||||
// Ignore fake error "UnhandledRejection: Non-Error promise rejection captured with value: Object Not Found Matching Id:3, MethodName:update, ParamCount:4"
|
// Ignore fake error "UnhandledRejection: Non-Error promise rejection captured with value: Object Not Found Matching Id:3, MethodName:update, ParamCount:4"
|
||||||
// Raised GH issue: https://github.com/getsentry/sentry-javascript/issues/3440
|
// Raised GH issue: https://github.com/getsentry/sentry-javascript/issues/3440
|
||||||
e.value?.includes("Non-Error promise rejection captured with") ||
|
e.value?.includes("Non-Error promise rejection captured with") ||
|
||||||
e.value?.includes("Object Not Found Matching Id")
|
e.value?.includes("Object Not Found Matching Id")
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.tags = {
|
event.tags = {
|
||||||
...event.tags,
|
...event.tags,
|
||||||
errorSource: "client",
|
errorSource: "client",
|
||||||
};
|
};
|
||||||
return event;
|
return event;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
|
export function onRouterTransitionStart(url: string, navigationType: "push" | "replace" | "traverse") {
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
Sentry.captureRouterTransitionStart(url, navigationType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
import { type Instrumentation } from 'next'
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
export async function register() {
|
export async function register() {
|
||||||
if (process.env.NEXT_PUBLIC_SENTRY_DSN && process.env.NEXT_RUNTIME === "nodejs") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
await import("./sentry.server.config");
|
if (process.env.NEXT_PUBLIC_SENTRY_DSN && process.env.NEXT_RUNTIME === "nodejs") {
|
||||||
}
|
await import("./sentry.server.config");
|
||||||
|
}
|
||||||
if (process.env.NEXT_PUBLIC_SENTRY_DSN && process.env.NEXT_RUNTIME === "edge") {
|
if (process.env.NEXT_PUBLIC_SENTRY_DSN && process.env.NEXT_RUNTIME === "edge") {
|
||||||
await import("./sentry.edge.config");
|
await import("./sentry.edge.config");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onRequestError = Sentry.captureRequestError;
|
export const onRequestError: Instrumentation.onRequestError = ( err,
|
||||||
|
request,
|
||||||
|
context) => {
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
Sentry.captureRequestError(err, request, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const getEventTypesFromDB = async (id: number) => {
|
|||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
email: true,
|
email: true,
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
|
isPlatformManaged: true,
|
||||||
};
|
};
|
||||||
const eventType = await prisma.eventType.findUnique({
|
const eventType = await prisma.eventType.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@@ -75,6 +76,7 @@ export const getEventTypesFromDB = async (id: number) => {
|
|||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
createdByOAuthClientId: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
workflows: {
|
workflows: {
|
||||||
|
|||||||
@@ -228,16 +228,20 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
? { ...previousBooking, rescheduledBy: bookingInfo.user?.name }
|
? { ...previousBooking, rescheduledBy: bookingInfo.user?.name }
|
||||||
: previousBooking;
|
: previousBooking;
|
||||||
|
|
||||||
|
const isPlatformBooking = eventType.users[0]?.isPlatformManaged || eventType.team?.createdByOAuthClientId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
orgSlug: currentOrgDomain,
|
orgSlug: currentOrgDomain,
|
||||||
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
|
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
|
||||||
hideBranding: await shouldHideBrandingForEvent({
|
hideBranding: isPlatformBooking
|
||||||
eventTypeId: eventType.id,
|
? true
|
||||||
team: eventType.team,
|
: await shouldHideBrandingForEvent({
|
||||||
owner: eventType.users[0] ?? null,
|
eventTypeId: eventType.id,
|
||||||
organizationId: session?.user?.profile?.organizationId ?? session?.user?.org?.id ?? null,
|
team: eventType.team,
|
||||||
}),
|
owner: eventType.users[0] ?? null,
|
||||||
|
organizationId: session?.user?.profile?.organizationId ?? session?.user?.org?.id ?? null,
|
||||||
|
}),
|
||||||
profile,
|
profile,
|
||||||
eventType,
|
eventType,
|
||||||
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
|
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ const OnboardingPage = (props: PageProps) => {
|
|||||||
"dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand:#111827] dark:[--cal-brand:#FFFFFF]",
|
"dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand:#111827] dark:[--cal-brand:#FFFFFF]",
|
||||||
"[--cal-brand-emphasis:#101010] dark:[--cal-brand-emphasis:#e1e1e1]",
|
"[--cal-brand-emphasis:#101010] dark:[--cal-brand-emphasis:#e1e1e1]",
|
||||||
"[--cal-brand-subtle:#9CA3AF]",
|
"[--cal-brand-subtle:#9CA3AF]",
|
||||||
"[--cal-brand-text:#FFFFFF] dark:[--cal-brand-text:#000000]"
|
"[--cal-brand-text:#FFFFFF] dark:[--cal-brand-text:#000000]",
|
||||||
|
"[--cal-brand-accent:#FFFFFF] dark:[--cal-brand-accent:#000000]"
|
||||||
)}
|
)}
|
||||||
data-testid="onboarding"
|
data-testid="onboarding"
|
||||||
key={pathname}>
|
key={pathname}>
|
||||||
|
|||||||
+490
-478
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "5.6.2",
|
"version": "5.6.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"yarn": "3.4.1"
|
"yarn": "3.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@boxyhq/saml-jackson": "1.42.0",
|
"@boxyhq/saml-jackson": "1.52.2",
|
||||||
"@calcom/app-store": "*",
|
"@calcom/app-store": "*",
|
||||||
"@calcom/app-store-cli": "*",
|
"@calcom/app-store-cli": "*",
|
||||||
"@calcom/dayjs": "*",
|
"@calcom/dayjs": "*",
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { wrapApiHandlerWithSentry } from "@sentry/nextjs";
|
|
||||||
|
|
||||||
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
|
||||||
import { highPerfRouter } from "@calcom/trpc/server/routers/viewer/highPerf/_router";
|
|
||||||
|
|
||||||
export default wrapApiHandlerWithSentry(createNextApiHandler(highPerfRouter), "/api/trpc/highPerf/[trpc]");
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
module.exports = {
|
const config = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
config.plugins.autoprefixer = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
|||||||
@@ -41,18 +41,6 @@
|
|||||||
},
|
},
|
||||||
"pages/api/trpc/workflows/[trpc].ts": {
|
"pages/api/trpc/workflows/[trpc].ts": {
|
||||||
"maxDuration": 800
|
"maxDuration": 800
|
||||||
},
|
|
||||||
"pages/api/trpc/highPerf/[trpc].ts": {
|
|
||||||
"memory": 3008
|
|
||||||
},
|
|
||||||
"pages/api/trpc/appRoutingForms/[trpc].ts": {
|
|
||||||
"memory": 2048
|
|
||||||
},
|
|
||||||
"pages/router/embed.tsx": {
|
|
||||||
"memory": 2048
|
|
||||||
},
|
|
||||||
"pages/api/book/event.ts": {
|
|
||||||
"memory": 2048
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,363 +0,0 @@
|
|||||||
import { describe, expect, it, afterEach, vi } from "vitest";
|
|
||||||
|
|
||||||
import { jsonLogicToPrisma } from "../jsonLogicToPrisma";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("jsonLogicToPrisma(Reporting)", () => {
|
|
||||||
describe("Text Operand", () => {
|
|
||||||
it("should support 'Equals' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "A"] }] },
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "A",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support 'Not Equals' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ "!=": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] }] },
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
NOT: {
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "abc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support 'Contains' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ in: ["A", { var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }] }] },
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
string_contains: "A",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support 'Not Contains' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ "!": { in: ["a", { var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }] } }] },
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
NOT: {
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
string_contains: "a",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Number Type", () => {
|
|
||||||
it("should support 'greater than' operator", () => {
|
|
||||||
let prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
">": [
|
|
||||||
{
|
|
||||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
|
||||||
},
|
|
||||||
// Giving a string here to test that it is converted to a number
|
|
||||||
"100",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], gt: 100 } }],
|
|
||||||
});
|
|
||||||
|
|
||||||
prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
">": [
|
|
||||||
{
|
|
||||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
|
||||||
},
|
|
||||||
// A number would also work
|
|
||||||
100,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], gt: 100 } }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("should support 'greater than or equal to' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
">=": [
|
|
||||||
{
|
|
||||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
|
||||||
},
|
|
||||||
// Giving a string here to test that it is converted to a number
|
|
||||||
"100",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], gte: 100 } }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("should support 'less than' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
"<": [
|
|
||||||
{
|
|
||||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
|
||||||
},
|
|
||||||
// Giving a string here to test that it is converted to a number
|
|
||||||
"100",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], lt: 100 } }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("should support 'less than or equal to' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
"<=": [
|
|
||||||
{
|
|
||||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
|
||||||
},
|
|
||||||
// Giving a string here to test that it is converted to a number
|
|
||||||
"100",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], lte: 100 } }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("'Equals' operator should query with string as well as number", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "1"] }] },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("MultiSelect", () => {
|
|
||||||
it("should support 'Equals' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{ all: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, { in: [{ var: "" }, ["C", "D"]] }] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
response: { path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], array_contains: ["C", "D"] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support 'Any In' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ in: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, ["C", "D"]] }] },
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
|
|
||||||
equals: "C",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
|
|
||||||
equals: "D",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support 'Not In' operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: { and: [{ "!": { in: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, ["C", "D"]] } }] },
|
|
||||||
});
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
NOT: {
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
|
|
||||||
equals: "C",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"],
|
|
||||||
equals: "D",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support where All Match ['Equals', 'Equals'] operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
and: [
|
|
||||||
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] },
|
|
||||||
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "a",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "b",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support where Any Match ['Equals', 'Equals'] operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
or: [
|
|
||||||
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "a"] },
|
|
||||||
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "b"] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "a",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
|
||||||
equals: "b",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support where None Match ['Equals', 'Equals'] operator", () => {
|
|
||||||
const prismaWhere = jsonLogicToPrisma({
|
|
||||||
logic: {
|
|
||||||
"!": {
|
|
||||||
or: [
|
|
||||||
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] },
|
|
||||||
{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abcd"] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(prismaWhere).toEqual({
|
|
||||||
NOT: {
|
|
||||||
OR: [
|
|
||||||
{ response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abc" } },
|
|
||||||
{ response: { path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"], equals: "abcd" } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
// It can have many shapes, so just use any and we rely on unit tests to test all those scenarios.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
type LogicData = Partial<Record<keyof typeof OPERATOR_MAP, any>>;
|
|
||||||
type NegatedLogicData = {
|
|
||||||
"!": LogicData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type JsonLogicQuery = {
|
|
||||||
logic: {
|
|
||||||
and?: LogicData[];
|
|
||||||
or?: LogicData[];
|
|
||||||
"!"?: {
|
|
||||||
and?: LogicData[];
|
|
||||||
or?: LogicData[];
|
|
||||||
};
|
|
||||||
} | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PrismaWhere = {
|
|
||||||
AND?: ReturnType<typeof convertQueriesToPrismaWhereClause>[];
|
|
||||||
OR?: ReturnType<typeof convertQueriesToPrismaWhereClause>[];
|
|
||||||
NOT?: PrismaWhere;
|
|
||||||
};
|
|
||||||
|
|
||||||
const OPERATOR_MAP = {
|
|
||||||
"==": {
|
|
||||||
operator: "equals",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
in: {
|
|
||||||
operator: "string_contains",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
"!=": {
|
|
||||||
operator: "NOT.equals",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
"!": {
|
|
||||||
operator: "equals",
|
|
||||||
secondaryOperand: "",
|
|
||||||
},
|
|
||||||
"!!": {
|
|
||||||
operator: "NOT.equals",
|
|
||||||
secondaryOperand: "",
|
|
||||||
},
|
|
||||||
">": {
|
|
||||||
operator: "gt",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
">=": {
|
|
||||||
operator: "gte",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
"<": {
|
|
||||||
operator: "lt",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
"<=": {
|
|
||||||
operator: "lte",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
all: {
|
|
||||||
operator: "array_contains",
|
|
||||||
secondaryOperand: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Operators supported on array of basic queries
|
|
||||||
*/
|
|
||||||
const GROUP_OPERATOR_MAP = {
|
|
||||||
and: "AND",
|
|
||||||
or: "OR",
|
|
||||||
"!": "NOT",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const NumberOperators = [">", ">=", "<", "<="];
|
|
||||||
|
|
||||||
const negatePrismaWhereClauseIfNeeded = (data: any, isNegation: boolean) => {
|
|
||||||
if (!isNegation) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
NOT: {
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertSingleQueryToPrismaWhereClause = (
|
|
||||||
operatorName: keyof typeof OPERATOR_MAP,
|
|
||||||
logicData: LogicData,
|
|
||||||
isNegation: boolean
|
|
||||||
) => {
|
|
||||||
const mappedOperator = OPERATOR_MAP[operatorName].operator;
|
|
||||||
const staticSecondaryOperand = OPERATOR_MAP[operatorName].secondaryOperand;
|
|
||||||
isNegation = isNegation || mappedOperator.startsWith("NOT.");
|
|
||||||
const prismaOperator = mappedOperator.replace("NOT.", "");
|
|
||||||
const operands =
|
|
||||||
logicData[operatorName] instanceof Array ? logicData[operatorName] : [logicData[operatorName]];
|
|
||||||
|
|
||||||
if (operatorName === "in" && operands[0]?.var && Array.isArray(operands[1])) {
|
|
||||||
// Case A: Item "in" array
|
|
||||||
// operands[0]: { var: ... }
|
|
||||||
// operands[1]: items to test against
|
|
||||||
|
|
||||||
// Convert 'in' operator to union of OR clauses
|
|
||||||
return negatePrismaWhereClauseIfNeeded(
|
|
||||||
{
|
|
||||||
OR: (operands[1] ?? []).map((value) => ({
|
|
||||||
response: {
|
|
||||||
path: [operands[0].var, "value"],
|
|
||||||
[`${OPERATOR_MAP["=="].operator}`]: value,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
isNegation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operatorName === "in" && typeof operands[0] === "string" && operands[1]?.var) {
|
|
||||||
// Case B: String "in" string
|
|
||||||
// operands[0]: string to test against
|
|
||||||
// operands[1]: { var: ... }
|
|
||||||
|
|
||||||
return negatePrismaWhereClauseIfNeeded(
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: [operands[1].var, "value"],
|
|
||||||
[`${prismaOperator}`]: operands[0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isNegation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainOperand = operands[0].var;
|
|
||||||
let secondaryOperand;
|
|
||||||
|
|
||||||
if (operatorName === "all") {
|
|
||||||
secondaryOperand = operands[1].in[1];
|
|
||||||
} else {
|
|
||||||
secondaryOperand = staticSecondaryOperand || operands[1] || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNumberOperator = NumberOperators.includes(operatorName);
|
|
||||||
const secondaryOperandAsNumber = typeof secondaryOperand === "string" ? Number(secondaryOperand) : null;
|
|
||||||
|
|
||||||
if (secondaryOperandAsNumber && isNumberOperator) {
|
|
||||||
// We know that it's number operator so Prisma should query number
|
|
||||||
// Note that if we get string values in DB(e.g. '100'), those values can't be filtered with number operators.
|
|
||||||
return negatePrismaWhereClauseIfNeeded(
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: [mainOperand, "value"],
|
|
||||||
[`${prismaOperator}`]: secondaryOperandAsNumber,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isNegation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secondaryOperandAsNumber && !isNumberOperator) {
|
|
||||||
// We know that it's not number operator but the input field might have been a number and thus stored value in DB as number.
|
|
||||||
// Also, even for input type=number we might accidentally get string value(e.g. '100'). So, let reporting do it's best job with both number and string.
|
|
||||||
return negatePrismaWhereClauseIfNeeded(
|
|
||||||
{
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: [mainOperand, "value"],
|
|
||||||
// Query as string e.g. equals '100'
|
|
||||||
[`${prismaOperator}`]: secondaryOperand,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: [mainOperand, "value"],
|
|
||||||
// Query as number e.g. equals 100
|
|
||||||
[`${prismaOperator}`]: secondaryOperandAsNumber,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
isNegation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return negatePrismaWhereClauseIfNeeded(
|
|
||||||
{
|
|
||||||
response: {
|
|
||||||
path: [mainOperand, "value"],
|
|
||||||
[`${prismaOperator}`]: secondaryOperand,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
isNegation
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNegation = (logicData: LogicData | NegatedLogicData) => {
|
|
||||||
if ("!" in logicData) {
|
|
||||||
const negatedLogicData = logicData["!"];
|
|
||||||
|
|
||||||
for (const [operatorName] of Object.entries(OPERATOR_MAP)) {
|
|
||||||
if (negatedLogicData[operatorName]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertQueriesToPrismaWhereClause = (logicData: LogicData) => {
|
|
||||||
const _isNegation = isNegation(logicData);
|
|
||||||
if (_isNegation) {
|
|
||||||
logicData = logicData["!"];
|
|
||||||
}
|
|
||||||
for (const [key] of Object.entries(OPERATOR_MAP)) {
|
|
||||||
const operatorName = key as keyof typeof OPERATOR_MAP;
|
|
||||||
if (logicData[operatorName]) {
|
|
||||||
return convertSingleQueryToPrismaWhereClause(operatorName, logicData, _isNegation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const jsonLogicToPrisma = (query: JsonLogicQuery) => {
|
|
||||||
try {
|
|
||||||
let logic = query.logic;
|
|
||||||
if (!logic) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
let prismaWhere: PrismaWhere = {};
|
|
||||||
let negateLogic = false;
|
|
||||||
|
|
||||||
// Case: Negation of "Any of these"
|
|
||||||
// Example: {"logic":{"!":{"or":[{"==":[{"var":"505d3c3c-aa71-4220-93a9-6fd1e1087939"},"1"]},{"==":[{"var":"505d3c3c-aa71-4220-93a9-6fd1e1087939"},"1"]}]}}}
|
|
||||||
if (logic["!"]) {
|
|
||||||
logic = logic["!"];
|
|
||||||
negateLogic = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case: All of these
|
|
||||||
if (logic.and) {
|
|
||||||
const where: PrismaWhere["AND"] = (prismaWhere[GROUP_OPERATOR_MAP["and"]] = []);
|
|
||||||
logic.and.forEach((and) => {
|
|
||||||
const res = convertQueriesToPrismaWhereClause(and);
|
|
||||||
if (!res) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
where.push(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Case: Any of these
|
|
||||||
else if (logic.or) {
|
|
||||||
const where: PrismaWhere["OR"] = (prismaWhere[GROUP_OPERATOR_MAP["or"]] = []);
|
|
||||||
|
|
||||||
logic.or.forEach((or) => {
|
|
||||||
const res = convertQueriesToPrismaWhereClause(or);
|
|
||||||
if (!res) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
where.push(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (negateLogic) {
|
|
||||||
prismaWhere = { NOT: { ...prismaWhere } };
|
|
||||||
}
|
|
||||||
|
|
||||||
return prismaWhere;
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error converting to prisma `where`", JSON.stringify(query), "Error is ", e);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -84,7 +84,7 @@ export function getQueryBuilderConfigForFormFields(form: Pick<RoutingForm, "fiel
|
|||||||
delete initialConfigCopy.operators.is_empty;
|
delete initialConfigCopy.operators.is_empty;
|
||||||
delete initialConfigCopy.operators.is_not_empty;
|
delete initialConfigCopy.operators.is_not_empty;
|
||||||
|
|
||||||
// Between and Not between aren't directly supported by prisma. So, we need to update jsonLogicToPrisma to generate gte and lte query for between. It can be implemented later.
|
// These operators can be implemented later if needed.
|
||||||
delete initialConfigCopy.operators.between;
|
delete initialConfigCopy.operators.between;
|
||||||
delete initialConfigCopy.operators.not_between;
|
delete initialConfigCopy.operators.not_between;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { ZFormQueryInputSchema } from "./formQuery.schema";
|
|||||||
import { ZGetAttributesForTeamInputSchema } from "./getAttributesForTeam.schema";
|
import { ZGetAttributesForTeamInputSchema } from "./getAttributesForTeam.schema";
|
||||||
import { ZGetIncompleteBookingSettingsInputSchema } from "./getIncompleteBookingSettings.schema";
|
import { ZGetIncompleteBookingSettingsInputSchema } from "./getIncompleteBookingSettings.schema";
|
||||||
import { forms } from "./procedures/forms";
|
import { forms } from "./procedures/forms";
|
||||||
import { ZReportInputSchema } from "./report.schema";
|
|
||||||
import { ZSaveIncompleteBookingSettingsInputSchema } from "./saveIncompleteBookingSettings.schema";
|
import { ZSaveIncompleteBookingSettingsInputSchema } from "./saveIncompleteBookingSettings.schema";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
@@ -76,11 +75,6 @@ const appRoutingForms = router({
|
|||||||
return handler({ ctx, input });
|
return handler({ ctx, input });
|
||||||
}),
|
}),
|
||||||
|
|
||||||
report: authedProcedure.input(ZReportInputSchema).query(async ({ ctx, input }) => {
|
|
||||||
const handler = await getHandler("report", () => import("./report.handler"));
|
|
||||||
return handler({ ctx, input });
|
|
||||||
}),
|
|
||||||
|
|
||||||
getAttributesForTeam: authedProcedure
|
getAttributesForTeam: authedProcedure
|
||||||
.input(ZGetAttributesForTeamInputSchema)
|
.input(ZGetAttributesForTeamInputSchema)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
import { describe, it, expect } from "vitest";
|
|
||||||
|
|
||||||
import { buildResponsesForReporting } from "./report.handler";
|
|
||||||
|
|
||||||
describe("buildResponsesForReporting", () => {
|
|
||||||
it("for fields with options, it should return the labels of the options", () => {
|
|
||||||
const field1Id = uuidv4();
|
|
||||||
const field2Id = uuidv4();
|
|
||||||
|
|
||||||
const responsesFromDb = [
|
|
||||||
{
|
|
||||||
[field1Id]: { value: "value1" },
|
|
||||||
[field2Id]: { value: ["option1", "option2"] },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
{ id: field1Id, label: "Field 1" },
|
|
||||||
{
|
|
||||||
id: field2Id,
|
|
||||||
label: "Field 2",
|
|
||||||
options: [
|
|
||||||
{ id: "option1", label: "Option 1" },
|
|
||||||
{ id: "option2", label: "Option 2" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const expectedResponses = [["value1", "Option 1, Option 2"]];
|
|
||||||
|
|
||||||
const { responses, headers } = buildResponsesForReporting({ responsesFromDb, fields });
|
|
||||||
expect(responses).toEqual(expectedResponses);
|
|
||||||
expect(headers).toEqual(["Field 1", "Field 2"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("for fields with options having null id, it should return what DB provided(as that would be labels only).", () => {
|
|
||||||
const field1Id = uuidv4();
|
|
||||||
const field2Id = uuidv4();
|
|
||||||
const responsesFromDb = [
|
|
||||||
{
|
|
||||||
[field1Id]: { value: "value1" },
|
|
||||||
[field2Id]: { value: ["Option 1", "Option 2"] },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
{ id: field1Id, label: "Field 1" },
|
|
||||||
{
|
|
||||||
id: field2Id,
|
|
||||||
label: "Field 2",
|
|
||||||
options: [
|
|
||||||
{ id: null, label: "Option 1" },
|
|
||||||
{ id: null, label: "Option 2" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const expectedResponses = [["value1", "Option 1, Option 2"]];
|
|
||||||
|
|
||||||
const { responses, headers } = buildResponsesForReporting({ responsesFromDb, fields });
|
|
||||||
expect(responses).toEqual(expectedResponses);
|
|
||||||
expect(headers).toEqual(["Field 1", "Field 2"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("for fields with no options but having array value, it should return the value as is", () => {
|
|
||||||
const field2Id = uuidv4();
|
|
||||||
const responsesFromDb = [
|
|
||||||
{
|
|
||||||
[field2Id]: { value: ["Option 1", "Option 2"] },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
{
|
|
||||||
id: field2Id,
|
|
||||||
label: "Field 2",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const expectedResponses = [["Option 1, Option 2"]];
|
|
||||||
|
|
||||||
const { responses, headers } = buildResponsesForReporting({ responsesFromDb, fields });
|
|
||||||
expect(responses).toEqual(expectedResponses);
|
|
||||||
expect(headers).toEqual(["Field 2"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should correctly handle numeric responses converting them to strings", () => {
|
|
||||||
const field1Id = uuidv4();
|
|
||||||
const field2Id = uuidv4();
|
|
||||||
const responsesFromDb = [
|
|
||||||
{
|
|
||||||
[field1Id]: { value: 1 },
|
|
||||||
[field2Id]: { value: [1, 2] },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
{ id: field1Id, label: "Field 1" },
|
|
||||||
{
|
|
||||||
id: field2Id,
|
|
||||||
label: "Field 2",
|
|
||||||
options: [
|
|
||||||
{ id: null, label: "Option 1" },
|
|
||||||
{ id: null, label: "Option 2" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const expectedResponses = [["1", "1, 2"]];
|
|
||||||
const { responses, headers } = buildResponsesForReporting({ responsesFromDb, fields });
|
|
||||||
expect(responses).toEqual(expectedResponses);
|
|
||||||
expect(headers).toEqual(["Field 1", "Field 2"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty responses", () => {
|
|
||||||
const field1Id = uuidv4();
|
|
||||||
const responsesFromDb = [{}];
|
|
||||||
|
|
||||||
const fields = [{ id: field1Id, label: "Field 1" }];
|
|
||||||
|
|
||||||
const expectedResponses = [[""]];
|
|
||||||
|
|
||||||
const { responses, headers } = buildResponsesForReporting({ responsesFromDb, fields });
|
|
||||||
expect(responses).toEqual(expectedResponses);
|
|
||||||
expect(headers).toEqual(["Field 1"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show correct header for deleted fields", () => {
|
|
||||||
const field1Id = uuidv4();
|
|
||||||
const field2Id = uuidv4();
|
|
||||||
const responsesFromDb = [
|
|
||||||
{
|
|
||||||
[field1Id]: { value: "value1" },
|
|
||||||
[field2Id]: { value: ["Option 1", "Option 2"] },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fields = [
|
|
||||||
{ id: field1Id, label: "Field 1" },
|
|
||||||
{
|
|
||||||
id: field2Id,
|
|
||||||
label: "Field 2",
|
|
||||||
deleted: true,
|
|
||||||
options: [
|
|
||||||
{ id: "option1", label: "Option 1" },
|
|
||||||
{ id: "option2", label: "Option 2" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const expectedResponses = [["value1", "Option 1, Option 2"]];
|
|
||||||
const expectedHeaders = ["Field 1", "Field 2(Deleted)"];
|
|
||||||
|
|
||||||
const { responses, headers } = buildResponsesForReporting({ responsesFromDb, fields });
|
|
||||||
expect(responses).toEqual(expectedResponses);
|
|
||||||
expect(headers).toEqual(expectedHeaders);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
import type { z } from "zod";
|
|
||||||
|
|
||||||
import logger from "@calcom/lib/logger";
|
|
||||||
import type { PrismaClient } from "@calcom/prisma";
|
|
||||||
import type { App_RoutingForms_FormResponse } from "@calcom/prisma/client";
|
|
||||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
import { jsonLogicToPrisma } from "../jsonLogicToPrisma";
|
|
||||||
import { getSerializableForm } from "../lib/getSerializableForm";
|
|
||||||
import { ensureStringOrStringArray, getLabelsFromOptionIds } from "../lib/reportingUtils";
|
|
||||||
import type { FormResponse } from "../types/types";
|
|
||||||
import type { zodFieldView } from "../zod";
|
|
||||||
import type { TReportInputSchema } from "./report.schema";
|
|
||||||
|
|
||||||
interface ReportHandlerOptions {
|
|
||||||
ctx: {
|
|
||||||
prisma: PrismaClient;
|
|
||||||
user: NonNullable<TrpcSessionUser>;
|
|
||||||
};
|
|
||||||
input: TReportInputSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeFormatDate = (locale: string, timeZone: string) => {
|
|
||||||
const formatDate = (date: Date): string => {
|
|
||||||
return new Intl.DateTimeFormat(locale, {
|
|
||||||
timeZone,
|
|
||||||
dateStyle: "medium",
|
|
||||||
timeStyle: "short",
|
|
||||||
}).format(date);
|
|
||||||
};
|
|
||||||
return formatDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRows = async ({ ctx: { prisma }, input }: ReportHandlerOptions) => {
|
|
||||||
// Can be any prisma `where` clause
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const prismaWhere: Record<string, any> = input.jsonLogicQuery
|
|
||||||
? jsonLogicToPrisma(input.jsonLogicQuery)
|
|
||||||
: {};
|
|
||||||
const skip = input.cursor ?? 0;
|
|
||||||
const take = input.limit ? input.limit + 1 : 50;
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Built Prisma where ${JSON.stringify(prismaWhere)} from jsonLogicQuery ${JSON.stringify(
|
|
||||||
input.jsonLogicQuery
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
const rows = await prisma.app_RoutingForms_FormResponse.findMany({
|
|
||||||
where: {
|
|
||||||
formId: input.formId,
|
|
||||||
...prismaWhere,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
routedToBooking: {
|
|
||||||
select: {
|
|
||||||
createdAt: true,
|
|
||||||
user: {
|
|
||||||
select: { id: true, name: true, email: true },
|
|
||||||
},
|
|
||||||
assignmentReason: {
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdAt: "desc",
|
|
||||||
},
|
|
||||||
take,
|
|
||||||
skip,
|
|
||||||
});
|
|
||||||
return { skip, take, rows };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reportHandler = async (options: ReportHandlerOptions) => {
|
|
||||||
const {
|
|
||||||
ctx: { prisma },
|
|
||||||
input,
|
|
||||||
} = options;
|
|
||||||
const form = await prisma.app_RoutingForms_Form.findUnique({
|
|
||||||
where: {
|
|
||||||
id: input.formId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!form) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Form not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// TODO: Second argument is required to return deleted operators.
|
|
||||||
const serializedForm = await getSerializableForm({ form, withDeletedFields: true });
|
|
||||||
const { rows, skip, take } = await getRows(options);
|
|
||||||
|
|
||||||
const fields = serializedForm?.fields || [];
|
|
||||||
const { responses, headers } = buildResponsesForReporting({
|
|
||||||
responsesFromDb: rows.map((r) => r.response),
|
|
||||||
fields,
|
|
||||||
});
|
|
||||||
|
|
||||||
const areThereNoResultsOrLessThanAskedFor = !rows.length || rows.length < take;
|
|
||||||
return presenter({
|
|
||||||
rows,
|
|
||||||
options,
|
|
||||||
headers,
|
|
||||||
responses,
|
|
||||||
nextCursor: areThereNoResultsOrLessThanAskedFor ? null : skip + rows.length,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a temporary solution to make the report work. It should be incorporated into the report data itself.
|
|
||||||
* Right now we cannot filter by Routed To and Booked At because they are not part of the response data.
|
|
||||||
*/
|
|
||||||
function presenter(args: {
|
|
||||||
rows: Awaited<ReturnType<typeof getRows>>["rows"];
|
|
||||||
options: ReportHandlerOptions;
|
|
||||||
headers: string[];
|
|
||||||
responses: string[][];
|
|
||||||
nextCursor: number | null;
|
|
||||||
}) {
|
|
||||||
const { headers, responses, nextCursor, options, rows } = args;
|
|
||||||
const { ctx } = options;
|
|
||||||
const formatDate = makeFormatDate(ctx.user.locale, ctx.user.timeZone);
|
|
||||||
return {
|
|
||||||
nextCursor,
|
|
||||||
headers: [...headers, "Routed To", "Assignment Reason", "Booked At", "Submitted At"],
|
|
||||||
responses: responses.map((r, i) => {
|
|
||||||
const currentRow = rows[i];
|
|
||||||
return [
|
|
||||||
...r,
|
|
||||||
currentRow.routedToBooking?.user?.email || "",
|
|
||||||
currentRow.routedToBooking?.assignmentReason.length
|
|
||||||
? currentRow.routedToBooking.assignmentReason[0].reasonString
|
|
||||||
: "",
|
|
||||||
currentRow.routedToBooking?.createdAt ? formatDate(currentRow.routedToBooking.createdAt) : "",
|
|
||||||
formatDate(currentRow.createdAt),
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default reportHandler;
|
|
||||||
|
|
||||||
export function buildResponsesForReporting({
|
|
||||||
responsesFromDb,
|
|
||||||
fields,
|
|
||||||
}: {
|
|
||||||
responsesFromDb: App_RoutingForms_FormResponse["response"][];
|
|
||||||
fields: Pick<z.infer<typeof zodFieldView>, "id" | "options" | "label" | "deleted">[];
|
|
||||||
}) {
|
|
||||||
const headers = fields.map((f) => f.label + (f.deleted ? "(Deleted)" : ""));
|
|
||||||
const responses: string[][] = [];
|
|
||||||
responsesFromDb.forEach((r) => {
|
|
||||||
const rowResponses: string[] = [];
|
|
||||||
responses.push(rowResponses);
|
|
||||||
fields.forEach((field) => {
|
|
||||||
if (!r) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = r as FormResponse;
|
|
||||||
const value = response[field.id]?.value || "";
|
|
||||||
if (field.options) {
|
|
||||||
const optionIds = ensureStringOrStringArray(value);
|
|
||||||
const labels = getLabelsFromOptionIds({ options: field.options, optionIds });
|
|
||||||
rowResponses.push(labels.join(", "));
|
|
||||||
} else {
|
|
||||||
const arrayOfValues = value instanceof Array ? value : [value];
|
|
||||||
const transformedValue = arrayOfValues.join(", ");
|
|
||||||
rowResponses.push(transformedValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return { responses, headers };
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import z from "zod";
|
|
||||||
|
|
||||||
export const ZReportInputSchema = z.object({
|
|
||||||
limit: z.number().default(50),
|
|
||||||
formId: z.string(),
|
|
||||||
jsonLogicQuery: z.object({
|
|
||||||
logic: z.union([z.record(z.any()), z.null()]),
|
|
||||||
}),
|
|
||||||
cursor: z.number().nullish(), // <-- "cursor" needs to exist when using useInfiniteQuery, but can be any type
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TReportInputSchema = z.infer<typeof ZReportInputSchema>;
|
|
||||||
@@ -1919,7 +1919,7 @@ async function handler(
|
|||||||
|
|
||||||
if (!isDryRun) {
|
if (!isDryRun) {
|
||||||
sendRoundRobinRescheduledEmailsAndSMS(
|
sendRoundRobinRescheduledEmailsAndSMS(
|
||||||
copyEventAdditionalInfo,
|
{ ...copyEventAdditionalInfo, iCalUID },
|
||||||
rescheduledMembers,
|
rescheduledMembers,
|
||||||
eventType.metadata
|
eventType.metadata
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ const opts: JacksonOption = {
|
|||||||
},
|
},
|
||||||
idpEnabled: true,
|
idpEnabled: true,
|
||||||
clientSecretVerifier,
|
clientSecretVerifier,
|
||||||
ory: {
|
|
||||||
projectId: undefined,
|
|
||||||
sdkToken: undefined,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -136,13 +136,11 @@ export const useSchedule = ({
|
|||||||
eventTypeId: eventId ?? undefined,
|
eventTypeId: eventId ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const schedule = isTeamEvent
|
const schedule = trpc.viewer.slots.getSchedule.useQuery(input, {
|
||||||
? trpc.viewer.highPerf.getTeamSchedule.useQuery(input, {
|
...options,
|
||||||
...options,
|
// Only enable if we're not using API V2
|
||||||
// Only enable if we're not using API V2
|
enabled: options.enabled && !isCallingApiV2Slots,
|
||||||
enabled: options.enabled && !isCallingApiV2Slots,
|
});
|
||||||
})
|
|
||||||
: trpc.viewer.slots.getSchedule.useQuery(input, options);
|
|
||||||
|
|
||||||
if (isCallingApiV2Slots && !teamScheduleV2.failureReason) {
|
if (isCallingApiV2Slots && !teamScheduleV2.failureReason) {
|
||||||
updateEmbedBookerState({
|
updateEmbedBookerState({
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const user: User & { credentials: CredentialPayload[] } = {
|
|||||||
timeFormat: 12,
|
timeFormat: 12,
|
||||||
travelSchedules: [],
|
travelSchedules: [],
|
||||||
locked: false,
|
locked: false,
|
||||||
|
isPlatformManaged: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const customInputs: CustomInputSchema[] = [];
|
const customInputs: CustomInputSchema[] = [];
|
||||||
|
|||||||
Binary file not shown.
@@ -10,6 +10,7 @@ export const availabilityUserSelect = {
|
|||||||
endTime: true,
|
endTime: true,
|
||||||
timeFormat: true,
|
timeFormat: true,
|
||||||
defaultScheduleId: true,
|
defaultScheduleId: true,
|
||||||
|
isPlatformManaged: true,
|
||||||
// Relationships
|
// Relationships
|
||||||
schedules: {
|
schedules: {
|
||||||
select: {
|
select: {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export const ENDPOINTS = [
|
|||||||
"dsync",
|
"dsync",
|
||||||
"eventTypes",
|
"eventTypes",
|
||||||
"features",
|
"features",
|
||||||
"highPerf",
|
|
||||||
"i18n",
|
"i18n",
|
||||||
"insights",
|
"insights",
|
||||||
"me",
|
"me",
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { dsyncRouter } from "./dsync/_router";
|
|||||||
import { eventTypesRouter } from "./eventTypes/_router";
|
import { eventTypesRouter } from "./eventTypes/_router";
|
||||||
import { filterSegmentsRouter } from "./filterSegments/_router";
|
import { filterSegmentsRouter } from "./filterSegments/_router";
|
||||||
import { googleWorkspaceRouter } from "./googleWorkspace/_router";
|
import { googleWorkspaceRouter } from "./googleWorkspace/_router";
|
||||||
import { highPerfRouter } from "./highPerf/_router";
|
|
||||||
import { i18nRouter } from "./i18n/_router";
|
import { i18nRouter } from "./i18n/_router";
|
||||||
import { meRouter } from "./me/_router";
|
import { meRouter } from "./me/_router";
|
||||||
import { oAuthRouter } from "./oAuth/_router";
|
import { oAuthRouter } from "./oAuth/_router";
|
||||||
@@ -79,7 +78,6 @@ export const viewerRouter = router({
|
|||||||
googleWorkspace: googleWorkspaceRouter,
|
googleWorkspace: googleWorkspaceRouter,
|
||||||
admin: adminRouter,
|
admin: adminRouter,
|
||||||
attributes: attributesRouter,
|
attributes: attributesRouter,
|
||||||
highPerf: highPerfRouter,
|
|
||||||
routingForms: routingFormsRouter,
|
routingForms: routingFormsRouter,
|
||||||
credits: creditsRouter,
|
credits: creditsRouter,
|
||||||
ooo: oooRouter,
|
ooo: oooRouter,
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import publicProcedure from "../../../procedures/publicProcedure";
|
|
||||||
import { router } from "../../../trpc";
|
|
||||||
import { ZGetTeamScheduleInputSchema } from "./getTeamSchedule.schema";
|
|
||||||
|
|
||||||
type SlotsRouterHandlerCache = {
|
|
||||||
getTeamSchedule?: typeof import("./getTeamSchedule.handler").getTeamScheduleHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** This should be called getAvailableSlots */
|
|
||||||
export const highPerfRouter = router({
|
|
||||||
getTeamSchedule: publicProcedure.input(ZGetTeamScheduleInputSchema).query(async ({ input, ctx }) => {
|
|
||||||
const { getTeamScheduleHandler } = await import("./getTeamSchedule.handler");
|
|
||||||
|
|
||||||
return getTeamScheduleHandler({
|
|
||||||
ctx,
|
|
||||||
input,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { IncomingMessage } from "http";
|
|
||||||
|
|
||||||
import { getAvailableSlotsService } from "@calcom/lib/di/containers/AvailableSlots";
|
|
||||||
|
|
||||||
import type { TGetTeamScheduleInputSchema } from "./getTeamSchedule.schema";
|
|
||||||
|
|
||||||
export type GetTeamScheduleOptions = {
|
|
||||||
ctx?: ContextForGetSchedule;
|
|
||||||
input: TGetTeamScheduleInputSchema;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ContextForGetSchedule extends Record<string, unknown> {
|
|
||||||
req?: (IncomingMessage & { cookies: Partial<{ [key: string]: string }> }) | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTeamScheduleHandler = async ({ ctx, input }: GetTeamScheduleOptions) => {
|
|
||||||
const availableSlotsService = getAvailableSlotsService();
|
|
||||||
return await availableSlotsService.getAvailableSlots({ ctx, input });
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { z } from "zod";
|
|
||||||
|
|
||||||
import { getScheduleSchema } from "../slots/types";
|
|
||||||
|
|
||||||
export const ZGetTeamScheduleInputSchema = getScheduleSchema;
|
|
||||||
|
|
||||||
export type TGetTeamScheduleInputSchema = z.infer<typeof ZGetTeamScheduleInputSchema>;
|
|
||||||
@@ -165,7 +165,7 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function
|
|||||||
"w-full min-w-0 truncate border-0 bg-transparent focus:outline-none focus:ring-0",
|
"w-full min-w-0 truncate border-0 bg-transparent focus:outline-none focus:ring-0",
|
||||||
"text-default rounded-lg text-sm font-medium leading-none",
|
"text-default rounded-lg text-sm font-medium leading-none",
|
||||||
"placeholder:text-muted disabled:cursor-not-allowed disabled:bg-transparent",
|
"placeholder:text-muted disabled:cursor-not-allowed disabled:bg-transparent",
|
||||||
addOnLeading && "pl-0.5 pr-0",
|
addOnLeading && "rounded-none pl-0.5 pr-0",
|
||||||
addOnSuffix && "pl-0",
|
addOnSuffix && "pl-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user