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.