This is an automated email from the ASF dual-hosted git repository.
mehul pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/master by this push:
new d14d03709 RANGER-4016 : Add row draggable feature for policy Item
table in policy form in react UI.
d14d03709 is described below
commit d14d037098bfd69888b64e3f217a1e12103b57c3
Author: Brijesh Bhalala <[email protected]>
AuthorDate: Mon Jan 23 19:12:29 2023 +0530
RANGER-4016 : Add row draggable feature for policy Item table in policy
form in react UI.
Signed-off-by: Mehul Parikh <[email protected]>
---
.../main/webapp/react-webapp/src/styles/style.css | 12 ++
.../webapp/react-webapp/src/utils/XAMessages.js | 10 +-
.../views/PolicyListing/AddUpdatePolicyForm.jsx | 133 ++++++++++++-----
.../views/PolicyListing/PolicyPermissionItem.jsx | 164 +++++++++++----------
.../src/views/Resources/ResourceComp.jsx | 2 +-
.../users_details/UserFormComp.jsx | 2 +-
6 files changed, 200 insertions(+), 123 deletions(-)
diff --git a/security-admin/src/main/webapp/react-webapp/src/styles/style.css
b/security-admin/src/main/webapp/react-webapp/src/styles/style.css
index 77ecb7105..6ffd55dfc 100644
--- a/security-admin/src/main/webapp/react-webapp/src/styles/style.css
+++ b/security-admin/src/main/webapp/react-webapp/src/styles/style.css
@@ -1873,3 +1873,15 @@ header {
position: absolute;
width: 130px;
}
+
+.error-highlighting {
+ border: 3px solid rgb(255, 0, 0, 0.6);
+ padding: 2px 3px;
+}
+
+.row-reorder-policyitems {
+ border: 2px dotted rgba(0, 0, 0, 0.3);
+ margin-right: 6px;
+ cursor: move;
+ width: 6px;
+}
diff --git
a/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
b/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
index 9dbea93e6..6570fb7f9 100644
--- a/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
+++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
@@ -82,15 +82,15 @@ export const RegexMessage = {
/* External User Edit Role Change */
-export const roleChngWarning = {
- roleChng: (
+export const roleChngWarning = (user) => {
+ return (
<>
- <b>Warning !! </b> : Please make sure that accumulo user's role change
- performed here is consistent with ranger.usersync.
+ <strong>Warning !! </strong> : Please make sure that {`${user}`} user's
+ role change performed here is consistent with ranger.usersync.
group.based.role.assignment.rules property in ranger usersync
configuration.
</>
- )
+ );
};
/* policyInfo Message */
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
index c06b14021..1b163a724 100644
---
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
+++
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/AddUpdatePolicyForm.jsx
@@ -45,7 +45,9 @@ import _, {
pick,
isObject,
isArray,
- isEqual
+ isEqual,
+ forIn,
+ has
} from "lodash";
import { toast } from "react-toastify";
import { Loader, scrollToError } from "Components/CommonComponents";
@@ -127,6 +129,7 @@ export default function AddUpdatePolicyForm(props) {
const [showPolicyExpire, setShowPolicyExpire] = useState(true);
const [showDelete, setShowDelete] = useState(false);
const [blockUI, setBlockUI] = useState(false);
+ const toastId = React.useRef(null);
// usePrompt("Leave screen?", true);
useEffect(() => {
@@ -310,7 +313,7 @@ export default function AddUpdatePolicyForm(props) {
const generateFormData = (policyData, serviceCompData) => {
let data = {};
- data.policyType = policyId ? policyData.policyType : policyType;
+ data.policyType = policyId ? policyData?.policyType : policyType;
data.policyItems =
policyId && policyData?.policyItems?.length > 0
? setPolicyItemVal(
@@ -524,14 +527,14 @@ export default function AddUpdatePolicyForm(props) {
}
if (
!isEmpty(obj) &&
- !_.isEmpty(obj?.delegateAdmin) &&
+ !isEmpty(obj?.delegateAdmin) &&
Object.keys(obj)?.length > 1
) {
policyResourceItem.push(obj);
}
if (
- !_.isEmpty(obj) &&
- _.isEmpty(obj?.delegateAdmin) &&
+ !isEmpty(obj) &&
+ isEmpty(obj?.delegateAdmin) &&
Object.keys(obj)?.length > 1
) {
policyResourceItem.push(obj);
@@ -764,8 +767,9 @@ export default function AddUpdatePolicyForm(props) {
data: dataVal
});
setBlockUI(false);
+ toast.dismiss(toastId.current);
+ toastId.current = toast.success("Policy updated successfully!!");
navigate(`/service/${serviceId}/policies/${policyData.policyType}`);
- toast.success("Policy updated successfully!!");
} catch (error) {
setBlockUI(false);
let errorMsg = `Failed to save policy form`;
@@ -785,7 +789,8 @@ export default function AddUpdatePolicyForm(props) {
});
setBlockUI(false);
- toast.success("Policy save successfully!!");
+ toast.dismiss(toastId.current);
+ toastId.current = toast.success("Policy save successfully!!");
navigate(`/service/${serviceId}/policies/${policyType}`, {
state: {
showLastPage: true,
@@ -814,7 +819,8 @@ export default function AddUpdatePolicyForm(props) {
method: "DELETE"
});
setBlockUI(false);
- toast.success(" Success! Policy deleted successfully");
+ toast.dismiss(toastId.current);
+ toastId.current = toast.success(" Success! Policy deleted successfully");
navigate(`/service/${serviceId}/policies/${policyType}`);
} catch (error) {
setBlockUI(false);
@@ -908,6 +914,44 @@ export default function AddUpdatePolicyForm(props) {
);
};
+ const getValidatePolicyItems = (errors) => {
+ let errorField;
+ errors?.find((value) => {
+ if (value !== undefined) {
+ return (errorField = value);
+ }
+ });
+
+ return errorField !== undefined && errorField?.accesses
+ ? toast.error(errorField?.accesses, { toastId: "error1" })
+ : toast.error(errorField?.delegateAdmin, { toastId: "error1" });
+ };
+ const resourceErrorCheck = (errors, values) => {
+ let serviceCompResourcesDetails;
+ if (
+ RangerPolicyType.RANGER_MASKING_POLICY_TYPE.value == values.policyType
+ ) {
+ serviceCompResourcesDetails = serviceCompDetails.dataMaskDef.resources;
+ } else if (
+ RangerPolicyType.RANGER_ROW_FILTER_POLICY_TYPE.value == values.policyType
+ ) {
+ serviceCompResourcesDetails = serviceCompDetails.rowFilterDef.resources;
+ } else {
+ serviceCompResourcesDetails = serviceCompDetails.resources;
+ }
+
+ const grpResources = groupBy(serviceCompResourcesDetails || [], "level");
+ let grpResourcesKeys = [];
+ for (const resourceKey in grpResources) {
+ grpResourcesKeys.push(+resourceKey);
+ }
+ grpResourcesKeys = grpResourcesKeys.sort();
+ for (const key of grpResourcesKeys) {
+ if (errors[`value-${key}`] !== undefined) {
+ return true;
+ }
+ }
+ };
return (
<>
{loader ? (
@@ -931,6 +975,7 @@ export default function AddUpdatePolicyForm(props) {
text: "Required"
};
}
+
return errors;
}}
render={({
@@ -941,7 +986,7 @@ export default function AddUpdatePolicyForm(props) {
errors,
dirty,
form: {
- mutators: { push: addPolicyItem, pop: removePolicyItem }
+ mutators: { push: addPolicyItem, pop: removePolicyItem, move
}
},
form,
dirtyFields,
@@ -965,17 +1010,24 @@ export default function AddUpdatePolicyForm(props) {
<form
onSubmit={(event) => {
if (invalid) {
- let selector =
- document.getElementById("isError") ||
- document.getElementById(Object.keys(errors)[0]) ||
- document.querySelector(
- `input[name=${Object.keys(errors)[0]}]`
- ) ||
- document.querySelector(
- `input[id=${Object.keys(errors)[0]}]`
- ) ||
-
document.querySelector(`span[class="invalid-field"]`);
- scrollToError(selector);
+ forIn(errors, function (value, key) {
+ if (
+ has(errors, "policyName") ||
+ resourceErrorCheck(errors, values)
+ ) {
+ let selector =
+ document.getElementById("isError") ||
+ document.getElementById(key) ||
+ document.querySelector(`input[name=${key}]`) ||
+ document.querySelector(`input[id=${key}]`) ||
+ document.querySelector(
+ `span[class="invalid-field"]`
+ );
+ scrollToError(selector);
+ } else {
+ getValidatePolicyItems(errors?.[key]);
+ }
+ });
}
handleSubmit(event);
}}
@@ -1552,25 +1604,32 @@ export default function AddUpdatePolicyForm(props) {
<div className="row form-actions">
<div className="col-md-9 offset-md-3">
<Button
- onClick={() => {
+ onClick={(event) => {
if (invalid) {
- let selector =
- document.getElementById("isError") ||
- document.getElementById(
- Object.keys(errors)[0]
- ) ||
- document.querySelector(
- `input[name=${Object.keys(errors)[0]}]`
- ) ||
- document.querySelector(
- `input[id=${Object.keys(errors)[0]}]`
- ) ||
- document.querySelector(
- `span[class="invalid-field"]`
- );
- scrollToError(selector);
+ forIn(errors, function (value, key) {
+ if (
+ has(errors, "policyName") ||
+ resourceErrorCheck(errors, values)
+ ) {
+ let selector =
+ document.getElementById("isError") ||
+ document.getElementById(key) ||
+ document.querySelector(
+ `input[name=${key}]`
+ ) ||
+ document.querySelector(
+ `input[id=${key}]`
+ ) ||
+ document.querySelector(
+ `span[class="invalid-field"]`
+ );
+ scrollToError(selector);
+ } else {
+ getValidatePolicyItems(errors?.[key]);
+ }
+ });
}
- handleSubmit(values, invalid);
+ handleSubmit(event);
}}
variant="primary"
size="sm"
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
index ac01efd1d..c088a95ef 100644
---
a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
+++
b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyPermissionItem.jsx
@@ -17,13 +17,13 @@
* under the License.
*/
-import React, { useMemo } from "react";
+import React, { useMemo, useRef } from "react";
import { Table, Button, Badge, Form } from "react-bootstrap";
import { FieldArray } from "react-final-form-arrays";
import { Col } from "react-bootstrap";
-import { Field } from "react-final-form";
+import { Field, useFormState } from "react-final-form";
import AsyncSelect from "react-select/async";
-import { find, groupBy, isEmpty, isArray } from "lodash";
+import { find, groupBy, isEmpty, isArray, has } from "lodash";
import { toast } from "react-toastify";
import Editable from "Components/Editable";
@@ -43,8 +43,12 @@ export default function PolicyPermissionItem(props) {
fetchUsersData,
fetchGroupsData,
fetchRolesData,
- formValues
+ formValues,
+ form
} = props;
+ const dragItem = useRef();
+ const dragOverItem = useRef();
+ let { values, errors, change, error, ...args } = useFormState();
const permList = ["Select Roles", "Select Groups", "Select Users"];
if (serviceCompDetails?.policyConditions?.length > 0) {
@@ -155,7 +159,7 @@ export default function PolicyPermissionItem(props) {
}
}
};
-
+ const required = (value) => (value ? undefined : "Required");
const requiredForPermission = (fieldVals, index) => {
if (fieldVals && !isEmpty(fieldVals[index])) {
let error, accTypes;
@@ -198,19 +202,20 @@ export default function PolicyPermissionItem(props) {
};
const requiredForDeleGateAdmin = (fieldVals, index) => {
if (
- !_.isEmpty(fieldVals[index]) &&
- _.has(fieldVals[index], "delegateAdmin")
+ !isEmpty(fieldVals?.[index]) &&
+ has(fieldVals?.[index], "delegateAdmin")
) {
- let error;
+ let delError;
let users = (fieldVals[index]?.users || []).length > 0;
let grps = (fieldVals[index]?.groups || []).length > 0;
let roles = (fieldVals[index]?.roles || []).length > 0;
let delegateAdmin = fieldVals[index]?.delegateAdmin;
if (delegateAdmin && !users && !grps && !roles) {
- error = "Please select user/group/role for the selected permission(s)";
+ delError =
+ "Please select user/group/role for the selected delegate Admin";
}
- return error;
+ return delError;
}
};
@@ -226,7 +231,6 @@ export default function PolicyPermissionItem(props) {
});
};
- const required = (value) => (value ? undefined : "Required");
const customStyles = {
control: (provided) => ({
...provided,
@@ -238,11 +242,44 @@ export default function PolicyPermissionItem(props) {
textOverflow: "ellipsis"
})
};
+
+ const dragStart = (e, position) => {
+ e.target.style.opacity = 0.4;
+ e.target.style.backgroundColor = "#fdf1a6";
+ e.stopPropagation();
+ dragItem.current = position;
+ };
+
+ const dragEnter = (e, position) => {
+ dragOverItem.current = position;
+ };
+
+ const dragOver = (e) => {
+ e.preventDefault();
+ };
+
+ const drop = (e, fields) => {
+ e.target.style.opacity = 1;
+ e.target.style.backgroundColor = "white";
+ if (dragItem.current == dragOverItem.current) {
+ return;
+ }
+
+ fields.move(dragItem.current, dragOverItem.current);
+
+ dragItem.current = null;
+ dragOverItem.current = null;
+ };
+
return (
<div>
<Col sm="12">
<div className="table-responsive">
- <Table bordered className="policy-permission-table">
+ <Table
+ bordered
+ className="policy-permission-table"
+ id={`${attrName}-table`}
+ >
<thead className="thead-light">
<tr>
{tableHeader()}
@@ -253,7 +290,15 @@ export default function PolicyPermissionItem(props) {
<FieldArray name={attrName}>
{({ fields }) =>
fields.map((name, index) => (
- <tr key={name}>
+ <tr
+ key={name}
+ onDragStart={(e) => dragStart(e, index)}
+ onDragEnter={(e) => dragEnter(e, index)}
+ onDragEnd={(e) => drop(e, fields)}
+ onDragOver={(e) => dragOver(e)}
+ draggable
+ id={index}
+ >
{permList.map((colName) => {
if (colName == "Select Roles") {
return (
@@ -262,7 +307,8 @@ export default function PolicyPermissionItem(props) {
className="form-control"
name={`${name}.roles`}
render={({ input, meta }) => (
- <div>
+ <div className="d-flex">
+ <span className="row-reorder-policyitems"
/>
<AsyncSelect
{...input}
menuPortalTarget={document.body}
@@ -272,9 +318,6 @@ export default function PolicyPermissionItem(props) {
cacheOptions
isMulti
/>
- {meta.touched && meta.error && (
- <span>{meta.error}</span>
- )}
</div>
)}
/>
@@ -298,9 +341,6 @@ export default function PolicyPermissionItem(props) {
cacheOptions
isMulti
/>
- {meta.touched && meta.error && (
- <span>{meta.error}</span>
- )}
</div>
)}
/>
@@ -324,9 +364,6 @@ export default function PolicyPermissionItem(props) {
cacheOptions
isMulti
/>
- {meta.touched && meta.error && (
- <span>{meta.error}</span>
- )}
</div>
)}
/>
@@ -352,11 +389,6 @@ export default function PolicyPermissionItem(props) {
servicedefName={serviceCompDetails.name}
selectProps={{ isMulti: true }}
/>
- {meta.error && (
- <span className="invalid-field">
- <p> {meta.error}</p>
- </span>
- )}
</div>
)}
/>
@@ -376,12 +408,6 @@ export default function PolicyPermissionItem(props) {
serviceCompDetails.policyConditions
}
/>
-
- {meta.error && (
- <span className="invalid-field">
- <p> {meta.error}</p>
- </span>
- )}
</div>
)}
/>
@@ -423,14 +449,6 @@ export default function PolicyPermissionItem(props) {
dataMaskIndex={index}
serviceCompDetails={serviceCompDetails}
/>
- {meta.error && (
- <>
- <br />
- <span className="invalid-field">
- {meta.error}
- </span>
- </>
- )}
</div>
)}
/>
@@ -458,13 +476,6 @@ export default function PolicyPermissionItem(props) {
showSelectAll={true}
selectAllLabel="Select All"
/>
- {meta.error && (
- <>
- <span className="invalid-field">
- {meta.error}
- </span>
- </>
- )}
</div>
)}
/>
@@ -512,7 +523,6 @@ export default function PolicyPermissionItem(props) {
type="text"
{...input}
placeholder="Enter masked
value or expression..."
- // width="80%"
/>
{meta.error && (
<span
className="invalid-field">
@@ -623,35 +633,31 @@ export default function PolicyPermissionItem(props) {
serviceCompDetails?.name !== "tag"
) {
return (
- <td className="text-center align-middle">
- <Field
- className="form-control"
- name={`${name}.delegateAdmin`}
- validate={(value, formValues) =>
- requiredForDeleGateAdmin(
- formValues[attrName],
- index
- )
- }
- data-js="delegatedAdmin"
- data-cy="delegatedAdmin"
- type="checkbox"
- >
- {({ input, meta }) => (
- <div>
- <input {...input} type="checkbox" />
-
- {meta.error && (
- <>
- <br />
- <span className="invalid-field">
- {meta.error}
- </span>
- </>
- )}
- </div>
- )}
- </Field>
+ <td
+ key={`${name}-${index}`}
+ className="text-center align-middle"
+ >
+ <div key={`${name}-${index}`}>
+ <Field
+ className="form-control"
+ name={`${name}.delegateAdmin`}
+ validate={(value, formValues) =>
+ requiredForDeleGateAdmin(
+ formValues[attrName],
+ index
+ )
+ }
+ data-js="delegatedAdmin"
+ data-cy="delegatedAdmin"
+ type="checkbox"
+ >
+ {({ input, meta }) => (
+ <div>
+ <input {...input} type="checkbox" />
+ </div>
+ )}
+ </Field>
+ </div>
</td>
);
}
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx
index 3c83b509e..11b240c46 100644
---
a/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx
+++
b/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx
@@ -197,7 +197,7 @@ export default function ResourceComp(props) {
"denyExceptions"
]) {
for (const policyObj of formValues[name]) {
- if (policyObj.accesses) {
+ if (policyObj?.accesses) {
policyObj.accesses = [];
}
}
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserFormComp.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserFormComp.jsx
index ddbb7bfc2..835291403 100644
---
a/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserFormComp.jsx
+++
b/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserFormComp.jsx
@@ -298,7 +298,7 @@ function UserFormComp(props) {
e.label != input.value.label
) {
toast.dismiss(toastId.current);
- toastId.current = toast.warning(roleChngWarning.roleChng);
+ toastId.current = toast.warning(roleChngWarning(userInfo?.name));
}
input.onChange(e);