diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ec4acafa7ac61e..825d7cd286e2b4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -57,6 +57,23 @@ jobs: const prNumber = pr.number; const headSha = pr.head.sha; + // Check if user has write access or higher (admin, maintain, write) + async function hasWriteAccess(username) { + try { + const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username, + }); + const hasAccess = ['admin', 'maintain', 'write'].includes(permission.permission); + console.log(`User ${username}: hasWriteAccess=${hasAccess}`); + return hasAccess; + } catch (e) { + console.log(`Could not check permission for ${username}: ${e.message}`); + return false; + } + } + console.log(`PR #${prNumber} by ${pr.user.login}`); console.log(`Author association: ${pr.author_association}`); console.log(`Head SHA: ${headSha}`); @@ -69,9 +86,16 @@ jobs: return; } - console.log(`Author ${pr.user.login} is external (${pr.author_association}), checking for core team approval...`); + // Check 2: Verify write access via API (author_association can be unreliable) + if (await hasWriteAccess(pr.user.login)) { + console.log(`Author ${pr.user.login} verified as having write access`); + core.setOutput('is-trusted', true); + return; + } + + console.log(`Author ${pr.user.login} does not have write access, checking for approval...`); - // Check 2: Has a core team member approved the current commit? + // Check 3: Has someone with write access approved the current commit? const reviews = await github.paginate(github.rest.pulls.listReviews, { owner, repo, @@ -105,33 +129,21 @@ jobs: } } - // Check if any approver is a core team member (has write access or higher) + // Check if any approver has write access for (const [reviewer, reviewData] of reviewerStates) { if (reviewData.state !== 'APPROVED') { continue; } - try { - const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner, - repo, - username: reviewer, - }); - - const isCoreTeam = ['admin', 'maintain', 'write'].includes(permission.permission); - console.log(`Reviewer ${reviewer}: permission=${permission.permission}, isCoreTeam=${isCoreTeam}`); - - if (isCoreTeam) { - console.log(`PR approved by core team member ${reviewer} for commit ${headSha}`); - core.setOutput('is-trusted', true); - return; - } - } catch (e) { - console.log(`Could not check permission for ${reviewer}: ${e.message}`); + if (await hasWriteAccess(reviewer)) { + console.log(`PR approved by ${reviewer} (has write access) for commit ${headSha}`); + core.setOutput('is-trusted', true); + return; } + console.log(`Reviewer ${reviewer} does not have write access`); } - console.log('PR requires approval from a core team member before CI can run'); + console.log('PR requires approval from someone with write access before CI can run'); core.setOutput('is-trusted', false); prepare: diff --git a/apps/web/modules/ee/workflows/components/VoiceSelectionDialog.tsx b/apps/web/modules/ee/workflows/components/VoiceSelectionDialog.tsx index 129a99e4a47836..0017c01250f4c7 100644 --- a/apps/web/modules/ee/workflows/components/VoiceSelectionDialog.tsx +++ b/apps/web/modules/ee/workflows/components/VoiceSelectionDialog.tsx @@ -1,6 +1,6 @@ import { getCoreRowModel, getSortedRowModel, useReactTable, type ColumnDef } from "@tanstack/react-table"; import { usePathname } from "next/navigation"; -import { useMemo, useState } from "react"; +import { useMemo, useState, useCallback } from "react"; import { DataTableProvider, DataTableWrapper } from "@calcom/features/data-table"; import { useSegments } from "@calcom/features/data-table/hooks/useSegments"; @@ -58,10 +58,10 @@ function VoiceSelectionContent({ const { data: voices, isLoading } = trpc.viewer.aiVoiceAgent.listVoices.useQuery(); - const handleUseVoice = (voiceId: string) => { + const handleUseVoice = useCallback((voiceId: string) => { onVoiceSelect(voiceId); showToast("Voice selected successfully", "success"); - }; + }, [onVoiceSelect]); const voiceData: Voice[] = useMemo(() => { if (!voices) return []; @@ -105,7 +105,7 @@ function VoiceSelectionContent({ header: t("trait"), size: 200, cell: ({ row }) => ( -
+
{row.original.accent && ( {row.original.accent} @@ -145,12 +145,12 @@ function VoiceSelectionContent({ color={selectedVoiceId === row.original.voice_id ? "primary" : "secondary"} onClick={() => handleUseVoice(row.original.voice_id)} className="whitespace-nowrap"> - {selectedVoiceId === row.original.voice_id ? <>{t("current_voice")} : t("use_voice")} + {selectedVoiceId === row.original.voice_id ? t("current_voice") : t("use_voice")} ), }, ], - [t, playingVoiceId, selectedVoiceId] + [t, playingVoiceId, selectedVoiceId, handlePlayVoice, handleUseVoice] ); const table = useReactTable({ @@ -196,7 +196,7 @@ export function VoiceSelectionDialog({ return ( - +
diff --git a/packages/app-store/apps.browser.generated.tsx b/packages/app-store/apps.browser.generated.tsx index 1bbd92de0c3ba1..cf6fef19fff46f 100644 --- a/packages/app-store/apps.browser.generated.tsx +++ b/packages/app-store/apps.browser.generated.tsx @@ -3,150 +3,68 @@ Don't modify this file manually. **/ import dynamic from "next/dynamic"; - export const InstallAppButtonMap = { - exchange2013calendar: dynamic( - () => import("./exchange2013calendar/components/InstallAppButton") - ), - exchange2016calendar: dynamic( - () => import("./exchange2016calendar/components/InstallAppButton") - ), - office365video: dynamic( - () => import("./office365video/components/InstallAppButton") - ), + exchange2013calendar: dynamic(() => import("./exchange2013calendar/components/InstallAppButton")), + exchange2016calendar: dynamic(() => import("./exchange2016calendar/components/InstallAppButton")), + office365video: dynamic(() => import("./office365video/components/InstallAppButton")), vital: dynamic(() => import("./vital/components/InstallAppButton")), }; - export const AppSettingsComponentsMap = { "general-app-settings": dynamic( - () => - import("./templates/general-app-settings/components/AppSettingsInterface") + () => import("./templates/general-app-settings/components/AppSettingsInterface") ), weather_in_your_calendar: dynamic( () => import("./weather_in_your_calendar/components/AppSettingsInterface") ), zapier: dynamic(() => import("./zapier/components/AppSettingsInterface")), }; - export const EventTypeAddonMap = { alby: dynamic(() => import("./alby/components/EventTypeAppCardInterface")), - basecamp3: dynamic( - () => import("./basecamp3/components/EventTypeAppCardInterface") - ), - btcpayserver: dynamic( - () => import("./btcpayserver/components/EventTypeAppCardInterface") - ), - closecom: dynamic( - () => import("./closecom/components/EventTypeAppCardInterface") - ), - databuddy: dynamic( - () => import("./databuddy/components/EventTypeAppCardInterface") - ), - fathom: dynamic( - () => import("./fathom/components/EventTypeAppCardInterface") - ), + basecamp3: dynamic(() => import("./basecamp3/components/EventTypeAppCardInterface")), + btcpayserver: dynamic(() => import("./btcpayserver/components/EventTypeAppCardInterface")), + closecom: dynamic(() => import("./closecom/components/EventTypeAppCardInterface")), + databuddy: dynamic(() => import("./databuddy/components/EventTypeAppCardInterface")), + fathom: dynamic(() => import("./fathom/components/EventTypeAppCardInterface")), ga4: dynamic(() => import("./ga4/components/EventTypeAppCardInterface")), giphy: dynamic(() => import("./giphy/components/EventTypeAppCardInterface")), gtm: dynamic(() => import("./gtm/components/EventTypeAppCardInterface")), - hitpay: dynamic( - () => import("./hitpay/components/EventTypeAppCardInterface") - ), - hubspot: dynamic( - () => import("./hubspot/components/EventTypeAppCardInterface") - ), - insihts: dynamic( - () => import("./insihts/components/EventTypeAppCardInterface") - ), - matomo: dynamic( - () => import("./matomo/components/EventTypeAppCardInterface") - ), - metapixel: dynamic( - () => import("./metapixel/components/EventTypeAppCardInterface") - ), - "mock-payment-app": dynamic( - () => import("./mock-payment-app/components/EventTypeAppCardInterface") - ), - paypal: dynamic( - () => import("./paypal/components/EventTypeAppCardInterface") - ), - "pipedrive-crm": dynamic( - () => import("./pipedrive-crm/components/EventTypeAppCardInterface") - ), - plausible: dynamic( - () => import("./plausible/components/EventTypeAppCardInterface") - ), - posthog: dynamic( - () => import("./posthog/components/EventTypeAppCardInterface") - ), - qr_code: dynamic( - () => import("./qr_code/components/EventTypeAppCardInterface") - ), - salesforce: dynamic( - () => import("./salesforce/components/EventTypeAppCardInterface") - ), - stripepayment: dynamic( - () => import("./stripepayment/components/EventTypeAppCardInterface") - ), + hitpay: dynamic(() => import("./hitpay/components/EventTypeAppCardInterface")), + hubspot: dynamic(() => import("./hubspot/components/EventTypeAppCardInterface")), + insihts: dynamic(() => import("./insihts/components/EventTypeAppCardInterface")), + matomo: dynamic(() => import("./matomo/components/EventTypeAppCardInterface")), + metapixel: dynamic(() => import("./metapixel/components/EventTypeAppCardInterface")), + "mock-payment-app": dynamic(() => import("./mock-payment-app/components/EventTypeAppCardInterface")), + paypal: dynamic(() => import("./paypal/components/EventTypeAppCardInterface")), + "pipedrive-crm": dynamic(() => import("./pipedrive-crm/components/EventTypeAppCardInterface")), + plausible: dynamic(() => import("./plausible/components/EventTypeAppCardInterface")), + posthog: dynamic(() => import("./posthog/components/EventTypeAppCardInterface")), + qr_code: dynamic(() => import("./qr_code/components/EventTypeAppCardInterface")), + salesforce: dynamic(() => import("./salesforce/components/EventTypeAppCardInterface")), + stripepayment: dynamic(() => import("./stripepayment/components/EventTypeAppCardInterface")), "booking-pages-tag": dynamic( - () => - import( - "./templates/booking-pages-tag/components/EventTypeAppCardInterface" - ) + () => import("./templates/booking-pages-tag/components/EventTypeAppCardInterface") ), "event-type-app-card": dynamic( - () => - import( - "./templates/event-type-app-card/components/EventTypeAppCardInterface" - ) - ), - twipla: dynamic( - () => import("./twipla/components/EventTypeAppCardInterface") + () => import("./templates/event-type-app-card/components/EventTypeAppCardInterface") ), + twipla: dynamic(() => import("./twipla/components/EventTypeAppCardInterface")), umami: dynamic(() => import("./umami/components/EventTypeAppCardInterface")), - "zoho-bigin": dynamic( - () => import("./zoho-bigin/components/EventTypeAppCardInterface") - ), - zohocrm: dynamic( - () => import("./zohocrm/components/EventTypeAppCardInterface") - ), + "zoho-bigin": dynamic(() => import("./zoho-bigin/components/EventTypeAppCardInterface")), + zohocrm: dynamic(() => import("./zohocrm/components/EventTypeAppCardInterface")), }; export const EventTypeSettingsMap = { - alby: dynamic( - () => import("./alby/components/EventTypeAppSettingsInterface") - ), - basecamp3: dynamic( - () => import("./basecamp3/components/EventTypeAppSettingsInterface") - ), - btcpayserver: dynamic( - () => import("./btcpayserver/components/EventTypeAppSettingsInterface") - ), - databuddy: dynamic( - () => import("./databuddy/components/EventTypeAppSettingsInterface") - ), - fathom: dynamic( - () => import("./fathom/components/EventTypeAppSettingsInterface") - ), + alby: dynamic(() => import("./alby/components/EventTypeAppSettingsInterface")), + basecamp3: dynamic(() => import("./basecamp3/components/EventTypeAppSettingsInterface")), + btcpayserver: dynamic(() => import("./btcpayserver/components/EventTypeAppSettingsInterface")), + databuddy: dynamic(() => import("./databuddy/components/EventTypeAppSettingsInterface")), + fathom: dynamic(() => import("./fathom/components/EventTypeAppSettingsInterface")), ga4: dynamic(() => import("./ga4/components/EventTypeAppSettingsInterface")), - giphy: dynamic( - () => import("./giphy/components/EventTypeAppSettingsInterface") - ), + giphy: dynamic(() => import("./giphy/components/EventTypeAppSettingsInterface")), gtm: dynamic(() => import("./gtm/components/EventTypeAppSettingsInterface")), - hitpay: dynamic( - () => import("./hitpay/components/EventTypeAppSettingsInterface") - ), - metapixel: dynamic( - () => import("./metapixel/components/EventTypeAppSettingsInterface") - ), - paypal: dynamic( - () => import("./paypal/components/EventTypeAppSettingsInterface") - ), - plausible: dynamic( - () => import("./plausible/components/EventTypeAppSettingsInterface") - ), - qr_code: dynamic( - () => import("./qr_code/components/EventTypeAppSettingsInterface") - ), - stripepayment: dynamic( - () => import("./stripepayment/components/EventTypeAppSettingsInterface") - ), + hitpay: dynamic(() => import("./hitpay/components/EventTypeAppSettingsInterface")), + metapixel: dynamic(() => import("./metapixel/components/EventTypeAppSettingsInterface")), + paypal: dynamic(() => import("./paypal/components/EventTypeAppSettingsInterface")), + plausible: dynamic(() => import("./plausible/components/EventTypeAppSettingsInterface")), + qr_code: dynamic(() => import("./qr_code/components/EventTypeAppSettingsInterface")), + stripepayment: dynamic(() => import("./stripepayment/components/EventTypeAppSettingsInterface")), };