diff --git a/src/qqq/components/forms/EntityForm.tsx b/src/qqq/components/forms/EntityForm.tsx
index 471735c..6e1a748 100644
--- a/src/qqq/components/forms/EntityForm.tsx
+++ b/src/qqq/components/forms/EntityForm.tsx
@@ -41,6 +41,7 @@ import QDynamicForm from "qqq/components/forms/DynamicForm";
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
import MDTypography from "qqq/components/legacy/MDTypography";
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
+import HtmlUtils from "qqq/utils/HtmlUtils";
import Client from "qqq/utils/qqq/Client";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
@@ -82,7 +83,6 @@ function EntityForm(props: Props): JSX.Element
const [warningContent, setWarningContent] = useState("");
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
- const [formValues, setFormValues] = useState({} as { [key: string]: string });
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [record, setRecord] = useState(null as QRecord);
const [tableSections, setTableSections] = useState(null as QTableSection[]);
@@ -185,8 +185,6 @@ function EntityForm(props: Props): JSX.Element
initialValues[key] = record.values.get(key);
});
- //? safe to delete? setFormValues(formValues);
-
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
{
setNotAllowedError("Records may not be edited in this table");
@@ -416,8 +414,8 @@ function EntityForm(props: Props): JSX.Element
}
else
{
- const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true`;
- navigate(path);
+ const path = location.pathname.replace(/\/edit$/, "");
+ navigate(path, {state: {updateSuccess: true}});
}
})
.catch((error) =>
@@ -427,12 +425,13 @@ function EntityForm(props: Props): JSX.Element
if(error.message.toLowerCase().startsWith("warning"))
{
- const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true&warning=${encodeURIComponent(error.message)}`;
- navigate(path);
+ const path = location.pathname.replace(/\/edit$/, "");
+ navigate(path, {state: {updateSuccess: true, warning: error.message}});
}
else
{
setAlertContent(error.message);
+ HtmlUtils.autoScroll(0);
}
});
}
@@ -448,20 +447,22 @@ function EntityForm(props: Props): JSX.Element
}
else
{
- const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true`;
- navigate(path);
+ const path = location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
+ navigate(path, {state: {createSuccess: true}});
}
})
.catch((error) =>
{
if(error.message.toLowerCase().startsWith("warning"))
{
- const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true&warning=${encodeURIComponent(error.message)}`;
+ const path = location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
navigate(path);
+ navigate(path, {state: {createSuccess: true, warning: error.message}});
}
else
{
setAlertContent(error.message);
+ HtmlUtils.autoScroll(0);
}
});
}
@@ -499,12 +500,12 @@ function EntityForm(props: Props): JSX.Element
{alertContent ? (
- {alertContent}
+ setAlertContent(null)}>{alertContent}
) : ("")}
{warningContent ? (
- {warningContent}
+ setWarningContent(null)}>{warningContent}
) : ("")}
diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx
index cdc303a..86d2f7f 100644
--- a/src/qqq/pages/records/query/RecordQuery.tsx
+++ b/src/qqq/pages/records/query/RecordQuery.tsx
@@ -96,12 +96,31 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const tableName = table.name;
const [searchParams] = useSearchParams();
- const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(searchParams.has("deleteSuccess"));
+ const [showSuccessfullyDeletedAlert, setShowSuccessfullyDeletedAlert] = useState(false);
+ const [warningAlert, setWarningAlert] = useState(null as string);
const [successAlert, setSuccessAlert] = useState(null as string);
const location = useLocation();
const navigate = useNavigate();
+ if(location.state)
+ {
+ let state: any = location.state;
+ if(state["deleteSuccess"])
+ {
+ setShowSuccessfullyDeletedAlert(true);
+ delete state["deleteSuccess"];
+ }
+
+ if(state["warning"])
+ {
+ setWarningAlert(state["warning"]);
+ delete state["warning"];
+ }
+
+ window.history.replaceState(state, "");
+ }
+
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
////////////////////////////////////////////
@@ -1736,23 +1755,20 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
)}
{
(tableLabel && showSuccessfullyDeletedAlert) ? (
-
- {
- setShowSuccessfullyDeletedAlert(false);
- }}>
- {`${tableLabel} successfully deleted`}
-
+ setShowSuccessfullyDeletedAlert(false)}>{`${tableLabel} successfully deleted`}
) : null
}
{
(successAlert) ? (
-
- {
- setSuccessAlert(null);
- }}>
- {successAlert}
-
+ setSuccessAlert(null)}>{successAlert}
+
+ ) : null
+ }
+ {
+ (warningAlert) ? (
+
+ setWarningAlert(null)}>{warningAlert}
) : null
}
diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx
index aac96eb..546648b 100644
--- a/src/qqq/pages/records/view/RecordView.tsx
+++ b/src/qqq/pages/records/view/RecordView.tsx
@@ -43,7 +43,7 @@ import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal";
import React, {useContext, useEffect, useState} from "react";
-import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom";
+import {useLocation, useNavigate, useParams} from "react-router-dom";
import QContext from "QContext";
import AuditBody from "qqq/components/audits/AuditBody";
import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton} from "qqq/components/buttons/DefaultButtons";
@@ -53,6 +53,7 @@ import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
import BaseLayout from "qqq/layouts/BaseLayout";
import ProcessRun from "qqq/pages/processes/ProcessRun";
import HistoryUtils from "qqq/utils/HistoryUtils";
+import HtmlUtils from "qqq/utils/HtmlUtils";
import Client from "qqq/utils/qqq/Client";
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
import TableUtils from "qqq/utils/qqq/TableUtils";
@@ -97,10 +98,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
const [actionsMenu, setActionsMenu] = useState(null);
- const [notFoundMessage, setNotFoundMessage] = useState(null);
+ const [notFoundMessage, setNotFoundMessage] = useState(null as string);
+ const [errorMessage, setErrorMessage] = useState(null as string)
const [successMessage, setSuccessMessage] = useState(null as string);
const [warningMessage, setWarningMessage] = useState(null as string);
- const [searchParams] = useSearchParams();
const {setPageHeader} = useContext(QContext);
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
const [reloadCounter, setReloadCounter] = useState(0);
@@ -116,6 +117,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{
setSuccessMessage(null);
setNotFoundMessage(null);
+ setErrorMessage(null);
setAsyncLoadInited(false);
setTableMetaData(null);
setRecord(null);
@@ -423,14 +425,26 @@ function RecordView({table, launchProcess}: Props): JSX.Element
setSectionFieldElements(sectionFieldElements);
setNonT1TableSections(nonT1TableSections);
- if (searchParams.get("createSuccess") || searchParams.get("updateSuccess"))
+ if(location.state)
{
- setSuccessMessage(`${tableMetaData.label} successfully ${searchParams.get("createSuccess") ? "created" : "updated"}`);
- }
- if (searchParams.get("warning"))
- {
- setWarningMessage(searchParams.get("warning"));
+ let state: any = location.state;
+ if (state["createSuccess"] || state["updateSuccess"])
+ {
+ setSuccessMessage(`${tableMetaData.label} successfully ${state["createSuccess"] ? "created" : "updated"}`);
+ }
+
+ if (state["warning"])
+ {
+ setWarningMessage(state["warning"]);
+ }
+
+ delete state["createSuccess"]
+ delete state["updateSuccess"]
+ delete state["warning"]
+
+ window.history.replaceState(state, "");
}
+
})();
}
@@ -452,8 +466,25 @@ function RecordView({table, launchProcess}: Props): JSX.Element
await qController.delete(tableName, id)
.then(() =>
{
- const path = `${pathParts.slice(0, -1).join("/")}?deleteSuccess=true`;
- navigate(path);
+ const path = pathParts.slice(0, -1).join("/");
+ navigate(path, {state: {deleteSuccess: true}});
+ })
+ .catch((error) =>
+ {
+ setDeleteConfirmationOpen(false);
+ console.log("Caught:");
+ console.log(error);
+
+ if(error.message.toLowerCase().startsWith("warning"))
+ {
+ const path = pathParts.slice(0, -1).join("/");
+ navigate(path, {state: {deleteSuccess: true, warning: error.message}});
+ }
+ else
+ {
+ setErrorMessage(error.message);
+ HtmlUtils.autoScroll(0);
+ }
});
})();
};
@@ -648,7 +679,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{
successMessage ?
-
+
{
setSuccessMessage(null);
}}>
@@ -666,6 +697,16 @@ function RecordView({table, launchProcess}: Props): JSX.Element
: ("")
}
+ {
+ errorMessage ?
+
+ {
+ setErrorMessage(null);
+ }}>
+ {errorMessage}
+
+ : ("")
+ }
diff --git a/src/test/java/com/kingsrook/qqq/materialdashboard/tests/BulkEditTest.java b/src/test/java/com/kingsrook/qqq/materialdashboard/tests/BulkEditTest.java
new file mode 100755
index 0000000..54ec041
--- /dev/null
+++ b/src/test/java/com/kingsrook/qqq/materialdashboard/tests/BulkEditTest.java
@@ -0,0 +1,114 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.materialdashboard.tests;
+
+
+import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
+import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
+import org.junit.jupiter.api.Test;
+
+
+/*******************************************************************************
+ ** Test for the scripts table
+ *******************************************************************************/
+public class BulkEditTest extends QBaseSeleniumTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
+ {
+ super.addJavalinRoutes(qSeleniumJavalin);
+ addCommonRoutesForThisTest(qSeleniumJavalin);
+ qSeleniumJavalin
+ .withRouteToFile("/metaData/process/person.bulkEdit", "metaData/process/person.bulkEdit.json")
+ .withRouteToFile("/processes/person.bulkEdit/init", "/processes/person.bulkEdit/init.json")
+ .withRouteToFile("/processes/person.bulkEdit/74a03a7d-2f53-4784-9911-3a21f7646c43/step/edit", "/processes/person.bulkEdit/step/edit.json")
+ .withRouteToFile("/processes/person.bulkEdit/74a03a7d-2f53-4784-9911-3a21f7646c43/step/review", "/processes/person.bulkEdit/step/review.json")
+ ;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void addCommonRoutesForThisTest(QSeleniumJavalin qSeleniumJavalin)
+ {
+ qSeleniumJavalin.withRouteToFile("/data/person/count", "data/person/count.json");
+ qSeleniumJavalin.withRouteToFile("/data/person/query", "data/person/index.json");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ // @RepeatedTest(100)
+ void test()
+ {
+ qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person", "Person");
+ qSeleniumLib.waitForSelectorContaining("button", "selection").click();
+ qSeleniumLib.waitForSelectorContaining("li", "This page").click();
+ qSeleniumLib.waitForSelectorContaining("div", "records on this page are selected");
+
+ qSeleniumLib.waitForSelectorContaining("button", "action").click();
+ qSeleniumLib.waitForSelectorContaining("li", "bulk edit").click();
+
+ /////////////////
+ // edit screen //
+ /////////////////
+ qSeleniumLib.waitForSelector("#bulkEditSwitch-firstName").click();
+ qSeleniumLib.waitForSelector("input[name=firstName]").click();
+ qSeleniumLib.waitForSelector("input[name=firstName]").sendKeys("John");
+ qSeleniumLib.waitForSelectorContaining("button", "next").click();
+
+ ///////////////////////
+ // validation screen //
+ ///////////////////////
+ qSeleniumLib.waitForSelectorContaining("span", "How would you like to proceed").click();
+ qSeleniumLib.waitForSelectorContaining("button", "next").click();
+
+ //////////////////////////////////////////////////////////////
+ // need to change the result of the 'review' step this time //
+ //////////////////////////////////////////////////////////////
+ qSeleniumLib.waitForSelectorContaining("div", "Person Bulk Edit: Review").click();
+ qSeleniumJavalin.clearRoutes();
+ qSeleniumJavalin.stop();
+ addCommonRoutesForThisTest(qSeleniumJavalin);
+ qSeleniumJavalin.withRouteToFile("/processes/person.bulkEdit/74a03a7d-2f53-4784-9911-3a21f7646c43/step/review", "/processes/person.bulkEdit/step/review-result.json");
+ qSeleniumJavalin.restart();
+ qSeleniumLib.waitForSelectorContaining("button", "submit").click();
+
+ ///////////////////
+ // result screen //
+ ///////////////////
+ qSeleniumLib.waitForSelectorContaining("div", "Person Bulk Edit: Result").click();
+ qSeleniumLib.waitForSelectorContaining("button", "close").click();
+
+ // qSeleniumLib.waitForever();
+ }
+
+}
diff --git a/src/test/resources/fixtures/processes/person.bulkEdit/step/edit.json b/src/test/resources/fixtures/processes/person.bulkEdit/step/edit.json
index b3e0fde..40a417b 100644
--- a/src/test/resources/fixtures/processes/person.bulkEdit/step/edit.json
+++ b/src/test/resources/fixtures/processes/person.bulkEdit/step/edit.json
@@ -1,12 +1,24 @@
{
- "values": {
- "firstName": "Kahhhhn",
- "valuesBeingUpdated": "First Name will be set to: Kahhhhn",
- "bulkEditEnabledFields": "firstName",
- "recordsParam": "recordIds",
- "recordIds": "1,2,3,4,5",
- "queryFilterJSON": "{\"criteria\":[{\"fieldName\":\"id\",\"operator\":\"IN\",\"values\":[\"1\",\"2\",\"3\",\"4\",\"5\"]}]}"
- },
- "processUUID": "74a03a7d-2f53-4784-9911-3a21f7646c43",
- "nextStep": "review"
-}
+ "values": {
+ "transactionLevel": "process",
+ "tableName": "person",
+ "recordsParam": "recordIds",
+ "supportsFullValidation": true,
+ "recordIds": "1,2,3,4,5",
+ "sourceTable": "person",
+ "extract": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep",
+ "codeType": "JAVA"
+ },
+ "recordCount": 5,
+ "previewMessage": "This is a preview of the records that will be updated.",
+ "transform": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep",
+ "codeType": "JAVA"
+ },
+ "destinationTable": "person",
+ "bulkEditEnabledFields": "firstName"
+ },
+ "processUUID": "74a03a7d-2f53-4784-9911-3a21f7646c43",
+ "nextStep": "review"
+}
\ No newline at end of file
diff --git a/src/test/resources/fixtures/processes/person.bulkEdit/step/review-result.json b/src/test/resources/fixtures/processes/person.bulkEdit/step/review-result.json
new file mode 100644
index 0000000..c7b2417
--- /dev/null
+++ b/src/test/resources/fixtures/processes/person.bulkEdit/step/review-result.json
@@ -0,0 +1,60 @@
+{
+ "values": {
+ "transactionLevel": "process",
+ "tableName": "person",
+ "recordsParam": "recordIds",
+ "supportsFullValidation": true,
+ "doFullValidation": true,
+ "recordIds": "1,2,3,4,5",
+ "sourceTable": "person",
+ "extract": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep",
+ "codeType": "JAVA"
+ },
+ "validationSummary": [
+ {
+ "status": "OK",
+ "count": 5,
+ "message": "Person records will be edited.",
+ "singularFutureMessage": "Person record will be edited.",
+ "pluralFutureMessage": "Person records will be edited.",
+ "singularPastMessage": "Person record was edited.",
+ "pluralPastMessage": "Person records were edited."
+ },
+ {
+ "status": "INFO",
+ "message": "First name will be set to John"
+ }
+ ],
+ "recordCount": 5,
+ "previewMessage": "This is a preview of the records that will be updated.",
+ "transform": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep",
+ "codeType": "JAVA"
+ },
+ "destinationTable": "person",
+ "bulkEditEnabledFields": "firstName",
+ "processResults": [
+ {
+ "status": "OK",
+ "count": 5,
+ "message": "Person records were edited.",
+ "singularFutureMessage": "Person record will be edited.",
+ "pluralFutureMessage": "Person records will be edited.",
+ "singularPastMessage": "Person record was edited.",
+ "pluralPastMessage": "Person records were edited."
+ },
+ {
+ "status": "INFO",
+ "message": "Mapping Exception Type was cleared out"
+ }
+ ],
+ "load": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaUpdateStep",
+ "codeType": "JAVA"
+ },
+ "basepullReadyToUpdateTimestamp": true
+ },
+ "processUUID": "74a03a7d-2f53-4784-9911-3a21f7646c43",
+ "nextStep": "result"
+}
\ No newline at end of file
diff --git a/src/test/resources/fixtures/processes/person.bulkEdit/step/review.json b/src/test/resources/fixtures/processes/person.bulkEdit/step/review.json
new file mode 100644
index 0000000..3f34c35
--- /dev/null
+++ b/src/test/resources/fixtures/processes/person.bulkEdit/step/review.json
@@ -0,0 +1,40 @@
+{
+ "values": {
+ "transactionLevel": "process",
+ "tableName": "person",
+ "recordsParam": "recordIds",
+ "supportsFullValidation": true,
+ "doFullValidation": true,
+ "recordIds": "1,2,3,4,5",
+ "sourceTable": "person",
+ "extract": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep",
+ "codeType": "JAVA"
+ },
+ "validationSummary": [
+ {
+ "status": "OK",
+ "count": 5,
+ "message": "Person records will be edited.",
+ "singularFutureMessage": "Person record will be edited.",
+ "pluralFutureMessage": "Person records will be edited.",
+ "singularPastMessage": "Person record was edited.",
+ "pluralPastMessage": "Person records were edited."
+ },
+ {
+ "status": "INFO",
+ "message": "First name will be set to John"
+ }
+ ],
+ "recordCount": 5,
+ "previewMessage": "This is a preview of the records that will be updated.",
+ "transform": {
+ "name": "com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep",
+ "codeType": "JAVA"
+ },
+ "destinationTable": "person",
+ "bulkEditEnabledFields": "firstName"
+ },
+ "processUUID": "74a03a7d-2f53-4784-9911-3a21f7646c43",
+ "nextStep": "review"
+}
\ No newline at end of file