Skip to main content
Glama

mcp-google-sheets

import-flow-dialog.tsx10.1 kB
import { useMutation } from '@tanstack/react-query'; import { HttpStatusCode } from 'axios'; import { t } from 'i18next'; import JSZip from 'jszip'; import { TriangleAlert } from 'lucide-react'; import React, { useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTelemetry } from '@/components/telemetry-provider'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTrigger, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectLabel, SelectItem, } from '@/components/ui/select'; import { LoadingSpinner } from '@/components/ui/spinner'; import { INTERNAL_ERROR_TOAST, toast } from '@/components/ui/use-toast'; import { foldersHooks } from '@/features/folders/lib/folders-hooks'; import { api } from '@/lib/api'; import { authenticationSession } from '@/lib/authentication-session'; import { FlowOperationType, FlowTemplate, PopulatedFlow, TelemetryEventName, } from '@activepieces/shared'; import { FormError } from '../../../components/ui/form'; import { flowsApi } from '../lib/flows-api'; export type ImportFlowDialogProps = | { insideBuilder: false; onRefresh: () => void; } | { insideBuilder: true; flowId: string; }; const readTemplateJson = async ( templateFile: File, ): Promise<FlowTemplate | null> => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => { try { const template = JSON.parse(reader.result as string) as FlowTemplate; const { template: tmpl, name } = template; if (!tmpl || !name || !tmpl.trigger) { resolve(null); } else { resolve(template); } } catch { resolve(null); } }; reader.readAsText(templateFile); }); }; const ImportFlowDialog = ( props: ImportFlowDialogProps & { children: React.ReactNode }, ) => { const { capture } = useTelemetry(); const [templates, setTemplates] = useState<FlowTemplate[]>([]); const fileInputRef = useRef<HTMLInputElement>(null); const [errorMessage, setErrorMessage] = useState(''); const [isDialogOpen, setIsDialogOpen] = useState(false); const [failedFiles, setFailedFiles] = useState<string[]>([]); const [selectedFolderName, setSelectedFolderName] = useState< string | undefined >(undefined); const { folders, isLoading } = foldersHooks.useFolders(); const navigate = useNavigate(); const { mutate: importFlows, isPending } = useMutation< PopulatedFlow[], Error, FlowTemplate[] >({ mutationFn: async (templates: FlowTemplate[]) => { const importPromises = templates.map(async (template) => { const flow = props.insideBuilder ? await flowsApi.get(props.flowId) : await flowsApi.create({ displayName: template.name, projectId: authenticationSession.getProjectId()!, folderName: selectedFolderName === undefined || selectedFolderName === 'Uncategorized' ? undefined : selectedFolderName, }); return await flowsApi.update(flow.id, { type: FlowOperationType.IMPORT_FLOW, request: { displayName: template.name, trigger: template.template.trigger, schemaVersion: template.template.schemaVersion, }, }); }); return Promise.all(importPromises); }, onSuccess: (flows: PopulatedFlow[]) => { capture({ name: TelemetryEventName.FLOW_IMPORTED_USING_FILE, payload: { location: props.insideBuilder ? 'inside the builder' : 'inside dashboard', multiple: flows.length > 1, }, }); toast({ title: t(`flowsImported`, { flowsCount: flows.length, }), variant: 'default', }); if (flows.length === 1) { navigate(`/flows/${flows[0].id}`, { replace: true }); return; } setIsDialogOpen(false); if (flows.length === 1 || props.insideBuilder) { navigate(`/flow-import-redirect/${flows[0].id}`); } if (!props.insideBuilder) { props.onRefresh(); } }, onError: (err) => { if ( api.isError(err) && err.response?.status === HttpStatusCode.BadRequest ) { setErrorMessage(t('Template file is invalid')); console.log(err); } else { toast(INTERNAL_ERROR_TOAST); } }, }); const handleSubmit = async () => { if (templates.length === 0) { setErrorMessage( failedFiles.length ? t( 'No valid templates found. The following files failed to import: ', ) + failedFiles.join(', ') : t('Please select a file first'), ); } else { setErrorMessage(''); importFlows(templates); } }; const handleFileChange = async ( event: React.ChangeEvent<HTMLInputElement>, ) => { const files = event.target.files; if (!files?.[0]) return; setTemplates([]); setFailedFiles([]); setErrorMessage(''); const file = files[0]; const newTemplates: FlowTemplate[] = []; const isZipFile = file.type === 'application/zip' || file.type === 'application/x-zip-compressed'; if (isZipFile && !props.insideBuilder) { const zip = new JSZip(); const zipContent = await zip.loadAsync(file); const jsonFiles = Object.keys(zipContent.files).filter((fileName) => fileName.endsWith('.json'), ); for (const fileName of jsonFiles) { const fileData = await zipContent.files[fileName].async('string'); const template = await readTemplateJson(new File([fileData], fileName)); if (template) { newTemplates.push(template); } else { setFailedFiles((prevFailedFiles) => [...prevFailedFiles, fileName]); } } } else if (file.type === 'application/json') { const template = await readTemplateJson(file); if (template) { newTemplates.push(template); } else { setFailedFiles((prevFailedFiles) => [...prevFailedFiles, file.name]); } } else { setErrorMessage(t('Unsupported file type')); return; } setTemplates(newTemplates); }; const handleFolderSelect = (folderName: string | undefined) => { setSelectedFolderName(folderName); }; return ( <Dialog open={isDialogOpen} onOpenChange={(open) => { setIsDialogOpen(open); if (!open) { setErrorMessage(''); setTemplates([]); setFailedFiles([]); } }} > <DialogTrigger asChild>{props.children}</DialogTrigger> <DialogContent className="sm:max-w-[500px]"> <DialogHeader> <div className="flex flex-col gap-3"> <DialogTitle>{t('Import Flow')}</DialogTitle> {props.insideBuilder && ( <div className="flex gap-1 items-center text-muted-foreground"> <TriangleAlert className="w-5 h-5 stroke-warning"></TriangleAlert> <div className="font-semibold">{t('Warning')}:</div> <div> {t('Importing a flow will overwrite your current one.')} </div> </div> )} </div> </DialogHeader> <div className="flex flex-col gap-4"> <div className="w-full flex flex-col gap-2 justify-between items-start"> <span className="w-16 text-sm font-medium text-gray-700"> {t('Flow')} </span> <Input id="file-input" type="file" accept={props.insideBuilder ? '.json' : '.json,.zip'} ref={fileInputRef} onChange={handleFileChange} /> </div> {!props.insideBuilder && ( <div className="w-full flex flex-col gap-2 justify-between items-start"> <span className="w-16 text-sm font-medium text-gray-700"> {t('Folder')} </span> {isLoading ? ( <div className="flex justify-center items-center w-full"> <LoadingSpinner /> </div> ) : ( <Select onValueChange={handleFolderSelect} defaultValue={selectedFolderName} > <SelectTrigger> <SelectValue placeholder={t('Select a folder')} /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectLabel>{t('Folders')}</SelectLabel> <SelectItem value="Uncategorized"> {t('Uncategorized')} </SelectItem> {folders?.map((folder) => ( <SelectItem key={folder.id} value={folder.displayName}> {folder.displayName} </SelectItem> ))} </SelectGroup> </SelectContent> </Select> )} </div> )} </div> {errorMessage && ( <FormError formMessageId="import-flow-error-message" className="mt-4"> {errorMessage} </FormError> )} <DialogFooter> <Button variant="outline" onClick={() => setIsDialogOpen(false)} disabled={isPending} > {t('Cancel')} </Button> <Button onClick={handleSubmit} loading={isPending}> {t('Import')} </Button> </DialogFooter> </DialogContent> </Dialog> ); }; export { ImportFlowDialog };

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