Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: platform onboarding flow and dashboard #14721

Merged
merged 99 commits into from May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
602ed87
add endpoint to fetch managed user from client id
Ryukemeister Apr 23, 2024
ec41c68
update typings
Ryukemeister Apr 23, 2024
da31c11
minor tweaks
Ryukemeister Apr 23, 2024
7bc4a6b
custom hook to fetch managed users from client id
Ryukemeister Apr 23, 2024
fc59a7f
add translations for platform onboarding
Ryukemeister Apr 23, 2024
bbf2c8c
add isPlatformOrg boolean to figure out which is platform and which i…
Ryukemeister Apr 23, 2024
78d3823
set isPlatform hook based on data obtained from org
Ryukemeister Apr 23, 2024
b239856
add limitWidth prop to control component width
Ryukemeister Apr 23, 2024
20f433c
add props to shell to make sidebar display different tabs based on if…
Ryukemeister Apr 23, 2024
51f56b0
platform related pages
Ryukemeister Apr 23, 2024
15c5189
fix merge conflicts
Ryukemeister Apr 23, 2024
0dcf188
fix merge conflicts
Ryukemeister Apr 23, 2024
793efbf
platform oauth client form and card
Ryukemeister Apr 23, 2024
1a650d2
Merge branch 'main' into platform-onboarding
Ryukemeister Apr 23, 2024
2745c75
Merge branch 'main' into platform-onboarding
Ryukemeister Apr 23, 2024
b4a5724
remove everything related to platform from organization
Ryukemeister Apr 24, 2024
fca0cf7
update oauth client card and form
Ryukemeister Apr 24, 2024
b038eff
fixup
Ryukemeister Apr 24, 2024
8ceb7f5
fix imports and remove logs
Ryukemeister Apr 24, 2024
46d8433
fixup
Ryukemeister Apr 24, 2024
6ef1c3e
update redirect url
Ryukemeister Apr 25, 2024
6bc4d0c
split oauth client form into separate update and create forms
Ryukemeister Apr 25, 2024
149b7a4
separate forms for create and edit oauth clients
Ryukemeister Apr 25, 2024
93e91ad
fixup
Ryukemeister Apr 25, 2024
e16c8d9
Merge branch 'main' into platform-onboarding
Ryukemeister Apr 25, 2024
4890edb
fixup
Ryukemeister Apr 25, 2024
f6f6d21
dynamic routes for oauth client edit page
Ryukemeister Apr 25, 2024
a2b8ae1
Merge branch 'main' into platform-onboarding
ThyMinimalDev Apr 25, 2024
25fc7b6
fixup fixup
Ryukemeister Apr 25, 2024
0bd4bfe
fix to not show error when redirect uri is empty
Ryukemeister Apr 25, 2024
f8dbab2
Merge remote-tracking branch 'origin/main' into platform-onboarding
exception Apr 30, 2024
70b7191
refactor create handler for org
Ryukemeister May 1, 2024
29f2571
cleaup comments
Ryukemeister May 1, 2024
f130e7c
Merge branch 'main' into platform-onboarding
CarinaWolli May 2, 2024
3e46d1e
add custom hook to check user billing
Ryukemeister May 2, 2024
ee570de
export managed user type
Ryukemeister May 2, 2024
64f1df5
refactor platform index page
Ryukemeister May 2, 2024
d67778e
refactor edit and create pages
Ryukemeister May 2, 2024
a20fba5
dashboard component containing oauth client list and managed user
Ryukemeister May 2, 2024
27e82ba
common oauth client form used for create and edit form
Ryukemeister May 2, 2024
618fbd4
platform pricing helper
Ryukemeister May 2, 2024
d46c923
platform pricing component
Ryukemeister May 2, 2024
f85960f
Merge branch 'main' into platform-onboarding
Ryukemeister May 2, 2024
8ea2d69
fix typing and data response for billing
Ryukemeister May 2, 2024
7b7b3af
use custom hook to check team billing info
Ryukemeister May 2, 2024
c4079a8
fix type checks
Ryukemeister May 2, 2024
1cc574a
upgrade conditional rendering for upgrade to org banner
Ryukemeister May 2, 2024
70191d1
add isLoading prop to check button loading state
Ryukemeister May 2, 2024
fa1cc9f
pass in button handler
Ryukemeister May 2, 2024
83e7651
add custom hook to subscribe to stripe and typings
Ryukemeister May 2, 2024
4b5be2d
update typings
Ryukemeister May 3, 2024
c88dc2c
fix incorrect endpoint
Ryukemeister May 3, 2024
65a0154
pass in team id as prop
Ryukemeister May 3, 2024
cfe929e
fix type check
Ryukemeister May 3, 2024
169889b
Merge branch 'main' into platform-onboarding
Ryukemeister May 3, 2024
ab670f1
update stripe success and cancel redirect url
Ryukemeister May 3, 2024
d840005
add and pass redirect url param to custom hook
Ryukemeister May 3, 2024
ba4d0c9
Merge branch 'main' into platform-onboarding
Ryukemeister May 3, 2024
33c2205
custom hooks for platform
Ryukemeister May 6, 2024
dfce9b9
cleanup
Ryukemeister May 6, 2024
e1c0c96
update imports
Ryukemeister May 6, 2024
bbcee0f
fix merge conflicts
Ryukemeister May 6, 2024
726281b
fixup
Ryukemeister May 6, 2024
5a1ef25
fixup fixup
Ryukemeister May 6, 2024
b27ad9e
fixup
Ryukemeister May 6, 2024
354c41e
merge conflicts fixup
Ryukemeister May 6, 2024
f64dce7
merge conlficts battle :(
Ryukemeister May 6, 2024
a430982
Merge branch 'main' into platform-onboarding
Ryukemeister May 6, 2024
5a36412
minor fixes
Ryukemeister May 6, 2024
1ed407c
skip admin checks for a platform client
Ryukemeister May 6, 2024
7a4f6a8
Merge branch 'main' into platform-onboarding
keithwillcode May 6, 2024
c98d361
Merge branch 'main' into platform-onboarding
Ryukemeister May 6, 2024
9da1748
Merge branch 'main' into platform-onboarding
PeerRich May 6, 2024
f6ce2b1
Merge branch 'main' into platform-onboarding
Ryukemeister May 7, 2024
903dad2
fix typo
Ryukemeister May 7, 2024
fff71d9
append slug with _platform for a platform user
Ryukemeister May 7, 2024
fcd9dfd
Merge branch 'main' into platform-onboarding
Ryukemeister May 7, 2024
bd4f5cd
Merge branch 'main' into platform-onboarding
PeerRich May 7, 2024
465d832
PR feedback
Ryukemeister May 8, 2024
6b4087c
dashboard refactor
Ryukemeister May 8, 2024
1a5bd54
bring back org form to its orginal state
Ryukemeister May 8, 2024
d8c0a59
add platform folder to ee
Ryukemeister May 8, 2024
5d835e1
update typings
Ryukemeister May 8, 2024
8a0a042
use new create platform form
Ryukemeister May 8, 2024
e65b0c1
fixup
Ryukemeister May 8, 2024
5f66058
fix typo and update plans
Ryukemeister May 8, 2024
6578b7f
Merge branch 'main' into platform-onboarding
Ryukemeister May 8, 2024
3a7dd7e
simplifying rendering
Ryukemeister May 8, 2024
d4939b0
Merge branch 'main' into platform-onboarding
ThyMinimalDev May 8, 2024
55a4801
remove managed users endpoint since it already exists
Ryukemeister May 8, 2024
ec8a1ec
url for endpoint
Ryukemeister May 8, 2024
a053092
Merge branch 'main' into platform-onboarding
Ryukemeister May 8, 2024
e7dfce8
rename tabs
Ryukemeister May 9, 2024
b4dc744
pr feedback
Ryukemeister May 9, 2024
d4a0bef
managed users endpoint
Ryukemeister May 9, 2024
1070ce9
update endpoint
Ryukemeister May 9, 2024
470212a
remove form
Ryukemeister May 9, 2024
e6325c8
Merge branch 'main' into platform-onboarding
Ryukemeister May 9, 2024
15f8f99
Merge branch 'main' into platform-onboarding
Ryukemeister May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -3,12 +3,17 @@ import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { NextAuthGuard } from "@/modules/auth/guards/next-auth/next-auth.guard";
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
import { ManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/managed-user.output";
import { CreateOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto";
import { GetOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientResponse.dto";
import {
GetOAuthClientResponseDto,
GetOAuthClientManagedUsersResponseDto,
} from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientResponse.dto";
import { GetOAuthClientsResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientsResponse.dto";
import { UpdateOAuthClientInput } from "@/modules/oauth-clients/inputs/update-oauth-client.input";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import { UsersRepository } from "@/modules/users/users.repository";
import {
Body,
Controller,
Expand All @@ -30,6 +35,7 @@ import {
ApiCreatedResponse as DocsCreatedResponse,
} from "@nestjs/swagger";
import { MembershipRole } from "@prisma/client";
import { User } from "@prisma/client";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { CreateOAuthClientInput } from "@calcom/platform-types";
Expand All @@ -47,7 +53,10 @@ Second, make sure that the logged in user has organizationId set to pass the Org
export class OAuthClientsController {
private readonly logger = new Logger("OAuthClientController");

constructor(private readonly oauthClientRepository: OAuthClientRepository) {}
constructor(
private readonly oauthClientRepository: OAuthClientRepository,
private readonly userRepository: UsersRepository
) {}

@Post("/")
@HttpCode(HttpStatus.CREATED)
Expand Down Expand Up @@ -98,6 +107,21 @@ export class OAuthClientsController {
return { status: SUCCESS_STATUS, data: client };
}

@Get("/managed-users/:clientId")
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to have @Get("/:clientId/managed-users") because IMO users can be seen as sub-resources of an OAuth client aka OAuth client is higher up in the hierarchy.

@HttpCode(HttpStatus.OK)
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
supalarry marked this conversation as resolved.
Show resolved Hide resolved
@DocsOperation({ description: AUTH_DOCUMENTATION })
async getOAuthClientManagedUsersById(
@Param("clientId") clientId: string
): Promise<GetOAuthClientManagedUsersResponseDto> {
const existingManagedUsers = await this.userRepository.findManagedUsersByOAuthClientId(clientId, 0, 50); // second argument is for offset while third is for limit
Copy link
Contributor

Choose a reason for hiding this comment

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

We should have limit and offset as query parameters using "@query" decorator. Example can be seen in "getBookings" of "https://github.com/calcom/cal.com/blob/1cfd2f705306ccf8c6234e600ae6b74547f80872/apps/api/v2/src/ee/bookings/controllers/bookings.controller.ts" and then remove comment "// second argument is for offset while third is for limit"

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, why do we need to add the "getOAuthClientManagedUsersById" endpoint? In "oauth-client-users.controller.ts" we have identical one:

Screenshot 2024-05-07 at 15 13 03


if (!existingManagedUsers) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need if (!existingManagedUsers) check because it can't be nullish. Here is its return type. If it's empty array its fine we just return empty array as data.

Screenshot 2024-05-07 at 15 09 40

throw new NotFoundException(`OAuth client with ID ${clientId} does not have any managed users`);
}
return { status: SUCCESS_STATUS, data: existingManagedUsers.map((user) => this.getResponseUser(user)) };
}

@Patch("/:clientId")
@HttpCode(HttpStatus.OK)
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER])
Expand All @@ -120,4 +144,17 @@ export class OAuthClientsController {
const client = await this.oauthClientRepository.deleteOAuthClient(clientId);
return { status: SUCCESS_STATUS, data: client };
}

private getResponseUser(user: User): ManagedUserOutput {
return {
id: user.id,
email: user.email,
username: user.username,
timeZone: user.timeZone,
weekStart: user.weekStart,
createdDate: user.createdDate,
timeFormat: user.timeFormat,
defaultScheduleId: user.defaultScheduleId,
};
}
}
@@ -1,3 +1,4 @@
import { ManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/managed-user.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import {
Expand Down Expand Up @@ -62,3 +63,17 @@ export class GetOAuthClientResponseDto {
@Type(() => PlatformOAuthClientDto)
data!: PlatformOAuthClientDto;
}

export class GetOAuthClientManagedUsersResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;

@ApiProperty({
type: ManagedUserOutput,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => ManagedUserOutput)
data!: ManagedUserOutput[];
}
Expand Up @@ -6,7 +6,7 @@ import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants";
import type { Avatar } from "@calcom/prisma/client";
import { Button, Icon, showToast } from "@calcom/ui";

import { hasPermission } from "../../../../../../../packages/platform/utils/permissions";
import { hasPermission } from "../../../../../../packages/platform/utils/permissions";

type OAuthClientCardProps = {
name: string;
Expand Down Expand Up @@ -115,7 +115,7 @@ export const OAuthClientCard = ({
</div>
<div className="border-subtle flex text-sm">
<span className="font-semibold">Permissions: </span>
<div className="flex">{clientPermissions}</div>
{permissions ? <div className="flex">{clientPermissions}</div> : <>&nbsp;Disabled</>}
</div>
<div className="flex gap-1 text-sm">
<span className="font-semibold">Redirect uris: </span>
Expand Down Expand Up @@ -145,7 +145,7 @@ export const OAuthClientCard = ({
className="bg-subtle hover:bg-emphasis text-white"
loading={isLoading}
disabled={isLoading}
onClick={() => router.push(`/settings/organizations/platform/oauth-clients/create?clientId=${id}`)}>
onClick={() => router.push(`/settings/platform/oauth-clients/create?clientId=${id}`)}>
Edit
</Button>
<Button
Expand Down
Expand Up @@ -6,7 +6,7 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants/permissions";
import { showToast } from "@calcom/ui";
import { Meta, Button, TextField, Label, Tooltip } from "@calcom/ui";
import { Button, TextField, Label, Tooltip } from "@calcom/ui";

import { useOAuthClient } from "@lib/hooks/settings/organizations/platform/oauth-clients/useOAuthClients";
import {
Expand All @@ -25,7 +25,7 @@
hasProfileWritePermission,
hasScheduleReadPermission,
hasScheduleWritePermission,
} from "../../../../../../../packages/platform/utils/permissions";
} from "../../../../../../packages/platform/utils/permissions";

type FormValues = {
name: string;
Expand All @@ -51,7 +51,7 @@
};

export const OAuthClientForm: FC<{ clientId?: string }> = ({ clientId }) => {
const { t } = useLocale();

Check warning on line 54 in apps/web/components/settings/platform/oauth-clients/OAuthClientForm.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

apps/web/components/settings/platform/oauth-clients/OAuthClientForm.tsx#L54

[@typescript-eslint/no-unused-vars] 't' is assigned a value but never used. Allowed unused vars must match /^_/u.
const router = useRouter();
const { data, isFetched, isError, refetch } = useOAuthClient(clientId);
const { register, control, handleSubmit, setValue } = useForm<FormValues>({
Expand All @@ -63,6 +63,7 @@
control,
name: "redirectUris",
});

useEffect(() => {
if (isFetched && data && !isError) {
setValue("name", data.name);
Expand All @@ -86,7 +87,7 @@
if (hasScheduleReadPermission(data.permissions)) setValue("scheduleRead", true);
if (hasScheduleWritePermission(data.permissions)) setValue("scheduleWrite", true);
}
}, [isFetched, data]);

Check warning on line 90 in apps/web/components/settings/platform/oauth-clients/OAuthClientForm.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

apps/web/components/settings/platform/oauth-clients/OAuthClientForm.tsx#L90

[react-hooks/exhaustive-deps] React Hook useEffect has missing dependencies: 'append', 'isError', and 'setValue'. Either include them or remove the dependency array.
const disabledForm = Boolean(clientId && !isFetched && isError);

const [isSelectAllPermissionsChecked, setIsSelectAllPermissionsChecked] = useState(false);
Expand All @@ -107,17 +108,18 @@
onSuccess: () => {
showToast("OAuth client created successfully", "success");
refetch();
router.push("/settings/organizations/platform/oauth-clients");
router.push("/settings/platform/");
},
onError: () => {
showToast("Internal server error, please try again later", "error");
},
});

const { mutateAsync: update, isPending: isUpdating } = useUpdateOAuthClient({
onSuccess: () => {
showToast("OAuth client updated successfully", "success");
refetch();
router.push("/settings/organizations/platform/oauth-clients");
router.push("/settings/platform/");
},
onError: () => {
showToast("Internal server error, please try again later", "error");
Expand Down Expand Up @@ -162,6 +164,7 @@
});
}
};

const isPending = isSaving || isUpdating;

const permissionsCheckboxes = Object.keys(PERMISSIONS_GROUPED_MAP).map((key) => {
Expand Down Expand Up @@ -204,11 +207,6 @@

return (
<div>
<Meta
title={t("oauth_form_title") + (clientId ? " - Update" : "")}
description={t("oauth_form_description") + (clientId ? " - Update" : "")}
borderInShellHeader={true}
/>
<form
className="border-subtle rounded-b-lg border border-t-0 px-4 pb-8 pt-2"
onSubmit={handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -337,19 +335,15 @@
Enable emails
</label>
</div>

<div className="mt-6">
<div className="flex justify-between">
<Tooltip side="right" content="Permissions once set cannot be modified">
<h1 className="text-base font-semibold underline">Permissions</h1>
</Tooltip>
<Button type="button" onClick={selectAllPermissions} disabled={disabledForm || Boolean(clientId)}>
<h1 className="text-base font-semibold underline">Permissions</h1>
<Button type="button" onClick={selectAllPermissions}>
{!isSelectAllPermissionsChecked ? "Select all" : "Discard all"}
</Button>
</div>
<div>{permissionsCheckboxes}</div>
</div>

<Button className="mt-6" type="submit" loading={isPending} disabled={disabledForm}>
{clientId ? "Update" : "Submit"}
</Button>
Expand Down
Expand Up @@ -3,6 +3,17 @@ import { useQuery } from "@tanstack/react-query";
import type { ApiSuccessResponse } from "@calcom/platform-types";
import type { PlatformOAuthClient } from "@calcom/prisma/client";

type ManagedUser = {
id: number;
email: string;
username: string | null;
timeZone: string;
weekStart: string;
createdDate: Date;
timeFormat: number | null;
defaultScheduleId: number | null;
};

export const useOAuthClients = () => {
const query = useQuery<ApiSuccessResponse<PlatformOAuthClient[]>>({
queryKey: ["oauth-clients"],
Expand Down Expand Up @@ -36,7 +47,7 @@ export const useOAuthClient = (clientId?: string) => {
headers: { "Content-type": "application/json" },
}).then((res) => res.json());
},
enabled: clientId !== undefined,
enabled: Boolean(clientId),
staleTime: Infinity,
});

Expand All @@ -52,3 +63,21 @@ export const useOAuthClient = (clientId?: string) => {
refetch,
};
};
export const useGetOAuthClientManagedUsers = (clientId: string) => {
const {
isLoading,
error,
data: response,
refetch,
} = useQuery<ApiSuccessResponse<ManagedUser[]>>({
queryKey: ["oauth-client-managed-users", clientId],
queryFn: () => {
return fetch(`/api/v2/oauth-clients/managed-users/${clientId}`, {
method: "get",
headers: { "Content-type": "application/json" },
}).then((res) => res.json());
},
});

return { isLoading, error, data: response?.data, refetch };
};

This file was deleted.

103 changes: 0 additions & 103 deletions apps/web/pages/settings/organizations/platform/oauth-clients/index.tsx

This file was deleted.