Skip to main content
Glama

mcp-google-sheets

index.tsx17 kB
import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation } from '@tanstack/react-query'; import { t } from 'i18next'; import { PencilIcon, Plus, TrashIcon } from 'lucide-react'; import { useState } from 'react'; import { useForm, UseFormReturn } from 'react-hook-form'; import * as z from 'zod'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { ScrollArea } from '@/components/ui/scroll-area'; import { LoadingSpinner } from '@/components/ui/spinner'; import { Textarea } from '@/components/ui/textarea'; import { gitSyncHooks } from '@/features/git-sync/lib/git-sync-hooks'; import { projectReleaseApi } from '@/features/project-version/lib/project-release-api'; import { platformHooks } from '@/hooks/platform-hooks'; import { authenticationSession } from '@/lib/authentication-session'; import { AgentOperationType, ConnectionOperationType, DiffReleaseRequest, ProjectReleaseType, ProjectSyncPlan, TableOperationType, } from '@activepieces/shared'; import { OperationChange } from './operation-change'; type CreateReleaseDialogProps = { open: boolean; setOpen: (open: boolean) => void; refetch: () => void; loading: boolean; diffRequest: DiffReleaseRequest; plan: ProjectSyncPlan; defaultName?: string; }; const formSchema = z.object({ name: z.string().min(1, t('Name is required')), description: z.string().optional(), }); type FormData = z.infer<typeof formSchema>; type CreateReleaseDialogContentProps = { form: UseFormReturn<FormData>; loading: boolean; diffRequest: DiffReleaseRequest; plan: ProjectSyncPlan; setOpen: (open: boolean) => void; refetch: () => void; }; const CreateReleaseDialogContent = ({ loading, diffRequest, plan, form, setOpen, refetch, }: CreateReleaseDialogContentProps) => { const isThereAnyChanges = (plan?.flows && plan?.flows.length > 0) || (plan?.tables && plan?.tables.length > 0) || (plan?.agents && plan?.agents.length > 0); const { platform } = platformHooks.useCurrentPlatform(); const { gitSync } = gitSyncHooks.useGitSync( authenticationSession.getProjectId()!, platform.plan.environmentsEnabled, ); const { mutate: applyChanges, isPending } = useMutation({ mutationFn: async () => { switch (diffRequest.type) { case ProjectReleaseType.GIT: if (!gitSync) { throw new Error('Git sync is not connected'); } await projectReleaseApi.create({ name: form.getValues('name'), description: form.getValues('description'), selectedFlowsIds: Array.from(selectedChanges), type: diffRequest.type, projectId: authenticationSession.getProjectId()!, }); break; case ProjectReleaseType.PROJECT: if (!diffRequest.targetProjectId) { throw new Error('Project ID is required'); } await projectReleaseApi.create({ name: form.getValues('name'), description: form.getValues('description'), selectedFlowsIds: Array.from(selectedChanges), targetProjectId: diffRequest.targetProjectId, type: diffRequest.type, projectId: authenticationSession.getProjectId()!, }); break; case ProjectReleaseType.ROLLBACK: await projectReleaseApi.create({ name: form.getValues('name'), description: form.getValues('description'), selectedFlowsIds: Array.from(selectedChanges), projectReleaseId: diffRequest.projectReleaseId, type: diffRequest.type, projectId: authenticationSession.getProjectId()!, }); break; } }, onSuccess: () => { refetch(); setOpen(false); }, }); const [selectedChanges, setSelectedChanges] = useState<Set<string>>( new Set(plan?.flows.map((op) => op.flow.id) || []), ); const [errorMessage, setErrorMessage] = useState(''); const handleSelectAll = (checked: boolean) => { if (!plan) return; setSelectedChanges( new Set(checked ? plan.flows.map((op) => op.flow.id) : []), ); }; return ( <> {loading && ( <div className="flex items-center justify-center h-24"> <LoadingSpinner /> </div> )} {!loading && isThereAnyChanges && ( <div className="space-y-4"> <div className="flex flex-col gap-2"> <Label className="text-sm" htmlFor="name"> {t('Name')} </Label> <Input id="name" {...form.register('name')} onChange={(e) => { if (e.target.value) { form.setError('name', { message: '' }); } }} placeholder={t('Meeting Summary Flow')} /> {form.formState.errors.name && ( <p className="text-sm text-destructive"> {form.formState.errors.name.message} </p> )} </div> <div className="flex flex-col gap-2"> <Label className="text-sm" htmlFor="description"> {t('Description')} </Label> <Textarea id="description" {...form.register('description')} placeholder={t('Added new features and fixed bugs')} /> {form.formState.errors.description && ( <p className="text-sm text-destructive"> {form.formState.errors.description.message} </p> )} </div> {plan?.flows && plan?.flows.length > 0 && ( <div className="space-y-2 "> <div className="flex flex-col gap-2"> <div className="flex items-center gap-2 py-2 border-b"> <Checkbox checked={selectedChanges.size === plan?.flows.length} onCheckedChange={handleSelectAll} /> <Label className="text-sm font-medium"> {t('Flows Changes')} ({selectedChanges.size}/ {plan?.flows.length || 0}) </Label> </div> </div> <ScrollArea viewPortClassName="max-h-[15vh]"> {plan?.flows.map((operation) => ( <OperationChange key={operation.flow.id} change={operation} selected={selectedChanges.has(operation.flow.id)} onSelect={(checked) => { const newSelectedChanges = new Set(selectedChanges); if (checked) { newSelectedChanges.add(operation.flow.id); } else { newSelectedChanges.delete(operation.flow.id); } setErrorMessage(''); setSelectedChanges(newSelectedChanges); }} /> ))} </ScrollArea> </div> )} {plan?.connections && plan?.connections.length > 0 && ( <div className="space-y-2"> <div className="flex flex-col gap-2"> <div className="flex flex-col justify -center gap-1 py-2 border-b"> <Label className="text-sm font-medium"> {t('Connections Changes')} ({plan?.connections?.length || 0} ) </Label> <div className="flex items-center text-sm text-muted-foreground"> <span className="flex items-center gap-2"> {t( 'New connections are placeholders and need to be reconnected again', )} </span> </div> </div> <ScrollArea viewPortClassName="max-h-[10vh]"> {plan?.connections.map((connection, index) => ( <div key={connection.connectionState.externalId} className="flex items-center gap-2 text-sm py-1" > {connection.type === ConnectionOperationType.UPDATE_CONNECTION && ( <div className="flex items-center gap-2"> <PencilIcon className="w-4 h-4 shrink-0" /> <div className="flex items-center gap-1"> <span> {connection.connectionState.displayName} </span> <span> {t('renamed to')} </span> <span> {connection.newConnectionState.displayName} </span> </div> </div> )} {connection.type === ConnectionOperationType.CREATE_CONNECTION && ( <div className="flex items-center gap-2"> <Plus className="w-4 h-4 shrink-0 text-success" /> <span className="text-success"> {connection.connectionState.displayName} </span> </div> )} </div> ))} </ScrollArea> </div> </div> )} {plan?.tables && plan?.tables.length > 0 && ( <div className="space-y-2"> <div className="flex flex-col gap-2"> <div className="flex flex-col justify -center gap-1 py-2 border-b"> <Label className="text-sm font-medium"> {t('Tables Changes')} ({plan?.tables?.length || 0}) </Label> </div> <ScrollArea viewPortClassName="max-h-[10vh]"> {plan?.tables.map((table, index) => ( <div key={table.tableState.externalId} className="flex items-center gap-2 text-sm py-1" > {table.type === TableOperationType.UPDATE_TABLE && ( <div className="flex items-center gap-2"> <PencilIcon className="w-4 h-4 shrink-0" /> <div className="flex items-center gap-1"> <span>{table.tableState.name}</span> </div> </div> )} {table.type === TableOperationType.CREATE_TABLE && ( <div className="flex items-center gap-2"> <Plus className="w-4 h-4 shrink-0 text-success" /> <span className="text-success"> {table.tableState.name} </span> </div> )} {table.type === TableOperationType.DELETE_TABLE && ( <div className="flex items-center gap-2"> <TrashIcon className="w-4 h-4 shrink-0 text-destructive" /> <span className="text-destructive"> {table.tableState.name} </span> </div> )} </div> ))} </ScrollArea> </div> </div> )} {plan?.agents && plan?.agents.length > 0 && ( <div className="space-y-2"> <div className="flex flex-col gap-2"> <div className="flex flex-col justify -center gap-1 py-2 border-b"> <Label className="text-sm font-medium"> {t('Agents Changes')} ({plan?.agents?.length || 0}) </Label> </div> <ScrollArea viewPortClassName="max-h-[10vh]"> {plan?.agents.map((agent, index) => ( <div key={agent.agentState.externalId} className="flex items-center gap-2 text-sm py-1" > {agent.type === AgentOperationType.UPDATE_AGENT && ( <div className="flex items-center gap-2"> <PencilIcon className="w-4 h-4 shrink-0" /> <div className="flex items-center gap-1"> <span>{agent.agentState.displayName}</span> </div> </div> )} {agent.type === AgentOperationType.CREATE_AGENT && ( <div className="flex items-center gap-2"> <Plus className="w-4 h-4 shrink-0 text-success" /> <span className="text-success"> {agent.agentState.displayName} </span> </div> )} {agent.type === AgentOperationType.DELETE_AGENT && ( <div className="flex items-center gap-2"> <TrashIcon className="w-4 h-4 shrink-0 text-destructive" /> <span className="text-destructive"> {agent.agentState.displayName} </span> </div> )} </div> ))} </ScrollArea> </div> </div> )} {errorMessage && ( <p className="text-sm text-destructive">{errorMessage}</p> )} </div> )} {loading || (!loading && !isThereAnyChanges && ( <div className="text-sm py-2">{t('No changes to apply')}</div> ))} {!loading && isThereAnyChanges && ( <DialogFooter className=" items-end gap-1 "> <Button size={'sm'} variant={'outline'} onClick={() => setOpen(false)} > {t('Cancel')} </Button> <Button size={'sm'} loading={isPending} disabled={isPending} onClick={() => { let error = false; if (form.getValues('name').trim() === '') { form.setError('name', { message: 'Release name is required' }); error = true; } if ( selectedChanges.size === 0 && plan.tables.length === 0 && plan.agents.length === 0 ) { setErrorMessage( 'Please select at least one change to include in the release', ); error = true; } if (error) { return; } applyChanges(); }} > {t('Apply Changes')} </Button> </DialogFooter> )} </> ); }; const CreateReleaseDialog = ({ open, setOpen, refetch, plan, loading, defaultName = '', diffRequest, }: CreateReleaseDialogProps) => { const form = useForm<FormData>({ resolver: zodResolver(formSchema), defaultValues: { name: defaultName, description: '', }, }); return ( <Dialog modal={true} open={open} onOpenChange={(newOpenState: boolean) => { if (newOpenState) { form.reset({ name: defaultName, description: '', }); } setOpen(newOpenState); }} > <DialogContent className="min-h-[100px] max-h-[850px] flex flex-col"> <DialogHeader className="flex-shrink-0"> <DialogTitle> {diffRequest.type === ProjectReleaseType.GIT ? t('Create Git Release') : diffRequest.type === ProjectReleaseType.PROJECT ? t('Create Project Release') : `${t('Create Rollback to')} ${form.getValues('name')}`} </DialogTitle> </DialogHeader> <CreateReleaseDialogContent key={`${loading}`} loading={loading} diffRequest={diffRequest} plan={plan} form={form} setOpen={setOpen} refetch={refetch} /> </DialogContent> </Dialog> ); }; export { CreateReleaseDialog };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server