Way to break out and go back to previous step in flow?

I have a fairly complicated flow, and now I'm running into a situation where I need to restart the flow when I receive a 404 response in a lower step. It would be great to have an action where I could say if a 404 response is received, then go back to this step (while also defining a new $param.q value).

1 Like

While loop is what you are looking for?

As for the param, use it via a variable (assign step).. and change this variable's value before the next iteration.

Thanks @sid. Since we don't have an easy way to move back to a previous step, I rearranged the conditions, so the steps that result in the 404 happen first.

I haven't tried this myself, but you could :slight_smile:

Take the code view of your API and copy/paste it into ChatGPT and ask it to simplify the logic for you. A new approach may remove the requirement to go back to previous steps.

GPT is good at a lot of coding tasks, but this workflow is 10,000 lines of code and GPT doesn't have enough knowledge about Wappler to successfully modify it.

The file is just JSON, no? I copy/pasted a change password API I wrote into GPT just now and it had a few suggestions for improving it.

Regardless, there's probably a limit to the characters you can paste into the free version of ChatGPT

Maybe one day it will work, but it doesn't know enough about Wappler.

It turned this type of code

{
  condition: {
    outputType: "boolean",
    if: "{{messagesAdded}}",
    then: {
      steps: {
        repeat: {
          name: "messagesAddedRepeat_SQLite",
          output: true,
          outputType: "array",
          meta: [
            {name: "$index", type: "number"},
            {name: "$number", type: "number"},
            {name: "$name", type: "text"},
            {name: "$value", type: "object"}
          ],
          repeat: "{{messagesAdded}}",
          outputFields: [],
          exec: {
            steps: [
              {
                bootbox.alert: {disabled: true, message: "MessagesAdded"}
              },
              {
                Capacitor.sqlite.single: {
                  name: "historyMessageAddedExists",
                  output: true,
                  outputType: "object",
                  meta: [
                    {type: "number", name: "GmailHistoryId"},
                    {type: "text", name: "GmailId"},
                    {type: "text", name: "GmailThreadId"}

Into this (Wappler doesn't use functions).

// Utility function to check if a message already exists
function checkMessageExists(messageId) {
  return Capacitor.sqlite.single({
    name: "historyMessageAddedExists",
    output: true,
    outputType: "object",
    meta: [
      {type: "number", name: "GmailHistoryId"},
      {type: "text", name: "GmailId"},
      {type: "text", name: "GmailThreadId"}
    ],
    connection: "mail",
    sql: {
      type: "select",
      columns: [
        {table: "Email", column: "GmailHistoryId", recid: 1},
        {table: "Email", column: "GmailId", recid: 2},
        {table: "Email", column: "GmailThreadId", recid: 3}
      ],

Fair enough

That said, GPT is amazing at creating complex SQL queries. I've used it a lot to build complex joins like the one below.

WITH SearchResults AS (
    SELECT
        "Activity"."ActivityId"
    FROM
        "Activity"
    LEFT JOIN "Contact" ON "Contact"."ContactId" = "Activity"."Contact"
    LEFT JOIN "Company" AS "DirectCompany" ON "DirectCompany"."CompanyId" = "Activity"."Company"
    LEFT JOIN "Company" AS "ContactCompany" ON "ContactCompany"."CompanyId" = "Contact"."Company"
    LEFT JOIN "AccountUser" AS "AssigneeUser" ON "AssigneeUser"."AccountUserId" = "Activity"."Assignee"
    LEFT JOIN "Activity_Label" ON "Activity_Label"."Activity_id" = "Activity"."ActivityId"
    LEFT JOIN "Label" ON "Label"."LabelId" = "Activity_Label"."Label_id"
    LEFT JOIN "Status" ON "Status"."StatusId" = "Activity"."TaskStatus"
    LEFT JOIN "Priority" ON "Priority"."PriorityId" = "Activity"."TaskPriority"
    WHERE
        "Activity"."Account" = :account
        AND "Activity"."Type" = 'task'
        AND (
            "Activity"."ShortDescription" ILIKE '%' || :searchTerm || '%' OR
            "Activity"."Description" ILIKE '%' || :searchTerm || '%' OR
            "Contact"."FirstName" ILIKE '%' || :searchTerm || '%' OR
            "Contact"."LastName" ILIKE '%' || :searchTerm || '%' OR
            "DirectCompany"."Name" ILIKE '%' || :searchTerm || '%' OR
            "ContactCompany"."Name" ILIKE '%' || :searchTerm || '%' OR
            "AssigneeUser"."FirstName" ILIKE '%' || :searchTerm || '%' OR
            "AssigneeUser"."LastName" ILIKE '%' || :searchTerm || '%' OR
            "Status"."Name" ILIKE '%' || :searchTerm || '%' OR
            "Status"."Value" ILIKE '%' || :searchTerm || '%' OR
            "Label"."Name" ILIKE '%' || :searchTerm || '%' OR
            "Label"."Value" ILIKE '%' || :searchTerm || '%' OR
          	"Priority"."Name" ILIKE '%' || :searchTerm || '%' OR
          	"Priority"."Value" ILIKE '%' || :searchTerm || '%'
          
        )
    GROUP BY "Activity"."ActivityId"
)

SELECT
    "Activity".*,
    "Contact"."FirstName" AS "ContactFirstName",
    "Contact"."LastName" AS "ContactLastName",
    COALESCE("DirectCompany"."Name", "ContactCompany"."Name") AS "CompanyName",
    "AssigneeUser"."FirstName" AS "AssigneeFirstName",
    "AssigneeUser"."LastName" AS "AssigneeLastName",
    "AssigneeUser"."Email" AS "AssigneeEmail",
    "Status"."Name" AS "StatusName",
    "Status"."Value" AS "StatusValue",
    "Priority"."Name" AS "PriorityName",
    "Priority"."Value" AS "PriorityValue",
    "Priority"."LabelColor" AS "PriorityLabelColor",
    ARRAY_AGG(DISTINCT "Label"."Name") FILTER (WHERE "Label"."Name" IS NOT NULL) AS "FormattedLabels",
    COUNT(*) OVER() AS total_count
FROM
    "Activity"
LEFT JOIN "Contact" ON "Contact"."ContactId" = "Activity"."Contact"
LEFT JOIN "Company" AS "DirectCompany" ON "DirectCompany"."CompanyId" = "Activity"."Company"
LEFT JOIN "Company" AS "ContactCompany" ON "ContactCompany"."CompanyId" = "Contact"."Company"
LEFT JOIN "AccountUser" AS "AssigneeUser" ON "AssigneeUser"."AccountUserId" = "Activity"."Assignee"
LEFT JOIN "Activity_Label" ON "Activity_Label"."Activity_id" = "Activity"."ActivityId"
LEFT JOIN "Label" ON "Label"."LabelId" = "Activity_Label"."Label_id"
LEFT JOIN "Status" ON "Status"."StatusId" = "Activity"."TaskStatus"
LEFT JOIN "Priority" ON "Priority"."PriorityId" = "Activity"."TaskPriority"
WHERE
    "Activity"."Account" = :account AND "Activity"."ActivityId" IN (SELECT "ActivityId" FROM SearchResults)
GROUP BY
    "Activity"."ActivityId", 
    "Contact"."FirstName", 
    "Contact"."LastName", 
    "DirectCompany"."Name", 
    "ContactCompany"."Name",
    "AssigneeUser"."FirstName", 
    "AssigneeUser"."LastName", 
    "AssigneeUser"."Email",
    "Status"."Name",
    "Status"."Value",
    "Priority"."Name",
    "Priority"."Value",
    "Priority"."LabelColor"
ORDER BY
    "Activity"."CreatedDateTime" DESC
LIMIT :limit OFFSET :offset;

A While step with a Guard variable, a Try/Catch inside, and the Throw Error checkbox in API Action.

While must evaluate to true until the desired end, in which you set the Guard variable to false

Thanks @Apple! I'm sure that's a superior solution, but I haven't designed something like that before and it makes my head hurt trying to visualize it. :smiley:

It seems a lot simpler to me if Wappler had a goTo step action that I could use after a condition that would allow going back to a previous step.

A lot of business process workflow engines have similar actions like the image below that allow you to define a path back to a previous action in the workflow.