mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 22:58:43 +00:00
Compare commits
24 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
580d4a90c9 | |||
eeb1b37d18 | |||
da0947b538 | |||
0c76371d59 | |||
19aebd631a | |||
5aac9ce069 | |||
53d5bc58c1 | |||
eac166b877 | |||
f49ac38e24 | |||
28bdfc19e8 | |||
fa076733fb | |||
8bebef1abe | |||
37fa578a59 | |||
069cbf52e1 | |||
efc423e819 | |||
a268219156 | |||
9ec442e218 | |||
f1dacea6f5 | |||
b9d81e730f | |||
c7622c12f5 | |||
953c4cc569 | |||
63430e1283 | |||
f189083a5a | |||
efcf137a0f |
@ -2,7 +2,7 @@ version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@5.1.0
|
||||
browser-tools: circleci/browser-tools@1.4.3
|
||||
browser-tools: circleci/browser-tools@1.4.5
|
||||
|
||||
executors:
|
||||
java17:
|
||||
@ -115,7 +115,7 @@ workflows:
|
||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /dev/
|
||||
ignore: /main/
|
||||
tags:
|
||||
ignore: /(version|snapshot)-.*/
|
||||
deploy:
|
||||
@ -124,7 +124,7 @@ workflows:
|
||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||
filters:
|
||||
branches:
|
||||
only: /dev/
|
||||
only: /main/
|
||||
tags:
|
||||
only: /(version|snapshot)-.*/
|
||||
|
||||
|
2101
package-lock.json
generated
2101
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
"@auth0/auth0-react": "1.10.2",
|
||||
"@emotion/react": "11.7.1",
|
||||
"@emotion/styled": "11.6.0",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.79",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.80",
|
||||
"@mui/icons-material": "5.4.1",
|
||||
"@mui/material": "5.11.1",
|
||||
"@mui/styles": "5.11.1",
|
||||
|
20
pom.xml
20
pom.xml
@ -29,7 +29,7 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<revision>0.16.0-SNAPSHOT</revision>
|
||||
<revision>0.19.0-SNAPSHOT</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
@ -66,7 +66,7 @@
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-core</artifactId>
|
||||
<version>feature-CTLE-503-optimization-weather-api-data-20230701.011918-3</version>
|
||||
<version>0.17.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@ -83,7 +83,7 @@
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<version>5.4.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -161,6 +161,20 @@
|
||||
<skipUpdateVersion>true</skipUpdateVersion>
|
||||
</configuration>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
|
@ -346,6 +346,12 @@ function EntityForm(props: Props): JSX.Element
|
||||
const fieldName = section.fieldNames[j];
|
||||
const field = tableMetaData.fields.get(fieldName);
|
||||
|
||||
if(!field)
|
||||
{
|
||||
console.log(`Omitting un-found field ${fieldName} from form`);
|
||||
continue;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. //
|
||||
// || (or) we're on the insert screen in which case, only show editable fields. //
|
||||
|
@ -216,7 +216,7 @@ export default function DataBagViewer({dataBagId}: Props): JSX.Element
|
||||
primaryTypographyProps={{fontSize: "1rem"}}
|
||||
secondaryTypographyProps={{fontSize: ".85rem"}}
|
||||
primary={
|
||||
<div style={{whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}} title={version.values.get("commitMessage")}>
|
||||
<div style={{overflow: "hidden", textOverflow: "ellipsis", maxHeight: "5rem"}} title={version.values.get("commitMessage")}>
|
||||
{currentVersionId == version?.values?.get("id") && <Chip label="CURRENT" color="success" variant="outlined" size="small" sx={{mr: 1, fontSize: "0.75rem"}} />}
|
||||
{version.values.get("commitMessage")}
|
||||
</div>
|
||||
|
@ -314,7 +314,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
||||
primaryTypographyProps={{fontSize: "1rem"}}
|
||||
secondaryTypographyProps={{fontSize: ".85rem"}}
|
||||
primary={
|
||||
<div style={{whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}} title={version.values.get("commitMessage")}>
|
||||
<div style={{overflow: "hidden", textOverflow: "ellipsis", maxHeight: "5rem"}} title={version.values.get("commitMessage")}>
|
||||
{scriptRecord.values.get("currentScriptRevisionId") == version?.values?.get("id") && <Chip label="CURRENT" color="success" variant="outlined" size="small" sx={{mr: 1, fontSize: "0.75rem"}} />}
|
||||
{version.values.get("commitMessage")}
|
||||
</div>
|
||||
|
@ -985,6 +985,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
}
|
||||
setQJobRunning(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn(`Process response was not of an expected type (need an npm clean?) ${JSON.stringify(lastProcessResponse)}`);
|
||||
}
|
||||
}
|
||||
}, [lastProcessResponse]);
|
||||
|
||||
@ -1143,6 +1147,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
}
|
||||
}
|
||||
|
||||
if(tableMetaData)
|
||||
{
|
||||
queryStringPairsForInit.push(`tableName=${tableMetaData.name}`)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const processResponse = await Client.getInstance().processInit(processName, queryStringPairsForInit.join("&"));
|
||||
|
@ -88,7 +88,7 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
}
|
||||
const processResult = await qController.processRun("columnStats", formData);
|
||||
|
||||
setStatusString(null)
|
||||
setStatusString(null);
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError;
|
||||
@ -107,7 +107,7 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
const newStatsFields = [] as QFieldMetaData[];
|
||||
for(let i = 0; i<statFieldObjects.length; i++)
|
||||
{
|
||||
newStatsFields.push(new QFieldMetaData(statFieldObjects[i]))
|
||||
newStatsFields.push(new QFieldMetaData(statFieldObjects[i]));
|
||||
}
|
||||
setStatsFields(newStatsFields);
|
||||
}
|
||||
@ -139,15 +139,15 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
fakeTableMetaData.fields = new Map<string, QFieldMetaData>();
|
||||
fakeTableMetaData.fields.set(fieldMetaData.name, fieldMetaData);
|
||||
fakeTableMetaData.fields.set("count", new QFieldMetaData({name: "count", label: "Count", type: "INTEGER"}));
|
||||
fakeTableMetaData.fields.set("percent", new QFieldMetaData({name: "percent", label: "Percent", type: "DECIMAL"}));
|
||||
fakeTableMetaData.sections = [] as QTableSection[];
|
||||
fakeTableMetaData.sections.push(new QTableSection({fieldNames: [fieldMetaData.name, "count"]}));
|
||||
fakeTableMetaData.sections.push(new QTableSection({fieldNames: [fieldMetaData.name, "count", "percent"]}));
|
||||
|
||||
const rows = DataGridUtils.makeRows(valueCounts, fakeTableMetaData);
|
||||
const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, null, null, "bySection");
|
||||
|
||||
columns.forEach((c) =>
|
||||
{
|
||||
c.width = 200;
|
||||
c.filterable = false;
|
||||
c.hideable = false;
|
||||
})
|
||||
@ -162,7 +162,7 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
function CustomPagination()
|
||||
{
|
||||
return (
|
||||
<Box pr={3}>
|
||||
<Box pr={3} fontSize="0.85rem">
|
||||
{rows && rows.length && countDistinct && rows.length < countDistinct ? <span>Showing the first {rows.length.toLocaleString()} of {countDistinct.toLocaleString()} values</span> : <></>}
|
||||
{rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length == 1 ? <span>Showing the only value</span> : <></>}
|
||||
{rows && rows.length && countDistinct && rows.length >= countDistinct && rows.length > 1 ? <span>Showing all {rows.length.toLocaleString()} values</span> : <></>}
|
||||
@ -172,9 +172,9 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
|
||||
const refresh = () =>
|
||||
{
|
||||
setLoading(true)
|
||||
setStatusString("Refreshing...")
|
||||
}
|
||||
setLoading(true);
|
||||
setStatusString("Refreshing...");
|
||||
};
|
||||
|
||||
const doExport = () =>
|
||||
{
|
||||
@ -188,7 +188,7 @@ function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Pro
|
||||
|
||||
const fileName = tableMetaData.label + " - " + fieldMetaData.label + " Column Stats " + ValueUtils.formatDateTimeForFileName(new Date()) + ".csv";
|
||||
HtmlUtils.download(fileName, csv);
|
||||
}
|
||||
};
|
||||
|
||||
function Loading()
|
||||
{
|
||||
|
@ -71,6 +71,7 @@ import DataGridUtils from "qqq/utils/DataGridUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
||||
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId";
|
||||
@ -628,6 +629,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
let models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, null, searchParams, filterLocalStorageKey, sortLocalStorageKey);
|
||||
setFilterModel(models.filter);
|
||||
setColumnSortModel(models.sort);
|
||||
setWarningAlert(models.warning);
|
||||
|
||||
setQueryFilter(FilterUtils.buildQFilterFromGridFilter(tableMetaData, models.filter, models.sort, rowsPerPage));
|
||||
return;
|
||||
}
|
||||
@ -708,16 +711,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
if (tableMetaData?.exposedJoins)
|
||||
{
|
||||
const visibleJoinTables = getVisibleJoinTables();
|
||||
|
||||
queryJoins = [];
|
||||
for (let i = 0; i < tableMetaData.exposedJoins.length; i++)
|
||||
{
|
||||
const join = tableMetaData.exposedJoins[i];
|
||||
if (visibleJoinTables.has(join.joinTable.name))
|
||||
{
|
||||
queryJoins.push(new QueryJoin(join.joinTable.name, true, "LEFT"));
|
||||
}
|
||||
}
|
||||
queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1400,6 +1394,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null);
|
||||
handleFilterChange(models.filter);
|
||||
handleSortChange(models.sort, models.filter);
|
||||
setWarningAlert(models.warning);
|
||||
|
||||
localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString());
|
||||
}
|
||||
else
|
||||
@ -1431,35 +1427,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
return (qRecord);
|
||||
}
|
||||
|
||||
const getFieldAndTable = (fieldName: string): [QFieldMetaData, QTableMetaData] =>
|
||||
{
|
||||
if(fieldName.indexOf(".") > -1)
|
||||
{
|
||||
const nameParts = fieldName.split(".", 2);
|
||||
for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++)
|
||||
{
|
||||
const join = tableMetaData?.exposedJoins[i];
|
||||
if(join?.joinTable.name == nameParts[0])
|
||||
{
|
||||
return ([join.joinTable.fields.get(nameParts[1]), join.joinTable]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ([tableMetaData.fields.get(fieldName), tableMetaData]);
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
const copyColumnValues = async (column: GridColDef) =>
|
||||
{
|
||||
let data = "";
|
||||
let counter = 0;
|
||||
if (latestQueryResults && latestQueryResults.length)
|
||||
{
|
||||
let [qFieldMetaData, fieldTable] = getFieldAndTable(column.field);
|
||||
let [qFieldMetaData, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, column.field);
|
||||
for (let i = 0; i < latestQueryResults.length; i++)
|
||||
{
|
||||
let record = latestQueryResults[i] as QRecord;
|
||||
@ -1489,7 +1463,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setFilterForColumnStats(buildQFilter(tableMetaData, filterModel));
|
||||
setColumnStatsFieldName(column.field);
|
||||
|
||||
const [field, fieldTable] = getFieldAndTable(column.field);
|
||||
const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, column.field);
|
||||
setColumnStatsField(field);
|
||||
setColumnStatsFieldTableName(fieldTable.name);
|
||||
};
|
||||
@ -1962,7 +1936,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
(warningAlert) ? (
|
||||
<Collapse in={Boolean(warningAlert)}>
|
||||
<Alert color="warning" sx={{mb: 3}} onClose={() => setWarningAlert(null)}>{warningAlert}</Alert>
|
||||
<Alert color="warning" icon={<Icon>warning</Icon>} sx={{mb: 3}} onClose={() => setWarningAlert(null)}>{warningAlert}</Alert>
|
||||
</Collapse>
|
||||
) : null
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
|
||||
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
||||
import {QTableVariant} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableVariant";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin";
|
||||
import {Alert, Typography} from "@mui/material";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
@ -103,7 +104,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
|
||||
const [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [notFoundMessage, setNotFoundMessage] = useState(null as string);
|
||||
const [errorMessage, setErrorMessage] = 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 [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData);
|
||||
@ -192,7 +193,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData])
|
||||
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location])
|
||||
|
||||
const gotoCreate = () =>
|
||||
{
|
||||
@ -325,6 +326,31 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
reload();
|
||||
}, [location.pathname, location.hash]);
|
||||
|
||||
const getVisibleJoinTables = (tableMetaData: QTableMetaData): Set<string> =>
|
||||
{
|
||||
const visibleJoinTables = new Set<string>();
|
||||
|
||||
for (let i = 0; i < tableMetaData?.sections.length; i++)
|
||||
{
|
||||
const section = tableMetaData?.sections[i];
|
||||
if (section.isHidden || !section.fieldNames || !section.fieldNames.length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
section.fieldNames.forEach((fieldName) =>
|
||||
{
|
||||
const [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||
if(tableForField && tableForField.name != tableMetaData.name)
|
||||
{
|
||||
visibleJoinTables.add(tableForField.name);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (visibleJoinTables);
|
||||
};
|
||||
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
@ -368,13 +394,20 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
setActiveModalProcess(launchingProcess);
|
||||
}
|
||||
|
||||
let queryJoins: QueryJoin[] = null;
|
||||
const visibleJoinTables = getVisibleJoinTables(tableMetaData);
|
||||
if(visibleJoinTables.size > 0)
|
||||
{
|
||||
queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// load the record //
|
||||
/////////////////////
|
||||
let record: QRecord;
|
||||
try
|
||||
{
|
||||
record = await qController.get(tableName, id, tableVariant);
|
||||
record = await qController.get(tableName, id, tableVariant, null, queryJoins);
|
||||
setRecord(record);
|
||||
}
|
||||
catch (e)
|
||||
@ -465,17 +498,22 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const fields = (
|
||||
<Box key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
|
||||
{
|
||||
section.fieldNames.map((fieldName: string) => (
|
||||
<Box key={fieldName} flexDirection="row" pr={2}>
|
||||
<Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)">
|
||||
{tableMetaData.fields.get(fieldName).label}:
|
||||
<div style={{display: "inline-block", width: 0}}> </div>
|
||||
</Typography>
|
||||
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
||||
{ValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")}
|
||||
</Typography>
|
||||
</Box>
|
||||
))
|
||||
section.fieldNames.map((fieldName: string) =>
|
||||
{
|
||||
let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||
let label = field.label;
|
||||
return (
|
||||
<Box key={fieldName} flexDirection="row" pr={2}>
|
||||
<Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)">
|
||||
{label}:
|
||||
<div style={{display: "inline-block", width: 0}}> </div>
|
||||
</Typography>
|
||||
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
||||
{ValueUtils.getDisplayValue(field, record, "view", fieldName)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
|
@ -259,7 +259,6 @@ export default class DataGridUtils
|
||||
public static makeColumnFromField = (field: QFieldMetaData, tableMetaData: QTableMetaData, namePrefix?: string, labelPrefix?: string): GridColDef =>
|
||||
{
|
||||
let columnType = "string";
|
||||
let columnWidth = 200;
|
||||
let filterOperators: GridFilterOperator<any>[] = QGridStringOperators;
|
||||
|
||||
if (field.possibleValueSourceName)
|
||||
@ -273,28 +272,18 @@ export default class DataGridUtils
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
columnType = "number";
|
||||
columnWidth = 100;
|
||||
|
||||
if (field.name === tableMetaData.primaryKeyField && field.label.length < 3)
|
||||
{
|
||||
columnWidth = 75;
|
||||
}
|
||||
|
||||
filterOperators = QGridNumericOperators;
|
||||
break;
|
||||
case QFieldType.DATE:
|
||||
columnType = "date";
|
||||
columnWidth = 100;
|
||||
filterOperators = QGridDateOperators;
|
||||
break;
|
||||
case QFieldType.DATE_TIME:
|
||||
columnType = "dateTime";
|
||||
columnWidth = 200;
|
||||
filterOperators = QGridDateTimeOperators;
|
||||
break;
|
||||
case QFieldType.BOOLEAN:
|
||||
columnType = "string"; // using boolean gives an odd 'no' for nulls.
|
||||
columnWidth = 75;
|
||||
filterOperators = QGridBooleanOperators;
|
||||
break;
|
||||
case QFieldType.BLOB:
|
||||
@ -305,6 +294,31 @@ export default class DataGridUtils
|
||||
}
|
||||
}
|
||||
|
||||
let headerName = labelPrefix ? labelPrefix + field.label : field.label;
|
||||
let fieldName = namePrefix ? namePrefix + field.name : field.name;
|
||||
|
||||
const column: GridColDef = {
|
||||
field: fieldName,
|
||||
type: columnType,
|
||||
headerName: headerName,
|
||||
width: DataGridUtils.getColumnWidthForField(field, tableMetaData),
|
||||
renderCell: null as any,
|
||||
filterOperators: filterOperators,
|
||||
};
|
||||
|
||||
column.renderCell = (cellValues: any) => (
|
||||
(cellValues.value)
|
||||
);
|
||||
|
||||
return (column);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static getColumnWidthForField = (field: QFieldMetaData, table?: QTableMetaData): number =>
|
||||
{
|
||||
if (field.hasAdornment(AdornmentType.SIZE))
|
||||
{
|
||||
const sizeAdornment = field.getAdornment(AdornmentType.SIZE);
|
||||
@ -318,7 +332,7 @@ export default class DataGridUtils
|
||||
]);
|
||||
if (widths.has(width))
|
||||
{
|
||||
columnWidth = widths.get(width);
|
||||
return widths.get(width);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -326,23 +340,31 @@ export default class DataGridUtils
|
||||
}
|
||||
}
|
||||
|
||||
let headerName = labelPrefix ? labelPrefix + field.label : field.label;
|
||||
let fieldName = namePrefix ? namePrefix + field.name : field.name;
|
||||
if(field.possibleValueSourceName)
|
||||
{
|
||||
return (200);
|
||||
}
|
||||
|
||||
const column: GridColDef = {
|
||||
field: fieldName,
|
||||
type: columnType,
|
||||
headerName: headerName,
|
||||
width: columnWidth,
|
||||
renderCell: null as any,
|
||||
filterOperators: filterOperators,
|
||||
};
|
||||
switch (field.type)
|
||||
{
|
||||
case QFieldType.DECIMAL:
|
||||
case QFieldType.INTEGER:
|
||||
|
||||
column.renderCell = (cellValues: any) => (
|
||||
(cellValues.value)
|
||||
);
|
||||
if (table && field.name === table.primaryKeyField && field.label.length < 3)
|
||||
{
|
||||
return (75);
|
||||
}
|
||||
|
||||
return (column);
|
||||
return (100);
|
||||
case QFieldType.DATE:
|
||||
return (100);
|
||||
case QFieldType.DATE_TIME:
|
||||
return (200);
|
||||
case QFieldType.BOOLEAN:
|
||||
return (75);
|
||||
}
|
||||
|
||||
return (200);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilt
|
||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||
import {ThisOrLastPeriodExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/ThisOrLastPeriodExpression";
|
||||
import {GridFilterItem, GridFilterModel, GridLinkOperator, GridSortItem} from "@mui/x-data-grid-pro";
|
||||
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId";
|
||||
@ -375,10 +376,11 @@ class FilterUtils
|
||||
** Get the default filter to use on the page - either from given filter string, query string param, or
|
||||
** local storage, or a default (empty).
|
||||
*******************************************************************************/
|
||||
public static async determineFilterAndSortModels(qController: QController, tableMetaData: QTableMetaData, filterString: string, searchParams: URLSearchParams, filterLocalStorageKey: string, sortLocalStorageKey: string): Promise<{ filter: GridFilterModel, sort: GridSortItem[] }>
|
||||
public static async determineFilterAndSortModels(qController: QController, tableMetaData: QTableMetaData, filterString: string, searchParams: URLSearchParams, filterLocalStorageKey: string, sortLocalStorageKey: string): Promise<{ filter: GridFilterModel, sort: GridSortItem[], warning: string }>
|
||||
{
|
||||
let defaultFilter = {items: []} as GridFilterModel;
|
||||
let defaultSort = [] as GridSortItem[];
|
||||
let warningParts = [] as string[];
|
||||
|
||||
if (tableMetaData && tableMetaData.fields !== undefined)
|
||||
{
|
||||
@ -396,30 +398,11 @@ class FilterUtils
|
||||
for (let i = 0; i < qQueryFilter?.criteria?.length; i++)
|
||||
{
|
||||
const criteria = qQueryFilter.criteria[i];
|
||||
let fieldTable = tableMetaData;
|
||||
let field = null;
|
||||
if (criteria.fieldName.indexOf(".") > -1)
|
||||
{
|
||||
const nameParts = criteria.fieldName.split(".", 2);
|
||||
for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++)
|
||||
{
|
||||
const joinTable = tableMetaData.exposedJoins[i].joinTable;
|
||||
if (joinTable.name == nameParts[0])
|
||||
{
|
||||
fieldTable = joinTable;
|
||||
field = joinTable.fields.get(nameParts[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
field = tableMetaData.fields.get(criteria.fieldName);
|
||||
}
|
||||
|
||||
let [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, criteria.fieldName);
|
||||
if (field == null)
|
||||
{
|
||||
console.log("Couldn't find field for filter: " + criteria.fieldName);
|
||||
warningParts.push("Your filter contained an unrecognized field name: " + criteria.fieldName)
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -449,12 +432,15 @@ class FilterUtils
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// replace objects that look like expressions with expression instances //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
for(let i = 0; i < values.length; i++)
|
||||
if(values && values.length)
|
||||
{
|
||||
const expression = this.gridCriteriaValueToExpression(values[i])
|
||||
if(expression)
|
||||
for (let i = 0; i < values.length; i++)
|
||||
{
|
||||
values[i] = expression;
|
||||
const expression = this.gridCriteriaValueToExpression(values[i])
|
||||
if (expression)
|
||||
{
|
||||
values[i] = expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,7 +483,7 @@ class FilterUtils
|
||||
localStorage.setItem(sortLocalStorageKey, JSON.stringify(defaultSort));
|
||||
}
|
||||
|
||||
return ({filter: defaultFilter, sort: defaultSort});
|
||||
return ({filter: defaultFilter, sort: defaultSort, warning: warningParts.length > 0 ? "Warning: " + warningParts.join("; ") : ""});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
@ -548,7 +534,7 @@ class FilterUtils
|
||||
});
|
||||
}
|
||||
|
||||
return ({filter: defaultFilter, sort: defaultSort});
|
||||
return ({filter: defaultFilter, sort: defaultSort, warning: warningParts.length > 0 ? "Warning: " + warningParts.join("; ") : ""});
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,8 +19,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
||||
import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin";
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for working with QQQ Tables
|
||||
@ -28,7 +30,6 @@ import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTa
|
||||
*******************************************************************************/
|
||||
class TableUtils
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -85,6 +86,61 @@ class TableUtils
|
||||
})]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static getFieldAndTable(tableMetaData: QTableMetaData, fieldName: string): [QFieldMetaData, QTableMetaData]
|
||||
{
|
||||
if (fieldName.indexOf(".") > -1)
|
||||
{
|
||||
const nameParts = fieldName.split(".", 2);
|
||||
for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++)
|
||||
{
|
||||
const join = tableMetaData?.exposedJoins[i];
|
||||
if (join?.joinTable.name == nameParts[0])
|
||||
{
|
||||
return ([join.joinTable.fields.get(nameParts[1]), join.joinTable]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ([tableMetaData.fields.get(fieldName), tableMetaData]);
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static getQueryJoins(tableMetaData: QTableMetaData, visibleJoinTables: Set<string>): QueryJoin[]
|
||||
{
|
||||
const queryJoins = [];
|
||||
for (let i = 0; i < tableMetaData.exposedJoins.length; i++)
|
||||
{
|
||||
const join = tableMetaData.exposedJoins[i];
|
||||
if (visibleJoinTables.has(join.joinTable.name))
|
||||
{
|
||||
let joinName = null;
|
||||
if (join.joinPath && join.joinPath.length == 1 && join.joinPath[0].name)
|
||||
{
|
||||
joinName = join.joinPath[0].name;
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - what about a join with a longer path? it would be nice to pass such joinNames through there too, //
|
||||
// but what, that would actually be multiple queryJoins? needs a fair amount of thought. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
queryJoins.push(new QueryJoin(join.joinTable.name, true, "LEFT", null, null, joinName));
|
||||
}
|
||||
}
|
||||
|
||||
return queryJoins;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default TableUtils;
|
||||
|
@ -18,7 +18,7 @@ import org.openqa.selenium.chrome.ChromeOptions;
|
||||
*******************************************************************************/
|
||||
public class QBaseSeleniumTest
|
||||
{
|
||||
private static ChromeOptions chromeOptions;
|
||||
protected static ChromeOptions chromeOptions;
|
||||
|
||||
protected WebDriver driver;
|
||||
protected QSeleniumJavalin qSeleniumJavalin;
|
||||
@ -52,15 +52,29 @@ public class QBaseSeleniumTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void beforeEach()
|
||||
public void beforeEach()
|
||||
{
|
||||
driver = new ChromeDriver(chromeOptions);
|
||||
driver.manage().window().setSize(new Dimension(1700, 1300));
|
||||
qSeleniumLib = new QSeleniumLib(driver);
|
||||
|
||||
qSeleniumJavalin = new QSeleniumJavalin();
|
||||
addJavalinRoutes(qSeleniumJavalin);
|
||||
qSeleniumJavalin.start();
|
||||
if(useInternalJavalin())
|
||||
{
|
||||
qSeleniumJavalin = new QSeleniumJavalin();
|
||||
addJavalinRoutes(qSeleniumJavalin);
|
||||
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/table/person", "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");
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
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
|
||||
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"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.ThisOrLastPeriod;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
|
||||
import com.kingsrook.qqq.materialdashboard.lib.QQQMaterialDashboardSelectors;
|
||||
import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test for the record query screen when a filter is given in the URL
|
||||
*******************************************************************************/
|
||||
public class QueryScreenFilterInUrlTest extends QBaseSeleniumTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
|
||||
{
|
||||
super.addJavalinRoutes(qSeleniumJavalin);
|
||||
qSeleniumJavalin
|
||||
.withRouteToFile("/data/person/count", "data/person/count.json")
|
||||
.withRouteToFile("/data/person/query", "data/person/index.json")
|
||||
.withRouteToFile("/data/person/possibleValues/homeCityId", "data/person/possibleValues/homeCityId.json")
|
||||
.withRouteToFile("/data/person/variants", "data/person/variants.json")
|
||||
.withRouteToFile("/processes/querySavedFilter/init", "processes/querySavedFilter/init.json");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUrlWithFilter()
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// not-blank -- criteria w/ no values //
|
||||
////////////////////////////////////////
|
||||
String filterJSON = JsonUtils.toJson(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("annualSalary", QCriteriaOperator.IS_NOT_BLANK)));
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||
waitForQueryToHaveRan();
|
||||
assertFilterButtonBadge(1);
|
||||
clickFilterButton();
|
||||
qSeleniumLib.waitForSelector("input[value=\"is not empty\"]");
|
||||
|
||||
///////////////////////////////
|
||||
// between on a number field //
|
||||
///////////////////////////////
|
||||
filterJSON = JsonUtils.toJson(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("annualSalary", QCriteriaOperator.BETWEEN, 1701, 74656)));
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||
waitForQueryToHaveRan();
|
||||
assertFilterButtonBadge(1);
|
||||
clickFilterButton();
|
||||
qSeleniumLib.waitForSelector("input[value=\"is between\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"1701\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"74656\"]");
|
||||
|
||||
//////////////////////////////////////////
|
||||
// not-equals on a possible-value field //
|
||||
//////////////////////////////////////////
|
||||
filterJSON = JsonUtils.toJson(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("homeCityId", QCriteriaOperator.NOT_EQUALS, 1)));
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||
waitForQueryToHaveRan();
|
||||
assertFilterButtonBadge(1);
|
||||
clickFilterButton();
|
||||
qSeleniumLib.waitForSelector("input[value=\"does not equal\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"St. Louis\"]");
|
||||
|
||||
//////////////////////////////////////
|
||||
// an IN for a possible-value field //
|
||||
//////////////////////////////////////
|
||||
filterJSON = JsonUtils.toJson(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("homeCityId", QCriteriaOperator.IN, 1, 2)));
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||
waitForQueryToHaveRan();
|
||||
assertFilterButtonBadge(1);
|
||||
clickFilterButton();
|
||||
qSeleniumLib.waitForSelector("input[value=\"is any of\"]");
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiChip-label", "St. Louis");
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiChip-label", "Chesterfield");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// greater than a date-time expression //
|
||||
/////////////////////////////////////////
|
||||
filterJSON = JsonUtils.toJson(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("createDate", QCriteriaOperator.GREATER_THAN, NowWithOffset.minus(5, ChronoUnit.DAYS))));
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||
waitForQueryToHaveRan();
|
||||
assertFilterButtonBadge(1);
|
||||
clickFilterButton();
|
||||
qSeleniumLib.waitForSelector("input[value=\"is after\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"5 days ago\"]");
|
||||
|
||||
///////////////////////
|
||||
// multiple criteria //
|
||||
///////////////////////
|
||||
filterJSON = JsonUtils.toJson(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.STARTS_WITH, "Dar"))
|
||||
.withCriteria(new QFilterCriteria("createDate", QCriteriaOperator.LESS_THAN_OR_EQUALS, ThisOrLastPeriod.this_(ChronoUnit.YEARS))));
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person?filter=" + URLEncoder.encode(filterJSON, StandardCharsets.UTF_8), "Person");
|
||||
waitForQueryToHaveRan();
|
||||
assertFilterButtonBadge(2);
|
||||
clickFilterButton();
|
||||
qSeleniumLib.waitForSelector("input[value=\"is at or before\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"start of this year\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"starts with\"]");
|
||||
qSeleniumLib.waitForSelector("input[value=\"Dar\"]");
|
||||
|
||||
////////////////
|
||||
// remove one //
|
||||
////////////////
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiIcon-root", "close").click();
|
||||
assertFilterButtonBadge(1);
|
||||
|
||||
qSeleniumLib.waitForever();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private WebElement assertFilterButtonBadge(int valueInBadge)
|
||||
{
|
||||
return qSeleniumLib.waitForSelectorContaining(".MuiBadge-root", String.valueOf(valueInBadge));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private WebElement waitForQueryToHaveRan()
|
||||
{
|
||||
return qSeleniumLib.waitForSelector(QQQMaterialDashboardSelectors.QUERY_GRID_CELL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void clickFilterButton()
|
||||
{
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiDataGrid-toolbarContainer BUTTON", "Filter").click();
|
||||
}
|
||||
|
||||
}
|
@ -64,8 +64,6 @@ public class ScriptTableTest extends QBaseSeleniumTest
|
||||
qSeleniumLib.waitForSelectorContaining("DIV.ace_line", "var hello;");
|
||||
qSeleniumLib.waitForSelectorContaining("DIV", "2nd commit");
|
||||
qSeleniumLib.waitForSelectorContaining("DIV", "Initial checkin");
|
||||
|
||||
qSeleniumLib.waitForever();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"id": 1,
|
||||
"label": "St. Louis"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"label": "Chesterfield"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,14 +1,22 @@
|
||||
{
|
||||
"tableName": "scriptRevision",
|
||||
"recordLabel": "Hello, Script Revision",
|
||||
"values": {
|
||||
"id": 100,
|
||||
"id": "100",
|
||||
"name": "Hello, Script Revision",
|
||||
"sequenceNo": "22",
|
||||
"commitMessage": "Initial checkin",
|
||||
"author": "Jon Programmer",
|
||||
"createDate": "2023-02-18T00:47:51Z",
|
||||
"modifyDate": "2023-02-18T00:47:51Z"
|
||||
},
|
||||
"displayValues": {
|
||||
|
||||
"id": "1",
|
||||
"name": "Hello, Script Revision",
|
||||
"scriptId": "1",
|
||||
"sequenceNo": "22",
|
||||
"createDate": "2023-02-18T00:47:51Z",
|
||||
"modifyDate": "2023-02-18T00:47:51Z"
|
||||
},
|
||||
"associatedRecords": {
|
||||
"files": [
|
||||
@ -25,4 +33,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +131,8 @@
|
||||
"capabilities": [
|
||||
"TABLE_COUNT",
|
||||
"TABLE_GET",
|
||||
"TABLE_QUERY"
|
||||
"TABLE_QUERY",
|
||||
"TABLE_UPDATE"
|
||||
],
|
||||
"readPermission": true,
|
||||
"insertPermission": true,
|
||||
|
@ -74,6 +74,15 @@
|
||||
"isEditable": true,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"homeCityId": {
|
||||
"name": "homeCityId",
|
||||
"label": "Home City",
|
||||
"type": "INTEGER",
|
||||
"possibleValueSourceName": "city",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"label": "Email",
|
||||
|
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