Skip to main content
Glama
dynamic-piece-property.tsx7.95 kB
import deepEqual from 'deep-equal'; import React, { useState, useRef, useContext } from 'react'; import { useFormContext, UseFormReturn, useWatch } from 'react-hook-form'; import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect'; import { useBuilderStateContext } from '@/app/builder/builder-hooks'; import { SkeletonList } from '@/components/ui/skeleton'; import { formUtils } from '@/features/pieces/lib/form-utils'; import { piecesHooks } from '@/features/pieces/lib/pieces-hooks'; import { authenticationSession } from '@/lib/authentication-session'; import { PiecePropertyMap, PropertyType } from '@activepieces/pieces-framework'; import { AUTHENTICATION_PROPERTY_NAME, isNil, PropertySettings, } from '@activepieces/shared'; import { DynamicPropertiesErrorBoundary } from './dynamic-piece-properties-error-boundary'; import { DynamicPropertiesContext } from './dynamic-properties-context'; import { GenericPropertiesForm } from './generic-properties-form'; const removeOptionsFromDropdownPropertiesSchema = ( schema: PiecePropertyMap, ): PiecePropertyMap => { return Object.fromEntries( Object.entries(schema).map(([key, value]) => { if ( value.type === PropertyType.STATIC_DROPDOWN || value.type === PropertyType.STATIC_MULTI_SELECT_DROPDOWN ) { return [key, { ...value, options: { disabled: false, options: [] } }]; } return [key, value]; }), ) as PiecePropertyMap; }; const DynamicPropertiesImplementation = React.memo( (props: DynamicPropertiesProps) => { const [flowVersion, readonly] = useBuilderStateContext((state) => [ state.flowVersion, state.readonly, ]); const form = useFormContext(); const watchConfig: Record<string, unknown> = { name: props.placedInside === 'stepSettings' ? 'settings.input' : undefined, }; const allInputsValues = useWatch(watchConfig); const refreshersPropertiesNames = [ ...props.refreshers, AUTHENTICATION_PROPERTY_NAME, ]; const refresherValues = refreshersPropertiesNames.reduce< Record<string, unknown> >((acc, refresher) => { acc[refresher] = allInputsValues[refresher]; return acc; }, {}); const previousRefresherValues = useRef<Record<string, unknown>>(refresherValues); const { propertyLoadingFinished, propertyLoadingStarted } = useContext( DynamicPropertiesContext, ); const [propertyMap, setPropertyMap] = useState< PiecePropertyMap | undefined >(undefined); const propertyPrefix = props.placedInside === 'stepSettings' ? 'settings.input' : ''; const { mutate, isPending } = piecesHooks.usePieceOptions<PropertyType.DYNAMIC>({ onMutate: () => { propertyLoadingStarted(props.propertyName); }, onError: (error) => { console.error(error); propertyLoadingFinished(props.propertyName); }, onSuccess: () => { propertyLoadingFinished(props.propertyName); }, }); const clearPropertyValue = () => { // the field state won't be cleared if you only unset the parent prop value if (propertyMap) { Object.keys(propertyMap).forEach((childPropName) => { form.setValue( prependPrefixToPropertyName({ propertyName: `${props.propertyName}.${childPropName}`, prefix: propertyPrefix, }), null, { //never validate for each prop, it can be a long list of props and cause the browser to freeze shouldValidate: false, }, ); }); } form.setValue( prependPrefixToPropertyName({ propertyName: props.propertyName, prefix: propertyPrefix, }), null, { shouldValidate: true, }, ); }; useDeepCompareEffectNoCheck(() => { if (!deepEqual(previousRefresherValues.current, refresherValues)) { clearPropertyValue(); } previousRefresherValues.current = refresherValues; mutate( { request: { projectId: authenticationSession.getProjectId()!, pieceName: props.pieceName, pieceVersion: props.pieceVersion, propertyName: props.propertyName, actionOrTriggerName: props.actionOrTriggerName, input: refresherValues, flowVersionId: flowVersion.id, flowId: flowVersion.flowId, }, propertyType: PropertyType.DYNAMIC, }, { onSuccess: (response) => { const currentValue = form.getValues( prependPrefixToPropertyName({ propertyName: props.propertyName, prefix: propertyPrefix, }), ); const defaultValue = formUtils.getDefaultValueForProperties({ props: response.options, existingInput: currentValue ?? {}, propertySettings: undefined, }); setPropertyMap(response.options); const schemaWithoutDropdownOptions = removeOptionsFromDropdownPropertiesSchema(response.options); props.updateFormSchema?.( prependPrefixToPropertyName({ propertyName: props.propertyName, prefix: propertyPrefix, }), schemaWithoutDropdownOptions, ); if (!readonly && props.updatePropertySettingsSchema) { props.updatePropertySettingsSchema( schemaWithoutDropdownOptions, props.propertyName, form, ); } form.setValue( prependPrefixToPropertyName({ propertyName: props.propertyName, prefix: propertyPrefix, }), defaultValue, { shouldValidate: true, shouldDirty: true, }, ); }, }, ); }, [refresherValues]); return ( <> {isPending && ( <SkeletonList numberOfItems={3} className="h-7"></SkeletonList> )} {!isPending && propertyMap && ( <GenericPropertiesForm prefixValue={prependPrefixToPropertyName({ propertyName: props.propertyName, prefix: propertyPrefix, })} props={propertyMap} useMentionTextInput={!isNil(props.propertySettings)} disabled={props.disabled} propertySettings={props.propertySettings} dynamicPropsInfo={null} ></GenericPropertiesForm> )} </> ); }, ); const DynamicProperties = React.memo((props: DynamicPropertiesProps) => { return ( <DynamicPropertiesErrorBoundary> <DynamicPropertiesImplementation {...props} /> </DynamicPropertiesErrorBoundary> ); }); DynamicPropertiesImplementation.displayName = 'DynamicPropertiesImplementation'; DynamicProperties.displayName = 'DynamicProperties'; export { DynamicProperties }; const prependPrefixToPropertyName = ({ propertyName, prefix, }: { propertyName: string; prefix: string; }) => { return prefix.length === 0 ? propertyName : `${prefix}.${propertyName}`; }; type DynamicPropertiesProps = { refreshers: string[]; propertyName: string; pieceName: string; pieceVersion: string; actionOrTriggerName: string; disabled: boolean; placedInside: 'stepSettings' | 'predefinedAgentInputs'; updateFormSchema: | ((key: string, newFieldSchema: PiecePropertyMap) => void) | null; propertySettings: Record<string, PropertySettings> | null; updatePropertySettingsSchema: | (( schema: PiecePropertyMap, propertyName: string, form: UseFormReturn, ) => void) | null; };

Latest Blog Posts

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