Compare commits

..
10 Commits
Author SHA1 Message Date
Alex van Andel e7f557f7f9 v2.2.2
release.yml / release (push) Canceled after 0s
2022-11-18 14:52:34 +00:00
Alex van Andel e2921c3126 Techdebt/improve sentry config (#5585)
* Fix console errors during build

* Create source maps for production
2022-11-18 14:23:14 +00:00
Jeroen Reumkens d8102615b1 Upgrade Sentry to latest to add excludeServerRoutes in order to exclude OG Edge endpoint since Sentry won't work on the Edge yet, but WILL crash the api route. (#5574) 2022-11-18 09:49:19 +00:00
1010e2894a Bringing back sendgrid app to review (#5501)
* Sendgrid app and code simplification

* Applying app-store-cli + impl

* Fixing types

* Adding features to readme

* Fixing unit tests

* A few last tweaks regarding UX and env vars

* Applying feedback

* Using calcom icons

* Renaming and applying feedback

* Testing user/type page fix

* Standarizing Sendgrid client usage

* Removing types

* Reverting CloseCom changes

* Stop relying on sendgrid client pkg

* Fixing button and more reverting closecom changes

* Revert "Stop relying on sendgrid client pkg"

This reverts commit dd61851572a17a1e4051b133683af85c934bc2d0.

* Revert "Removing types"

This reverts commit 1ec5ed8de2f3139bbe84f867f229bc5759256806.

* Is this it?

* Standardizing apis

* Fixing path

* Fixing throwing errors the standard way

* Stop relying on getInstalledAppPath

* Removing seemingly troubling code

* Returning error and avoiding any outer reference

* Revert "Returning error and avoiding any outer reference"

This reverts commit 7d32e30154423c95f54ebae81a76ab16a1c7bc94.

* Revert "Removing seemingly troubling code"

This reverts commit eaae772abcd04c8f046e4960116f42c5aaf87adf.

* Revert "Stop relying on getInstalledAppPath"

This reverts commit bcc70fc337bbe7fb5e74609abaeee7cd3ede90a3.

* Revert "Fixing throwing errors the standard way"

This reverts commit bb1bb410fac6f8c6ad14c3163a8433d125f7a885.

* Revert "Fixing path"

This reverts commit a7bd83c4fb7597594d0470cb530378c826b45481.

* Revert "Standardizing apis"

This reverts commit 0258a182298af3ebad321854ef4f34a65f4c700a.

* Revert "Is this it?"

This reverts commit 70b3f7b98e3003dfa225dc539e02a1e17abdd840.

* Converting APIs to legacy style

* Missing reverted CloseCom test mock

* Needed for the renaming

* Reverting Closecom and yarn unneeded changes

* Ununsed type

* Testing rearranging exports

* Update apps/web/components/apps/OmniInstallAppButton.tsx

Co-authored-by: Omar López <zomars@me.com>

* Standardizing APIs

* Fixing wrong toast message on app page

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2022-11-17 21:38:34 +00:00
Udit TakkarandAlex van Andel c728174c14 fix: return when success status 204 (#5562)
* fix: return when success status 204

* fix: handle login in handleZoomResponse

* Add response.status check after the response.ok check

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-11-17 21:27:22 +00:00
Udit TakkarandPeer Richelsen 3889b49fa1 fix: use zod for validation (#5498)
* fix: use zod for validation

* fix: use slug instead of url

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-11-17 18:56:47 +00:00
sean-brydon 461389fb0b increase limits (#5554) 2022-11-17 09:17:07 +00:00
Jeroen Reumkens a8186e3039 Fixed wrong calculation of duration type on event type page. (#5557) 2022-11-17 13:38:03 +05:30
zomars 5e2db12396 Update release.yml 2022-11-16 15:17:35 -07:00
zomars 6bc9d2fd94 Update release.yml 2022-11-16 15:06:20 -07:00
48 changed files with 762 additions and 175 deletions
+3 -3
View File
@@ -77,7 +77,7 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
SEND_FEEDBACK_EMAIL=
# Sengrid
# Used for email reminders in workflows
# Used for email reminders in workflows and internal sync services
SENDGRID_API_KEY=
SENDGRID_EMAIL=
@@ -134,8 +134,8 @@ NEXT_PUBLIC_TEAM_IMPERSONATION=false
# Close.com internal CRM
CLOSECOM_API_KEY=
# Sendgrid internal email sender
SENDGRID_API_KEY=
# Sendgrid internal sync service
SENDGRID_SYNC_API_KEY=
# Sentry
NEXT_PUBLIC_SENTRY_DSN=
+3 -1
View File
@@ -1,4 +1,5 @@
on:
workflow_dispatch:
push:
# Pattern matched against refs/tags
tags:
@@ -8,7 +9,8 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.GH_ACCESS_TOKEN }}
- run: git push origin +main:production
+4
View File
@@ -50,6 +50,10 @@
"description": "Sendgrid api key. Used for email reminders in workflows",
"value": ""
},
"SENDGRID_SYNC_API_KEY": {
"description": "Sendgrid internal sync service",
"value": ""
},
"SENDGRID_EMAIL": {
"description": "Sendgrid email. Used for email reminders in workflows",
"value": ""
+2 -1
View File
@@ -43,7 +43,8 @@ const Component = ({
const router = useRouter();
const mutation = useAddAppMutation(null, {
onSuccess: () => {
onSuccess: (data) => {
if (data.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@@ -4,6 +4,7 @@ import { ReactNode, useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import { showToast } from "@calcom/ui/v2";
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/core/List";
import classNames from "@lib/classNames";
@@ -27,6 +28,13 @@ function IntegrationListItem(props: {
const [highlight, setHighlight] = useState(hl === props.slug);
const title = props.name || props.title;
// The highlight is to show a newly installed app, coming from the app's
// redirection after installation, so we proceed to show the corresponding
// message
if (highlight) {
showToast(t("app_successfully_installed"), "success");
}
useEffect(() => {
const timer = setTimeout(() => setHighlight(false), 3000);
return () => {
@@ -18,10 +18,11 @@ export default function OmniInstallAppButton({ appId, className }: { appId: stri
const utils = trpc.useContext();
const mutation = useAddAppMutation(null, {
onSuccess: () => {
onSuccess: (data) => {
//TODO: viewer.appById might be replaced with viewer.apps so that a single query needs to be invalidated.
utils.viewer.appById.invalidate({ appId });
utils.viewer.apps.invalidate({ extendsFeature: "EventType" });
if (data.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
@@ -173,7 +173,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
</div>
</div>
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
<div className="flex w-full items-end">
<div className="flex w-full items-end justify-end">
<Controller
name="minimumBookingNotice"
control={formMethods.control}
@@ -201,7 +201,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
label={t("minimum_booking_notice")}
type="number"
placeholder="120"
className="mr-2 rounded-[4px]"
className="mr-2 mb-0 h-[38px] rounded-[4px]"
{...formMethods.register("minimumBookingNoticeInDurationType", {
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
onMinimumNoticeChange(event.target.value),
@@ -211,7 +211,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
</div>
<Select
isSearchable={false}
className="mb-2 ml-2 w-full capitalize md:min-w-[150px] md:max-w-[200px]"
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
defaultValue={durationTypeOptions.find(
(option) => option.value === minimumBookingNoticeType.current
)}
@@ -234,7 +234,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
label: t("slot_interval_default"),
value: -1,
},
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
label: minutes + " " + t("minutes"),
value: minutes,
})),
+11 -7
View File
@@ -80,6 +80,7 @@ plugins.push(withAxiom);
/** @type {import("next").NextConfig} */
const nextConfig = {
i18n,
productionBrowserSourceMaps: true,
/* We already do type check on GH actions */
typescript: {
ignoreBuildErrors: !!process.env.CI,
@@ -88,10 +89,8 @@ const nextConfig = {
eslint: {
ignoreDuringBuilds: !!process.env.CI,
},
experimental: {
images: {
unoptimized: true,
},
images: {
unoptimized: true,
},
webpack: (config) => {
config.plugins.push(
@@ -230,9 +229,6 @@ const nextConfig = {
return redirects;
},
sentry: {
hideSourceMaps: true,
},
};
const sentryWebpackPluginOptions = {
@@ -241,6 +237,14 @@ const sentryWebpackPluginOptions = {
const moduleExports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
nextConfig.sentry = {
hideSourceMaps: true,
// Prevents Sentry from running on this Edge function, where Sentry doesn't work yet (build whould crash the api route).
excludeServerRoutes: [/\/api\/social\/og\/image\/?/],
};
}
// Sentry should be the last thing to export to catch everything right
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
? withSentryConfig(moduleExports, sentryWebpackPluginOptions)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.2.1",
"version": "2.2.2",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -56,7 +56,7 @@
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-toggle-group": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"@sentry/nextjs": "^7.17.3",
"@sentry/nextjs": "^7.20.0",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9",
+3 -1
View File
@@ -179,7 +179,9 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
})}
description={t(`no_category_apps_description_${variant || "other"}`)}
buttonRaw={
<Button color="secondary" href={variant ? `/apps/categories/${variant}` : "/apps"}>
<Button
color="secondary"
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
{t(`connect_${variant || "other"}_apps`)}
</Button>
}
@@ -10,6 +10,7 @@ export const AppSetupMap = {
"caldav-calendar": dynamic(() => import("../../caldavcalendar/pages/setup")),
zapier: dynamic(() => import("../../zapier/pages/setup")),
closecom: dynamic(() => import("../../closecomothercalendar/pages/setup")),
sendgrid: dynamic(() => import("../../sendgrid/pages/setup")),
};
export const AppSetupPage = (props: { slug: string }) => {
+4 -1
View File
@@ -8,7 +8,10 @@ const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
export const getCalendar = (credential: CredentialPayload | null): Calendar | null => {
if (!credential || !credential.key) return null;
const { type: calendarType } = credential;
let { type: calendarType } = credential;
if (calendarType === "sendgrid_other_calendar") {
calendarType = "sendgrid";
}
const calendarApp = appStore[calendarType.split("_").join("") as keyof typeof appStore];
if (!(calendarApp && "lib" in calendarApp && "CalendarService" in calendarApp.lib)) {
log.warn(`calendar of type ${calendarType} is not implemented`);
+18 -5
View File
@@ -1,4 +1,4 @@
import { useMutation } from "@tanstack/react-query";
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
import type { IntegrationOAuthCallbackState } from "@calcom/app-store/types";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -14,9 +14,17 @@ function gotoUrl(url: string, newTab?: boolean) {
window.location.href = url;
}
function useAddAppMutation(_type: App["type"] | null, options?: Parameters<typeof useMutation>[2]) {
type CustomUseMutationOptions =
| Omit<UseMutationOptions<unknown, unknown, unknown, unknown>, "mutationKey" | "mutationFn" | "onSuccess">
| undefined;
type UseAddAppMutationOptions = CustomUseMutationOptions & {
onSuccess: (data: { setupPending: boolean }) => void;
};
function useAddAppMutation(_type: App["type"] | null, options?: UseAddAppMutationOptions) {
const mutation = useMutation<
unknown,
{ setupPending: boolean },
Error,
{ type?: App["type"]; variant?: string; slug?: string; isOmniInstall?: boolean } | ""
>(async (variables) => {
@@ -28,6 +36,9 @@ function useAddAppMutation(_type: App["type"] | null, options?: Parameters<typeo
isOmniInstall = variables.isOmniInstall;
type = variables.type;
}
if (type === "sendgrid_other_calendar") {
type = "sendgrid";
}
const state: IntegrationOAuthCallbackState = {
returnTo:
WEBAPP_URL +
@@ -47,20 +58,22 @@ function useAddAppMutation(_type: App["type"] | null, options?: Parameters<typeo
}
const json = await res.json();
const externalUrl = /https?:\/\//.test(json.url) && !json.url.startsWith(window.location.origin);
if (!isOmniInstall) {
gotoUrl(json.url, json.newTab);
return;
}
// Skip redirection only if it is an OmniInstall and redirect URL isn't of some other origin
// This allows installation of apps like Stripe to still redirect to their authentication pages.
// Check first that the URL is absolute, then check that it is of different origin from the current.
if (/https?:\/\//.test(json.url) && !json.url.startsWith(window.location.origin)) {
if (externalUrl) {
// TODO: For Omni installation to authenticate and come back to the page where installation was initiated, some changes need to be done in all apps' add callbacks
gotoUrl(json.url, json.newTab);
}
return { setupPending: !externalUrl && json.url.endsWith("/setup") };
}, options);
return mutation;
@@ -30,6 +30,7 @@ import { metadata as qr_code_meta } from "./qr_code/_metadata";
import { metadata as rainbow_meta } from "./rainbow/_metadata";
import { metadata as raycast_meta } from "./raycast/_metadata";
import { metadata as riverside_meta } from "./riverside/_metadata";
import { metadata as sendgrid_meta } from "./sendgrid/_metadata";
import { metadata as sirius_video_meta } from "./sirius_video/_metadata";
import { metadata as stripepayment_meta } from "./stripepayment/_metadata";
import { metadata as tandemvideo_meta } from "./tandemvideo/_metadata";
@@ -70,6 +71,7 @@ export const appStoreMetadata = {
rainbow: rainbow_meta,
raycast: raycast_meta,
riverside: riverside_meta,
sendgrid: sendgrid_meta,
sirius_video: sirius_video_meta,
stripepayment: stripepayment_meta,
tandemvideo: tandemvideo_meta,
@@ -29,6 +29,7 @@ export const apiHandlers = {
rainbow: import("./rainbow/api"),
raycast: import("./raycast/api"),
riverside: import("./riverside/api"),
sendgrid: import("./sendgrid/api"),
sirius_video: import("./sirius_video/api"),
stripepayment: import("./stripepayment/api"),
tandemvideo: import("./tandemvideo/api"),
@@ -1,8 +1,11 @@
jest.mock("@calcom/lib/logger", () => ({
debug: jest.fn(),
error: jest.fn(),
log: jest.fn(),
getChildLogger: jest.fn(),
default: {
getChildLogger: () => ({
debug: jest.fn(),
error: jest.fn(),
log: jest.fn(),
}),
},
}));
jest.mock("@calcom/lib/crypto", () => ({
@@ -168,7 +168,7 @@ test("prepare data to create custom activity type instance: two attendees, no ad
const event = {
attendees,
startTime: now.toISOString(),
} as CalendarEvent;
} as unknown as CalendarEvent;
CloseCom.prototype.activity = {
type: {
@@ -57,6 +57,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const state = decodeOAuthState(req);
res.redirect(
getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "other_calendar", slug: "hubspot" })
getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "other", slug: "hubspot" })
);
}
+2
View File
@@ -15,6 +15,7 @@ import * as jitsivideo from "./jitsivideo";
import * as larkcalendar from "./larkcalendar";
import * as office365calendar from "./office365calendar";
import * as office365video from "./office365video";
import * as sendgrid from "./sendgrid";
import * as stripepayment from "./stripepayment";
import * as tandemvideo from "./tandemvideo";
import * as vital from "./vital";
@@ -36,6 +37,7 @@ const appStore = {
larkcalendar,
office365calendar,
office365video,
sendgrid,
stripepayment,
tandemvideo,
vital,
+10
View File
@@ -0,0 +1,10 @@
---
description: SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform.
items:
- /api/app-store/sendgrid/1.png
---
{description}
Features:
- Creates event attendees as contacts in Sendgrid
+10
View File
@@ -0,0 +1,10 @@
import type { AppMeta } from "@calcom/types/App";
import config from "./config.json";
export const metadata = {
category: "other",
...config,
} as AppMeta;
export default metadata;
@@ -0,0 +1,14 @@
import type { NextApiRequest } from "next";
import { defaultResponder } from "@calcom/lib/server";
import checkSession from "../../_utils/auth";
import { checkInstalled } from "../../_utils/installation";
export async function getHandler(req: NextApiRequest) {
const session = checkSession(req);
await checkInstalled("sendgrid", session.user?.id);
return { url: "/apps/sendgrid/setup" };
}
export default defaultResponder(getHandler);
@@ -0,0 +1,39 @@
import type { NextApiRequest } from "next";
import { symmetricEncrypt } from "@calcom/lib/crypto";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import checkSession from "../../_utils/auth";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
export async function getHandler(req: NextApiRequest) {
const session = checkSession(req);
const { api_key } = req.body;
if (!api_key) throw new HttpError({ statusCode: 400, message: "No Api Key provided to check" });
const encrypted = symmetricEncrypt(JSON.stringify({ api_key }), process.env.CALENDSO_ENCRYPTION_KEY || "");
const data = {
type: "sendgrid_other_calendar",
key: { encrypted },
userId: session.user?.id,
appId: "sendgrid",
};
try {
await prisma.credential.create({
data,
});
} catch (reason) {
logger.error("Could not add Sendgrid app", reason);
throw new HttpError({ statusCode: 500, message: "Could not add Sendgrid app" });
}
return { url: getInstalledAppPath({ variant: "other", slug: "sendgrid" }) };
}
export default defaultResponder(getHandler);
+6
View File
@@ -0,0 +1,6 @@
import { defaultHandler } from "@calcom/lib/server";
export default defaultHandler({
GET: import("./_getAdd"),
POST: import("./_postAdd"),
});
+31
View File
@@ -0,0 +1,31 @@
import type { NextApiRequest } from "next";
import Sendgrid from "@calcom/lib/Sendgrid";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import checkSession from "../../_utils/auth";
export async function getHandler(req: NextApiRequest) {
const { api_key } = req.body;
if (!api_key) throw new HttpError({ statusCode: 400, message: "No Api Key provoided to check" });
checkSession(req);
const sendgrid: Sendgrid = new Sendgrid(api_key);
try {
const usernameInfo = await sendgrid.username();
if (usernameInfo.username) {
return {};
} else {
throw new HttpError({ statusCode: 404 });
}
} catch (e) {
throw new HttpError({ statusCode: 500, message: e as string });
}
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(getHandler) }),
});
+2
View File
@@ -0,0 +1,2 @@
export { default as add } from "./add";
export { default as check } from "./check";
+16
View File
@@ -0,0 +1,16 @@
{
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Sendgrid",
"slug": "sendgrid",
"type": "sendgrid_other_calendar",
"imageSrc": "/api/app-store/sendgrid/logo.png",
"logo": "/api/app-store/sendgrid/logo.png",
"url": "https://cal.com/apps/sendgrid",
"variant": "other_calendar",
"categories": ["other"],
"publisher": "Cal.com",
"email": "help@cal.com",
"description": "SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform.",
"extendsFeature": "User",
"__createdUsingCli": true
}
+3
View File
@@ -0,0 +1,3 @@
export * as api from "./api";
export * as lib from "./lib";
export { metadata } from "./_metadata";
@@ -0,0 +1,99 @@
import z from "zod";
import Sendgrid, { SendgridNewContact } from "@calcom/lib/Sendgrid";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import logger from "@calcom/lib/logger";
import type {
Calendar,
CalendarEvent,
EventBusyDate,
IntegrationCalendar,
NewCalendarEventType,
} from "@calcom/types/Calendar";
import { CredentialPayload } from "@calcom/types/Credential";
const apiKeySchema = z.object({
encrypted: z.string(),
});
const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
/**
* Authentication
* Sendgrid requires Basic Auth for any request to their APIs, which is far from
* ideal considering that such a strategy requires generating an API Key by the
* user and input it in our system. A Setup page was created when trying to install
* Sendgrid in order to instruct how to create such resource and to obtain it.
*/
export default class CloseComCalendarService implements Calendar {
private integrationName = "";
private sendgrid: Sendgrid;
private log: typeof logger;
constructor(credential: CredentialPayload) {
this.integrationName = "sendgrid_other_calendar";
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
const parsedCredentialKey = apiKeySchema.safeParse(credential.key);
let decrypted;
if (parsedCredentialKey.success) {
decrypted = symmetricDecrypt(parsedCredentialKey.data.encrypted, CALENDSO_ENCRYPTION_KEY);
const { api_key } = JSON.parse(decrypted);
this.sendgrid = new Sendgrid(api_key);
} else {
throw Error(
`No API Key found for userId ${credential.userId} and appId ${credential.appId}: ${parsedCredentialKey.error}`
);
}
}
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
// Proceeding to just creating the user in Sendgrid, no event entity exists in Sendgrid
const contactsData = event.attendees.map((attendee) => ({
first_name: attendee.name,
email: attendee.email,
}));
const result = await this.sendgrid.sendgridRequest<SendgridNewContact>({
url: `/v3/marketing/contacts`,
method: "PUT",
body: {
contacts: contactsData,
},
});
return Promise.resolve({
id: "",
uid: result.job_id,
password: "",
url: "",
type: this.integrationName,
additionalInfo: {
result,
},
});
}
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
// Unless we want to be able to support modifying an event to add more attendees
// to have them created in Sendgrid, ingoring this use case for now
return Promise.resolve();
}
async deleteEvent(uid: string): Promise<void> {
// Unless we want to delete the contact in Sendgrid once the event
// is deleted just ingoring this use case for now
return Promise.resolve();
}
async getAvailability(
dateFrom: string,
dateTo: string,
selectedCalendars: IntegrationCalendar[]
): Promise<EventBusyDate[]> {
return Promise.resolve([]);
}
async listCalendars(event?: CalendarEvent): Promise<IntegrationCalendar[]> {
return Promise.resolve([]);
}
}
+1
View File
@@ -0,0 +1 @@
export { default as CalendarService } from "./CalendarService";
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/sendgrid",
"version": "0.0.0",
"main": "./index.ts",
"description": "SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform.",
"dependencies": {
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@sendgrid/client": "^7.7.0"
},
"devDependencies": {
"@calcom/types": "*"
}
}
@@ -0,0 +1,152 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import { useForm, Controller } from "react-hook-form";
import { Toaster } from "react-hot-toast";
import z from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Button } from "@calcom/ui/components/button";
import { Form, TextField } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/v2";
const formSchema = z.object({
api_key: z.string(),
});
export default function SendgridSetup() {
const { t } = useLocale();
const router = useRouter();
const [testPassed, setTestPassed] = useState<boolean | undefined>(undefined);
const [testLoading, setTestLoading] = useState<boolean>(false);
const form = useForm<{
api_key: string;
}>({
resolver: zodResolver(formSchema),
});
useEffect(() => {
const timer = setTimeout(() => {
if (testPassed === false) {
setTestPassed(undefined);
}
}, 3000);
return () => clearTimeout(timer);
}, [testPassed]);
return (
<div className="flex h-screen bg-gray-200">
<div className="m-auto rounded bg-white p-5 md:w-[520px] md:p-10">
<div className="flex flex-col space-y-5 md:flex-row md:space-y-0 md:space-x-5">
<div>
<img src="/api/app-store/sendgrid/logo.png" alt="Sendgrid" className="h-12 w-12 max-w-2xl" />
</div>
<div>
<h1 className="text-gray-600">{t("provide_api_key")}</h1>
<div className="mt-1 text-sm">
{t("generate_api_key_description")}{" "}
<a
className="text-indigo-400"
href="https://app.sendgrid.com/settings/api_keys"
target="_blank"
rel="noopener noreferrer">
Sendgrid
</a>
. {t("it_stored_encrypted")}
</div>
<div className="my-2 mt-3">
<Form
form={form}
handleSubmit={async (values) => {
const res = await fetch("/api/integrations/sendgrid/add", {
method: "POST",
body: JSON.stringify(values),
headers: {
"Content-Type": "application/json",
},
});
const json = await res.json();
if (res.ok) {
router.push(json.url);
} else {
showToast(json.message, "error");
}
}}>
<fieldset className="space-y-2" disabled={form.formState.isSubmitting}>
<Controller
name="api_key"
control={form.control}
render={({ field: { onBlur, onChange } }) => (
<TextField
className="my-0"
onBlur={onBlur}
disabled={testPassed === true}
name="api_key"
placeholder="api_xyz..."
onChange={async (e) => {
onChange(e.target.value);
form.setValue("api_key", e.target.value);
await form.trigger("api_key");
}}
/>
)}
/>
</fieldset>
<div className="mt-5 justify-end space-x-2 sm:mt-4 sm:flex">
<Button type="button" color="secondary" onClick={() => router.back()}>
{t("cancel")}
</Button>
<Button
type="submit"
loading={testLoading}
disabled={testPassed === true}
StartIcon={testPassed !== undefined ? (testPassed ? Icon.FiCheck : Icon.FiX) : undefined}
className={
testPassed !== undefined
? testPassed
? " !bg-green-100 !text-green-700 hover:bg-green-100"
: "!border-red-700 bg-red-100 !text-red-700 hover:bg-red-100"
: "secondary"
}
color={testPassed === true ? "minimal" : "secondary"}
onClick={async () => {
const check = await form.trigger("api_key");
if (!check) return;
const api_key = form.getValues("api_key");
setTestLoading(true);
const res = await fetch("/api/integrations/sendgrid/check", {
method: "POST",
body: JSON.stringify({ api_key }),
headers: {
"Content-Type": "application/json",
},
});
if (res.status === 200) {
setTestPassed(true);
} else {
setTestPassed(false);
}
setTestLoading(false);
}}>
{t(
testPassed !== undefined ? (testPassed ? "test_passed" : "test_failed") : "test_api_key"
)}
</Button>
<Button type="submit" loading={form.formState.isSubmitting}>
{t("save")}
</Button>
</div>
</Form>
</div>
</div>
</div>
</div>
<Toaster position="bottom-right" />
</div>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -105,7 +105,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
<li>You&apos;re set!</li>
</Trans>
</ol>
<Link href="/apps/installed" passHref={true}>
<Link href="/apps/installed/automation?hl=zapier" passHref={true}>
<Button color="secondary">{t("done")}</Button>
</Link>
</div>
@@ -339,7 +339,10 @@ const handleZoomResponse = async (response: Response, credentialId: Credential["
}
throw Error(response.statusText);
}
// handle 204 response code with empty response (causes crash otherwise as "" is invalid JSON)
if (response.status === 204) {
return;
}
return responseClone.json();
};
@@ -1,8 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { MembershipRole, Prisma } from "@prisma/client";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
@@ -19,12 +21,19 @@ import Meta from "@calcom/ui/v2/core/Meta";
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
import showToast from "@calcom/ui/v2/core/notifications";
interface TeamProfileValues {
name: string;
url: string;
logo: string;
bio: string;
}
const regex = new RegExp("^[a-zA-Z0-9-]*$");
const teamProfileFormSchema = z.object({
name: z.string(),
slug: z
.string()
.regex(regex, {
message: "Url can only have alphanumeric characters(a-z, 0-9) and hyphen(-) symbol.",
})
.min(1, { message: "Url cannot be left empty" }),
logo: z.string(),
bio: z.string(),
});
const ProfileView = () => {
const { t } = useLocale();
@@ -42,7 +51,9 @@ const ProfileView = () => {
},
});
const form = useForm<TeamProfileValues>();
const form = useForm({
resolver: zodResolver(teamProfileFormSchema),
});
const { data: team, isLoading } = trpc.viewer.teams.get.useQuery(
{ teamId: Number(router.query.id) },
@@ -53,11 +64,11 @@ const ProfileView = () => {
onSuccess: (team) => {
if (team) {
form.setValue("name", team.name || "");
form.setValue("url", team.slug || "");
form.setValue("slug", team.slug || "");
form.setValue("logo", team.logo || "");
form.setValue("bio", team.bio || "");
if (team.slug === null && (team?.metadata as Prisma.JsonObject)?.requestedSlug) {
form.setValue("url", ((team?.metadata as Prisma.JsonObject)?.requestedSlug as string) || "");
form.setValue("slug", ((team?.metadata as Prisma.JsonObject)?.requestedSlug as string) || "");
}
}
},
@@ -124,7 +135,7 @@ const ProfileView = () => {
const variables = {
logo: values.logo,
name: values.name,
slug: values.url,
slug: values.slug,
bio: values.bio,
};
objectKeys(variables).forEach((key) => {
@@ -176,16 +187,17 @@ const ProfileView = () => {
/>
<Controller
control={form.control}
name="url"
name="slug"
render={({ field: { value } }) => (
<div className="mt-8">
<TextField
name="url"
name="slug"
label={t("team_url")}
value={value}
addOnLeading={`${WEBAPP_URL}/team/`}
onChange={(e) => {
form.setValue("url", e?.target.value);
form.clearErrors("slug");
form.setValue("slug", e?.target.value);
}}
/>
</div>
+129
View File
@@ -0,0 +1,129 @@
import client from "@sendgrid/client";
import { ClientRequest } from "@sendgrid/client/src/request";
import { ClientResponse } from "@sendgrid/client/src/response";
import logger from "@calcom/lib/logger";
export type SendgridFieldOptions = [string, string][];
type SendgridUsernameResult = {
username: string;
user_id: number;
};
export type SendgridCustomField = {
id: string;
name: string;
field_type: string;
_metadata: {
self: string;
};
};
export type SendgridContact = {
id: string;
first_name: string;
last_name: string;
email: string;
};
export type SendgridSearchResult = {
result: SendgridContact[];
};
export type SendgridFieldDefinitions = {
custom_fields: SendgridCustomField[];
};
export type SendgridNewContact = {
job_id: string;
};
const environmentApiKey = process.env.SENDGRID_SYNC_API_KEY || "";
/**
* This class to instance communicating to Sendgrid APIs requires an API Key.
*
* You can either pass to the constructor an API Key or have one defined as an
* environment variable in case the communication to Sendgrid is just for
* one account only, not configurable by any user at any moment.
*/
export default class Sendgrid {
private log: typeof logger;
constructor(providedApiKey = "") {
this.log = logger.getChildLogger({ prefix: [`[[lib] sendgrid`] });
if (!providedApiKey && !environmentApiKey) throw Error("Sendgrid Api Key not present");
client.setApiKey(providedApiKey || environmentApiKey);
}
public username = async () => {
const username = await this.sendgridRequest<SendgridUsernameResult>({
url: `/v3/user/username`,
method: "GET",
});
return username;
};
public async sendgridRequest<R = ClientResponse>(data: ClientRequest): Promise<R> {
this.log.debug("sendgridRequest:request", data);
const results = await client.request(data);
this.log.debug("sendgridRequest:results", results);
if (results[1].errors) throw Error(`Sendgrid request error: ${results[1].errors}`);
return results[1];
}
public async getSendgridContactId(email: string) {
const search = await this.sendgridRequest<SendgridSearchResult>({
url: `/v3/marketing/contacts/search`,
method: "POST",
body: {
query: `email LIKE '${email}'`,
},
});
this.log.debug("sync:sendgrid:getSendgridContactId:search", search);
return search.result || [];
}
public async getSendgridCustomFieldsIds(customFields: SendgridFieldOptions) {
// Get Custom Activity Fields
const allFields = await this.sendgridRequest<SendgridFieldDefinitions>({
url: `/v3/marketing/field_definitions`,
method: "GET",
});
allFields.custom_fields = allFields.custom_fields ?? [];
this.log.debug("sync:sendgrid:getCustomFieldsIds:allFields", allFields);
const customFieldsNames = allFields.custom_fields.map((fie) => fie.name);
this.log.debug("sync:sendgrid:getCustomFieldsIds:customFieldsNames", customFieldsNames);
const customFieldsExist = customFields.map((cusFie) => customFieldsNames.includes(cusFie[0]));
this.log.debug("sync:sendgrid:getCustomFieldsIds:customFieldsExist", customFieldsExist);
return await Promise.all(
customFieldsExist.map(async (exist, idx) => {
if (!exist) {
const [name, field_type] = customFields[idx];
const created = await this.sendgridRequest<SendgridCustomField>({
url: `/v3/marketing/field_definitions`,
method: "POST",
body: {
name,
field_type,
},
});
this.log.debug("sync:sendgrid:getCustomFieldsIds:customField:created", created);
return created.id;
} else {
const index = customFieldsNames.findIndex((val) => val === customFields[idx][0]);
if (index >= 0) {
this.log.debug(
"sync:sendgrid:getCustomFieldsIds:customField:existed",
allFields.custom_fields[index].id
);
return allFields.custom_fields[index].id;
} else {
throw Error("Couldn't find the field index");
}
}
})
);
}
}
+5 -5
View File
@@ -12,13 +12,13 @@ export default function convertToNewDurationType(
/** Convert `prevValue` from `prevType` to `newType` */
const newDurationTypeMap = {
minutes_minutes: () => prevValue,
minutes_hours: () => prevValue * MINUTES_IN_HOUR,
minutes_days: () => prevValue * MINUTES_IN_DAY,
hours_minutes: () => prevValue / MINUTES_IN_HOUR,
minutes_hours: () => prevValue / MINUTES_IN_HOUR,
minutes_days: () => prevValue / MINUTES_IN_DAY,
hours_minutes: () => prevValue * MINUTES_IN_HOUR,
hours_hours: () => prevValue,
hours_days: () => prevValue * HOURS_IN_DAY,
days_minutes: () => prevValue / MINUTES_IN_DAY,
days_hours: () => prevValue / HOURS_IN_DAY,
days_minutes: () => prevValue * MINUTES_IN_DAY,
days_hours: () => prevValue * HOURS_IN_DAY,
days_days: () => prevValue,
};
const getNewValue = newDurationTypeMap[`${prevType}_${newType}`];
+1 -3
View File
@@ -5,7 +5,7 @@ type Handlers = {
};
/** Allows us to split big API handlers by method */
const defaultHandler = (handlers: Handlers) => async (req: NextApiRequest, res: NextApiResponse) => {
export const defaultHandler = (handlers: Handlers) => async (req: NextApiRequest, res: NextApiResponse) => {
const handler = (await handlers[req.method as keyof typeof handlers])?.default;
// auto catch unsupported methods.
if (!handler) {
@@ -22,5 +22,3 @@ const defaultHandler = (handlers: Handlers) => async (req: NextApiRequest, res:
return res.status(500).json({ message: "Something went wrong" });
}
};
export default defaultHandler;
+1 -3
View File
@@ -6,7 +6,7 @@ import { performance } from "./perfObserver";
type Handle<T> = (req: NextApiRequest, res: NextApiResponse) => Promise<T>;
/** Allows us to get type inference from API handler responses */
function defaultResponder<T>(f: Handle<T>) {
export function defaultResponder<T>(f: Handle<T>) {
return async (req: NextApiRequest, res: NextApiResponse) => {
let ok = false;
try {
@@ -25,5 +25,3 @@ function defaultResponder<T>(f: Handle<T>) {
}
};
}
export default defaultResponder;
+2 -2
View File
@@ -1,7 +1,7 @@
export { checkBookingLimits, checkLimit } from "./checkBookingLimits";
export { default as defaultHandler } from "./defaultHandler";
export { default as defaultResponder } from "./defaultResponder";
export { defaultHandler } from "./defaultHandler";
export { defaultResponder } from "./defaultResponder";
export { getLuckyUser } from "./getLuckyUser";
export { getServerErrorFromUnknown } from "./getServerErrorFromUnknown";
export { getTranslation } from "./i18n";
@@ -20,6 +20,8 @@ const calComSharedFields: CloseComFieldOptions = [["Contact Role", "text", false
const serviceName = "closecom_service";
export default class CloseComService extends SyncServiceCore implements ISyncService {
protected declare service: CloseCom;
constructor() {
super(serviceName, CloseCom, logger.getChildLogger({ prefix: [`[[sync] ${serviceName}`] }));
}
@@ -120,7 +122,7 @@ export default class CloseComService extends SyncServiceCore implements ISyncSer
this.log.debug("sync:closecom:web:team:update", { prevTeam, updatedTeam });
const leadId = await getCloseComLeadId(this.service, { companyName: prevTeam.name });
this.log.debug("sync:closecom:web:team:update:leadId", { leadId });
this.service.lead.update(leadId, updatedTeam);
this.service.lead.update(leadId, { companyName: updatedTeam.name });
},
},
membership: {
+9 -113
View File
@@ -1,41 +1,11 @@
import sendgrid from "@sendgrid/client";
import { ClientRequest } from "@sendgrid/client/src/request";
import { ClientResponse } from "@sendgrid/client/src/response";
import logger from "@calcom/lib/logger";
import ISyncService, { ConsoleUserInfoType, WebUserInfoType } from "@calcom/lib/sync/ISyncService";
import SyncServiceCore from "@calcom/lib/sync/ISyncService";
type SendgridCustomField = {
id: string;
name: string;
field_type: string;
_metadata: {
self: string;
};
};
type SendgridContact = {
id: string;
first_name: string;
last_name: string;
email: string;
};
type SendgridSearchResult = {
result: SendgridContact[];
};
type SendgridFieldDefinitions = {
custom_fields: SendgridCustomField[];
};
type SendgridNewContact = {
job_id: string;
};
import Sendgrid, { SendgridFieldOptions, SendgridNewContact } from "../../Sendgrid";
import ISyncService, { ConsoleUserInfoType, WebUserInfoType } from "../ISyncService";
import SyncServiceCore from "../ISyncService";
// Cal.com Custom Contact Fields
const calComCustomContactFields: [string, string][] = [
const calComCustomContactFields: SendgridFieldOptions = [
// Field name, field type
["username", "Text"],
["plan", "Text"],
@@ -43,92 +13,18 @@ const calComCustomContactFields: [string, string][] = [
["createdAt", "Date"],
];
type SendgridRequest = <R = ClientResponse>(data: ClientRequest) => Promise<R>;
// TODO: When creating Sendgrid app, move this to the corresponding file
class Sendgrid {
constructor() {
if (!process.env.SENDGRID_API_KEY) throw Error("Sendgrid Api Key not present");
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
return sendgrid;
}
}
const serviceName = "sendgrid_service";
export default class SendgridService extends SyncServiceCore implements ISyncService {
protected declare service: Sendgrid;
constructor() {
super(serviceName, Sendgrid, logger.getChildLogger({ prefix: [`[[sync] ${serviceName}`] }));
}
sendgridRequest: SendgridRequest = async (data: ClientRequest) => {
this.log.debug("sendgridRequest:request", data);
const results = await this.service.request(data);
this.log.debug("sendgridRequest:results", results);
if (results[1].errors) throw Error(`Sendgrid request error: ${results[1].errors}`);
return results[1];
};
getSendgridContactId = async (email: string) => {
const search = await this.sendgridRequest<SendgridSearchResult>({
url: `/v3/marketing/contacts/search`,
method: "POST",
body: {
query: `email LIKE '${email}'`,
},
});
this.log.debug("sync:sendgrid:getSendgridContactId:search", search);
return search.result || [];
};
getSendgridCustomFieldsIds = async () => {
// Get Custom Activity Fields
const allFields = await this.sendgridRequest<SendgridFieldDefinitions>({
url: `/v3/marketing/field_definitions`,
method: "GET",
});
allFields.custom_fields = allFields.custom_fields ?? [];
this.log.debug("sync:sendgrid:getCustomFieldsIds:allFields", allFields);
const customFieldsNames = allFields.custom_fields.map((fie) => fie.name);
this.log.debug("sync:sendgrid:getCustomFieldsIds:customFieldsNames", customFieldsNames);
const customFieldsExist = calComCustomContactFields.map((cusFie) =>
customFieldsNames.includes(cusFie[0])
);
this.log.debug("sync:sendgrid:getCustomFieldsIds:customFieldsExist", customFieldsExist);
return await Promise.all(
customFieldsExist.map(async (exist, idx) => {
if (!exist) {
const [name, field_type] = calComCustomContactFields[idx];
const created = await this.sendgridRequest<SendgridCustomField>({
url: `/v3/marketing/field_definitions`,
method: "POST",
body: {
name,
field_type,
},
});
this.log.debug("sync:sendgrid:getCustomFieldsIds:customField:created", created);
return created.id;
} else {
const index = customFieldsNames.findIndex((val) => val === calComCustomContactFields[idx][0]);
if (index >= 0) {
this.log.debug(
"sync:sendgrid:getCustomFieldsIds:customField:existed",
allFields.custom_fields[index].id
);
return allFields.custom_fields[index].id;
} else {
throw Error("Couldn't find the field index");
}
}
})
);
};
upsert = async (user: WebUserInfoType | ConsoleUserInfoType) => {
this.log.debug("sync:sendgrid:user", user);
// Get Custom Contact fields ids
const customFieldsIds = await this.getSendgridCustomFieldsIds();
const customFieldsIds = await this.service.getSendgridCustomFieldsIds(calComCustomContactFields);
this.log.debug("sync:sendgrid:user:customFieldsIds", customFieldsIds);
const lastBooking = "email" in user ? await this.getUserLastBooking(user) : null;
this.log.debug("sync:sendgrid:user:lastBooking", lastBooking);
@@ -159,7 +55,7 @@ export default class SendgridService extends SyncServiceCore implements ISyncSer
),
};
this.log.debug("sync:sendgrid:contact:contactData", contactData);
const newContact = await this.sendgridRequest<SendgridNewContact>({
const newContact = await this.service.sendgridRequest<SendgridNewContact>({
url: `/v3/marketing/contacts`,
method: "PUT",
body: {
@@ -185,9 +81,9 @@ export default class SendgridService extends SyncServiceCore implements ISyncSer
return this.upsert(webUser);
},
delete: async (webUser: WebUserInfoType) => {
const [contactId] = await this.getSendgridContactId(webUser.email);
const [contactId] = await this.service.getSendgridContactId(webUser.email);
if (contactId) {
return this.sendgridRequest({
return this.service.sendgridRequest({
url: `/v3/marketing/contacts`,
method: "DELETE",
qs: {
@@ -101,5 +101,11 @@
"categories": ["video"],
"slug": "sirius_video",
"type": "sirius_video_video"
},
{
"dirName": "sendgrid",
"categories": ["other"],
"slug": "sendgrid",
"type": "sendgrid_other_calendar"
}
]
+2 -1
View File
@@ -18,9 +18,10 @@ export default function AppCard({ app, credentials }: AppCardProps) {
const { t } = useLocale();
const router = useRouter();
const mutation = useAddAppMutation(null, {
onSuccess: () => {
onSuccess: (data) => {
// Refresh SSR page content without actual reload
router.replace(router.asPath);
if (data.setupPending) return;
showToast(t("app_successfully_installed"), "success");
},
onError: (error) => {
+1
View File
@@ -182,6 +182,7 @@
"$CI",
"$CLOSECOM_API_KEY",
"$SENDGRID_API_KEY",
"$SENDGRID_SYNC_API_KEY",
"$SENDGRID_EMAIL",
"$TWILIO_TOKEN",
"$TWILIO_SID",
+95
View File
@@ -5033,6 +5033,16 @@
"@sentry/utils" "7.17.4"
tslib "^1.9.3"
"@sentry/browser@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.20.0.tgz#e917cab48d3bba2fe8a2e3a8987b0bd9b9149db8"
integrity sha512-L84CdB7DPQ2ohVcWh/KivemndWSZyXRvBZBr+tHFlQchzcaZZ/8lIPvjwvb8OJhzhecDq6JCAyUxaZwyItdyAg==
dependencies:
"@sentry/core" "7.20.0"
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
tslib "^1.9.3"
"@sentry/cli@^1.74.6":
version "1.74.6"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.74.6.tgz#c4f276e52c6f5e8c8d692845a965988068ebc6f5"
@@ -5055,6 +5065,15 @@
"@sentry/utils" "7.17.4"
tslib "^1.9.3"
"@sentry/core@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.20.0.tgz#7becd848c982d5422627f69c4061d5914946c54a"
integrity sha512-8dIHk8niyEyVayUQpgECHnV2p444nPBjIyuQrtkdTxL7sBLC5+Y0DhRjxg9cJyZe/bZnXVerGkgcA7niKW4W8A==
dependencies:
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
tslib "^1.9.3"
"@sentry/integrations@7.17.4":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.17.4.tgz#facdc4abb1d4714175c7d66bab634a898cc7eead"
@@ -5065,6 +5084,16 @@
localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/integrations@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.20.0.tgz#78fc544847c5516acb86fc507d0bfdaedb295fd8"
integrity sha512-wT00rtdKiVOZm/2fGEotIQ2ItCU9xTODz3KveQLq2X2nI5Jg4opkDOY2NorEnuOfBNg6BncBxaC8TG8ujvRyfw==
dependencies:
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/nextjs@^7.17.3":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.17.4.tgz#9c6d71d17e37da3fcbe753a374e93307a56dbf02"
@@ -5084,6 +5113,25 @@
rollup "2.78.0"
tslib "^1.9.3"
"@sentry/nextjs@^7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.20.0.tgz#e30cdf43be4bf0bc955b4e8d3f8eacccada21900"
integrity sha512-riPRp/+1awbOQ+TJXp1nmNKwvdtQrGFvTf1l6eONUbmIqN0ZWfToyEGYfFX1jbHamRHNvXcwKhixuPlJG+Nqyg==
dependencies:
"@rollup/plugin-sucrase" "4.0.4"
"@rollup/plugin-virtual" "3.0.0"
"@sentry/core" "7.20.0"
"@sentry/integrations" "7.20.0"
"@sentry/node" "7.20.0"
"@sentry/react" "7.20.0"
"@sentry/tracing" "7.20.0"
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
"@sentry/webpack-plugin" "1.20.0"
chalk "3.0.0"
rollup "2.78.0"
tslib "^1.9.3"
"@sentry/node@7.17.4":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.17.4.tgz#647ebcbd7228e5fa326dc3f95aceab34771205d3"
@@ -5097,6 +5145,19 @@
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/node@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.20.0.tgz#a32e37b2c93607e8b8d7f3741f60418ec2d99ddb"
integrity sha512-aB0VSueCiWPg53DIevnKNYSu2OGsPweWO1eKhMhu1uJNbL+ZYAFRczdcLdkWCqa38X5jjqO82GKuIZYpXUGX+A==
dependencies:
"@sentry/core" "7.20.0"
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
cookie "^0.4.1"
https-proxy-agent "^5.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/react@7.17.4":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.17.4.tgz#1d2c752841ace0561043be6a863de9fc7437f64c"
@@ -5108,6 +5169,17 @@
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/react@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.20.0.tgz#41e1edb5a7330bf15ab06e0c0b3e0be7cf36a7d3"
integrity sha512-hXPObzl4I7TgeCGEb3b03yLI7zF/oYQ5NoGz65fmhUainOGuW+S8KSyCWmAvaHXZ1cZao+sLfQSxl29sVVQyww==
dependencies:
"@sentry/browser" "7.20.0"
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@7.17.4":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.17.4.tgz#1254f4eb7397e1134248de1dade33d2fec864005"
@@ -5118,11 +5190,26 @@
"@sentry/utils" "7.17.4"
tslib "^1.9.3"
"@sentry/tracing@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.20.0.tgz#346bf72bc5574ac78de43bd8ab6f6027f8b64cbd"
integrity sha512-qg3sMvjuMQl/NEaF8I2IpvUcJ4HGGVIwEqqqZ6hkeHXIKt02p6f+nls45pVhluMiNHAaQJ+vefMTUc3E1yZwDA==
dependencies:
"@sentry/core" "7.20.0"
"@sentry/types" "7.20.0"
"@sentry/utils" "7.20.0"
tslib "^1.9.3"
"@sentry/types@7.17.4":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.17.4.tgz#476522bc988989101e7aee9eee3c3f8f16fa59ea"
integrity sha512-QJj8vO4AtxuzQfJIzDnECSmoxwnS+WJsm1Ta2Cwdy+TUCBJyWpW7aIJJGta76zb9gNPGb3UcAbeEjhMJBJeRMQ==
"@sentry/types@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.20.0.tgz#ace232e00407355dccedf65e27471635b4331265"
integrity sha512-x17ddduGWqW95neBFVvxzmInb5WXVw+2PcNASHXpGFhi7v2gz2a7/w2CcIKxsqODNnc+z/k1t0Y+uy9B6aH6ag==
"@sentry/utils@7.17.4":
version "7.17.4"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.17.4.tgz#b8f4fb6a329765b97668b34f6d397e8de2169fef"
@@ -5131,6 +5218,14 @@
"@sentry/types" "7.17.4"
tslib "^1.9.3"
"@sentry/utils@7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.20.0.tgz#cca09e2256b64bfb41a78c89af3c88fcc7ca84c2"
integrity sha512-4lc122TFgkaCAvoPRy+uc5vgOCumTa/2nPkzCSxVsezQs+ebHxyMJQK7GWBLI6P+EzKfEjlgyMzRWaPJ3iJatA==
dependencies:
"@sentry/types" "7.20.0"
tslib "^1.9.3"
"@sentry/webpack-plugin@1.20.0":
version "1.20.0"
resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.20.0.tgz#e7add76122708fb6b4ee7951294b521019720e58"