This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 7d0c8f6d50 add retry, retry delay options, i18n and other minor 
cleanup. fixes #7040 (#7044)
7d0c8f6d50 is described below

commit 7d0c8f6d503c6617e3a93fadcee423613dd4868f
Author: Bart Maertens <[email protected]>
AuthorDate: Mon May 4 07:37:45 2026 +0000

    add retry, retry delay options, i18n and other minor cleanup. fixes #7040 
(#7044)
---
 .../pipeline/transforms/google-sheets-output.adoc  |   4 +-
 .../googlesheets/GoogleSheetsOutput.java           | 360 +++++++++++++++++----
 .../googlesheets/GoogleSheetsOutputDialog.java     |  98 ++++--
 .../googlesheets/GoogleSheetsOutputMeta.java       |  12 +-
 .../messages/messages_en_US.properties             |   8 +-
 5 files changed, 398 insertions(+), 84 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/google-sheets-output.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/google-sheets-output.adoc
index e54c711e21..453d716989 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/google-sheets-output.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/google-sheets-output.adoc
@@ -50,7 +50,9 @@ This transform requires a Google service account (JSON file) 
and a Google Cloud
 |option|description
 |JSON credential key file|Lets you specify or browse for spreadsheets existing 
in the service account drive or for the ones that are shared with the service 
account email.
 |Application Name|Your application name for the service account in the Google 
Developer Console.
-|Timeout|lets you specify an https timeout (in minutes, defaults to 5).
+|Time out (minutes) |lets you specify an https timeout (in minutes, defaults 
to 5).
+|Retry attempts|Number of retry attempts for retryable Google Sheets API 
errors (HTTP 429/500/503). Defaults to 3.
+|Retry delay (seconds)|Initial delay in seconds between retries for retryable 
Google Sheets API errors. Defaults to 2 seconds and uses exponential backoff 
for subsequent retries.
 |Impersonation|lets you impersonate your service account. Check the 
https://cloud.google.com/iam/docs/impersonating-service-accounts[Google docs] 
for more information.
 |===
 
diff --git 
a/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutput.java
 
b/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutput.java
index 1614644020..fbebf3d1a2 100644
--- 
a/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutput.java
+++ 
b/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutput.java
@@ -22,20 +22,17 @@ import 
com.google.api.client.googleapis.batch.json.JsonBatchCallback;
 import com.google.api.client.googleapis.json.GoogleJsonError;
 import com.google.api.client.http.HttpHeaders;
 import com.google.api.client.http.HttpRequestInitializer;
+import com.google.api.client.http.HttpResponseException;
 import com.google.api.client.http.javanet.NetHttpTransport;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.json.jackson2.JacksonFactory;
 import com.google.api.services.drive.Drive;
-import com.google.api.services.drive.model.File;
-import com.google.api.services.drive.model.FileList;
 import com.google.api.services.drive.model.Permission;
 import com.google.api.services.sheets.v4.Sheets;
 import com.google.api.services.sheets.v4.SheetsScopes;
 import com.google.api.services.sheets.v4.model.AddSheetRequest;
-import com.google.api.services.sheets.v4.model.AppendValuesResponse;
 import com.google.api.services.sheets.v4.model.BatchUpdateSpreadsheetRequest;
 import com.google.api.services.sheets.v4.model.ClearValuesRequest;
-import com.google.api.services.sheets.v4.model.ClearValuesResponse;
 import com.google.api.services.sheets.v4.model.DeleteSheetRequest;
 import com.google.api.services.sheets.v4.model.Request;
 import com.google.api.services.sheets.v4.model.Sheet;
@@ -43,12 +40,12 @@ import 
com.google.api.services.sheets.v4.model.SheetProperties;
 import com.google.api.services.sheets.v4.model.Spreadsheet;
 import com.google.api.services.sheets.v4.model.SpreadsheetProperties;
 import com.google.api.services.sheets.v4.model.UpdateSheetPropertiesRequest;
-import com.google.api.services.sheets.v4.model.UpdateValuesResponse;
 import com.google.api.services.sheets.v4.model.ValueRange;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.util.Utils;
@@ -63,6 +60,11 @@ public class GoogleSheetsOutput
   private String spreadsheetID;
   private NetHttpTransport httpTransport;
 
+  @FunctionalInterface
+  private interface RequestExecutor<T> {
+    T execute() throws Exception;
+  }
+
   public GoogleSheetsOutput(
       TransformMeta transformMeta,
       GoogleSheetsOutputMeta meta,
@@ -79,7 +81,7 @@ public class GoogleSheetsOutput
     JsonFactory jsonFactory;
     NetHttpTransport httpTransport;
     String scope;
-    Boolean exists = false;
+    boolean exists = false;
 
     if (super.init()) {
 
@@ -106,28 +108,16 @@ public class GoogleSheetsOutput
                 .setApplicationName(GoogleSheetsCredentials.APPLICATION_NAME)
                 .build();
         spreadsheetID = resolve(meta.getSpreadsheetKey());
-        @SuppressWarnings("java:S125")
-        // "properties has { key='id' and value='"+wsID+"'}";
-        String q = "mimeType='application/vnd.google-apps.spreadsheet'";
-        FileList result =
-            service
-                .files()
-                .list()
-                .setSupportsAllDrives(true)
-                .setIncludeItemsFromAllDrives(true)
-                .setQ(q)
-                .setPageSize(100)
-                .setFields("nextPageToken, files(id, name)")
-                .execute();
-        List<File> spreadsheets = result.getFiles();
-
-        for (File spreadsheet : spreadsheets) {
-          if (spreadsheetID.equals(spreadsheet.getId())) {
-            exists = true; // file exists
-            if (isBasic()) {
-              logBasic("Spreadsheet:" + spreadsheetID + " exists");
-            }
-          }
+
+        service
+            .files()
+            .get(spreadsheetID)
+            .setSupportsAllDrives(true)
+            .setFields("id, mimeType")
+            .execute();
+        exists = true;
+        if (isBasic()) {
+          logBasic("Spreadsheet:" + spreadsheetID + " exists");
         }
 
         boolean worksheetExists = false;
@@ -142,7 +132,10 @@ public class GoogleSheetsOutput
                   .build();
 
           Spreadsheet spreadSheet =
-              
data.service.spreadsheets().get(resolve(meta.getSpreadsheetKey())).execute();
+              executeSheetsRequestWithRetry(
+                  () ->
+                      
data.service.spreadsheets().get(resolve(meta.getSpreadsheetKey())).execute(),
+                  "loading spreadsheet metadata");
           List<Sheet> sheets = spreadSheet.getSheets();
           for (Sheet sheet : sheets) {
             if 
(sheet.getProperties().getTitle().equals(resolve(meta.getWorksheetId()))) {
@@ -155,10 +148,13 @@ public class GoogleSheetsOutput
                 List<Request> requests = Collections.singletonList(request);
                 BatchUpdateSpreadsheetRequest batchUpdateSpreadsheetRequest =
                     new BatchUpdateSpreadsheetRequest().setRequests(requests);
-                data.service
-                    .spreadsheets()
-                    .batchUpdate(spreadsheetID, batchUpdateSpreadsheetRequest)
-                    .execute();
+                executeSheetsRequestWithRetry(
+                    () ->
+                        data.service
+                            .spreadsheets()
+                            .batchUpdate(spreadsheetID, 
batchUpdateSpreadsheetRequest)
+                            .execute(),
+                    "deleting worksheet before replace");
                 worksheetExists = false;
                 if (isDetailed()) {
                   logDetailed("deleted sheet " + 
sheet.getProperties().getTitle());
@@ -177,10 +173,13 @@ public class GoogleSheetsOutput
                                 new 
SheetProperties().setTitle(resolve(meta.getWorksheetId())))));
             BatchUpdateSpreadsheetRequest body =
                 new BatchUpdateSpreadsheetRequest().setRequests(requests);
-            data.service
-                .spreadsheets()
-                .batchUpdate(resolve(meta.getSpreadsheetKey()), body)
-                .execute();
+            executeSheetsRequestWithRetry(
+                () ->
+                    data.service
+                        .spreadsheets()
+                        .batchUpdate(resolve(meta.getSpreadsheetKey()), body)
+                        .execute(),
+                "creating worksheet");
           }
         }
 
@@ -204,11 +203,12 @@ public class GoogleSheetsOutput
                 new Spreadsheet()
                     .setProperties(new 
SpreadsheetProperties().setTitle(spreadsheetID));
             Sheets.Spreadsheets.Create request = 
data.service.spreadsheets().create(spreadsheet);
-            Spreadsheet response = request.execute();
+            Spreadsheet response =
+                executeSheetsRequestWithRetry(() -> request.execute(), 
"creating spreadsheet");
             spreadsheetID = response.getSpreadsheetId();
             meta.setSpreadsheetKey(spreadsheetID); //
             // If it does not exist we use the Worksheet ID to rename 'Sheet 
ID'
-            if (resolve(meta.getWorksheetId()) != "Sheet1") {
+            if (!"Sheet1".equals(resolve(meta.getWorksheetId()))) {
 
               SheetProperties title =
                   new 
SheetProperties().setSheetId(0).setTitle(resolve(meta.getWorksheetId()));
@@ -226,7 +226,10 @@ public class GoogleSheetsOutput
               BatchUpdateSpreadsheetRequest requestBody = new 
BatchUpdateSpreadsheetRequest();
               requestBody.setRequests(requests);
               // now you can execute batchUpdate with your sheetsService and 
SHEET_ID
-              data.service.spreadsheets().batchUpdate(spreadsheetID, 
requestBody).execute();
+              executeSheetsRequestWithRetry(
+                  () ->
+                      data.service.spreadsheets().batchUpdate(spreadsheetID, 
requestBody).execute(),
+                  "renaming default worksheet");
             }
           } else {
             logError("Append and Create options cannot be activated 
altogether");
@@ -294,18 +297,44 @@ public class GoogleSheetsOutput
         }
 
         if (!exists && !meta.isCreate()) {
-          logError("File does not Exist");
+          logError(
+              "Spreadsheet not found (ID: "
+                  + spreadsheetID
+                  + "). Verify the spreadsheet key and that the service 
account has access. "
+                  + "If running inside GCP, rate limiting can also cause empty 
responses.");
           return false;
         }
 
-      } catch (Exception e) {
-        logError(
-            "Error: for worksheet : "
+      } catch (HttpResponseException e) {
+        // Sheets/Drive API calls (spreadsheets().get(), batchUpdate, 
permissions, etc.) can throw
+        // 429/503 under resource limits - never report these as "file does 
not exist"
+        logResourceLimitOrApiError(
+            e,
+            "worksheet: "
                 + resolve(meta.getWorksheetId())
-                + " in spreadsheet :"
-                + resolve(meta.getSpreadsheetKey())
-                + e.getMessage(),
-            e);
+                + " in spreadsheet: "
+                + resolve(meta.getSpreadsheetKey()));
+        return false;
+      } catch (Exception e) {
+        // Unwrap in case a wrapper (e.g. HopException) has 
HttpResponseException as cause
+        HttpResponseException httpEx = findHttpResponseException(e);
+        if (httpEx != null) {
+          logResourceLimitOrApiError(
+              httpEx,
+              "worksheet: "
+                  + resolve(meta.getWorksheetId())
+                  + " in spreadsheet: "
+                  + resolve(meta.getSpreadsheetKey()));
+        } else {
+          logError(
+              "Error: for worksheet : "
+                  + resolve(meta.getWorksheetId())
+                  + " in spreadsheet :"
+                  + resolve(meta.getSpreadsheetKey())
+                  + " - "
+                  + e.getMessage(),
+              e);
+        }
         return false;
       }
 
@@ -314,6 +343,154 @@ public class GoogleSheetsOutput
     return false;
   }
 
+  /**
+   * Log a clear error for resource limits (429, 500, 503) or other API 
errors, so they are never
+   * mistaken for "file does not exist".
+   */
+  private void logResourceLimitOrApiError(HttpResponseException e, String 
context) {
+    int statusCode = e.getStatusCode();
+    if (statusCode == 429) {
+      logError(
+          "Google API rate limit exceeded (429 Too Many Requests) for "
+              + context
+              + ". This often occurs when running inside GCP with low latency. 
"
+              + "Consider adding delays between pipeline runs. Details: "
+              + e.getMessage(),
+          e);
+    } else if (statusCode == 403) {
+      logError(
+          "Access denied (403) for "
+              + context
+              + ". Ensure the service account has access. Details: "
+              + e.getMessage(),
+          e);
+    } else if (statusCode == 500) {
+      logError(
+          "Google API internal server error (500) for "
+              + context
+              + ". This is a transient Google backend error ('backendError'). "
+              + "Try again later. Details: "
+              + e.getMessage(),
+          e);
+    } else if (statusCode == 503) {
+      logError(
+          "Google API temporarily unavailable (503) for "
+              + context
+              + ". Try again later. Details: "
+              + e.getMessage(),
+          e);
+    } else if (statusCode == 404) {
+      logError(
+          "Spreadsheet or worksheet not found (404) for "
+              + context
+              + ". Verify the spreadsheet key and worksheet name. Details: "
+              + e.getMessage(),
+          e);
+    } else {
+      logError(
+          "Google API error (HTTP "
+              + statusCode
+              + ") for "
+              + context
+              + ". Details: "
+              + e.getMessage(),
+          e);
+    }
+  }
+
+  /** Walk the cause chain to find an HttpResponseException (e.g. when wrapped 
by HopException). */
+  private HttpResponseException findHttpResponseException(Throwable t) {
+    for (Throwable current = t; current != null; current = current.getCause()) 
{
+      if (current instanceof HttpResponseException) {
+        return (HttpResponseException) current;
+      }
+    }
+    return null;
+  }
+
+  private boolean isRetryableSheetsStatus(int statusCode) {
+    return statusCode == 429 || statusCode == 500 || statusCode == 503;
+  }
+
+  private <T> T executeSheetsRequestWithRetry(RequestExecutor<T> executor, 
String context)
+      throws Exception {
+    int maxRetries = getRetryAttempts();
+    long retryDelayMs = getRetryDelayMs();
+    for (int attempt = 0; attempt < maxRetries; attempt++) {
+      try {
+        return executor.execute();
+      } catch (Exception e) {
+        HttpResponseException httpEx =
+            (e instanceof HttpResponseException)
+                ? (HttpResponseException) e
+                : findHttpResponseException(e);
+        if (httpEx == null) {
+          throw e;
+        }
+        int statusCode = httpEx.getStatusCode();
+        if (!isRetryableSheetsStatus(statusCode) || attempt >= maxRetries - 1) 
{
+          throw httpEx;
+        }
+        if (isBasic()) {
+          logBasic(
+              "Retrying "
+                  + context
+                  + " in "
+                  + retryDelayMs
+                  + "ms after HTTP "
+                  + statusCode
+                  + " (attempt "
+                  + (attempt + 1)
+                  + "/"
+                  + maxRetries
+                  + ")");
+        }
+        try {
+          TimeUnit.MILLISECONDS.sleep(retryDelayMs);
+        } catch (InterruptedException ie) {
+          Thread.currentThread().interrupt();
+          throw ie;
+        }
+        retryDelayMs *= 2;
+      }
+    }
+    throw new HopException("Unexpected retry loop exit for: " + context);
+  }
+
+  private int getRetryAttempts() {
+    return parsePositiveInt(resolve(meta.getRetryAttempts()), 3, "retry 
attempts");
+  }
+
+  private long getRetryDelayMs() {
+    long retryDelaySeconds =
+        parsePositiveInt(resolve(meta.getRetryDelayMs()), 2, "retry delay 
(s)");
+    return retryDelaySeconds * 1000L;
+  }
+
+  private int parsePositiveInt(String value, int defaultValue, String 
fieldName) {
+    if (Utils.isEmpty(value)) {
+      return defaultValue;
+    }
+    try {
+      int parsed = Integer.parseInt(value.trim());
+      if (parsed < 1) {
+        logBasic(
+            "Invalid " + fieldName + " value '" + value + "', falling back to 
" + defaultValue);
+        return defaultValue;
+      }
+      return parsed;
+    } catch (NumberFormatException e) {
+      logBasic(
+          "Unable to parse "
+              + fieldName
+              + " value '"
+              + value
+              + "', falling back to "
+              + defaultValue);
+      return defaultValue;
+    }
+  }
+
   @Override
   public boolean processRow() throws HopException {
     Object[] row = getRow();
@@ -391,7 +568,8 @@ public class GoogleSheetsOutput
                       + resolve(meta.getSpreadsheetKey()));
             }
             if (request != null) {
-              ClearValuesResponse response = request.execute();
+              executeSheetsRequestWithRetry(
+                  () -> request.execute(), "clearing worksheet before write");
             } else {
               if (isBasic()) {
                 logBasic("Nothing to clear");
@@ -403,13 +581,15 @@ public class GoogleSheetsOutput
             }
             ValueRange body = new ValueRange().setValues(data.rows);
             String valueInputOption = "USER_ENTERED";
-            UpdateValuesResponse result =
-                data.service
-                    .spreadsheets()
-                    .values()
-                    .update(resolve(meta.getSpreadsheetKey()), range, body)
-                    .setValueInputOption(valueInputOption)
-                    .execute();
+            executeSheetsRequestWithRetry(
+                () ->
+                    data.service
+                        .spreadsheets()
+                        .values()
+                        .update(resolve(meta.getSpreadsheetKey()), range, body)
+                        .setValueInputOption(valueInputOption)
+                        .execute(),
+                "updating worksheet values");
 
           } else { // Appending if option is checked
 
@@ -435,7 +615,7 @@ public class GoogleSheetsOutput
                     .append(resolve(meta.getSpreadsheetKey()), range, body);
             request.setValueInputOption(valueInputOption);
             request.setInsertDataOption(insertDataOption);
-            AppendValuesResponse response = request.execute();
+            executeSheetsRequestWithRetry(() -> request.execute(), "appending 
worksheet values");
           }
         } else {
           if (isBasic()) {
@@ -459,8 +639,72 @@ public class GoogleSheetsOutput
 
         putRow(data.outputRowMeta, row);
       }
+    } catch (HttpResponseException e) {
+      String context =
+          "worksheet: "
+              + resolve(meta.getWorksheetId())
+              + " in spreadsheet: "
+              + resolve(meta.getSpreadsheetKey());
+      int statusCode = e.getStatusCode();
+      String msg;
+      if (statusCode == 429) {
+        msg =
+            "Google API rate limit exceeded (429) while writing to "
+                + context
+                + ". This often occurs when running inside GCP. Details: "
+                + e.getMessage();
+      } else if (statusCode == 503) {
+        msg =
+            "Google API temporarily unavailable (503) while writing to "
+                + context
+                + ". Try again later. Details: "
+                + e.getMessage();
+      } else if (statusCode == 403) {
+        msg = "Access denied (403) while writing to " + context + ". Details: 
" + e.getMessage();
+      } else {
+        msg =
+            "Google API error (HTTP "
+                + statusCode
+                + ") while writing to "
+                + context
+                + ": "
+                + e.getMessage();
+      }
+      throw new HopException(msg, e);
     } catch (Exception e) {
-      throw new HopException(e.getMessage());
+      HttpResponseException httpEx = findHttpResponseException(e);
+      if (httpEx != null) {
+        String context =
+            "worksheet: "
+                + resolve(meta.getWorksheetId())
+                + " in spreadsheet: "
+                + resolve(meta.getSpreadsheetKey());
+        int statusCode = httpEx.getStatusCode();
+        String msg;
+        if (statusCode == 429) {
+          msg =
+              "Google API rate limit exceeded (429) while writing to "
+                  + context
+                  + ". This often occurs when running inside GCP. Details: "
+                  + httpEx.getMessage();
+        } else if (statusCode == 503) {
+          msg =
+              "Google API temporarily unavailable (503) while writing to "
+                  + context
+                  + ". Try again later. Details: "
+                  + httpEx.getMessage();
+        } else {
+          msg =
+              "Google API error (HTTP "
+                  + statusCode
+                  + ") while writing to "
+                  + context
+                  + ": "
+                  + httpEx.getMessage();
+        }
+        throw new HopException(msg, httpEx);
+      }
+      throw new HopException(e.getMessage(), e);
     } finally {
       data.currentRow++;
     }
diff --git 
a/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputDialog.java
 
b/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputDialog.java
index d394820752..b6778247a8 100644
--- 
a/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputDialog.java
+++ 
b/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputDialog.java
@@ -65,6 +65,8 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
   private Button wbAppend;
   private Button wbReplace;
   private TextVar wTimeout;
+  private TextVar wRetryAttempts;
+  private TextVar wRetryDelayMs;
   private TextVar wImpersonation;
   private TextVar wAppName;
   private TextVar wProxyHost;
@@ -80,20 +82,6 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
     this.meta = transformMeta;
   }
 
-  private static HttpRequestInitializer setHttpTimeout(
-      final HttpRequestInitializer requestInitializer, final String timeout) {
-    return httpRequest -> {
-      requestInitializer.initialize(httpRequest);
-      Integer to = 5;
-      if (!timeout.isEmpty()) {
-        to = Integer.parseInt(timeout);
-      }
-
-      httpRequest.setConnectTimeout(to * 60000); // 3 minutes connect timeout
-      httpRequest.setReadTimeout(to * 60000); // 3 minutes read timeout
-    };
-  }
-
   @Override
   public String open() {
     createShell(BaseMessages.getString(PKG, 
"GoogleSheetsOutput.transform.Name"));
@@ -149,7 +137,8 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
 
     // Appname - Label
     Label appNameLabel = new Label(serviceAccountComposite, SWT.RIGHT);
-    appNameLabel.setText("Google Application Name :");
+    appNameLabel.setText(
+        BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.ApplicationName.Label"));
     PropsUi.setLook(appNameLabel);
     FormData appNameLabelForm = new FormData();
     appNameLabelForm.top = new FormAttachment(wbPrivateKey, margin);
@@ -168,7 +157,7 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
 
     // Timeout - Label
     Label timeoutLabel = new Label(serviceAccountComposite, SWT.RIGHT);
-    timeoutLabel.setText("Time out in minutes :");
+    timeoutLabel.setText(BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.TimeOut.Label"));
     PropsUi.setLook(timeoutLabel);
     FormData timeoutLabelForm = new FormData();
     timeoutLabelForm.top = new FormAttachment(wAppName, margin);
@@ -185,13 +174,55 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
     timeoutData.right = new FormAttachment(wbPrivateKey, -margin);
     wTimeout.setLayoutData(timeoutData);
 
+    // Retry attempts - Label
+    Label retryAttemptsLabel = new Label(serviceAccountComposite, SWT.RIGHT);
+    retryAttemptsLabel.setText(
+        BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.RetryAttempts.Label"));
+    PropsUi.setLook(retryAttemptsLabel);
+    FormData retryAttemptsLabelForm = new FormData();
+    retryAttemptsLabelForm.top = new FormAttachment(wTimeout, margin);
+    retryAttemptsLabelForm.left = new FormAttachment(0, 0);
+    retryAttemptsLabelForm.right = new FormAttachment(middle, -margin);
+    retryAttemptsLabel.setLayoutData(retryAttemptsLabelForm);
+
+    // Retry attempts - Text
+    wRetryAttempts =
+        new TextVar(variables, serviceAccountComposite, SWT.SINGLE | SWT.LEFT 
| SWT.BORDER);
+    PropsUi.setLook(wRetryAttempts);
+    FormData retryAttemptsData = new FormData();
+    retryAttemptsData.top = new FormAttachment(wTimeout, margin);
+    retryAttemptsData.left = new FormAttachment(middle, 0);
+    retryAttemptsData.right = new FormAttachment(wbPrivateKey, -margin);
+    wRetryAttempts.setLayoutData(retryAttemptsData);
+
+    // Retry delay - Label
+    Label retryDelayLabel = new Label(serviceAccountComposite, SWT.RIGHT);
+    retryDelayLabel.setText(
+        BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.RetryDelaySeconds.Label"));
+    PropsUi.setLook(retryDelayLabel);
+    FormData retryDelayLabelForm = new FormData();
+    retryDelayLabelForm.top = new FormAttachment(wRetryAttempts, margin);
+    retryDelayLabelForm.left = new FormAttachment(0, 0);
+    retryDelayLabelForm.right = new FormAttachment(middle, -margin);
+    retryDelayLabel.setLayoutData(retryDelayLabelForm);
+
+    // Retry delay - Text
+    wRetryDelayMs =
+        new TextVar(variables, serviceAccountComposite, SWT.SINGLE | SWT.LEFT 
| SWT.BORDER);
+    PropsUi.setLook(wRetryDelayMs);
+    FormData retryDelayData = new FormData();
+    retryDelayData.top = new FormAttachment(wRetryAttempts, margin);
+    retryDelayData.left = new FormAttachment(middle, 0);
+    retryDelayData.right = new FormAttachment(wbPrivateKey, -margin);
+    wRetryDelayMs.setLayoutData(retryDelayData);
+
     // Impersonation - Label
     Label impersonationLabel = new Label(serviceAccountComposite, SWT.RIGHT);
     impersonationLabel.setText(
         BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.ImpersonationAccount"));
     PropsUi.setLook(impersonationLabel);
     FormData impersonationLabelForm = new FormData();
-    impersonationLabelForm.top = new FormAttachment(wTimeout, margin);
+    impersonationLabelForm.top = new FormAttachment(wRetryDelayMs, margin);
     impersonationLabelForm.left = new FormAttachment(0, 0);
     impersonationLabelForm.right = new FormAttachment(middle, -margin);
     impersonationLabel.setLayoutData(impersonationLabelForm);
@@ -201,7 +232,7 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
         new TextVar(variables, serviceAccountComposite, SWT.SINGLE | SWT.LEFT 
| SWT.BORDER);
     PropsUi.setLook(wImpersonation);
     FormData impersonationData = new FormData();
-    impersonationData.top = new FormAttachment(wTimeout, margin);
+    impersonationData.top = new FormAttachment(wRetryDelayMs, margin);
     impersonationData.left = new FormAttachment(middle, 0);
     impersonationData.right = new FormAttachment(wbPrivateKey, -margin);
     wImpersonation.setLayoutData(impersonationData);
@@ -529,7 +560,11 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
       }
 
       EnterSelectionDialog esd =
-          new EnterSelectionDialog(shell, names, "Worksheets", "Select a 
Worksheet.");
+          new EnterSelectionDialog(
+              shell,
+              names,
+              BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.Worksheets.Title"),
+              BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.Worksheets.Prompt"));
       if (selectedSheet > -1) {
         esd.setSelectedNrs(new int[] {selectedSheet});
       }
@@ -597,7 +632,11 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
       }
 
       EnterSelectionDialog esd =
-          new EnterSelectionDialog(shell, titles, "Spreadsheets", "Select a 
Spreadsheet.");
+          new EnterSelectionDialog(
+              shell,
+              titles,
+              BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.Spreadsheets.Title"),
+              BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.Spreadsheets.Prompt"));
       if (selectedSpreadsheet > -1) {
         esd.setSelectedNrs(new int[] {selectedSpreadsheet});
       }
@@ -613,7 +652,8 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
       }
 
     } catch (Exception err) {
-      new ErrorDialog(shell, "System.Dialog.Error.Title", err.getMessage(), 
err);
+      new ErrorDialog(
+          shell, BaseMessages.getString(PKG, "System.Dialog.Error.Title"), 
err.getMessage(), err);
     }
   }
 
@@ -641,9 +681,13 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
                   credential, variables.resolve(meta.getTimeout())))
           .setApplicationName(GoogleSheetsCredentials.APPLICATION_NAME)
           .build();
-      wlTestServiceAccountInfo.setText("Google Drive API : Success!");
+      wlTestServiceAccountInfo.setText(
+          BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.TestConnectionSuccess.Message"));
     } catch (Exception error) {
-      wlTestServiceAccountInfo.setText("Connection Failed: " + 
error.getMessage());
+      wlTestServiceAccountInfo.setText(
+          BaseMessages.getString(PKG, 
"GoogleSheetsOutputDialog.TestConnectionFailed.Message")
+              + ": "
+              + error.getMessage());
     }
   }
 
@@ -673,6 +717,12 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
     if (!StringUtils.isEmpty(meta.getTimeout())) {
       this.wTimeout.setText(meta.getTimeout());
     }
+    if (!StringUtils.isEmpty(meta.getRetryAttempts())) {
+      this.wRetryAttempts.setText(meta.getRetryAttempts());
+    }
+    if (!StringUtils.isEmpty(meta.getRetryDelayMs())) {
+      this.wRetryDelayMs.setText(meta.getRetryDelayMs());
+    }
     if (!StringUtils.isEmpty(meta.getImpersonation())) {
       this.wImpersonation.setText(meta.getImpersonation());
     }
@@ -709,6 +759,8 @@ public class GoogleSheetsOutputDialog extends 
BaseTransformDialog {
     meta.setShareDomain(this.wShareDomainWise.getText());
 
     meta.setTimeout(this.wTimeout.getText());
+    meta.setRetryAttempts(this.wRetryAttempts.getText());
+    meta.setRetryDelayMs(this.wRetryDelayMs.getText());
     meta.setAppName(this.wAppName.getText());
     meta.setImpersonation(this.wImpersonation.getText());
 
diff --git 
a/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputMeta.java
 
b/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputMeta.java
index 291193fa73..562fed4584 100644
--- 
a/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputMeta.java
+++ 
b/plugins/tech/google/src/main/java/org/apache/hop/pipeline/transforms/googlesheets/GoogleSheetsOutputMeta.java
@@ -72,6 +72,12 @@ public class GoogleSheetsOutputMeta
   @HopMetadataProperty(key = "timeout", injectionGroupKey = "SHEET")
   private String timeout;
 
+  @HopMetadataProperty(key = "retryAttempts", injectionGroupKey = "SHEET")
+  private String retryAttempts;
+
+  @HopMetadataProperty(key = "retryDelayMs", injectionGroupKey = "SHEET")
+  private String retryDelayMs;
+
   @HopMetadataProperty(key = "impersonation", injectionGroupKey = "SHEET")
   private String impersonation;
 
@@ -87,7 +93,7 @@ public class GoogleSheetsOutputMeta
 
   @Override
   public void setDefault() {
-    this.jsonCredentialPath = "" + "client_secret.json";
+    this.jsonCredentialPath = "client_secret.json";
     this.spreadsheetKey = "";
     this.worksheetId = "";
     this.shareDomain = "";
@@ -98,6 +104,8 @@ public class GoogleSheetsOutputMeta
     this.impersonation = "";
     this.appName = "";
     this.timeout = "5";
+    this.retryAttempts = "3";
+    this.retryDelayMs = "2";
   }
 
   @Override
@@ -110,6 +118,8 @@ public class GoogleSheetsOutputMeta
     retval.setAppend(this.append);
     retval.setShareEmail(this.shareEmail);
     retval.setShareDomain(this.shareDomain);
+    retval.setRetryAttempts(this.retryAttempts);
+    retval.setRetryDelayMs(this.retryDelayMs);
     return retval;
   }
 
diff --git 
a/plugins/tech/google/src/main/resources/org/apache/hop/pipeline/transforms/googlesheets/messages/messages_en_US.properties
 
b/plugins/tech/google/src/main/resources/org/apache/hop/pipeline/transforms/googlesheets/messages/messages_en_US.properties
index 10321d9f66..18fed2dafb 100644
--- 
a/plugins/tech/google/src/main/resources/org/apache/hop/pipeline/transforms/googlesheets/messages/messages_en_US.properties
+++ 
b/plugins/tech/google/src/main/resources/org/apache/hop/pipeline/transforms/googlesheets/messages/messages_en_US.properties
@@ -58,10 +58,16 @@ GoogleSheetsOutputDialog.Share.Label=Share Full rights (RW) 
with
 GoogleSheetsOutputDialog.Share.LabelDW=Domain Wise Permission :
 GoogleSheetsOutputDialog.Replace.Label=Replace sheet if exists
 GoogleSheetsOutputDialog.Replace.Tooltip=Delete the existing sheet and create 
a new one to avoid building up excessive revision history (useful for 
frequently executed pipelines)
+GoogleSheetsOutputDialog.RetryAttempts.Label=Retry attempts :
+GoogleSheetsOutputDialog.RetryDelaySeconds.Label=Retry delay (seconds) :
 GoogleSheetsInputDialog.Proxy=Proxy
 GoogleSheetsInputDialog.ProxyHost=Proxy host
 GoogleSheetsInputDialog.ProxyPort=Proxy port
 GoogleSheetsOutputDialog.ApplicationName.Label=Google Application Name :
-GoogleSheetsOutputDialog.TimeOut.Label=Time out in minutes :
+GoogleSheetsOutputDialog.TimeOut.Label=Time out (minutes) :
 GoogleSheetsOutputDialog.TestConnectionSuccess.Message=Google Drive API : 
Success!
 GoogleSheetsOutputDialog.TestConnectionFailed.Message=Connection Failed
+GoogleSheetsOutputDialog.Worksheets.Title=Worksheets
+GoogleSheetsOutputDialog.Worksheets.Prompt=Select a Worksheet.
+GoogleSheetsOutputDialog.Spreadsheets.Title=Spreadsheets
+GoogleSheetsOutputDialog.Spreadsheets.Prompt=Select a Spreadsheet.


Reply via email to