mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 14:48:43 +00:00
Compare commits
5 Commits
snapshot-f
...
snapshot-H
Author | SHA1 | Date | |
---|---|---|---|
7b364a1e09 | |||
6ef4dd8fbe | |||
17893a0cfd | |||
33056963a4 | |||
ef8eecd6cb |
@ -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.96",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.97",
|
||||
"@mui/icons-material": "5.4.1",
|
||||
"@mui/material": "5.11.1",
|
||||
"@mui/styles": "5.11.1",
|
||||
|
@ -664,10 +664,10 @@ export default function App()
|
||||
const [dotMenuOpen, setDotMenuOpen] = useState(false);
|
||||
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
||||
const [helpHelpActive] = useState(queryParams.has("helpHelp"));
|
||||
const [userId] = useState(user.email);
|
||||
|
||||
const [googleAnalyticsUtils] = useState(new GoogleAnalyticsUtils());
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -689,6 +689,7 @@ export default function App()
|
||||
dotMenuOpen: dotMenuOpen,
|
||||
keyboardHelpOpen: keyboardHelpOpen,
|
||||
helpHelpActive: helpHelpActive,
|
||||
userId: userId,
|
||||
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
||||
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
||||
setAccentColorLight: (accentColorLight: string) => setAccentColorLight(accentColorLight),
|
||||
|
@ -59,6 +59,7 @@ interface QContext
|
||||
pathToLabelMap?: {[path: string]: string};
|
||||
branding?: QBrandingMetaData;
|
||||
helpHelpActive?: boolean;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
|
@ -28,16 +28,17 @@ import Box from "@mui/material/Box";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import {ErrorMessage, useFormikContext} from "formik";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
interface Props
|
||||
{
|
||||
tableName?: string;
|
||||
processName?: string;
|
||||
fieldName: string;
|
||||
fieldName?: string;
|
||||
possibleValueSourceName?: string;
|
||||
overrideId?: string;
|
||||
fieldLabel: string;
|
||||
inForm: boolean;
|
||||
@ -57,6 +58,8 @@ interface Props
|
||||
DynamicSelect.defaultProps = {
|
||||
tableName: null,
|
||||
processName: null,
|
||||
fieldName: null,
|
||||
possibleValueSourceName: null,
|
||||
inForm: true,
|
||||
initialValue: null,
|
||||
initialDisplayValue: null,
|
||||
@ -73,16 +76,78 @@ DynamicSelect.defaultProps = {
|
||||
},
|
||||
};
|
||||
|
||||
const {inputBorderColor} = colors;
|
||||
|
||||
|
||||
export const getAutocompleteOutlinedStyle = (isDisabled: boolean) =>
|
||||
{
|
||||
return ({
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "0.75rem",
|
||||
},
|
||||
"& .MuiInputBase-root": {
|
||||
padding: "0.5rem",
|
||||
background: isDisabled ? "#f0f2f5!important" : "initial",
|
||||
},
|
||||
"& .MuiOutlinedInput-root .MuiAutocomplete-input": {
|
||||
padding: "0",
|
||||
fontSize: "1rem"
|
||||
},
|
||||
"& .Mui-disabled .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: inputBorderColor
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant, initiallyOpen}: Props)
|
||||
function DynamicSelect({tableName, processName, fieldName, possibleValueSourceName, overrideId, fieldLabel, inForm, initialValue, initialDisplayValue, initialValues, onChange, isEditable, isMultiple, bulkEditMode, bulkEditSwitchChangeHandler, otherValues, variant, initiallyOpen}: Props)
|
||||
{
|
||||
const [open, setOpen] = useState(initiallyOpen);
|
||||
const [options, setOptions] = useState<readonly QPossibleValue[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState(null);
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
const [otherValuesWhenResultsWereLoaded, setOtherValuesWhenResultsWereLoaded] = useState(JSON.stringify(Object.fromEntries((otherValues))))
|
||||
const {inputBorderColor} = colors;
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(tableName && processName)
|
||||
{
|
||||
console.log("DynamicSelect - you may not provide both a tableName and a processName")
|
||||
}
|
||||
if(tableName && !fieldName)
|
||||
{
|
||||
console.log("DynamicSelect - if you provide a tableName, you must also provide a fieldName");
|
||||
}
|
||||
if(processName && !fieldName)
|
||||
{
|
||||
console.log("DynamicSelect - if you provide a processName, you must also provide a fieldName");
|
||||
}
|
||||
if(fieldName && possibleValueSourceName)
|
||||
{
|
||||
console.log("DynamicSelect - if you provide a fieldName and a possibleValueSourceName, the possibleValueSourceName will be ignored");
|
||||
}
|
||||
if(!fieldName && !possibleValueSourceName)
|
||||
{
|
||||
console.log("DynamicSelect - you must provide either a fieldName (and a tableName or processName) or a possibleValueSourceName");
|
||||
}
|
||||
if(fieldName)
|
||||
{
|
||||
if(!tableName || !processName)
|
||||
{
|
||||
console.log("DynamicSelect - if you provide a fieldName, you must also provide a tableName or processName");
|
||||
}
|
||||
}
|
||||
if(possibleValueSourceName)
|
||||
{
|
||||
if(tableName || processName)
|
||||
{
|
||||
console.log("DynamicSelect - if you provide a possibleValueSourceName, you should not also provide a tableName or processName");
|
||||
}
|
||||
}
|
||||
|
||||
}, [tableName, processName, fieldName, possibleValueSourceName]);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default value - needs to be an array (from initialValues (array) prop) for multiple mode - //
|
||||
@ -133,7 +198,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
(async () =>
|
||||
{
|
||||
// console.log(`doing a search with ${searchTerm}`);
|
||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName, searchTerm ?? "", null, otherValues);
|
||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName ?? possibleValueSourceName, searchTerm ?? "", null, otherValues);
|
||||
|
||||
if(tableMetaData == null && tableName)
|
||||
{
|
||||
@ -166,7 +231,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
setLoading(true);
|
||||
setOptions([]);
|
||||
console.log("Refreshing possible values...");
|
||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName, searchTerm ?? "", null, otherValues);
|
||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, processName, fieldName ?? possibleValueSourceName, searchTerm ?? "", null, otherValues);
|
||||
setLoading(false);
|
||||
setOptions([ ...results ]);
|
||||
setOtherValuesWhenResultsWereLoaded(JSON.stringify(Object.fromEntries(otherValues)));
|
||||
@ -206,7 +271,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
onChange(value ? new QPossibleValue(value) : null);
|
||||
}
|
||||
}
|
||||
else if(setFieldValueRef)
|
||||
else if(setFieldValueRef && fieldName)
|
||||
{
|
||||
setFieldValueRef(fieldName, value ? value.id : null);
|
||||
}
|
||||
@ -282,28 +347,13 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
let autocompleteSX = {};
|
||||
if (variant == "outlined")
|
||||
{
|
||||
autocompleteSX = {
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "0.75rem",
|
||||
},
|
||||
"& .MuiInputBase-root": {
|
||||
padding: "0.5rem",
|
||||
background: isDisabled ? "#f0f2f5!important" : "initial",
|
||||
},
|
||||
"& .MuiOutlinedInput-root .MuiAutocomplete-input": {
|
||||
padding: "0",
|
||||
fontSize: "1rem"
|
||||
},
|
||||
"& .Mui-disabled .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: inputBorderColor
|
||||
}
|
||||
}
|
||||
autocompleteSX = getAutocompleteOutlinedStyle(isDisabled);
|
||||
}
|
||||
|
||||
const autocomplete = (
|
||||
<Box>
|
||||
<Autocomplete
|
||||
id={overrideId ?? fieldName}
|
||||
id={overrideId ?? fieldName ?? possibleValueSourceName}
|
||||
sx={autocompleteSX}
|
||||
open={open}
|
||||
fullWidth
|
||||
@ -383,7 +433,7 @@ function DynamicSelect({tableName, processName, fieldName, overrideId, fieldLabe
|
||||
inForm &&
|
||||
<Box mt={0.75}>
|
||||
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
|
||||
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={fieldName} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
||||
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={fieldName ?? possibleValueSourceName} render={msg => <span data-field-error="true">{msg}</span>} /></div>}
|
||||
</MDTypography>
|
||||
</Box>
|
||||
}
|
||||
|
481
src/qqq/components/sharing/ShareModal.tsx
Normal file
481
src/qqq/components/sharing/ShareModal.tsx
Normal file
@ -0,0 +1,481 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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/>.
|
||||
*/
|
||||
|
||||
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {Alert} from "@mui/material";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import FormData from "form-data";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import {QCancelButton} from "qqq/components/buttons/DefaultButtons";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import React, {useEffect, useReducer, useState} from "react";
|
||||
|
||||
interface ShareModalProps
|
||||
{
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
tableMetaData: QTableMetaData;
|
||||
record: QRecord;
|
||||
}
|
||||
|
||||
ShareModal.defaultProps = {};
|
||||
|
||||
|
||||
interface CurrentShare
|
||||
{
|
||||
shareId: any;
|
||||
scopeId: string;
|
||||
audienceType: string;
|
||||
audienceId: any;
|
||||
audienceLabel: string;
|
||||
}
|
||||
|
||||
interface Scope
|
||||
{
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const scopeOptions: Scope[] = [
|
||||
{id: "READ_ONLY", label: "Read-Only"},
|
||||
{id: "READ_WRITE", label: "Read and Edit"}
|
||||
];
|
||||
|
||||
const defaultScope = scopeOptions[0];
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
/*******************************************************************************
|
||||
** component containing a Modal dialog for sharing records
|
||||
*******************************************************************************/
|
||||
export default function ShareModal({open, onClose, tableMetaData, record}: ShareModalProps): JSX.Element
|
||||
{
|
||||
const [statusString, setStatusString] = useState("Loading...");
|
||||
const [alert, setAlert] = useState(null as string);
|
||||
|
||||
const [selectedAudienceType, setSelectedAudienceType] = useState(null);
|
||||
const [selectedAudienceId, setSelectedAudienceId] = useState(null);
|
||||
const [selectedScopeId, setSelectedScopeId] = useState(defaultScope.id);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const [currentShares, setCurrentShares] = useState([] as CurrentShare[])
|
||||
const [needToLoadCurrentShares, setNeedToLoadCurrentShares] = useState(true);
|
||||
const [everLoadedCurrentShares, setEverLoadedCurrentShares] = useState(false);
|
||||
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// trigger initial load, and post any changes, re-load //
|
||||
/////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
if(needToLoadCurrentShares)
|
||||
{
|
||||
loadShares();
|
||||
}
|
||||
}, [needToLoadCurrentShares]);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function close(event: object, reason: string)
|
||||
{
|
||||
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
onClose();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function handleAudienceChange(event: React.SyntheticEvent, value: any | any[], reason: string)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
const [audienceType, audienceId] = value.id.split(":");
|
||||
setSelectedAudienceType(audienceType);
|
||||
setSelectedAudienceId(audienceId);
|
||||
}
|
||||
else
|
||||
{
|
||||
setSelectedAudienceType(null);
|
||||
setSelectedAudienceId(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function handleScopeChange(event: React.SyntheticEvent, value: any | any[], reason: string)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
setSelectedScopeId(value.id);
|
||||
}
|
||||
else
|
||||
{
|
||||
setSelectedScopeId(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
async function editingExistingShareScope(shareId: number, value: any | any[])
|
||||
{
|
||||
setStatusString("Saving...");
|
||||
setAlert(null);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||
formData.append("shareId", shareId);
|
||||
formData.append("scopeId", value.id);
|
||||
|
||||
const processResult = await qController.processRun("editSharedRecord", formData, null, true);
|
||||
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError;
|
||||
setStatusString(null);
|
||||
setAlert("Error editing shared record: " + jobError.error);
|
||||
setSubmitting(false)
|
||||
}
|
||||
else
|
||||
{
|
||||
const result = processResult as QJobComplete;
|
||||
setStatusString(null);
|
||||
setAlert(null);
|
||||
setNeedToLoadCurrentShares(true);
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
async function loadShares()
|
||||
{
|
||||
setNeedToLoadCurrentShares(false);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||
const processResult = await qController.processRun("getSharedRecords", formData, null, true);
|
||||
|
||||
setStatusString("Loading...");
|
||||
setAlert(null)
|
||||
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError;
|
||||
setStatusString(null);
|
||||
setAlert("Error loading: " + jobError.error);
|
||||
}
|
||||
else
|
||||
{
|
||||
const result = processResult as QJobComplete;
|
||||
|
||||
const newCurrentShares: CurrentShare[] = [];
|
||||
for (let i in result.values["resultList"])
|
||||
{
|
||||
newCurrentShares.push(result.values["resultList"][i].values);
|
||||
}
|
||||
setCurrentShares(newCurrentShares);
|
||||
setEverLoadedCurrentShares(true);
|
||||
|
||||
setStatusString(null);
|
||||
setAlert(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
async function saveNewShare()
|
||||
{
|
||||
setSubmitting(true)
|
||||
setStatusString("Saving...");
|
||||
setAlert(null);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||
formData.append("audienceType", selectedAudienceType);
|
||||
formData.append("audienceId", selectedAudienceId);
|
||||
formData.append("scopeId", selectedScopeId);
|
||||
|
||||
const processResult = await qController.processRun("insertSharedRecord", formData, null, true);
|
||||
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError;
|
||||
setStatusString(null);
|
||||
setAlert("Error sharing record: " + jobError.error);
|
||||
setSubmitting(false)
|
||||
}
|
||||
else
|
||||
{
|
||||
const result = processResult as QJobComplete;
|
||||
setStatusString(null);
|
||||
setAlert(null);
|
||||
setNeedToLoadCurrentShares(true);
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
async function removeShare(shareId: number)
|
||||
{
|
||||
setStatusString("Deleting...");
|
||||
setAlert(null);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("tableName", tableMetaData.name);
|
||||
formData.append("recordId", record.values.get(tableMetaData.primaryKeyField));
|
||||
formData.append("shareId", shareId);
|
||||
|
||||
const processResult = await qController.processRun("deleteSharedRecord", formData, null, true);
|
||||
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError;
|
||||
setStatusString(null);
|
||||
setAlert("Error deleting share: " + jobError.error);
|
||||
}
|
||||
else
|
||||
{
|
||||
const result = processResult as QJobComplete;
|
||||
setNeedToLoadCurrentShares(true);
|
||||
setStatusString(null);
|
||||
setAlert(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// todo - need this to be real
|
||||
const audienceOptions = [
|
||||
{id: "user:1", label: "Darin Kelkhoff"},
|
||||
{id: "user:2", label: "Tom Chutterloin"},
|
||||
{id: "user:3", label: "Tylers Ample"},
|
||||
{id: "user:4", label: "Mames Mames"},
|
||||
{id: "group:2", label: "Cold Track Engineering"}
|
||||
];
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function getScopeOption(scopeId: string): Scope
|
||||
{
|
||||
for (let scopeOption of scopeOptions)
|
||||
{
|
||||
if(scopeOption.id == scopeId)
|
||||
{
|
||||
return (scopeOption);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function renderScopeDropdown(id: string, defaultValue: Scope, onChange: (event: React.SyntheticEvent, value: any | any[], reason: string) => void)
|
||||
{
|
||||
return (
|
||||
<Autocomplete
|
||||
id={id}
|
||||
disabled={id == "new-share-scope" && submitting}
|
||||
renderInput={(params) => (<TextField {...params} label="Scope" variant="outlined" autoComplete="off" type="search" InputProps={{...params.InputProps}} />)}
|
||||
options={scopeOptions}
|
||||
// @ts-ignore
|
||||
defaultValue={defaultValue}
|
||||
onChange={onChange}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
// @ts-ignore Property label does not exist on string | {thing with label}
|
||||
getOptionLabel={(option) => option.label}
|
||||
autoSelect={true}
|
||||
autoHighlight={true}
|
||||
disableClearable
|
||||
fullWidth
|
||||
sx={autocompleteSX}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// render the modal //
|
||||
//////////////////////
|
||||
return (<Modal open={open} onClose={close}>
|
||||
<div className="share">
|
||||
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%", display: "flex", height: "100%", flexDirection: "column", justifyContent: "center"}}>
|
||||
<Card sx={{my: 5, mx: "auto", p: 3}}>
|
||||
|
||||
{/* header */}
|
||||
<Box display="flex" flexDirection="row" justifyContent="space-between" alignItems="flex-start">
|
||||
<Typography variant="h4" pb={1} fontWeight="600">
|
||||
Share {tableMetaData.label}: {record?.recordLabel ?? record?.values?.get(tableMetaData.primaryKeyField) ?? "Unknown"}
|
||||
<Box color={colors.gray.main} pb={"0.5rem"} fontSize={"0.875rem"} fontWeight="400" maxWidth="590px">
|
||||
{/* todo move to helpContent (what do we attach the meta-data too??) */}
|
||||
Select a user or a group to share this record with.
|
||||
You can choose if they should only be able to Read the record, or also make Edits to it.
|
||||
</Box>
|
||||
<Box fontSize={14} maxWidth="590px" pb={1} fontWeight="300">
|
||||
{alert && <Alert color="error" onClose={() => setAlert(null)}>{alert}</Alert>}
|
||||
{statusString}
|
||||
</Box>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* body */}
|
||||
<Box pb={3} display="flex" flexDirection="column">
|
||||
{/* row for adding a new share */}
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<Box width="350px" pr={2}>
|
||||
<Autocomplete
|
||||
id="new-share-audience"
|
||||
disabled={submitting}
|
||||
renderInput={(params) => (<TextField {...params} label="User or Group" variant="outlined" autoComplete="off" type="search" InputProps={{...params.InputProps}} />)}
|
||||
options={audienceOptions}
|
||||
onChange={handleAudienceChange}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
// @ts-ignore Property label does not exist on string | {thing with label}
|
||||
getOptionLabel={(option) => option.label}
|
||||
autoSelect={true}
|
||||
autoHighlight={true}
|
||||
disableClearable
|
||||
fullWidth
|
||||
sx={autocompleteSX}
|
||||
/>
|
||||
</Box>
|
||||
<Box width="180px" pr={2}>
|
||||
{renderScopeDropdown("new-share-scope", defaultScope, handleScopeChange)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Tooltip title={selectedAudienceId == null ? "Select a user or group to share with." : null}>
|
||||
<span>
|
||||
<Button disabled={submitting || selectedAudienceId == null} sx={iconButtonSX} onClick={() => saveNewShare()}>
|
||||
<Icon color={selectedAudienceId == null ? "secondary" : "info"}>save</Icon>
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* row showing existing shares */}
|
||||
<Box pt={3}>
|
||||
<Box pb="0.25rem">
|
||||
<h5 style={{fontWeight: "600"}}>Current Shares
|
||||
{
|
||||
everLoadedCurrentShares ? <> ({currentShares.length})</> : <></>
|
||||
}
|
||||
</h5>
|
||||
</Box>
|
||||
<Box sx={{border: `1px solid ${colors.grayLines.main}`, borderRadius: "1rem", overflow: "auto"}} height="180px" pt="0.5rem">
|
||||
{
|
||||
currentShares.map((share) => (
|
||||
<Box key={share.shareId} display="flex" justifyContent="space-between" alignItems="center" p="0.25rem" fontSize="1rem">
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box width="310px" pl="1rem">{share.audienceLabel}</Box>
|
||||
<Box width="160px">{renderScopeDropdown(`scope-${share.shareId}`, getScopeOption(share.scopeId), (event: React.SyntheticEvent, value: any | any[], reason: string) => editingExistingShareScope(share.shareId, value))}</Box>
|
||||
</Box>
|
||||
<Box pr="1rem">
|
||||
<Button sx={{...iconButtonSX, ...redIconButton}} onClick={() => removeShare(share.shareId)}><Icon>clear</Icon></Button>
|
||||
</Box>
|
||||
</Box>
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
{/* footer */}
|
||||
<Box display="flex" flexDirection="row" justifyContent="flex-end">
|
||||
<QCancelButton label="Done" iconName="check" onClickHandler={() => close(null, null)} disabled={false} />
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
</div>
|
||||
</Modal>);
|
||||
|
||||
}
|
||||
|
||||
const autocompleteSX =
|
||||
{
|
||||
"& .MuiAutocomplete-input": {padding: "0.125rem 0.5rem !important"},
|
||||
"& .MuiOutlinedInput-root": {borderRadius: "0.75rem !important"}
|
||||
};
|
||||
|
||||
const iconButtonSX =
|
||||
{
|
||||
border: `1px solid ${colors.grayLines.main} !important`,
|
||||
borderRadius: "0.75rem",
|
||||
textTransform: "none",
|
||||
fontSize: "1rem",
|
||||
fontWeight: "400",
|
||||
width: "40px",
|
||||
minWidth: "40px",
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
color: colors.secondary.main,
|
||||
"&:hover": {color: colors.secondary.main},
|
||||
"&:focus": {color: colors.secondary.main},
|
||||
"&:focus:not(:hover)": {color: colors.secondary.main},
|
||||
};
|
||||
|
||||
const redIconButton =
|
||||
{
|
||||
color: colors.error.main,
|
||||
"&:hover": {color: colors.error.main},
|
||||
"&:focus": {color: colors.error.main},
|
||||
"&:focus:not(:hover)": {color: colors.error.main},
|
||||
};
|
||||
|
@ -49,11 +49,13 @@ import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||
import QContext from "QContext";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import AuditBody from "qqq/components/audits/AuditBody";
|
||||
import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton} from "qqq/components/buttons/DefaultButtons";
|
||||
import {QActionsMenuButton, QCancelButton, QDeleteButton, QEditButton, standardWidth} from "qqq/components/buttons/DefaultButtons";
|
||||
import EntityForm from "qqq/components/forms/EntityForm";
|
||||
import MDButton from "qqq/components/legacy/MDButton";
|
||||
import {GotoRecordButton} from "qqq/components/misc/GotoRecordDialog";
|
||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
||||
import ShareModal from "qqq/components/sharing/ShareModal";
|
||||
import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
|
||||
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||
@ -82,6 +84,10 @@ RecordView.defaultProps =
|
||||
|
||||
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Record View Screen component.
|
||||
*******************************************************************************/
|
||||
function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
const {id} = useParams();
|
||||
@ -117,6 +123,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const [launchingProcess, setLaunchingProcess] = useState(launchProcess);
|
||||
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
|
||||
const [showAudit, setShowAudit] = useState(false);
|
||||
const [showShareModal, setShowShareModal] = useState(false);
|
||||
|
||||
const [isDeleteSubmitting, setIsDeleteSubmitting] = useState(false);
|
||||
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
const closeActionsMenu = () => setActionsMenu(null);
|
||||
@ -622,6 +631,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const handleClickDeleteButton = () =>
|
||||
{
|
||||
setDeleteConfirmationOpen(true);
|
||||
setIsDeleteSubmitting(false);
|
||||
};
|
||||
|
||||
const handleDeleteConfirmClose = () =>
|
||||
@ -631,6 +641,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
const handleDelete = (event: { preventDefault: () => void }) =>
|
||||
{
|
||||
setIsDeleteSubmitting(true);
|
||||
event?.preventDefault();
|
||||
(async () =>
|
||||
{
|
||||
@ -639,11 +650,13 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
await qController.delete(tableName, id)
|
||||
.then(() =>
|
||||
{
|
||||
setIsDeleteSubmitting(false);
|
||||
const path = pathParts.slice(0, -1).join("/");
|
||||
navigate(path, {state: {deleteSuccess: true}});
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
setIsDeleteSubmitting(false);
|
||||
setDeleteConfirmationOpen(false);
|
||||
console.log("Caught:");
|
||||
console.log(error);
|
||||
@ -759,6 +772,44 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
</Menu>
|
||||
);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** function to open the sharing modal
|
||||
*******************************************************************************/
|
||||
const openShareModal = () =>
|
||||
{
|
||||
setShowShareModal(true);
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** function to close the sharing modal
|
||||
*******************************************************************************/
|
||||
const closeShareModal = () =>
|
||||
{
|
||||
setShowShareModal(false);
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** render the share button (if allowed for table)
|
||||
*******************************************************************************/
|
||||
const renderShareButton = () =>
|
||||
{
|
||||
if (tableMetaData && (tableMetaData.name == "savedReport" || tableMetaData.name == "savedView")) // todo - not just based on name
|
||||
{
|
||||
const shareDisabled = false; // todo - only share if you're the owner? or do that in the modal?
|
||||
return (<Box width={standardWidth} mr={3}>
|
||||
<MDButton id="shareButton" type="button" color="info" size="small" onClick={() => openShareModal()} fullWidth startIcon={<Icon>share</Icon>} disabled={shareDisabled}>
|
||||
Share
|
||||
</MDButton>
|
||||
</Box>);
|
||||
}
|
||||
|
||||
return (<React.Fragment />);
|
||||
};
|
||||
|
||||
|
||||
const openModalProcess = (process: QProcessMetaData = null) =>
|
||||
{
|
||||
navigate(process.name);
|
||||
@ -914,6 +965,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
</Typography>
|
||||
<Box display="flex">
|
||||
<GotoRecordButton metaData={metaData} tableMetaData={tableMetaData} />
|
||||
{renderShareButton()}
|
||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||
</Box>
|
||||
{renderActionsMenu}
|
||||
@ -962,7 +1014,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
||||
<Button onClick={handleDelete} autoFocus>
|
||||
<Button onClick={handleDelete} autoFocus disabled={isDeleteSubmitting}>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@ -1010,6 +1062,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
</Modal>
|
||||
}
|
||||
|
||||
{
|
||||
showShareModal && tableMetaData && record &&
|
||||
<ShareModal open={showShareModal} onClose={closeShareModal} tableMetaData={tableMetaData} record={record}></ShareModal>
|
||||
}
|
||||
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
|
@ -261,7 +261,9 @@ public class QueryScreenLib
|
||||
if(StringUtils.hasContent(value))
|
||||
{
|
||||
qSeleniumLib.waitForSelector(".filterValuesColumn INPUT").click();
|
||||
qSeleniumLib.waitForSelector(".filterValuesColumn INPUT").sendKeys(value);
|
||||
// todo - no, not in a listbox/LI here...
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiAutocomplete-listbox LI", value).click();
|
||||
System.out.println(value);
|
||||
}
|
||||
|
||||
qSeleniumLib.clickBackdrop();
|
||||
@ -269,21 +271,6 @@ public class QueryScreenLib
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBasicBooleanFilter(String fieldLabel, String operatorLabel)
|
||||
{
|
||||
qSeleniumLib.waitForSelectorContaining("BUTTON", fieldLabel).click();
|
||||
qSeleniumLib.waitForMillis(250);
|
||||
qSeleniumLib.waitForSelector("#criteriaOperator").click();
|
||||
qSeleniumLib.waitForSelectorContaining("LI", operatorLabel).click();
|
||||
|
||||
qSeleniumLib.clickBackdrop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -1,172 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.frontend.materialdashboard.selenium.tests;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QBaseSeleniumTest;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QSeleniumLib;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.QueryScreenLib;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.selenium.lib.javalin.QSeleniumJavalin;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Keys;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test for Saved Report screen (table has some special behaviors)
|
||||
*******************************************************************************/
|
||||
public class SavedReportTest extends QBaseSeleniumTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
|
||||
{
|
||||
super.addJavalinRoutes(qSeleniumJavalin);
|
||||
qSeleniumJavalin
|
||||
.withRouteToFile("/metaData/table/savedReport", "metaData/table/savedReport.json")
|
||||
.withRouteToFile("/widget/reportSetupWidget", "widget/reportSetupWidget.json")
|
||||
.withRouteToFile("/widget/pivotTableSetupWidget", "widget/pivotTableSetupWidget.json")
|
||||
.withRouteToFile("/data/savedReport/possibleValues/tableName", "data/savedReport/possibleValues/tableName.json")
|
||||
|
||||
.withRouteToFile("/data/person/count", "data/person/count.json")
|
||||
.withRouteToFile("/data/person/query", "data/person/index.json")
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testCreate()
|
||||
{
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeaderToContain("/userCustomizations/savedReport/create", "Creating New Saved Report");
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// make sure things are disabled before a table is selected //
|
||||
//////////////////////////////////////////////////////////////
|
||||
WebElement webElement = qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns");
|
||||
assertEquals("true", webElement.getAttribute("disabled"));
|
||||
|
||||
qSeleniumLib.waitForSelector("#label").click();
|
||||
qSeleniumLib.waitForSelector("#label").sendKeys("My Report");
|
||||
|
||||
qSeleniumLib.waitForSelector("#tableName").click();
|
||||
qSeleniumLib.waitForSelector("#tableName").sendKeys("Person" + Keys.DOWN + Keys.ENTER);
|
||||
|
||||
//////////////////////////////////
|
||||
// make sure things enabled now //
|
||||
//////////////////////////////////
|
||||
webElement = qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns");
|
||||
assertNull(webElement.getAttribute("disabled"));
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// open query-screen popup, wait for query to run //
|
||||
////////////////////////////////////////////////////
|
||||
qSeleniumJavalin.beginCapture();
|
||||
qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns").click();
|
||||
qSeleniumJavalin.waitForCapturedPath("/data/person/count");
|
||||
qSeleniumJavalin.waitForCapturedPath("/data/person/query");
|
||||
qSeleniumJavalin.endCapture();
|
||||
|
||||
QueryScreenLib queryScreenLib = new QueryScreenLib(qSeleniumLib);
|
||||
queryScreenLib.setBasicFilter("First Name", "contains", "Darin");
|
||||
|
||||
////////////////////////
|
||||
// close query screen //
|
||||
////////////////////////
|
||||
qSeleniumLib.waitForSelectorContaining("button", "OK").click();
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// make sure query things appear on edit screen now //
|
||||
//////////////////////////////////////////////////////
|
||||
qSeleniumLib.waitForSelectorContaining(".advancedQueryString", "First Name");
|
||||
qSeleniumLib.waitForSelectorContaining(".advancedQueryString", "contains");
|
||||
qSeleniumLib.waitForSelectorContaining(".advancedQueryString", "Darin");
|
||||
List<WebElement> columns = qSeleniumLib.waitForSelectorContaining("h5", "Columns")
|
||||
.findElement(QSeleniumLib.PARENT)
|
||||
.findElements(By.cssSelector("DIV"));
|
||||
|
||||
assertThat(columns)
|
||||
.hasSizeGreaterThanOrEqualTo(5) // at least this many
|
||||
.anyMatch(we -> we.getText().equals("Home City")); // a few fields are found
|
||||
|
||||
///////////////////
|
||||
// turn on pivot //
|
||||
///////////////////
|
||||
qSeleniumLib.waitForSelectorContaining("label", "Use Pivot Table").click();
|
||||
qSeleniumLib.waitForSelectorContaining("button", "Edit Pivot Table").click();
|
||||
qSeleniumLib.waitForSelectorContaining("h3", "Edit Pivot Table");
|
||||
|
||||
///////////////
|
||||
// add a row //
|
||||
///////////////
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiModal-root button", "Add new row").click();
|
||||
WebElement row0Input = qSeleniumLib.waitForSelector("#rows-0");
|
||||
row0Input.click();
|
||||
row0Input.sendKeys("Last Name" + Keys.ENTER);
|
||||
|
||||
//////////////////
|
||||
// add a column //
|
||||
//////////////////
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiModal-root button", "Add new column").click();
|
||||
WebElement column0Input = qSeleniumLib.waitForSelector("#columns-0");
|
||||
column0Input.click();
|
||||
column0Input.sendKeys("Home City" + Keys.ENTER);
|
||||
|
||||
/////////////////
|
||||
// add a value //
|
||||
/////////////////
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiModal-root button", "Add new value").click();
|
||||
WebElement value0Input = qSeleniumLib.waitForSelector("#values-field-0");
|
||||
value0Input.click();
|
||||
value0Input.sendKeys("Id" + Keys.ENTER);
|
||||
|
||||
/////////////////////////////////////////
|
||||
// try to submit - but expect an error //
|
||||
/////////////////////////////////////////
|
||||
qSeleniumLib.waitForSelectorContaining("button", "OK").click();
|
||||
qSeleniumLib.waitForSelectorContaining(".MuiAlert-standard", "Missing value in 1 field.").click();
|
||||
|
||||
///////////////////////////
|
||||
// now select a function //
|
||||
///////////////////////////
|
||||
WebElement function0Input = qSeleniumLib.waitForSelector("#values-function-0");
|
||||
function0Input.click();
|
||||
function0Input.sendKeys("Count" + Keys.ENTER);
|
||||
|
||||
qSeleniumLib.waitForSelectorContaining("button", "OK").click();
|
||||
qSeleniumLib.waitForSelectorContainingToNotExist("h3", "Edit Pivot Table");
|
||||
|
||||
// qSeleniumLib.waitForever();
|
||||
}
|
||||
|
||||
}
|
@ -153,16 +153,16 @@ public class QueryScreenTest extends QBaseSeleniumTest
|
||||
|
||||
queryScreenLib.addBasicFilter("Is Employed");
|
||||
|
||||
testBasicBooleanCriteria(queryScreenLib, "Is Employed", "equals yes", "(?s).*Is Employed:.*yes.*", """
|
||||
testBasicCriteria(queryScreenLib, "Is Employed", "equals yes", null, "(?s).*Is Employed:.*yes.*", """
|
||||
{"fieldName":"isEmployed","operator":"EQUALS","values":[true]}""");
|
||||
|
||||
testBasicBooleanCriteria(queryScreenLib, "Is Employed", "equals no", "(?s).*Is Employed:.*no.*", """
|
||||
testBasicCriteria(queryScreenLib, "Is Employed", "equals no", null, "(?s).*Is Employed:.*no.*", """
|
||||
{"fieldName":"isEmployed","operator":"EQUALS","values":[false]}""");
|
||||
|
||||
testBasicBooleanCriteria(queryScreenLib, "Is Employed", "is empty", "(?s).*Is Employed:.*is empty.*", """
|
||||
testBasicCriteria(queryScreenLib, "Is Employed", "is empty", null, "(?s).*Is Employed:.*is empty.*", """
|
||||
{"fieldName":"isEmployed","operator":"IS_BLANK","values":[]}""");
|
||||
|
||||
testBasicBooleanCriteria(queryScreenLib, "Is Employed", "is not empty", "(?s).*Is Employed:.*is not empty.*", """
|
||||
testBasicCriteria(queryScreenLib, "Is Employed", "is not empty", null, "(?s).*Is Employed:.*is not empty.*", """
|
||||
{"fieldName":"isEmployed","operator":"IS_NOT_BLANK","values":[]}""");
|
||||
}
|
||||
|
||||
@ -203,10 +203,10 @@ public class QueryScreenTest extends QBaseSeleniumTest
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void testBasicBooleanCriteria(QueryScreenLib queryScreenLib, String fieldLabel, String operatorLabel, String expectButtonStringRegex, String expectFilterJsonContains)
|
||||
private void testBasicCriteria(QueryScreenLib queryScreenLib, String fieldLabel, String operatorLabel, String value, String expectButtonStringRegex, String expectFilterJsonContains)
|
||||
{
|
||||
qSeleniumJavalin.beginCapture();
|
||||
queryScreenLib.setBasicBooleanFilter(fieldLabel, operatorLabel);
|
||||
queryScreenLib.setBasicFilter(fieldLabel, operatorLabel, value);
|
||||
queryScreenLib.waitForBasicFilterButtonMatchingRegex(expectButtonStringRegex);
|
||||
qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectFilterJsonContains);
|
||||
qSeleniumJavalin.endCapture();
|
||||
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"id": "person",
|
||||
"label": "Person"
|
||||
},
|
||||
{
|
||||
"id": "city",
|
||||
"label": "City"
|
||||
},
|
||||
{
|
||||
"id": "savedReport",
|
||||
"label": "Saved Report"
|
||||
}
|
||||
]
|
||||
}
|
@ -189,26 +189,6 @@
|
||||
"insertPermission": true,
|
||||
"editPermission": true,
|
||||
"deletePermission": true
|
||||
},
|
||||
"savedReport": {
|
||||
"name": "savedReport",
|
||||
"label": "Saved Report",
|
||||
"isHidden": false,
|
||||
"iconName": "article",
|
||||
"capabilities": [
|
||||
"TABLE_COUNT",
|
||||
"TABLE_GET",
|
||||
"TABLE_QUERY",
|
||||
"QUERY_STATS",
|
||||
"TABLE_UPDATE",
|
||||
"TABLE_INSERT",
|
||||
"TABLE_DELETE"
|
||||
],
|
||||
"readPermission": true,
|
||||
"insertPermission": true,
|
||||
"editPermission": true,
|
||||
"deletePermission": true,
|
||||
"usesVariants": false
|
||||
}
|
||||
},
|
||||
"processes": {
|
||||
@ -440,40 +420,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"userCustomizations": {
|
||||
"name": "userCustomizations",
|
||||
"label": "User Customizations",
|
||||
"iconName": "article",
|
||||
"widgets": [],
|
||||
"children": [
|
||||
{
|
||||
"type": "TABLE",
|
||||
"name": "savedReport",
|
||||
"label": "Saved Report",
|
||||
"iconName": "article"
|
||||
}
|
||||
],
|
||||
"childMap": {
|
||||
"savedReport": {
|
||||
"type": "TABLE",
|
||||
"name": "savedReport",
|
||||
"label": "Saved Report",
|
||||
"iconName": "article"
|
||||
}
|
||||
},
|
||||
"sections": [
|
||||
{
|
||||
"name": "userCustomizations",
|
||||
"label": "User Customizations",
|
||||
"icon": {
|
||||
"name": "badge"
|
||||
},
|
||||
"tables": [
|
||||
"savedReport"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"miscellaneous": {
|
||||
"name": "miscellaneous",
|
||||
"label": "Miscellaneous",
|
||||
@ -784,20 +730,6 @@
|
||||
}
|
||||
],
|
||||
"iconName": "data_object"
|
||||
},
|
||||
{
|
||||
"type": "APP",
|
||||
"name": "userCustomizations",
|
||||
"label": "User Customizations",
|
||||
"children": [
|
||||
{
|
||||
"type": "TABLE",
|
||||
"name": "savedReport",
|
||||
"label": "Saved Report",
|
||||
"iconName": "article"
|
||||
}
|
||||
],
|
||||
"iconName": "data_object"
|
||||
}
|
||||
],
|
||||
"branding": {
|
||||
@ -818,28 +750,6 @@
|
||||
"isCard": true,
|
||||
"storeDropdownSelections": false,
|
||||
"hasPermission": true
|
||||
},
|
||||
"reportSetupWidget": {
|
||||
"name": "reportSetupWidget",
|
||||
"label": "Filters and Columns",
|
||||
"type": "reportSetup",
|
||||
"isCard": true,
|
||||
"storeDropdownSelections": false,
|
||||
"showReloadButton": true,
|
||||
"showExportButton": false,
|
||||
"defaultValues": {},
|
||||
"hasPermission": true
|
||||
},
|
||||
"pivotTableSetupWidget": {
|
||||
"name": "pivotTableSetupWidget",
|
||||
"label": "Pivot Table",
|
||||
"type": "pivotTableSetup",
|
||||
"isCard": true,
|
||||
"storeDropdownSelections": false,
|
||||
"showReloadButton": true,
|
||||
"showExportButton": false,
|
||||
"defaultValues": {},
|
||||
"hasPermission": true
|
||||
}
|
||||
},
|
||||
"environmentValues": {
|
||||
|
@ -1,218 +0,0 @@
|
||||
{
|
||||
"table": {
|
||||
"name": "savedReport",
|
||||
"label": "Saved Report",
|
||||
"isHidden": false,
|
||||
"primaryKeyField": "id",
|
||||
"iconName": "article",
|
||||
"fields": {
|
||||
"queryFilterJson": {
|
||||
"name": "queryFilterJson",
|
||||
"label": "Query Filter",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"columnsJson": {
|
||||
"name": "columnsJson",
|
||||
"label": "Columns",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"inputFieldsJson": {
|
||||
"name": "inputFieldsJson",
|
||||
"label": "Input Fields",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"pivotTableJson": {
|
||||
"name": "pivotTableJson",
|
||||
"label": "Pivot Table",
|
||||
"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"
|
||||
},
|
||||
"label": {
|
||||
"name": "label",
|
||||
"label": "Report Name",
|
||||
"type": "STRING",
|
||||
"isRequired": true,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"label": "Id",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": false,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"label": "User Id",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"tableName": {
|
||||
"name": "tableName",
|
||||
"label": "Table",
|
||||
"type": "STRING",
|
||||
"isRequired": true,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"possibleValueSourceName": "tables",
|
||||
"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",
|
||||
"label",
|
||||
"tableName"
|
||||
],
|
||||
"icon": {
|
||||
"name": "badge"
|
||||
},
|
||||
"isHidden": false
|
||||
},
|
||||
{
|
||||
"name": "filtersAndColumns",
|
||||
"label": "Filters and Columns",
|
||||
"tier": "T2",
|
||||
"widgetName": "reportSetupWidget",
|
||||
"icon": {
|
||||
"name": "table_chart"
|
||||
},
|
||||
"isHidden": false
|
||||
},
|
||||
{
|
||||
"name": "pivotTable",
|
||||
"label": "Pivot Table",
|
||||
"tier": "T2",
|
||||
"widgetName": "pivotTableSetupWidget",
|
||||
"icon": {
|
||||
"name": "pivot_table_chart"
|
||||
},
|
||||
"isHidden": false
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"label": "Data",
|
||||
"tier": "T2",
|
||||
"fieldNames": [
|
||||
"queryFilterJson",
|
||||
"columnsJson",
|
||||
"pivotTableJson"
|
||||
],
|
||||
"icon": {
|
||||
"name": "text_snippet"
|
||||
},
|
||||
"isHidden": true
|
||||
},
|
||||
{
|
||||
"name": "hidden",
|
||||
"label": "Hidden",
|
||||
"tier": "T2",
|
||||
"fieldNames": [
|
||||
"inputFieldsJson",
|
||||
"userId"
|
||||
],
|
||||
"icon": {
|
||||
"name": "text_snippet"
|
||||
},
|
||||
"isHidden": true
|
||||
},
|
||||
{
|
||||
"name": "dates",
|
||||
"label": "Dates",
|
||||
"tier": "T3",
|
||||
"fieldNames": [
|
||||
"createDate",
|
||||
"modifyDate"
|
||||
],
|
||||
"icon": {
|
||||
"name": "calendar_month"
|
||||
},
|
||||
"isHidden": false
|
||||
}
|
||||
],
|
||||
"exposedJoins": [],
|
||||
"supplementalTableMetaData": {
|
||||
"materialDashboard": {
|
||||
"fieldRules": [
|
||||
{
|
||||
"trigger": "ON_CHANGE",
|
||||
"sourceField": "tableName",
|
||||
"action": "CLEAR_TARGET_FIELD",
|
||||
"targetField": "queryFilterJson"
|
||||
},
|
||||
{
|
||||
"trigger": "ON_CHANGE",
|
||||
"sourceField": "tableName",
|
||||
"action": "CLEAR_TARGET_FIELD",
|
||||
"targetField": "columnsJson"
|
||||
},
|
||||
{
|
||||
"trigger": "ON_CHANGE",
|
||||
"sourceField": "tableName",
|
||||
"action": "CLEAR_TARGET_FIELD",
|
||||
"targetField": "pivotTableJson"
|
||||
}
|
||||
],
|
||||
"type": "materialDashboard"
|
||||
}
|
||||
},
|
||||
"capabilities": [
|
||||
"TABLE_COUNT",
|
||||
"TABLE_GET",
|
||||
"TABLE_QUERY",
|
||||
"QUERY_STATS",
|
||||
"TABLE_UPDATE",
|
||||
"TABLE_INSERT",
|
||||
"TABLE_DELETE"
|
||||
],
|
||||
"readPermission": true,
|
||||
"insertPermission": true,
|
||||
"editPermission": true,
|
||||
"deletePermission": true,
|
||||
"usesVariants": false
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"type":"pivotTableSetup"}
|
@ -1 +0,0 @@
|
||||
{"type":"reportSetup"}
|
Reference in New Issue
Block a user