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);

Reply via email to