diff --git a/docs/content/docs/reference/components/jotform_v1.mdx b/docs/content/docs/reference/components/jotform_v1.mdx index 4269f9ccf5b..911adeef3db 100644 --- a/docs/content/docs/reference/components/jotform_v1.mdx +++ b/docs/content/docs/reference/components/jotform_v1.mdx @@ -25,12 +25,26 @@ Version: 1 | Name | Label | Type | Description | Required | |:---------------:|:--------------:|:------------:|:-------------------:|:--------:| -| region | Region | STRING
Options us, eu
| | true | +| region | Region | STRING
Options us, eu, hipaa
| | true | | key | Key | STRING | | true | | value | API Key | STRING | | true | +## Connection Setup + +To connect ByteChef with Jotform, you need a Jotform API key. + +1. Log into your [Jotform account](https://www.jotform.com/myaccount/api). +2. Navigate to the **API** Section. +3. Click **Create New Key**. +4. In the **Permissions** dropdown, select **Full Access**. +5. Give your key a name, like `Bytechef Integration`. +6. Copy the generated API Key. + +
+ +
@@ -96,6 +110,48 @@ Type: OBJECT +## Triggers + + +### New Submission +Name: newSubmission + +`Triggers when someone submits a response to a form.` + +Type: DYNAMIC_WEBHOOK + +#### Properties + +| Name | Label | Type | Description | Required | +|:---------------:|:--------------:|:------------:|:-------------------:|:--------:| +| formId | Form ID | STRING | The ID of the form to watch for new submissions. | true | + + +#### Output + +The output for this action is dynamic and may vary depending on the input parameters. To determine the exact structure of the output, you need to execute the action. + +#### JSON Example +```json +{ + "label" : "New Submission", + "name" : "newSubmission", + "parameters" : { + "formId" : "" + }, + "type" : "jotform/v1/newSubmission" +} +``` +#### How to find Form ID + +1. Log into your Jotform Account. +2. Hover over the form in your dashboard and click **Edit Form**. +3. Look at the URL in your browser's address bar. The URL will look similar to: `https://www.jotform.com/build/1111111111111`. +4. The long number in the URL (between `/build/` and anything that follows) is your **Form ID**. In the example, the Form ID is `1111111111111`. + + + + ## What to do if your action is not listed here? diff --git a/server/libs/modules/components/ai/llm/router/src/main/java/com/bytechef/component/ai/llm/router/model/RouterChatModel.java b/server/libs/modules/components/ai/llm/router/src/main/java/com/bytechef/component/ai/llm/router/model/RouterChatModel.java index 246caebff8a..1e5204f5fe5 100644 --- a/server/libs/modules/components/ai/llm/router/src/main/java/com/bytechef/component/ai/llm/router/model/RouterChatModel.java +++ b/server/libs/modules/components/ai/llm/router/src/main/java/com/bytechef/component/ai/llm/router/model/RouterChatModel.java @@ -321,7 +321,7 @@ public Double getTemperature() { } public List getStop() { - return stop; + return stop == null ? null : new ArrayList<>(stop); } public Integer getSeed() { @@ -353,7 +353,7 @@ public Boolean getLogprobs() { } public Map getLogitBias() { - return logitBias; + return logitBias == null ? null : new HashMap<>(logitBias); } public Double getFrequencyPenalty() { diff --git a/server/libs/modules/components/jotform/openapi.yaml b/server/libs/modules/components/jotform/openapi.yaml index 1f34fcdc587..4313d943f7d 100644 --- a/server/libs/modules/components/jotform/openapi.yaml +++ b/server/libs/modules/components/jotform/openapi.yaml @@ -12,6 +12,7 @@ paths: summary: "Get Form Submissions" description: "Get all submissions for a specific form." operationId: "getFormSubmissions" + x-help: "https://docs.bytechef.io/reference/components/jotform_v1#get-form-submissions" parameters: - name: "formId" description: "ID of the form to retrieve submissions for." diff --git a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/JotformComponentHandler.java b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/JotformComponentHandler.java index 0c03236073a..269d8498677 100644 --- a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/JotformComponentHandler.java +++ b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/JotformComponentHandler.java @@ -25,7 +25,9 @@ import com.bytechef.component.definition.ComponentDsl.ModifiableAuthorization; import com.bytechef.component.definition.ComponentDsl.ModifiableComponentDefinition; import com.bytechef.component.definition.ComponentDsl.ModifiableConnectionDefinition; +import com.bytechef.component.definition.ComponentDsl.ModifiableTriggerDefinition; import com.bytechef.component.definition.Property; +import com.bytechef.component.jotform.trigger.JotformNewSubmissionTrigger; import com.google.auto.service.AutoService; import java.util.ArrayList; import java.util.List; @@ -39,6 +41,11 @@ public class JotformComponentHandler extends AbstractJotformComponentHandler { private static final String REGION = "region"; + @Override + public List getTriggers() { + return List.of(JotformNewSubmissionTrigger.TRIGGER_DEFINITION); + } + @Override public ModifiableComponentDefinition modifyComponent(ModifiableComponentDefinition modifiableComponentDefinition) { return modifiableComponentDefinition @@ -66,7 +73,8 @@ public ModifiableConnectionDefinition modifyConnection( .label("Region") .options( option("US (jotform.com)", "us"), - option("EU (eu.jotform.com)", "eu")) + option("EU (eu.jotform.com)", "eu"), + option("HIPAA (hipaa-api.jotform.com)", "hipaa")) .required(true)); modifiableAuthorization.properties(properties); @@ -76,7 +84,14 @@ public ModifiableConnectionDefinition modifyConnection( .baseUri((connectionParameters, context) -> { String region = connectionParameters.getRequiredString(REGION); - return region.equals("us") ? "https://api.jotform.com" : "https://eu-api.jotform.com"; - }); + return switch (region) { + case "us" -> "https://api.jotform.com"; + case "eu" -> "https://eu-api.jotform.com"; + case "hipaa" -> "https://hipaa-api.jotform.com"; + default -> throw new IllegalArgumentException("Invalid region: " + region); + }; + }) + .help("", "https://docs.bytechef.io/reference/components/jotform_v1#connection-setup") + .version(1); } } diff --git a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/action/JotformGetFormSubmissionsAction.java b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/action/JotformGetFormSubmissionsAction.java index 6252b628f62..20e1ef77016 100644 --- a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/action/JotformGetFormSubmissionsAction.java +++ b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/action/JotformGetFormSubmissionsAction.java @@ -67,7 +67,8 @@ public class JotformGetFormSubmissionsAction { .required(false)) .metadata( Map.of( - "responseType", ResponseType.JSON)))); + "responseType", ResponseType.JSON)))) + .help("", "https://docs.bytechef.io/reference/components/jotform_v1#get-form-submissions"); private JotformGetFormSubmissionsAction() { } diff --git a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/trigger/JotformNewSubmissionTrigger.java b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/trigger/JotformNewSubmissionTrigger.java new file mode 100644 index 00000000000..21a60a97a9d --- /dev/null +++ b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/trigger/JotformNewSubmissionTrigger.java @@ -0,0 +1,120 @@ +/* + * Copyright 2025 ByteChef + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bytechef.component.jotform.trigger; + +import static com.bytechef.component.definition.ComponentDsl.string; +import static com.bytechef.component.definition.ComponentDsl.trigger; + +import com.bytechef.component.definition.ComponentDsl.ModifiableTriggerDefinition; +import com.bytechef.component.definition.Context.Http; +import com.bytechef.component.definition.Parameters; +import com.bytechef.component.definition.TriggerContext; +import com.bytechef.component.definition.TriggerDefinition.HttpHeaders; +import com.bytechef.component.definition.TriggerDefinition.HttpParameters; +import com.bytechef.component.definition.TriggerDefinition.OptionsFunction; +import com.bytechef.component.definition.TriggerDefinition.TriggerType; +import com.bytechef.component.definition.TriggerDefinition.WebhookBody; +import com.bytechef.component.definition.TriggerDefinition.WebhookEnableOutput; +import com.bytechef.component.definition.TriggerDefinition.WebhookMethod; +import com.bytechef.component.definition.TypeReference; +import com.bytechef.component.jotform.util.JotformUtils; +import java.util.Map; + +/** + * @author Monika Kušter + */ +public class JotformNewSubmissionTrigger { + + public static final String FORM_ID = "formId"; + public static final String WEBHOOK_URL = "webhookURL"; + + public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("newSubmission") + .title("New Submission") + .description("Triggers when someone submits a response to a form.") + .help("", "https://docs.bytechef.io/reference/components/jotform_v1#new-submission") + .type(TriggerType.DYNAMIC_WEBHOOK) + .properties( + string(FORM_ID) + .label("Form ID") + .description("The ID of the form to watch for new submissions.") + .options((OptionsFunction) JotformUtils::getFormIdOptions) + .required(true)) + .output() + .webhookDisable(JotformNewSubmissionTrigger::webhookDisable) + .webhookEnable(JotformNewSubmissionTrigger::webhookEnable) + .webhookRequest(JotformNewSubmissionTrigger::webhookRequest); + + private JotformNewSubmissionTrigger() { + } + + protected static void webhookDisable( + Parameters inputParameters, Parameters connectionParameters, Parameters outputParameters, + String workflowExecutionId, TriggerContext triggerContext) { + + Map body = triggerContext + .http(http -> http.get("/form/%s/webhooks".formatted(inputParameters.getRequiredString(FORM_ID)))) + .configuration(Http.responseType(Http.ResponseType.JSON)) + .execute() + .getBody(new TypeReference<>() {}); + + if (body.get("content") instanceof Map map) { + for (Map.Entry entry : map.entrySet()) { + Object value = entry.getValue(); + + if (value.equals(outputParameters.getRequiredString(WEBHOOK_URL))) { + triggerContext + .http(http -> http.delete( + "/form/%s/webhooks/%s".formatted(inputParameters.getRequiredString(FORM_ID), + entry.getKey()))) + .execute(); + + break; + } + } + } + } + + protected static WebhookEnableOutput webhookEnable( + Parameters inputParameters, Parameters connectionParameters, String webhookUrl, String workflowExecutionId, + TriggerContext triggerContext) { + + triggerContext + .http(http -> http.post("/form/%s/webhooks".formatted(inputParameters.getRequiredString(FORM_ID)))) + .header("Content-Type", "multipart/form-data") + .body(Http.Body.of(Map.of(WEBHOOK_URL, webhookUrl), Http.BodyContentType.FORM_DATA)) + .configuration(Http.responseType(Http.ResponseType.JSON)) + .execute(); + + return new WebhookEnableOutput(Map.of(WEBHOOK_URL, webhookUrl), null); + } + + protected static Object webhookRequest( + Parameters inputParameters, Parameters connectionParameters, HttpHeaders headers, + HttpParameters parameters, WebhookBody body, WebhookMethod method, Parameters webhookEnableOutputParameters, + TriggerContext context) { + + Map content = body.getContent(new TypeReference<>() {}); + + Object o = content.get("rawRequest"); + + if (o instanceof String s) { + content.put("parsedRawRequest", context.json(json -> json.read(s))); + } + + return content; + } +} diff --git a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/util/JotformUtils.java b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/util/JotformUtils.java index 59bea893ca1..636493a63d1 100644 --- a/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/util/JotformUtils.java +++ b/server/libs/modules/components/jotform/src/main/java/com/bytechef/component/jotform/util/JotformUtils.java @@ -39,20 +39,26 @@ public static List> getFormIdOptions( Parameters inputParameters, Parameters connectionParameters, Map lookupDependsOnPaths, String searchText, Context context) { - Map body = context.http(http -> http.get("/user/forms")) - .configuration(Http.responseType(Http.ResponseType.JSON)) - .execute() - .getBody(new TypeReference<>() {}); - List> options = new ArrayList<>(); + int offset = 0; + List list; + + do { + Map body = context.http(http -> http.get("/user/forms")) + .queryParameters("offset", offset, "limit", 1000) + .configuration(Http.responseType(Http.ResponseType.JSON)) + .execute() + .getBody(new TypeReference<>() {}); + + list = body.get("content") instanceof List content ? content : List.of(); - if (body.get("content") instanceof List list) { for (Object o : list) { if (o instanceof Map map) { options.add(option((String) map.get("title"), (String) map.get("id"))); + offset++; } } - } + } while (!list.isEmpty()); return options; } diff --git a/server/libs/modules/components/jotform/src/main/resources/connection.mdx b/server/libs/modules/components/jotform/src/main/resources/connection.mdx new file mode 100644 index 00000000000..8117cee764c --- /dev/null +++ b/server/libs/modules/components/jotform/src/main/resources/connection.mdx @@ -0,0 +1,14 @@ +## Connection Setup + +To connect ByteChef with Jotform, you need a Jotform API key. + +1. Log into your [Jotform account](https://www.jotform.com/myaccount/api). +2. Navigate to the **API** Section. +3. Click **Create New Key**. +4. In the **Permissions** dropdown, select **Full Access**. +5. Give your key a name, like `Bytechef Integration`. +6. Copy the generated API Key. + +
+ +
diff --git a/server/libs/modules/components/jotform/src/main/resources/newSubmission.mdx b/server/libs/modules/components/jotform/src/main/resources/newSubmission.mdx new file mode 100644 index 00000000000..3e0d4a8a839 --- /dev/null +++ b/server/libs/modules/components/jotform/src/main/resources/newSubmission.mdx @@ -0,0 +1,6 @@ +#### How to find Form ID + +1. Log into your Jotform Account. +2. Hover over the form in your dashboard and click **Edit Form**. +3. Look at the URL in your browser's address bar. The URL will look similar to: `https://www.jotform.com/build/1111111111111`. +4. The long number in the URL (between `/build/` and anything that follows) is your **Form ID**. In the example, the Form ID is `1111111111111`. diff --git a/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/trigger/JotformNewSubmissionTriggerTest.java b/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/trigger/JotformNewSubmissionTriggerTest.java new file mode 100644 index 00000000000..b9ef38b0678 --- /dev/null +++ b/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/trigger/JotformNewSubmissionTriggerTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2025 ByteChef + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bytechef.component.jotform.trigger; + +import static com.bytechef.component.jotform.trigger.JotformNewSubmissionTrigger.FORM_ID; +import static com.bytechef.component.jotform.trigger.JotformNewSubmissionTrigger.WEBHOOK_URL; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.bytechef.component.definition.Context.ContextFunction; +import com.bytechef.component.definition.Context.Http; +import com.bytechef.component.definition.Context.Http.Body; +import com.bytechef.component.definition.Context.Http.Configuration; +import com.bytechef.component.definition.Context.Http.Configuration.ConfigurationBuilder; +import com.bytechef.component.definition.Context.Http.Executor; +import com.bytechef.component.definition.Context.Http.Response; +import com.bytechef.component.definition.Context.Http.ResponseType; +import com.bytechef.component.definition.Context.Json; +import com.bytechef.component.definition.Parameters; +import com.bytechef.component.definition.TriggerContext; +import com.bytechef.component.definition.TriggerDefinition.WebhookBody; +import com.bytechef.component.definition.TriggerDefinition.WebhookEnableOutput; +import com.bytechef.component.definition.TypeReference; +import com.bytechef.component.test.definition.MockParametersFactory; +import com.bytechef.component.test.definition.extension.MockContextSetupExtension; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; + +/** + * @author Monika Kušter + */ +@ExtendWith(MockContextSetupExtension.class) +class JotformNewSubmissionTriggerTest { + + @SuppressWarnings("unchecked") + private final ArgumentCaptor> jsonFunctionArgumentCaptor = + forClass(ContextFunction.class); + private final Json mockedJson = mock(Json.class); + private final Parameters mockedInputParameters = MockParametersFactory.create(Map.of(FORM_ID, "xy")); + private final Parameters mockedOutputParameters = MockParametersFactory.create(Map.of(WEBHOOK_URL, "url1")); + private final ArgumentCaptor stringArgumentCaptor = forClass(String.class); + private final ArgumentCaptor bodyArgumentCaptor = forClass(Body.class); + + @Test + void testWebhookDisable( + TriggerContext mockedContext, Response mockedResponse, Executor mockedExecutor, Http mockedHttp, + ArgumentCaptor> httpFunctionArgumentCaptor, + ArgumentCaptor configurationBuilderArgumentCaptor) { + + when(mockedHttp.get(stringArgumentCaptor.capture())) + .thenReturn(mockedExecutor); + when(mockedResponse.getBody(any(TypeReference.class))) + .thenReturn(Map.of("content", Map.of("1", "url1", "2", "url2"))); + when(mockedHttp.delete(stringArgumentCaptor.capture())) + .thenReturn(mockedExecutor); + + JotformNewSubmissionTrigger.webhookDisable(mockedInputParameters, mockedInputParameters, mockedOutputParameters, + "", mockedContext); + + assertNotNull(httpFunctionArgumentCaptor.getValue()); + assertEquals(List.of("/form/xy/webhooks", "/form/xy/webhooks/1"), stringArgumentCaptor.getAllValues()); + + ConfigurationBuilder configurationBuilder = configurationBuilderArgumentCaptor.getValue(); + Configuration configuration = configurationBuilder.build(); + + assertEquals(ResponseType.JSON, configuration.getResponseType()); + } + + @Test + void testWebhookEnable( + TriggerContext mockedContext, Executor mockedExecutor, Http mockedHttp, + ArgumentCaptor> httpFunctionArgumentCaptor, + ArgumentCaptor configurationBuilderArgumentCaptor) { + + when(mockedHttp.post(stringArgumentCaptor.capture())) + .thenReturn(mockedExecutor); + when(mockedExecutor.header(stringArgumentCaptor.capture(), stringArgumentCaptor.capture())) + .thenReturn(mockedExecutor); + when(mockedExecutor.body(bodyArgumentCaptor.capture())) + .thenReturn(mockedExecutor); + + WebhookEnableOutput webhookEnableOutput = JotformNewSubmissionTrigger.webhookEnable( + mockedInputParameters, mockedInputParameters, "url", "", mockedContext); + + assertEquals(new WebhookEnableOutput(Map.of(WEBHOOK_URL, "url"), null), webhookEnableOutput); + assertNotNull(httpFunctionArgumentCaptor.getValue()); + assertEquals( + List.of("/form/xy/webhooks", "Content-Type", "multipart/form-data"), stringArgumentCaptor.getAllValues()); + + ConfigurationBuilder configurationBuilder = configurationBuilderArgumentCaptor.getValue(); + Configuration configuration = configurationBuilder.build(); + + assertEquals(ResponseType.JSON, configuration.getResponseType()); + assertEquals( + Body.of(Map.of(WEBHOOK_URL, "url"), Http.BodyContentType.FORM_DATA), bodyArgumentCaptor.getValue()); + } + + @Test + void testWebhookRequest() { + WebhookBody mockedWebhookBody = mock(WebhookBody.class); + TriggerContext mockedTriggerContext = mock(TriggerContext.class); + + Map contentMap = new HashMap<>(); + + contentMap.put("rawRequest", "{\"name\":\"xy\"}"); + + when(mockedWebhookBody.getContent(any(TypeReference.class))) + .thenReturn(contentMap); + when(mockedTriggerContext.json(jsonFunctionArgumentCaptor.capture())) + .thenAnswer(inv -> { + ContextFunction value = jsonFunctionArgumentCaptor.getValue(); + + return value.apply(mockedJson); + }); + when(mockedJson.read(stringArgumentCaptor.capture())) + .thenReturn(Map.of("name", "xy")); + + Object result = JotformNewSubmissionTrigger.webhookRequest( + mockedInputParameters, null, null, null, mockedWebhookBody, + null, null, mockedTriggerContext); + + assertEquals(Map.of("rawRequest", "{\"name\":\"xy\"}", "parsedRawRequest", Map.of("name", "xy")), result); + assertEquals("{\"name\":\"xy\"}", stringArgumentCaptor.getValue()); + } +} diff --git a/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/util/JotformUtilsTest.java b/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/util/JotformUtilsTest.java index 70bd1fa4fc6..d6b164a63e3 100644 --- a/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/util/JotformUtilsTest.java +++ b/server/libs/modules/components/jotform/src/test/java/com/bytechef/component/jotform/util/JotformUtilsTest.java @@ -17,45 +17,81 @@ package com.bytechef.component.jotform.util; import static com.bytechef.component.definition.ComponentDsl.option; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.bytechef.component.definition.Context; +import com.bytechef.component.definition.ActionContext; +import com.bytechef.component.definition.Context.ContextFunction; import com.bytechef.component.definition.Context.Http; -import com.bytechef.component.definition.Option; +import com.bytechef.component.definition.Context.Http.Configuration; +import com.bytechef.component.definition.Context.Http.Configuration.ConfigurationBuilder; +import com.bytechef.component.definition.Context.Http.Executor; +import com.bytechef.component.definition.Context.Http.Response; +import com.bytechef.component.definition.Context.Http.ResponseType; import com.bytechef.component.definition.Parameters; import com.bytechef.component.definition.TypeReference; +import com.bytechef.component.test.definition.extension.MockContextSetupExtension; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; /** * @author Monika Kušter */ +@ExtendWith(MockContextSetupExtension.class) class JotformUtilsTest { - private final Context mockedContext = mock(Context.class); - private final Http.Executor mockedExecutor = mock(Http.Executor.class); private final Parameters mockedParameters = mock(Parameters.class); - private final Http.Response mockedResponse = mock(Http.Response.class); + private final ArgumentCaptor objectsArgumentCaptor = forClass(Object[].class); + private final ArgumentCaptor stringArgumentCaptor = forClass(String.class); @Test - void testGetRootFolderOptions() { - when(mockedContext.http(any())) + void testGetFormIdOptions( + ActionContext mockedContext, Response mockedResponse, Executor mockedExecutor, Http mockedHttp, + ArgumentCaptor> httpFunctionArgumentCaptor, + ArgumentCaptor configurationBuilderArgumentCaptor) { + + when(mockedHttp.get(stringArgumentCaptor.capture())) .thenReturn(mockedExecutor); - when(mockedExecutor.configuration(any())) + when(mockedExecutor.queryParameters(objectsArgumentCaptor.capture())) .thenReturn(mockedExecutor); - when(mockedExecutor.execute()) - .thenReturn(mockedResponse); when(mockedResponse.getBody(any(TypeReference.class))) - .thenReturn(Map.of("content", List.of(Map.of("title", "form", "id", "123")))); - - List> expectedOptions = List.of(option("form", "123")); + .thenReturn(Map.of("content", List.of(Map.of("title", "form", "id", "123")))) + .thenReturn(Map.of("content", List.of())); assertEquals( - expectedOptions, + List.of(option("form", "123")), JotformUtils.getFormIdOptions(mockedParameters, mockedParameters, Map.of(), "", mockedContext)); + assertNotNull(httpFunctionArgumentCaptor.getValue()); + assertEquals(List.of("/user/forms", "/user/forms"), stringArgumentCaptor.getAllValues()); + + for (ConfigurationBuilder configurationBuilder : configurationBuilderArgumentCaptor.getAllValues()) { + Configuration configuration = configurationBuilder.build(); + + assertEquals(ResponseType.JSON, configuration.getResponseType()); + } + + Object[] queryParameters1 = { + "offset", 0, + "limit", 1000, + }; + + Object[] queryParameters2 = { + "offset", 1, + "limit", 1000, + }; + + List objectsArgumentCaptorAllValues = objectsArgumentCaptor.getAllValues(); + + assertEquals(2, objectsArgumentCaptorAllValues.size()); + assertArrayEquals(queryParameters1, objectsArgumentCaptorAllValues.get(0)); + assertArrayEquals(queryParameters2, objectsArgumentCaptorAllValues.get(1)); } } diff --git a/server/libs/modules/components/jotform/src/test/resources/definition/jotform_v1.json b/server/libs/modules/components/jotform/src/test/resources/definition/jotform_v1.json index 10886602b8b..ace9de33b31 100644 --- a/server/libs/modules/components/jotform/src/test/resources/definition/jotform_v1.json +++ b/server/libs/modules/components/jotform/src/test/resources/definition/jotform_v1.json @@ -6,7 +6,10 @@ "beforeTimeoutResume": null, "deprecated": null, "description": "Get all submissions for a specific form.", - "help": null, + "help": { + "body": "", + "learnMoreUrl": "https://docs.bytechef.io/reference/components/jotform_v1#get-form-submissions" + }, "metadata": { "method": "GET", "path": "/form/{formId}/submissions" @@ -450,6 +453,10 @@ "description": null, "label": "EU (eu.jotform.com)", "value": "eu" + }, { + "description": null, + "label": "HIPAA (hipaa-api.jotform.com)", + "value": "hipaa" } ], "optionsDataSource": null, "optionsLoadedDynamically": null, @@ -512,7 +519,10 @@ "type": "API_KEY" } ], "baseUri": { }, - "help": null, + "help": { + "body": "", + "learnMoreUrl": "https://docs.bytechef.io/reference/components/jotform_v1#connection-setup" + }, "processErrorResponse": null, "properties": null, "test": null, @@ -527,7 +537,64 @@ "resources": null, "tags": null, "title": "JotForm", - "triggers": [ ], + "triggers": [ { + "batch": null, + "deduplicate": null, + "deprecated": null, + "description": "Triggers when someone submits a response to a form.", + "dynamicWebhookRefresh": null, + "help": { + "body": "", + "learnMoreUrl": "https://docs.bytechef.io/reference/components/jotform_v1#new-submission" + }, + "listenerDisable": null, + "listenerEnable": null, + "name": "newSubmission", + "outputDefinition": { + "output": null, + "outputResponse": null, + "outputSchema": null, + "sampleOutput": null + }, + "poll": null, + "processErrorResponse": null, + "properties": [ { + "advancedOption": null, + "controlType": "SELECT", + "defaultValue": null, + "description": "The ID of the form to watch for new submissions.", + "displayCondition": null, + "exampleValue": null, + "expressionEnabled": null, + "hidden": null, + "label": "Form ID", + "languageId": null, + "maxLength": null, + "metadata": { }, + "minLength": null, + "name": "formId", + "options": null, + "optionsDataSource": { + "options": { }, + "optionsLookupDependsOn": null + }, + "optionsLoadedDynamically": null, + "placeholder": null, + "regex": null, + "required": true, + "type": "STRING" + } ], + "title": "New Submission", + "type": "DYNAMIC_WEBHOOK", + "webhookDisable": { }, + "webhookEnable": { }, + "webhookRawBody": null, + "webhookRequest": { }, + "webhookValidate": null, + "webhookValidateOnEnable": null, + "workflowNodeDescription": null, + "workflowSyncExecution": null + } ], "unifiedApi": null, "version": 1 } \ No newline at end of file