From 78393a2b8bf1c231d48411464637f18ca8fad678 Mon Sep 17 00:00:00 2001
From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Date: Mon, 26 Jun 2023 06:37:59 -0400
Subject: [PATCH] feat: disable workflows and webhooks for orgs + UI changes
[CAL-1873] (#9504)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Leo Giovanetti
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom Balhara
Co-authored-by: Udit Takkar
Co-authored-by: CarinaWolli
Co-authored-by: zomars
Co-authored-by: Efraín Rochín
---
.../ee/teams/components/MemberListItem.tsx | 3 +-
.../ee/teams/pages/team-profile-view.tsx | 18 +--
.../ee/workflows/components/DeleteDialog.tsx | 2 +-
.../ee/workflows/components/EmptyScreen.tsx | 15 +-
.../workflows/components/WorkflowListPage.tsx | 62 +++++---
.../features/ee/workflows/pages/index.tsx | 123 +++++----------
.../features/ee/workflows/pages/workflow.tsx | 2 +-
.../filters/components/TeamsFilter.tsx | 45 +++---
.../features/webhooks/pages/webhooks-view.tsx | 104 +++----------
.../viewer/eventTypes/getByViewer.handler.ts | 12 +-
.../viewer/webhook/getByViewer.handler.ts | 51 +++++--
.../routers/viewer/workflows/_router.tsx | 17 ++-
.../viewer/workflows/filteredList.handler.tsx | 143 ++++++++++++++++++
.../viewer/workflows/filteredList.schema.tsx | 11 ++
.../routers/viewer/workflows/get.handler.ts | 1 +
.../viewer/workflows/getByViewer.handler.ts | 121 ---------------
.../viewer/workflows/getByViewer.schema.ts | 1 -
.../viewer/workflows/update.handler.ts | 1 +
packages/ui/components/avatar/Avatar.tsx | 3 +-
.../CreateButtonWithTeamsList.tsx | 8 +-
20 files changed, 362 insertions(+), 381 deletions(-)
create mode 100644 packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx
create mode 100644 packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx
delete mode 100644 packages/trpc/server/routers/viewer/workflows/getByViewer.handler.ts
delete mode 100644 packages/trpc/server/routers/viewer/workflows/getByViewer.schema.ts
diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx
index d0d1466cfe..178650f6b5 100644
--- a/packages/features/ee/teams/components/MemberListItem.tsx
+++ b/packages/features/ee/teams/components/MemberListItem.tsx
@@ -1,6 +1,5 @@
import classNames from "classnames";
import { signIn } from "next-auth/react";
-
import { useState } from "react";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -231,7 +230,7 @@ export default function MemberListItem(props: Props) {
diff --git a/packages/features/ee/teams/pages/team-profile-view.tsx b/packages/features/ee/teams/pages/team-profile-view.tsx
index 9923a268f0..c88bb30aed 100644
--- a/packages/features/ee/teams/pages/team-profile-view.tsx
+++ b/packages/features/ee/teams/pages/team-profile-view.tsx
@@ -328,40 +328,40 @@ const ProfileView = () => {
)}
>
- ):(
+ ) : (
<>
-
+
-
+
-
+
-
- >
+
+ >
)}
>
);
diff --git a/packages/features/ee/workflows/components/DeleteDialog.tsx b/packages/features/ee/workflows/components/DeleteDialog.tsx
index ddb1848799..433dc30c24 100644
--- a/packages/features/ee/workflows/components/DeleteDialog.tsx
+++ b/packages/features/ee/workflows/components/DeleteDialog.tsx
@@ -19,7 +19,7 @@ export const DeleteDialog = (props: IDeleteDialog) => {
const deleteMutation = trpc.viewer.workflows.delete.useMutation({
onSuccess: async () => {
- await utils.viewer.workflows.list.invalidate();
+ await utils.viewer.workflows.filteredList.invalidate();
additionalFunction();
showToast(t("workflow_deleted_successfully"), "success");
setIsOpenDialog(false);
diff --git a/packages/features/ee/workflows/components/EmptyScreen.tsx b/packages/features/ee/workflows/components/EmptyScreen.tsx
index d3a7bfaff1..d8a62e0b92 100644
--- a/packages/features/ee/workflows/components/EmptyScreen.tsx
+++ b/packages/features/ee/workflows/components/EmptyScreen.tsx
@@ -4,7 +4,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import type { SVGComponent } from "@calcom/types/SVGComponent";
-import { CreateButton, showToast, EmptyScreen as ClassicEmptyScreen } from "@calcom/ui";
+import { showToast, EmptyScreen as ClassicEmptyScreen, CreateButtonWithTeamsList } from "@calcom/ui";
import { Smartphone, Mail, Zap } from "@calcom/ui/components/icon";
type WorkflowExampleType = {
@@ -31,15 +31,7 @@ function WorkflowExample(props: WorkflowExampleType) {
);
}
-export default function EmptyScreen(props: {
- profileOptions: {
- label: string | null;
- image?: string | null;
- teamId: number | null | undefined;
- slug: string | null;
- }[];
- isFilteredView: boolean;
-}) {
+export default function EmptyScreen(props: { isFilteredView: boolean }) {
const { t } = useLocale();
const router = useRouter();
@@ -86,9 +78,8 @@ export default function EmptyScreen(props: {
{t("no_workflows_description")}
-
createMutation.mutate({ teamId })}
buttonText={t("create_workflow")}
isLoading={createMutation.isLoading}
diff --git a/packages/features/ee/workflows/components/WorkflowListPage.tsx b/packages/features/ee/workflows/components/WorkflowListPage.tsx
index e873cbf997..83badc1fea 100644
--- a/packages/features/ee/workflows/components/WorkflowListPage.tsx
+++ b/packages/features/ee/workflows/components/WorkflowListPage.tsx
@@ -4,6 +4,8 @@ import { useRouter } from "next/router";
import { useState } from "react";
import classNames from "@calcom/lib/classNames";
+import { CAL_URL } from "@calcom/lib/constants";
+import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
@@ -16,12 +18,14 @@ import {
DropdownMenuTrigger,
Tooltip,
Badge,
+ Avatar,
} from "@calcom/ui";
import { Edit2, Link as LinkIcon, MoreHorizontal, Trash2 } from "@calcom/ui/components/icon";
+import { useOrgBrandingValues } from "../../organizations/hooks";
+import { subdomainSuffix } from "../../organizations/lib/orgDomains";
import { getActionIcon } from "../lib/getActionIcon";
import { DeleteDialog } from "./DeleteDialog";
-import EmptyScreen from "./EmptyScreen";
export type WorkflowType = Workflow & {
team: {
@@ -29,6 +33,7 @@ export type WorkflowType = Workflow & {
name: string;
members: Membership[];
slug: string | null;
+ logo?: string | null;
} | null;
steps: WorkflowStep[];
activeOn: {
@@ -45,21 +50,17 @@ export type WorkflowType = Workflow & {
};
interface Props {
workflows: WorkflowType[] | undefined;
- profileOptions: {
- image?: string | null;
- label: string | null;
- teamId: number | null | undefined;
- slug: string | null;
- }[];
- hasNoWorkflows?: boolean;
}
-export default function WorkflowListPage({ workflows, profileOptions, hasNoWorkflows }: Props) {
+export default function WorkflowListPage({ workflows }: Props) {
const { t } = useLocale();
const utils = trpc.useContext();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [workflowToDeleteId, setwWorkflowToDeleteId] = useState(0);
const router = useRouter();
+ const orgBranding = useOrgBrandingValues();
+ const urlPrefix = orgBranding ? `${orgBranding.slug}.${subdomainSuffix()}` : CAL_URL;
+
return (
<>
{workflows && workflows.length > 0 ? (
@@ -143,16 +144,41 @@ export default function WorkflowListPage({ workflows, profileOptions, hasNoWorkf
)}
- {workflow.teamId && (
-
-
- <>{workflow.team?.name}>
-
-
- )}
+
+ {workflow.team?.name && (
+
+
+ <>{workflow.team.name}>
+
+
+ )}
+
+
+
+ {workflow.team?.name && (
+
+
+ {workflow.team.name}
+
+ )}
+
+
@@ -222,12 +248,12 @@ export default function WorkflowListPage({ workflows, profileOptions, hasNoWorkf
setIsOpenDialog={setDeleteDialogOpen}
workflowId={workflowToDeleteId}
additionalFunction={async () => {
- await utils.viewer.workflows.list.invalidate();
+ await utils.viewer.workflows.filteredList.invalidate();
}}
/>
) : (
-
+ <>>
)}
>
);
diff --git a/packages/features/ee/workflows/pages/index.tsx b/packages/features/ee/workflows/pages/index.tsx
index 2331e1d52a..f309d8d5fc 100644
--- a/packages/features/ee/workflows/pages/index.tsx
+++ b/packages/features/ee/workflows/pages/index.tsx
@@ -1,7 +1,7 @@
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import type { Dispatch, SetStateAction } from "react";
-import { useState, useEffect } from "react";
+import { useState } from "react";
import Shell from "@calcom/features/shell/Shell";
import { classNames } from "@calcom/lib";
@@ -9,11 +9,14 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
-import { AnimatedPopover, Avatar, CreateButton, showToast } from "@calcom/ui";
+import { AnimatedPopover, Avatar, showToast, CreateButtonWithTeamsList } from "@calcom/ui";
+import { FilterResults } from "../../../filters/components/FilterResults";
+import { TeamsFilter } from "../../../filters/components/TeamsFilter";
+import { getTeamsFiltersFromQuery } from "../../../filters/lib/getTeamsFiltersFromQuery";
import LicenseRequired from "../../common/components/LicenseRequired";
+import EmptyScreen from "../components/EmptyScreen";
import SkeletonLoader from "../components/SkeletonLoaderList";
-import type { WorkflowType } from "../components/WorkflowListPage";
import WorkflowList from "../components/WorkflowListPage";
function WorkflowsPage() {
@@ -25,9 +28,11 @@ function WorkflowsPage() {
teamIds: [],
});
- const { data: allWorkflowsData, isLoading } = trpc.viewer.workflows.list.useQuery();
+ const filters = getTeamsFiltersFromQuery(router.query);
- const [filteredWorkflows, setFilteredWorkflows] = useState
([]);
+ const queryRes = trpc.viewer.workflows.filteredList.useQuery({
+ filters,
+ });
const createMutation = trpc.viewer.workflows.create.useMutation({
onSuccess: async ({ workflow }) => {
@@ -46,50 +51,6 @@ function WorkflowsPage() {
},
});
- const query = trpc.viewer.workflows.getByViewer.useQuery();
-
- useEffect(() => {
- const allWorkflows = allWorkflowsData?.workflows;
- if (allWorkflows && allWorkflows.length > 0) {
- const filtered = allWorkflows.filter((workflow) => {
- if (checkedFilterItems.teamIds.includes(workflow.teamId || 0)) return workflow;
- if (!workflow.teamId) {
- if (!!workflow.userId && workflow.userId === checkedFilterItems.userId) return workflow;
- }
- });
- setFilteredWorkflows(filtered);
- } else {
- setFilteredWorkflows([]);
- }
- }, [checkedFilterItems, allWorkflowsData]);
-
- useEffect(() => {
- if (session.status !== "loading" && !query.isLoading) {
- if (!query.data) return;
- setCheckedFilterItems({
- userId: session.data?.user.id || null,
- teamIds: query.data.profiles
- .map((profile) => {
- if (!!profile.teamId) {
- return profile.teamId;
- }
- })
- .filter((teamId): teamId is number => !!teamId),
- });
- }
- }, [session.status, query.isLoading, allWorkflowsData]);
-
- const profileOptions = query?.data?.profiles
- .filter((profile) => !profile.readOnly)
- .map((profile) => {
- return {
- teamId: profile.teamId,
- label: profile.name || profile.slug,
- image: profile.image,
- slug: profile.slug,
- };
- });
-
return (
{
createMutation.mutate({ teamId });
}}
isLoading={createMutation.isLoading}
disableMobileButton={true}
+ onlyShowWithNoTeams={true}
/>
) : null
}>
- {isLoading ? (
-
- ) : (
- <>
- {query?.data?.profiles &&
- query?.data?.profiles.length > 1 &&
- allWorkflowsData?.workflows &&
- allWorkflowsData.workflows.length &&
- profileOptions ? (
-
-
+ {queryRes.data?.totalCount ? (
+
+
+
+
createMutation.mutate({ teamId })}
+ isLoading={createMutation.isLoading}
+ disableMobileButton={true}
+ onlyShowWithTeams={true}
/>
-
- createMutation.mutate({ teamId })}
- isLoading={createMutation.isLoading}
- disableMobileButton={true}
- />
-
- ) : null}
- {profileOptions && profileOptions?.length ? (
-
- ) : null}
- >
- )}
+
+ ) : null}
+ }
+ noResultsScreen={}
+ SkeletonLoader={SkeletonLoader}>
+
+
+ >
);
diff --git a/packages/features/ee/workflows/pages/workflow.tsx b/packages/features/ee/workflows/pages/workflow.tsx
index fb5868e431..3c066c733e 100644
--- a/packages/features/ee/workflows/pages/workflow.tsx
+++ b/packages/features/ee/workflows/pages/workflow.tsx
@@ -271,7 +271,7 @@ function WorkflowPage() {
{workflow && workflow.team && (
- {workflow.team.slug}
+ {workflow.team.name}
)}
{readOnly && (
diff --git a/packages/features/filters/components/TeamsFilter.tsx b/packages/features/filters/components/TeamsFilter.tsx
index 6e070c50fd..81b473021e 100644
--- a/packages/features/filters/components/TeamsFilter.tsx
+++ b/packages/features/filters/components/TeamsFilter.tsx
@@ -5,6 +5,7 @@ import { forwardRef } from "react";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
+import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import type { RouterOutputs } from "@calcom/trpc/react";
import { AnimatedPopover, Avatar, Divider, VerticalDivider } from "@calcom/ui";
@@ -84,28 +85,30 @@ export const TeamsFilter = ({
label={t("yours")}
/>
- {teams?.map((team) => (
- {
- if (e.target.checked) {
- pushItemToKey("teamIds", team.id);
- } else if (!e.target.checked) {
- removeItemByKeyAndValue("teamIds", team.id);
+ {teams
+ ?.filter((team) => !teamMetadataSchema.parse(team.metadata)?.isOrganization)
+ .map((team) => (
+ {
+ if (e.target.checked) {
+ pushItemToKey("teamIds", team.id);
+ } else if (!e.target.checked) {
+ removeItemByKeyAndValue("teamIds", team.id);
+ }
+ }}
+ icon={
+
}
- }}
- icon={
-
- }
- />
- ))}
+ />
+ ))}
{showVerticalDivider && }
diff --git a/packages/features/webhooks/pages/webhooks-view.tsx b/packages/features/webhooks/pages/webhooks-view.tsx
index 7cca44f188..a74125bfe4 100644
--- a/packages/features/webhooks/pages/webhooks-view.tsx
+++ b/packages/features/webhooks/pages/webhooks-view.tsx
@@ -1,4 +1,3 @@
-import Link from "next/link";
import { useRouter } from "next/router";
import { Suspense } from "react";
@@ -6,20 +5,9 @@ import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { WebhooksByViewer } from "@calcom/trpc/server/routers/viewer/webhook/getByViewer.handler";
-import {
- Button,
- Meta,
- SkeletonText,
- EmptyScreen,
- Dropdown,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownItem,
- DropdownMenuLabel,
-} from "@calcom/ui";
+import { Meta, EmptyScreen, CreateButtonWithTeamsList } from "@calcom/ui";
import { Avatar } from "@calcom/ui";
-import { Plus, Link as LinkIcon } from "@calcom/ui/components/icon";
+import { Link as LinkIcon } from "@calcom/ui/components/icon";
import { getLayout } from "../../settings/layouts/SettingsLayout";
import { WebhookListItem, WebhookListSkeleton } from "../components";
@@ -33,14 +21,24 @@ const WebhooksView = () => {
enabled: router.isReady,
});
- const profiles = data?.profiles.filter((profile) => !profile.readOnly);
-
return (
<>
0 ? : <>>}
+ CTA={
+ data && data.webhookGroups.length > 0 ? (
+ {
+ router.push(`webhooks/new${teamId ? `?teamId=${teamId}` : ""}`);
+ }}
+ data-testid="new_webhook"
+ />
+ ) : (
+ <>>
+ )
+ }
/>
}>
@@ -51,68 +49,6 @@ const WebhooksView = () => {
);
};
-const NewWebhookButton = ({
- teamId,
- profiles,
-}: {
- teamId?: number | null;
- profiles?: {
- readOnly?: boolean | undefined;
- slug: string | null;
- name: string | null;
- image?: string | undefined;
- teamId: number | null | undefined;
- }[];
-}) => {
- const { t, isLocaleReady } = useLocale();
-
- const url = new URL(`${WEBAPP_URL}/settings/developer/webhooks/new`);
- if (!!teamId) {
- url.searchParams.set("teamId", `${teamId}`);
- }
- const href = url.href;
-
- if (!profiles || profiles.length < 2) {
- return (
-
- );
- }
- return (
-
-
-
-
-
-
- {t("create_for").toUpperCase()}
-
- {profiles.map((profile, idx) => (
-
- (
-
- )}>
-
- {profile.name}
-
-
-
- ))}
-
-
- );
-};
-
const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer }) => {
const { t } = useLocale();
const router = useRouter();
@@ -166,7 +102,15 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
Icon={LinkIcon}
headline={t("create_your_first_webhook")}
description={t("create_your_first_webhook_description", { appName: APP_NAME })}
- buttonRaw={
}
+ buttonRaw={
+
{
+ router.push(`webhooks/new${teamId ? `?teamId=${teamId}` : ""}`);
+ }}
+ data-testid="new_webhook"
+ />
+ }
/>
)}
>
diff --git a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts
index d7e36a9598..ac04f17b3e 100644
--- a/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts
+++ b/packages/trpc/server/routers/viewer/eventTypes/getByViewer.handler.ts
@@ -68,6 +68,12 @@ const eventTypeSelect = Prisma.validator()({
...baseEventTypeSelect,
});
+export const compareMembership = (mship1: MembershipRole, mship2: MembershipRole) => {
+ const mshipToNumber = (mship: MembershipRole) =>
+ Object.keys(MembershipRole).findIndex((mmship) => mmship === mship);
+ return mshipToNumber(mship1) > mshipToNumber(mship2);
+};
+
export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) => {
const { prisma } = ctx;
@@ -214,12 +220,6 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
membershipRole: membership.role,
}));
- const compareMembership = (mship1: MembershipRole, mship2: MembershipRole) => {
- const mshipToNumber = (mship: MembershipRole) =>
- Object.keys(MembershipRole).findIndex((mmship) => mmship === mship);
- return mshipToNumber(mship1) > mshipToNumber(mship2);
- };
-
const filterTeamsEventTypesBasedOnInput = (eventType: ReturnType) => {
if (!input?.filters || !hasFilter(input?.filters)) {
return true;
diff --git a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts
index 6faaea5053..b0b33e7fe4 100644
--- a/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts
+++ b/packages/trpc/server/routers/viewer/webhook/getByViewer.handler.ts
@@ -2,10 +2,13 @@ import { CAL_URL } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma";
import type { Webhook } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
+import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import { TRPCError } from "@trpc/server";
+import { compareMembership } from "../eventTypes/getByViewer.handler";
+
type GetByViewerOptions = {
ctx: {
user: NonNullable;
@@ -57,6 +60,8 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
id: true,
name: true,
slug: true,
+ parentId: true,
+ metadata: true,
members: {
select: {
userId: true,
@@ -91,19 +96,44 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
},
});
- const teamWebhookGroups: WebhookGroup[] = user.teams.map((membership) => ({
+ const teamMemberships = user.teams.map((membership) => ({
teamId: membership.team.id,
- profile: {
- name: membership.team.name,
- slug: "team/" + membership.team.slug,
- image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
- },
- metadata: {
- readOnly: membership.role !== MembershipRole.ADMIN && membership.role !== MembershipRole.OWNER,
- },
- webhooks: membership.team.webhooks,
+ membershipRole: membership.role,
}));
+ const teamWebhookGroups: WebhookGroup[] = user.teams
+ .filter((mmship) => {
+ const metadata = teamMetadataSchema.parse(mmship.team.metadata);
+ return !metadata?.isOrganization;
+ })
+ .map((membership) => {
+ const orgMembership = teamMemberships.find(
+ (teamM) => teamM.teamId === membership.team.parentId
+ )?.membershipRole;
+ return {
+ teamId: membership.team.id,
+ profile: {
+ name: membership.team.name,
+ slug: membership.team.slug
+ ? !membership.team.parentId
+ ? `/team`
+ : "" + membership.team.slug
+ : null,
+ image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
+ },
+ metadata: {
+ readOnly:
+ membership.role ===
+ (membership.team.parentId
+ ? orgMembership && compareMembership(orgMembership, membership.role)
+ ? orgMembership
+ : MembershipRole.MEMBER
+ : MembershipRole.MEMBER),
+ },
+ webhooks: membership.team.webhooks,
+ };
+ });
+
webhookGroups = webhookGroups.concat(teamWebhookGroups);
return {
@@ -112,6 +142,7 @@ export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
teamId: group.teamId,
...group.profile,
...group.metadata,
+ image: `${CAL_URL}/${group.profile.slug}/avatar.png`,
})),
};
};
diff --git a/packages/trpc/server/routers/viewer/workflows/_router.tsx b/packages/trpc/server/routers/viewer/workflows/_router.tsx
index 8b8033b37b..1d146aa05c 100644
--- a/packages/trpc/server/routers/viewer/workflows/_router.tsx
+++ b/packages/trpc/server/routers/viewer/workflows/_router.tsx
@@ -3,6 +3,7 @@ import { router } from "../../../trpc";
import { ZActivateEventTypeInputSchema } from "./activateEventType.schema";
import { ZCreateInputSchema } from "./create.schema";
import { ZDeleteInputSchema } from "./delete.schema";
+import { ZFilteredListInputSchema } from "./filteredList.schema";
import { ZGetInputSchema } from "./get.schema";
import { ZGetVerifiedNumbersInputSchema } from "./getVerifiedNumbers.schema";
import { ZListInputSchema } from "./list.schema";
@@ -21,7 +22,7 @@ type WorkflowsRouterHandlerCache = {
verifyPhoneNumber?: typeof import("./verifyPhoneNumber.handler").verifyPhoneNumberHandler;
getVerifiedNumbers?: typeof import("./getVerifiedNumbers.handler").getVerifiedNumbersHandler;
getWorkflowActionOptions?: typeof import("./getWorkflowActionOptions.handler").getWorkflowActionOptionsHandler;
- getByViewer?: typeof import("./getByViewer.handler").getByViewerHandler;
+ filteredList?: typeof import("./filteredList.handler").filteredListHandler;
};
const UNSTABLE_HANDLER_CACHE: WorkflowsRouterHandlerCache = {};
@@ -197,21 +198,21 @@ export const workflowsRouter = router({
ctx,
});
}),
-
- getByViewer: authedProcedure.query(async ({ ctx }) => {
- if (!UNSTABLE_HANDLER_CACHE.getByViewer) {
- UNSTABLE_HANDLER_CACHE.getByViewer = await import("./getByViewer.handler").then(
- (mod) => mod.getByViewerHandler
+ filteredList: authedProcedure.input(ZFilteredListInputSchema).query(async ({ ctx, input }) => {
+ if (!UNSTABLE_HANDLER_CACHE.filteredList) {
+ UNSTABLE_HANDLER_CACHE.filteredList = await import("./filteredList.handler").then(
+ (mod) => mod.filteredListHandler
);
}
// Unreachable code but required for type safety
- if (!UNSTABLE_HANDLER_CACHE.getByViewer) {
+ if (!UNSTABLE_HANDLER_CACHE.filteredList) {
throw new Error("Failed to load handler");
}
- return UNSTABLE_HANDLER_CACHE.getByViewer({
+ return UNSTABLE_HANDLER_CACHE.filteredList({
ctx,
+ input,
});
}),
});
diff --git a/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx b/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx
new file mode 100644
index 0000000000..db2e5b2ec6
--- /dev/null
+++ b/packages/trpc/server/routers/viewer/workflows/filteredList.handler.tsx
@@ -0,0 +1,143 @@
+import type { WorkflowType } from "@calcom/ee/workflows/components/WorkflowListPage";
+import { hasFilter } from "@calcom/features/filters/lib/hasFilter";
+import type { Prisma, PrismaClient } from "@calcom/prisma/client";
+import { MembershipRole } from "@calcom/prisma/client";
+import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
+
+import type { TFilteredListInputSchema } from "./filteredList.schema";
+
+type FilteredListOptions = {
+ ctx: {
+ user: NonNullable;
+ prisma: PrismaClient;
+ };
+ input: TFilteredListInputSchema;
+};
+
+const includedFields = {
+ activeOn: {
+ select: {
+ eventType: {
+ select: {
+ id: true,
+ title: true,
+ parentId: true,
+ _count: {
+ select: {
+ children: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ steps: true,
+ team: {
+ select: {
+ id: true,
+ slug: true,
+ name: true,
+ members: true,
+ logo: true,
+ },
+ },
+};
+
+export const filteredListHandler = async ({ ctx, input }: FilteredListOptions) => {
+ const { prisma, user } = ctx;
+
+ const filters = input?.filters;
+
+ const filtered = filters && hasFilter(filters);
+
+ const allWorkflows = await prisma.workflow.findMany({
+ where: {
+ OR: [
+ {
+ userId: user.id,
+ },
+ {
+ team: {
+ members: {
+ some: {
+ userId: user.id,
+ accepted: true,
+ },
+ },
+ },
+ },
+ ],
+ },
+ include: includedFields,
+ orderBy: {
+ id: "asc",
+ },
+ });
+
+ if (!filtered) {
+ const workflowsWithReadOnly: WorkflowType[] = allWorkflows.map((workflow) => {
+ const readOnly = !!workflow.team?.members?.find(
+ (member) => member.userId === ctx.user.id && member.role === MembershipRole.MEMBER
+ );
+
+ return { readOnly, ...workflow };
+ });
+
+ return {
+ filtered: workflowsWithReadOnly,
+ totalCount: allWorkflows.length,
+ };
+ }
+
+ const where = {
+ OR: [] as Prisma.WorkflowWhereInput[],
+ };
+
+ if (filtered) {
+ if (!!filters.teamIds) {
+ where.OR.push({
+ team: {
+ id: {
+ in: filters.teamIds ?? [],
+ },
+ members: {
+ some: {
+ userId: user.id,
+ accepted: true,
+ },
+ },
+ },
+ });
+ }
+
+ if (!!filters.userIds) {
+ where.OR.push({
+ userId: {
+ in: filters.userIds,
+ },
+ teamId: null,
+ });
+ }
+
+ const filteredWorkflows = await prisma.workflow.findMany({
+ where,
+ include: includedFields,
+ orderBy: {
+ id: "asc",
+ },
+ });
+
+ const workflowsWithReadOnly: WorkflowType[] = filteredWorkflows.map((workflow) => {
+ const readOnly = !!workflow.team?.members?.find(
+ (member) => member.userId === ctx.user.id && member.role === MembershipRole.MEMBER
+ );
+
+ return { readOnly, ...workflow };
+ });
+
+ return {
+ filtered: workflowsWithReadOnly,
+ totalCount: allWorkflows.length,
+ };
+ }
+};
diff --git a/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx b/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx
new file mode 100644
index 0000000000..3bb74fbb1f
--- /dev/null
+++ b/packages/trpc/server/routers/viewer/workflows/filteredList.schema.tsx
@@ -0,0 +1,11 @@
+import { z } from "zod";
+
+import { filterQuerySchemaStrict } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
+
+export const ZFilteredListInputSchema = z
+ .object({
+ filters: filterQuerySchemaStrict.optional(),
+ })
+ .nullish();
+
+export type TFilteredListInputSchema = z.infer;
diff --git a/packages/trpc/server/routers/viewer/workflows/get.handler.ts b/packages/trpc/server/routers/viewer/workflows/get.handler.ts
index e9bdefaa7e..3f74f982b3 100644
--- a/packages/trpc/server/routers/viewer/workflows/get.handler.ts
+++ b/packages/trpc/server/routers/viewer/workflows/get.handler.ts
@@ -28,6 +28,7 @@ export const getHandler = async ({ ctx, input }: GetOptions) => {
id: true,
slug: true,
members: true,
+ name: true,
},
},
time: true,
diff --git a/packages/trpc/server/routers/viewer/workflows/getByViewer.handler.ts b/packages/trpc/server/routers/viewer/workflows/getByViewer.handler.ts
deleted file mode 100644
index 45050d8df1..0000000000
--- a/packages/trpc/server/routers/viewer/workflows/getByViewer.handler.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { CAL_URL } from "@calcom/lib/constants";
-import { prisma } from "@calcom/prisma";
-import { MembershipRole } from "@calcom/prisma/enums";
-import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
-
-import { TRPCError } from "@trpc/server";
-
-type GetByViewerOptions = {
- ctx: {
- user: NonNullable;
- };
-};
-
-export const getByViewerHandler = async ({ ctx }: GetByViewerOptions) => {
- const user = await prisma.user.findUnique({
- where: {
- id: ctx.user.id,
- },
- select: {
- id: true,
- username: true,
- avatar: true,
- name: true,
- startTime: true,
- endTime: true,
- bufferTime: true,
- workflows: {
- select: {
- id: true,
- name: true,
- },
- },
- teams: {
- where: {
- accepted: true,
- },
- select: {
- role: true,
- team: {
- select: {
- id: true,
- name: true,
- slug: true,
- members: {
- select: {
- userId: true,
- },
- },
- workflows: {
- select: {
- id: true,
- name: true,
- },
- },
- },
- },
- },
- },
- },
- });
-
- if (!user) {
- throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
- }
-
- const userWorkflows = user.workflows;
-
- type WorkflowGroup = {
- teamId?: number | null;
- profile: {
- slug: (typeof user)["username"];
- name: (typeof user)["name"];
- image?: string;
- };
- metadata?: {
- readOnly: boolean;
- };
- workflows: typeof userWorkflows;
- };
-
- let workflowGroups: WorkflowGroup[] = [];
-
- const image = user?.username ? `${CAL_URL}/${user.username}/avatar.png` : undefined;
- workflowGroups.push({
- teamId: null,
- profile: {
- slug: user.username,
- name: user.name,
- image,
- },
- workflows: userWorkflows,
- metadata: {
- readOnly: false,
- },
- });
-
- workflowGroups = ([] as WorkflowGroup[]).concat(
- workflowGroups,
- user.teams.map((membership) => ({
- teamId: membership.team.id,
- profile: {
- name: membership.team.name,
- slug: "team/" + membership.team.slug,
- image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
- },
- metadata: {
- readOnly: membership.role === MembershipRole.MEMBER,
- },
- workflows: membership.team.workflows,
- }))
- );
-
- return {
- workflowGroups: workflowGroups.filter((groupBy) => !!groupBy.workflows?.length),
- profiles: workflowGroups.map((group) => ({
- teamId: group.teamId,
- ...group.profile,
- ...group.metadata,
- })),
- };
-};
diff --git a/packages/trpc/server/routers/viewer/workflows/getByViewer.schema.ts b/packages/trpc/server/routers/viewer/workflows/getByViewer.schema.ts
deleted file mode 100644
index cb0ff5c3b5..0000000000
--- a/packages/trpc/server/routers/viewer/workflows/getByViewer.schema.ts
+++ /dev/null
@@ -1 +0,0 @@
-export {};
diff --git a/packages/trpc/server/routers/viewer/workflows/update.handler.ts b/packages/trpc/server/routers/viewer/workflows/update.handler.ts
index 64c7fe0ed9..8c5f4f6464 100644
--- a/packages/trpc/server/routers/viewer/workflows/update.handler.ts
+++ b/packages/trpc/server/routers/viewer/workflows/update.handler.ts
@@ -698,6 +698,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
id: true,
slug: true,
members: true,
+ name: true,
},
},
steps: {
diff --git a/packages/ui/components/avatar/Avatar.tsx b/packages/ui/components/avatar/Avatar.tsx
index de59434da1..1c6c4c34c5 100644
--- a/packages/ui/components/avatar/Avatar.tsx
+++ b/packages/ui/components/avatar/Avatar.tsx
@@ -12,7 +12,7 @@ import { Tooltip } from "../tooltip";
export type AvatarProps = {
className?: string;
- size: "xs" | "xsm" | "sm" | "md" | "mdLg" | "lg" | "xl";
+ size: "xxs" | "xs" | "xsm" | "sm" | "md" | "mdLg" | "lg" | "xl";
imageSrc?: Maybe;
title?: string;
alt: string;
@@ -24,6 +24,7 @@ export type AvatarProps = {
};
const sizesPropsBySize = {
+ xxs: "w-3.5 h-3.5 min-w-3.5 min-h-3.5", // 14px
xs: "w-4 h-4 min-w-4 min-h-4 max-h-4", // 16px
xsm: "w-5 h-5 min-w-5 min-h-5", // 20px
sm: "w-6 h-6 min-w-6 min-h-6", // 24px
diff --git a/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx b/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx
index 5aabdfb5e7..f5b8f86f89 100644
--- a/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx
+++ b/packages/ui/components/createButton/CreateButtonWithTeamsList.tsx
@@ -3,7 +3,9 @@ import { trpc } from "@calcom/trpc/react";
import type { CreateBtnProps } from "./CreateButton";
import { CreateButton } from "./CreateButton";
-export function CreateButtonWithTeamsList(props: Omit) {
+export function CreateButtonWithTeamsList(
+ props: Omit & { onlyShowWithTeams?: boolean; onlyShowWithNoTeams?: boolean }
+) {
const query = trpc.viewer.teamsAndUserProfilesQuery.useQuery();
if (!query.data) return null;
@@ -18,5 +20,9 @@ export function CreateButtonWithTeamsList(props: Omit
};
});
+ if (props.onlyShowWithTeams && teamsAndUserProfiles.length < 2) return null;
+
+ if (props.onlyShowWithNoTeams && teamsAndUserProfiles.length > 1) return null;
+
return ;
}