mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 22:58:43 +00:00
Compare commits
16 Commits
snapshot-f
...
snapshot-i
Author | SHA1 | Date | |
---|---|---|---|
f41b71d3c7 | |||
57fefe9671 | |||
8a018c34f6 | |||
1b4f70a547 | |||
1f343abbb5 | |||
37a18bbe0d | |||
98cc2ceb00 | |||
e351883d73 | |||
25599d0ca6 | |||
01d18902d7 | |||
580d4a90c9 | |||
eeb1b37d18 | |||
da0947b538 | |||
0c76371d59 | |||
19aebd631a | |||
5aac9ce069 |
@ -2,7 +2,7 @@ version: 2.1
|
|||||||
|
|
||||||
orbs:
|
orbs:
|
||||||
node: circleci/node@5.1.0
|
node: circleci/node@5.1.0
|
||||||
browser-tools: circleci/browser-tools@1.4.3
|
browser-tools: circleci/browser-tools@1.4.5
|
||||||
|
|
||||||
executors:
|
executors:
|
||||||
java17:
|
java17:
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.81",
|
"@kingsrook/qqq-frontend-core": "1.0.82",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
@ -33,6 +33,7 @@
|
|||||||
"html-react-parser": "1.4.8",
|
"html-react-parser": "1.4.8",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.6",
|
||||||
|
"jwt-decode": "3.1.2",
|
||||||
"rapidoc": "9.3.4",
|
"rapidoc": "9.3.4",
|
||||||
"react": "18.0.0",
|
"react": "18.0.0",
|
||||||
"react-ace": "10.1.0",
|
"react-ace": "10.1.0",
|
||||||
@ -56,9 +57,7 @@
|
|||||||
"npm-install": "npm install --legacy-peer-deps",
|
"npm-install": "npm install --legacy-peer-deps",
|
||||||
"prepublishOnly": "tsc -p ./ --outDir lib/",
|
"prepublishOnly": "tsc -p ./ --outDir lib/",
|
||||||
"start": "BROWSER=none react-scripts --max-http-header-size=65535 start",
|
"start": "BROWSER=none react-scripts --max-http-header-size=65535 start",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test"
|
||||||
"cypress:open": "cypress open",
|
|
||||||
"cypress:run": "cypress run"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@ -86,8 +85,6 @@
|
|||||||
"@types/react-table": "7.7.9",
|
"@types/react-table": "7.7.9",
|
||||||
"@typescript-eslint/eslint-plugin": "5.10.2",
|
"@typescript-eslint/eslint-plugin": "5.10.2",
|
||||||
"@typescript-eslint/parser": "5.10.2",
|
"@typescript-eslint/parser": "5.10.2",
|
||||||
"cypress": "11.0.1",
|
|
||||||
"cypress-wait-for-stable-dom": "0.1.0",
|
|
||||||
"eslint": "8.8.0",
|
"eslint": "8.8.0",
|
||||||
"eslint-import-resolver-typescript": "2.5.0",
|
"eslint-import-resolver-typescript": "2.5.0",
|
||||||
"eslint-plugin-import": "2.25.4",
|
"eslint-plugin-import": "2.25.4",
|
||||||
|
14
pom.xml
14
pom.xml
@ -161,6 +161,20 @@
|
|||||||
<skipUpdateVersion>true</skipUpdateVersion>
|
<skipUpdateVersion>true</skipUpdateVersion>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Publish this project's test code as a jar -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
17
src/App.tsx
17
src/App.tsx
@ -33,6 +33,7 @@ import CssBaseline from "@mui/material/CssBaseline";
|
|||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import {ThemeProvider} from "@mui/material/styles";
|
import {ThemeProvider} from "@mui/material/styles";
|
||||||
import {LicenseInfo} from "@mui/x-license-pro";
|
import {LicenseInfo} from "@mui/x-license-pro";
|
||||||
|
import jwt_decode from "jwt-decode";
|
||||||
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
|
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
|
||||||
import {useCookies} from "react-cookie";
|
import {useCookies} from "react-cookie";
|
||||||
import {Navigate, Route, Routes, useLocation,} from "react-router-dom";
|
import {Navigate, Route, Routes, useLocation,} from "react-router-dom";
|
||||||
@ -72,18 +73,6 @@ export default function App()
|
|||||||
const [loggedInUser, setLoggedInUser] = useState({} as { name?: string, email?: string });
|
const [loggedInUser, setLoggedInUser] = useState({} as { name?: string, email?: string });
|
||||||
const [defaultRoute, setDefaultRoute] = useState("/no-apps");
|
const [defaultRoute, setDefaultRoute] = useState("/no-apps");
|
||||||
|
|
||||||
const decodeJWT = (jwt: string): any =>
|
|
||||||
{
|
|
||||||
const base64Url = jwt.split(".")[1];
|
|
||||||
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
const jsonPayload = decodeURIComponent(window.atob(base64).split("").map(function (c)
|
|
||||||
{
|
|
||||||
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
|
||||||
}).join(""));
|
|
||||||
|
|
||||||
return JSON.parse(jsonPayload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldStoreNewToken = (newToken: string, oldToken: string): boolean =>
|
const shouldStoreNewToken = (newToken: string, oldToken: string): boolean =>
|
||||||
{
|
{
|
||||||
if (!oldToken)
|
if (!oldToken)
|
||||||
@ -93,8 +82,8 @@ export default function App()
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const oldJSON = decodeJWT(oldToken);
|
const oldJSON: any = jwt_decode(oldToken);
|
||||||
const newJSON = decodeJWT(newToken);
|
const newJSON: any = jwt_decode(newToken);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
// if the old (local storage) token is expired, then we need to store the new one //
|
// if the old (local storage) token is expired, then we need to store the new one //
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QTableVariant} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableVariant";
|
||||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
@ -45,8 +46,10 @@ interface Props
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
metaData: QInstance;
|
metaData: QInstance;
|
||||||
tableMetaData: QTableMetaData;
|
tableMetaData: QTableMetaData;
|
||||||
|
tableVariant?: QTableVariant;
|
||||||
closeHandler: () => void;
|
closeHandler: () => void;
|
||||||
mayClose: boolean;
|
mayClose: boolean;
|
||||||
|
subHeader?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
GotoRecordDialog.defaultProps = {
|
GotoRecordDialog.defaultProps = {
|
||||||
@ -155,15 +158,17 @@ function GotoRecordDialog(props: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
setError("");
|
setError("");
|
||||||
const filter = new QQueryFilter([new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [values[fieldName]])], null, "AND", null, 10);
|
const filter = new QQueryFilter([new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [values[fieldName]])], null, "AND", null, 10);
|
||||||
const queryResult = await qController.query(props.tableMetaData.name, filter)
|
try
|
||||||
if(queryResult.length == 0)
|
{
|
||||||
|
const queryResult = await qController.query(props.tableMetaData.name, filter, null, props.tableVariant)
|
||||||
|
if (queryResult.length == 0)
|
||||||
{
|
{
|
||||||
setError("Record not found.");
|
setError("Record not found.");
|
||||||
setTimeout(() => setError(""), 3000);
|
setTimeout(() => setError(""), 3000);
|
||||||
}
|
}
|
||||||
else if(queryResult.length == 1)
|
else if (queryResult.length == 1)
|
||||||
{
|
{
|
||||||
navigate(`${props.metaData.getTablePathByName(props.tableMetaData.name)}/${queryResult[0].values.get(props.tableMetaData.primaryKeyField)}`);
|
navigate(`${props.metaData.getTablePathByName(props.tableMetaData.name)}/${encodeURIComponent(queryResult[0].values.get(props.tableMetaData.primaryKeyField))}`);
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -172,6 +177,13 @@ function GotoRecordDialog(props: Props): JSX.Element
|
|||||||
setTimeout(() => setError(""), 3000);
|
setTimeout(() => setError(""), 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
// @ts-ignore
|
||||||
|
setError(`Error: ${(e && e.message) ? e.message : e}`);
|
||||||
|
setTimeout(() => setError(""), 6000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(props.tableMetaData)
|
if(props.tableMetaData)
|
||||||
{
|
{
|
||||||
@ -184,7 +196,9 @@ function GotoRecordDialog(props: Props): JSX.Element
|
|||||||
return (
|
return (
|
||||||
<Dialog open={props.isOpen} onClose={() => closeRequested} onKeyPress={(e) => keyPressed(e)} fullWidth maxWidth={"sm"}>
|
<Dialog open={props.isOpen} onClose={() => closeRequested} onKeyPress={(e) => keyPressed(e)} fullWidth maxWidth={"sm"}>
|
||||||
<DialogTitle>Go To...</DialogTitle>
|
<DialogTitle>Go To...</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
{props.subHeader}
|
||||||
{
|
{
|
||||||
fields.map((field, index) =>
|
fields.map((field, index) =>
|
||||||
(
|
(
|
||||||
@ -237,9 +251,11 @@ interface GotoRecordButtonProps
|
|||||||
{
|
{
|
||||||
metaData: QInstance;
|
metaData: QInstance;
|
||||||
tableMetaData: QTableMetaData;
|
tableMetaData: QTableMetaData;
|
||||||
|
tableVariant?: QTableVariant;
|
||||||
autoOpen?: boolean;
|
autoOpen?: boolean;
|
||||||
buttonVisible?: boolean;
|
buttonVisible?: boolean;
|
||||||
mayClose?: boolean;
|
mayClose?: boolean;
|
||||||
|
subHeader?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
GotoRecordButton.defaultProps = {
|
GotoRecordButton.defaultProps = {
|
||||||
@ -268,7 +284,7 @@ export function GotoRecordButton(props: GotoRecordButtonProps): JSX.Element
|
|||||||
{
|
{
|
||||||
props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && <Button onClick={openGoto} >Go To...</Button>
|
props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && <Button onClick={openGoto} >Go To...</Button>
|
||||||
}
|
}
|
||||||
<GotoRecordDialog metaData={props.metaData} tableMetaData={props.tableMetaData} isOpen={gotoIsOpen} closeHandler={closeGoto} mayClose={props.mayClose} />
|
<GotoRecordDialog metaData={props.metaData} tableMetaData={props.tableMetaData} tableVariant={props.tableVariant} isOpen={gotoIsOpen} closeHandler={closeGoto} mayClose={props.mayClose} subHeader={props.subHeader} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,12 @@ import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDri
|
|||||||
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
|
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
|
||||||
import ValidationReview from "qqq/components/processes/ValidationReview";
|
import ValidationReview from "qqq/components/processes/ValidationReview";
|
||||||
import BaseLayout from "qqq/layouts/BaseLayout";
|
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||||
|
import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/RecordQuery";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
import TableUtils from "qqq/utils/qqq/TableUtils";
|
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
|
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
process?: QProcessMetaData;
|
process?: QProcessMetaData;
|
||||||
@ -74,7 +76,7 @@ interface Props
|
|||||||
isModal?: boolean;
|
isModal?: boolean;
|
||||||
isWidget?: boolean;
|
isWidget?: boolean;
|
||||||
isReport?: boolean;
|
isReport?: boolean;
|
||||||
recordIds?: string | QQueryFilter;
|
recordIds?: string[] | QQueryFilter;
|
||||||
closeModalHandler?: (event: object, reason: string) => void;
|
closeModalHandler?: (event: object, reason: string) => void;
|
||||||
forceReInit?: number;
|
forceReInit?: number;
|
||||||
overrideLabel?: string;
|
overrideLabel?: string;
|
||||||
@ -88,6 +90,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
{
|
{
|
||||||
const processNameParam = useParams().processName;
|
const processNameParam = useParams().processName;
|
||||||
const processName = process === null ? processNameParam : process.name;
|
const processName = process === null ? processNameParam : process.name;
|
||||||
|
let tableVariantLocalStorageKey: string | null = null;
|
||||||
|
if(table)
|
||||||
|
{
|
||||||
|
tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${table.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// process state //
|
// process state //
|
||||||
@ -375,15 +382,15 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
// but our first use case, they're the same, so... this needs fixed. //
|
// but our first use case, they're the same, so... this needs fixed. //
|
||||||
// they also need to know the 'otherValues' in this process - e.g., for filtering //
|
// they also need to know the 'otherValues' in this process - e.g., for filtering //
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(formFields && processValues)
|
if (formFields && processValues)
|
||||||
{
|
{
|
||||||
Object.keys(formFields).forEach((key) =>
|
Object.keys(formFields).forEach((key) =>
|
||||||
{
|
{
|
||||||
if(formFields[key].possibleValueProps)
|
if (formFields[key].possibleValueProps)
|
||||||
{
|
{
|
||||||
if(processValues[key])
|
if (processValues[key])
|
||||||
{
|
{
|
||||||
formFields[key].possibleValueProps.initialDisplayValue = processValues[key]
|
formFields[key].possibleValueProps.initialDisplayValue = processValues[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
formFields[key].possibleValueProps.otherValues = formFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
|
formFields[key].possibleValueProps.otherValues = formFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
|
||||||
@ -392,7 +399,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
formFields[key].possibleValueProps.otherValues.set(otherKey, processValues[otherKey]);
|
formFields[key].possibleValueProps.otherValues.set(otherKey, processValues[otherKey]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -748,7 +755,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
formValidations[fieldName] = validation;
|
formValidations[fieldName] = validation;
|
||||||
};
|
};
|
||||||
|
|
||||||
if(tableMetaData)
|
if (tableMetaData)
|
||||||
{
|
{
|
||||||
console.log("Adding table name field... ?", tableMetaData.name);
|
console.log("Adding table name field... ?", tableMetaData.name);
|
||||||
addField("tableName", {type: "hidden", omitFromQDynamicForm: true}, tableMetaData.name, null);
|
addField("tableName", {type: "hidden", omitFromQDynamicForm: true}, tableMetaData.name, null);
|
||||||
@ -801,15 +808,15 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
Object.keys(dynamicFormFields).forEach((key: any) =>
|
Object.keys(dynamicFormFields).forEach((key: any) =>
|
||||||
{
|
{
|
||||||
if(dynamicFormFields[key].possibleValueProps)
|
if (dynamicFormFields[key].possibleValueProps)
|
||||||
{
|
{
|
||||||
dynamicFormFields[key].possibleValueProps.otherValues = dynamicFormFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
|
dynamicFormFields[key].possibleValueProps.otherValues = dynamicFormFields[key].possibleValueProps.otherValues ?? new Map<string, any>();
|
||||||
Object.keys(initialValues).forEach((ivKey: any) =>
|
Object.keys(initialValues).forEach((ivKey: any) =>
|
||||||
{
|
{
|
||||||
dynamicFormFields[key].possibleValueProps.otherValues.set(ivKey, initialValues[ivKey]);
|
dynamicFormFields[key].possibleValueProps.otherValues.set(ivKey, initialValues[ivKey]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
// disable all fields if this is a bulk edit form //
|
// disable all fields if this is a bulk edit form //
|
||||||
@ -1082,8 +1089,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
let queryStringPairsForInit = [];
|
let queryStringPairsForInit = [];
|
||||||
if (urlSearchParams.get("recordIds"))
|
if (urlSearchParams.get("recordIds"))
|
||||||
{
|
{
|
||||||
|
const recordIdsFromQueryString = urlSearchParams.get("recordIds").split(",");
|
||||||
|
const encodedRecordIds = recordIdsFromQueryString.map(r => encodeURIComponent(r)).join(",");
|
||||||
queryStringPairsForInit.push("recordsParam=recordIds");
|
queryStringPairsForInit.push("recordsParam=recordIds");
|
||||||
queryStringPairsForInit.push(`recordIds=${urlSearchParams.get("recordIds")}`);
|
queryStringPairsForInit.push(`recordIds=${encodedRecordIds}`);
|
||||||
}
|
}
|
||||||
else if (urlSearchParams.get("filterJSON"))
|
else if (urlSearchParams.get("filterJSON"))
|
||||||
{
|
{
|
||||||
@ -1097,16 +1106,23 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
// }
|
// }
|
||||||
else if (recordIds)
|
else if (recordIds)
|
||||||
{
|
{
|
||||||
if (typeof recordIds === "string")
|
if (recordIds instanceof QQueryFilter)
|
||||||
{
|
|
||||||
queryStringPairsForInit.push("recordsParam=recordIds");
|
|
||||||
queryStringPairsForInit.push(`recordIds=${recordIds}`);
|
|
||||||
}
|
|
||||||
else if (recordIds instanceof QQueryFilter)
|
|
||||||
{
|
{
|
||||||
queryStringPairsForInit.push("recordsParam=filterJSON");
|
queryStringPairsForInit.push("recordsParam=filterJSON");
|
||||||
queryStringPairsForInit.push(`filterJSON=${JSON.stringify(recordIds)}`);
|
queryStringPairsForInit.push(`filterJSON=${JSON.stringify(recordIds)}`);
|
||||||
}
|
}
|
||||||
|
else if (typeof recordIds === "object" && recordIds.length)
|
||||||
|
{
|
||||||
|
const encodedRecordIds = recordIds.map(r => encodeURIComponent(r)).join(",");
|
||||||
|
queryStringPairsForInit.push("recordsParam=recordIds");
|
||||||
|
queryStringPairsForInit.push(`recordIds=${encodedRecordIds}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableVariantLocalStorageKey && localStorage.getItem(tableVariantLocalStorageKey))
|
||||||
|
{
|
||||||
|
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
|
||||||
|
queryStringPairsForInit.push(`tableVariant=${JSON.stringify(tableVariant)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -1154,9 +1170,9 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tableMetaData)
|
if (tableMetaData)
|
||||||
{
|
{
|
||||||
queryStringPairsForInit.push(`tableName=${tableMetaData.name}`)
|
queryStringPairsForInit.push(`tableName=${encodeURIComponent(tableMetaData.name)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -1194,6 +1210,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
formData.append(key, values[key]);
|
formData.append(key, values[key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (tableVariantLocalStorageKey && localStorage.getItem(tableVariantLocalStorageKey))
|
||||||
|
{
|
||||||
|
let tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
|
||||||
|
formData.append("tableVariant", JSON.stringify(tableVariant));
|
||||||
|
}
|
||||||
|
|
||||||
if (doesStepHaveComponent(activeStep, QComponentType.BULK_EDIT_FORM))
|
if (doesStepHaveComponent(activeStep, QComponentType.BULK_EDIT_FORM))
|
||||||
{
|
{
|
||||||
const bulkEditEnabledFields: string[] = [];
|
const bulkEditEnabledFields: string[] = [];
|
||||||
|
@ -82,7 +82,8 @@ const ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT = "qqq.rowsPerPage";
|
|||||||
const PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT = "qqq.pinnedColumns";
|
const PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT = "qqq.pinnedColumns";
|
||||||
const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables";
|
const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables";
|
||||||
const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density";
|
const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density";
|
||||||
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
|
||||||
|
export const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
@ -233,7 +234,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
||||||
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
|
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
|
||||||
const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter);
|
const [recordIdsForProcess, setRecordIdsForProcess] = useState([] as string[] | QQueryFilter);
|
||||||
const [columnStatsFieldName, setColumnStatsFieldName] = useState(null as string);
|
const [columnStatsFieldName, setColumnStatsFieldName] = useState(null as string);
|
||||||
const [columnStatsField, setColumnStatsField] = useState(null as QFieldMetaData);
|
const [columnStatsField, setColumnStatsField] = useState(null as QFieldMetaData);
|
||||||
const [columnStatsFieldTableName, setColumnStatsFieldTableName] = useState(null as string)
|
const [columnStatsFieldTableName, setColumnStatsFieldTableName] = useState(null as string)
|
||||||
@ -538,15 +539,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
<CustomWidthTooltip title={tooltipHTML}>
|
<CustomWidthTooltip title={tooltipHTML}>
|
||||||
<IconButton sx={{p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton>
|
<IconButton sx={{p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton>
|
||||||
</CustomWidthTooltip>
|
</CustomWidthTooltip>
|
||||||
{
|
{tableVariant && getTableVariantHeader()}
|
||||||
tableVariant &&
|
|
||||||
<Typography variant="h6" color="text" fontWeight="light">
|
|
||||||
{tableMetaData.variantTableLabel}: {tableVariant.name}
|
|
||||||
<Tooltip title={`Change ${tableMetaData.variantTableLabel}`}>
|
|
||||||
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -554,19 +547,23 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{label}
|
{label}
|
||||||
{
|
{tableVariant && getTableVariantHeader()}
|
||||||
tableVariant &&
|
|
||||||
<Typography variant="h6" color="text" fontWeight="light">
|
|
||||||
{tableMetaData.variantTableLabel}: {tableVariant.name}
|
|
||||||
<Tooltip title={`Change ${tableMetaData.variantTableLabel}`}>
|
|
||||||
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTableVariantHeader = () =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Typography variant="h6" color="text" fontWeight="light">
|
||||||
|
{tableMetaData?.variantTableLabel}: {tableVariant?.name}
|
||||||
|
<Tooltip title={`Change ${tableMetaData?.variantTableLabel}`}>
|
||||||
|
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const updateTable = () =>
|
const updateTable = () =>
|
||||||
{
|
{
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -925,11 +922,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
if (table.primaryKeyField !== "id")
|
if (table.primaryKeyField !== "id")
|
||||||
{
|
{
|
||||||
navigate(`${metaData.getTablePathByName(tableName)}/${params.row[tableMetaData.primaryKeyField]}`);
|
navigate(`${metaData.getTablePathByName(tableName)}/${encodeURIComponent(params.row[tableMetaData.primaryKeyField])}`);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
navigate(`${metaData.getTablePathByName(tableName)}/${params.id}`);
|
navigate(`${metaData.getTablePathByName(tableName)}/${encodeURIComponent(params.id)}`);
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@ -1189,17 +1186,17 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
if (selectFullFilterState === "filter")
|
if (selectFullFilterState === "filter")
|
||||||
{
|
{
|
||||||
return `?recordsParam=filterJSON&filterJSON=${JSON.stringify(buildQFilter(tableMetaData, filterModel))}`;
|
return `?recordsParam=filterJSON&filterJSON=${encodeURIComponent(JSON.stringify(buildQFilter(tableMetaData, filterModel)))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectFullFilterState === "filterSubset")
|
if (selectFullFilterState === "filterSubset")
|
||||||
{
|
{
|
||||||
return `?recordsParam=filterJSON&filterJSON=${JSON.stringify(buildQFilter(tableMetaData, filterModel, selectionSubsetSize))}`;
|
return `?recordsParam=filterJSON&filterJSON=${encodeURIComponent(JSON.stringify(buildQFilter(tableMetaData, filterModel, selectionSubsetSize)))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedIds.length > 0)
|
if (selectedIds.length > 0)
|
||||||
{
|
{
|
||||||
return `?recordsParam=recordIds&recordIds=${selectedIds.join(",")}`;
|
return `?recordsParam=recordIds&recordIds=${selectedIds.map(r => encodeURIComponent(r)).join(",")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
@ -1217,11 +1214,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
else if (selectedIds.length > 0)
|
else if (selectedIds.length > 0)
|
||||||
{
|
{
|
||||||
setRecordIdsForProcess(selectedIds.join(","));
|
setRecordIdsForProcess(selectedIds);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setRecordIdsForProcess("");
|
setRecordIdsForProcess([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}${getRecordsQueryString()}`);
|
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}${getRecordsQueryString()}`);
|
||||||
@ -1887,10 +1884,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
// instead, try to just render a Goto Record button, in auto-open, and may-not-close modes //
|
// instead, try to just render a Goto Record button, in auto-open, and may-not-close modes //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_QUERY) && tableMetaData.capabilities.has(Capability.TABLE_GET))
|
if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_QUERY) && tableMetaData.capabilities.has(Capability.TABLE_GET))
|
||||||
|
{
|
||||||
|
if(tableMetaData?.usesVariants && (!tableVariant || tableVariantPromptOpen))
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} autoOpen={true} buttonVisible={false} mayClose={false} />
|
<TableVariantDialog table={tableMetaData} isOpen={true} closeHandler={(value: QTableVariant) =>
|
||||||
|
{
|
||||||
|
setTableVariantPromptOpen(false);
|
||||||
|
setTableVariant(value);
|
||||||
|
}} />
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} tableVariant={tableVariant} autoOpen={true} buttonVisible={false} mayClose={false} subHeader={
|
||||||
|
<Box mb={2}>
|
||||||
|
{getTableVariantHeader()}
|
||||||
|
</Box>
|
||||||
|
} />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
document.removeEventListener("keydown", down)
|
document.removeEventListener("keydown", down)
|
||||||
}
|
}
|
||||||
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData])
|
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location])
|
||||||
|
|
||||||
const gotoCreate = () =>
|
const gotoCreate = () =>
|
||||||
{
|
{
|
||||||
@ -931,7 +931,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
activeModalProcess &&
|
activeModalProcess &&
|
||||||
<Modal open={activeModalProcess !== null} onClose={(event, reason) => closeModalProcess(event, reason)}>
|
<Modal open={activeModalProcess !== null} onClose={(event, reason) => closeModalProcess(event, reason)}>
|
||||||
<div className="modalProcess">
|
<div className="modalProcess">
|
||||||
<ProcessRun process={activeModalProcess} isModal={true} table={tableMetaData} recordIds={id} closeModalHandler={closeModalProcess} />
|
<ProcessRun process={activeModalProcess} isModal={true} table={tableMetaData} recordIds={[id]} closeModalHandler={closeModalProcess} />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,7 @@ export default class DataGridUtils
|
|||||||
if (key === tableMetaData.primaryKeyField && linkBase)
|
if (key === tableMetaData.primaryKeyField && linkBase)
|
||||||
{
|
{
|
||||||
column.renderCell = (cellValues: any) => (
|
column.renderCell = (cellValues: any) => (
|
||||||
<Link to={`${linkBase}${cellValues.value}`} onClick={(e) => e.stopPropagation()}>{cellValues.value}</Link>
|
<Link to={`${linkBase}${encodeURIComponent(cellValues.value)}`} onClick={(e) => e.stopPropagation()}>{cellValues.value}</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,7 @@ import org.openqa.selenium.chrome.ChromeOptions;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QBaseSeleniumTest
|
public class QBaseSeleniumTest
|
||||||
{
|
{
|
||||||
private static ChromeOptions chromeOptions;
|
protected static ChromeOptions chromeOptions;
|
||||||
|
|
||||||
protected WebDriver driver;
|
protected WebDriver driver;
|
||||||
protected QSeleniumJavalin qSeleniumJavalin;
|
protected QSeleniumJavalin qSeleniumJavalin;
|
||||||
@ -52,16 +52,30 @@ public class QBaseSeleniumTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach()
|
public void beforeEach()
|
||||||
{
|
{
|
||||||
driver = new ChromeDriver(chromeOptions);
|
driver = new ChromeDriver(chromeOptions);
|
||||||
driver.manage().window().setSize(new Dimension(1700, 1300));
|
driver.manage().window().setSize(new Dimension(1700, 1300));
|
||||||
qSeleniumLib = new QSeleniumLib(driver);
|
qSeleniumLib = new QSeleniumLib(driver);
|
||||||
|
|
||||||
|
if(useInternalJavalin())
|
||||||
|
{
|
||||||
qSeleniumJavalin = new QSeleniumJavalin();
|
qSeleniumJavalin = new QSeleniumJavalin();
|
||||||
addJavalinRoutes(qSeleniumJavalin);
|
addJavalinRoutes(qSeleniumJavalin);
|
||||||
qSeleniumJavalin.start();
|
qSeleniumJavalin.start();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** control if the test needs to start its own javalin server, or if we're running
|
||||||
|
** in an environment where an external web server is being used.
|
||||||
|
*******************************************************************************/
|
||||||
|
protected boolean useInternalJavalin()
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +89,8 @@ public class QBaseSeleniumTest
|
|||||||
.withRouteToFile("/metaData/authentication", "metaData/authentication.json")
|
.withRouteToFile("/metaData/authentication", "metaData/authentication.json")
|
||||||
.withRouteToFile("/metaData/table/person", "metaData/table/person.json")
|
.withRouteToFile("/metaData/table/person", "metaData/table/person.json")
|
||||||
.withRouteToFile("/metaData/table/city", "metaData/table/person.json")
|
.withRouteToFile("/metaData/table/city", "metaData/table/person.json")
|
||||||
|
.withRouteToFile("/metaData/table/script", "metaData/table/script.json")
|
||||||
|
.withRouteToFile("/metaData/table/scriptRevision", "metaData/table/scriptRevision.json")
|
||||||
.withRouteToFile("/processes/querySavedFilter/init", "processes/querySavedFilter/init.json");
|
.withRouteToFile("/processes/querySavedFilter/init", "processes/querySavedFilter/init.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import java.io.File;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -96,6 +97,17 @@ public class QSeleniumLib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for BASE_URL
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getBaseUrl()
|
||||||
|
{
|
||||||
|
return BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -265,6 +277,31 @@ public class QSeleniumLib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void waitForNumberOfWindowsToBe(int number)
|
||||||
|
{
|
||||||
|
LOG.debug("Waiting for number of windows (tabs) to be [" + number + "]");
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if(driver.getWindowHandles().size() == number)
|
||||||
|
{
|
||||||
|
LOG.debug("Number of windows (tabs) is [" + number + "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepABit();
|
||||||
|
}
|
||||||
|
while(start + (1000 * WAIT_SECONDS) > System.currentTimeMillis());
|
||||||
|
|
||||||
|
fail("Failed waiting for number of windows (tabs) to be [" + number + "] after [" + WAIT_SECONDS + "] seconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -293,6 +330,53 @@ public class QSeleniumLib
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void switchToSecondaryTab()
|
||||||
|
{
|
||||||
|
String originalWindow = driver.getWindowHandle();
|
||||||
|
|
||||||
|
waitForNumberOfWindowsToBe(2);
|
||||||
|
|
||||||
|
Set<String> windowHandles = driver.getWindowHandles();
|
||||||
|
for(String windowHandle : windowHandles)
|
||||||
|
{
|
||||||
|
if(!windowHandle.equals(originalWindow))
|
||||||
|
{
|
||||||
|
driver.switchTo().window(windowHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail("Failed to find a window handle not equal to the original window handle. Original=[" + originalWindow + "]. All=[" + windowHandles + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void closeSecondaryTab()
|
||||||
|
{
|
||||||
|
String originalWindow = driver.getWindowHandle();
|
||||||
|
driver.close();
|
||||||
|
|
||||||
|
Set<String> windowHandles = driver.getWindowHandles();
|
||||||
|
for(String windowHandle : windowHandles)
|
||||||
|
{
|
||||||
|
if(!windowHandle.equals(originalWindow))
|
||||||
|
{
|
||||||
|
driver.switchTo().window(windowHandle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail("Failed to find a window handle not equal to the original window handle. Original=[" + originalWindow + "]. All=[" + windowHandles + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface Code<T>
|
public interface Code<T>
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Test for Associated Record Scripts functionality.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class ClickLinkOnRecordThenEditShortcutTest extends QBaseSeleniumTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
|
||||||
|
{
|
||||||
|
super.addJavalinRoutes(qSeleniumJavalin);
|
||||||
|
qSeleniumJavalin.withRouteToFile("/data/script/1", "data/script/1.json");
|
||||||
|
qSeleniumJavalin.withRouteToFile("/data/scriptRevision/100", "data/scriptRevision/100.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testClickLinkOnRecordThenEditShortcutTest()
|
||||||
|
{
|
||||||
|
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/developer/script/1", "Hello, Script");
|
||||||
|
qSeleniumLib.waitForSelectorContaining("A", "100").click();
|
||||||
|
|
||||||
|
qSeleniumLib.waitForSelectorContaining("BUTTON", "actions").sendKeys("e");
|
||||||
|
assertTrue(qSeleniumLib.driver.getCurrentUrl().endsWith("/scriptRevision/100/edit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -64,8 +64,6 @@ public class ScriptTableTest extends QBaseSeleniumTest
|
|||||||
qSeleniumLib.waitForSelectorContaining("DIV.ace_line", "var hello;");
|
qSeleniumLib.waitForSelectorContaining("DIV.ace_line", "var hello;");
|
||||||
qSeleniumLib.waitForSelectorContaining("DIV", "2nd commit");
|
qSeleniumLib.waitForSelectorContaining("DIV", "2nd commit");
|
||||||
qSeleniumLib.waitForSelectorContaining("DIV", "Initial checkin");
|
qSeleniumLib.waitForSelectorContaining("DIV", "Initial checkin");
|
||||||
|
|
||||||
qSeleniumLib.waitForever();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
{
|
{
|
||||||
"tableName": "scriptRevision",
|
"tableName": "scriptRevision",
|
||||||
|
"recordLabel": "Hello, Script Revision",
|
||||||
"values": {
|
"values": {
|
||||||
"id": 100,
|
"id": "100",
|
||||||
|
"name": "Hello, Script Revision",
|
||||||
|
"sequenceNo": "22",
|
||||||
"commitMessage": "Initial checkin",
|
"commitMessage": "Initial checkin",
|
||||||
"author": "Jon Programmer",
|
"author": "Jon Programmer",
|
||||||
"createDate": "2023-02-18T00:47:51Z",
|
"createDate": "2023-02-18T00:47:51Z",
|
||||||
"modifyDate": "2023-02-18T00:47:51Z"
|
"modifyDate": "2023-02-18T00:47:51Z"
|
||||||
},
|
},
|
||||||
"displayValues": {
|
"displayValues": {
|
||||||
|
"id": "1",
|
||||||
|
"name": "Hello, Script Revision",
|
||||||
|
"scriptId": "1",
|
||||||
|
"sequenceNo": "22",
|
||||||
|
"createDate": "2023-02-18T00:47:51Z",
|
||||||
|
"modifyDate": "2023-02-18T00:47:51Z"
|
||||||
},
|
},
|
||||||
"associatedRecords": {
|
"associatedRecords": {
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -131,7 +131,8 @@
|
|||||||
"capabilities": [
|
"capabilities": [
|
||||||
"TABLE_COUNT",
|
"TABLE_COUNT",
|
||||||
"TABLE_GET",
|
"TABLE_GET",
|
||||||
"TABLE_QUERY"
|
"TABLE_QUERY",
|
||||||
|
"TABLE_UPDATE"
|
||||||
],
|
],
|
||||||
"readPermission": true,
|
"readPermission": true,
|
||||||
"insertPermission": true,
|
"insertPermission": true,
|
||||||
|
152
src/test/resources/fixtures/metaData/table/scriptRevision.json
Normal file
152
src/test/resources/fixtures/metaData/table/scriptRevision.json
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"table": {
|
||||||
|
"name": "scriptRevision",
|
||||||
|
"label": "Script Revision",
|
||||||
|
"isHidden": false,
|
||||||
|
"primaryKeyField": "id",
|
||||||
|
"iconName": "history_edu",
|
||||||
|
"fields": {
|
||||||
|
"scriptId": {
|
||||||
|
"name": "scriptId",
|
||||||
|
"label": "Script",
|
||||||
|
"type": "INTEGER",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": true,
|
||||||
|
"isHeavy": false,
|
||||||
|
"possibleValueSourceName": "script",
|
||||||
|
"displayFormat": "%s",
|
||||||
|
"adornments": [
|
||||||
|
{
|
||||||
|
"type": "SIZE",
|
||||||
|
"values": {
|
||||||
|
"width": "large"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "LINK",
|
||||||
|
"values": {
|
||||||
|
"toRecordFromTable": "script"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"apiName": {
|
||||||
|
"name": "apiName",
|
||||||
|
"label": "API Name",
|
||||||
|
"type": "STRING",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": true,
|
||||||
|
"isHeavy": false,
|
||||||
|
"possibleValueSourceName": "apiName",
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"sequenceNo": {
|
||||||
|
"name": "sequenceNo",
|
||||||
|
"label": "Sequence No",
|
||||||
|
"type": "INTEGER",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": true,
|
||||||
|
"isHeavy": false,
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"apiVersion": {
|
||||||
|
"name": "apiVersion",
|
||||||
|
"label": "API Version",
|
||||||
|
"type": "STRING",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": true,
|
||||||
|
"isHeavy": false,
|
||||||
|
"possibleValueSourceName": "apiVersion",
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"commitMessage": {
|
||||||
|
"name": "commitMessage",
|
||||||
|
"label": "Commit Message",
|
||||||
|
"type": "STRING",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": true,
|
||||||
|
"isHeavy": false,
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"modifyDate": {
|
||||||
|
"name": "modifyDate",
|
||||||
|
"label": "Modify Date",
|
||||||
|
"type": "DATE_TIME",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": false,
|
||||||
|
"isHeavy": false,
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "author",
|
||||||
|
"label": "Author",
|
||||||
|
"type": "STRING",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": true,
|
||||||
|
"isHeavy": false,
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"label": "Id",
|
||||||
|
"type": "INTEGER",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": false,
|
||||||
|
"isHeavy": false,
|
||||||
|
"displayFormat": "%s"
|
||||||
|
},
|
||||||
|
"createDate": {
|
||||||
|
"name": "createDate",
|
||||||
|
"label": "Create Date",
|
||||||
|
"type": "DATE_TIME",
|
||||||
|
"isRequired": false,
|
||||||
|
"isEditable": false,
|
||||||
|
"isHeavy": false,
|
||||||
|
"displayFormat": "%s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"name": "identity",
|
||||||
|
"label": "Identity",
|
||||||
|
"tier": "T1",
|
||||||
|
"fieldNames": [
|
||||||
|
"id",
|
||||||
|
"scriptId",
|
||||||
|
"sequenceNo"
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"name": "badge"
|
||||||
|
},
|
||||||
|
"isHidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dates",
|
||||||
|
"label": "Dates",
|
||||||
|
"tier": "T3",
|
||||||
|
"fieldNames": [
|
||||||
|
"createDate",
|
||||||
|
"modifyDate"
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"name": "calendar_month"
|
||||||
|
},
|
||||||
|
"isHidden": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"exposedJoins": [],
|
||||||
|
"capabilities": [
|
||||||
|
"TABLE_COUNT",
|
||||||
|
"TABLE_GET",
|
||||||
|
"TABLE_QUERY",
|
||||||
|
"TABLE_INSERT",
|
||||||
|
"TABLE_UPDATE",
|
||||||
|
"QUERY_STATS"
|
||||||
|
],
|
||||||
|
"readPermission": true,
|
||||||
|
"insertPermission": true,
|
||||||
|
"editPermission": true,
|
||||||
|
"deletePermission": true,
|
||||||
|
"usesVariants": false
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user