This is an automated email from the ASF dual-hosted git repository. dhavalshah9131 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ranger.git
commit f2dcff02a053c9d633770ae9ea08c7b702b591bb Author: Dhaval.Rajpara <[email protected]> AuthorDate: Tue Jun 27 16:04:39 2023 +0530 RANGER-4263 -LookupResource give blank response in new react UI --- .../AuditEvent/AdminLogs/PolicyViewDetails.jsx | 15 +- .../views/PolicyListing/AddUpdatePolicyForm.jsx | 4 +- .../src/views/Resources/ResourceComp.jsx | 158 ++------------- .../src/views/Resources/ResourceSelectComp.jsx | 215 +++++++++++++++++++++ 4 files changed, 246 insertions(+), 146 deletions(-) diff --git a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AdminLogs/PolicyViewDetails.jsx b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AdminLogs/PolicyViewDetails.jsx index e5c8bc399..d74f9a83e 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AdminLogs/PolicyViewDetails.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/AuditEvent/AdminLogs/PolicyViewDetails.jsx @@ -143,7 +143,8 @@ export function PolicyViewDetails(props) { updateTime, createdBy, createTime, - validitySchedules + validitySchedules, + zoneName } = access; const getPolicyDetails = () => { @@ -325,7 +326,7 @@ export function PolicyViewDetails(props) { {getPolicyResources(policyType, resources)} <tr> <td className="text-nowrap">Description</td> - <td>{description}</td> + <td>{!isEmpty(description) ? description : "--"}</td> </tr> <tr> <td className="text-nowrap">Audit Logging </td> @@ -337,6 +338,16 @@ export function PolicyViewDetails(props) { </h6> </td> </tr> + {!isEmpty(zoneName) && ( + <tr> + <td className="text-nowrap">Zone Name </td> + <td> + <h6 className="d-inline mr-1"> + <Badge variant="dark">{zoneName}</Badge> + </h6> + </td> + </tr> + )} </> ); }; 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 065f19e31..cc34488ea 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 @@ -696,7 +696,9 @@ export default function AddUpdatePolicyForm(props) { isRecursive: defObj.recursiveSupported && !(values[`isRecursiveSupport-${level}`] === false), - values: values[`value-${level}`]?.map(({ value }) => value) + values: isArray(values[`value-${level}`]) + ? values[`value-${level}`]?.map(({ value }) => value) + : [values[`value-${level}`].value] }; } } 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 ae46317c1..452f05ace 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 @@ -17,17 +17,16 @@ * under the License. */ -import React, { useEffect, useReducer, useState, useRef } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { Form as FormB, Row, Col } from "react-bootstrap"; import { Field } from "react-final-form"; import Select from "react-select"; import BootstrapSwitchButton from "bootstrap-switch-button-react"; -import CreatableSelect from "react-select/creatable"; -import { debounce, filter, groupBy, some, sortBy } from "lodash"; +import { filter, groupBy, some, sortBy } from "lodash"; import { toast } from "react-toastify"; import { udfResourceWarning } from "../../utils/XAMessages"; -import { fetchApi } from "Utils/fetchAPI"; import { RangerPolicyType } from "Utils/XAEnums"; +import ResourceSelectComp from "./ResourceSelectComp"; const noneOptions = { label: "None", @@ -44,8 +43,6 @@ export default function ResourceComp(props) { policyId } = props; const [rsrcState, setLoader] = useState({ loader: false, resourceKey: -1 }); - const [options, setOptions] = useState([]); - const [isLoading, setIsLoading] = useState(false); const toastId = useRef(null); let resources = sortBy(serviceCompDetails.resources, "itemId"); @@ -73,52 +70,6 @@ export default function ResourceComp(props) { } grpResourcesKeys = grpResourcesKeys.sort(); - const fetchResourceLookup = async ( - inputValue, - resourceObj, - selectedValues - ) => { - let resourceName = resourceObj.name; - let data = { - resourceName, - resources: { - [resourceName]: selectedValues?.map?.(({ value }) => value) || [] - } - }; - if (inputValue) { - data["userInput"] = inputValue || ""; - } - - let op = []; - try { - if (resourceObj.lookupSupported) { - const resourceResp = await fetchApi({ - url: `plugins/services/lookupResource/${serviceDetails.name}`, - method: "POST", - data - }); - op = - resourceResp.data?.map?.((name) => ({ - label: name, - value: name - })) || []; - } - } catch (error) { - toast.dismiss(toastId.current); - if (error?.response?.data?.msgDesc) { - toastId.current = toast.error(error.response.data.msgDesc); - } else { - toastId.current = toast.error( - "Resouce lookup failed for current resource" - ); - } - } - - setOptions(op); - }; - - const fetchDelayResourceLookup = debounce(fetchResourceLookup, 1000); - const getResourceLabelOp = (levelKey, index) => { let op = grpResources[levelKey]; if (index !== 0) { @@ -150,10 +101,7 @@ export default function ResourceComp(props) { if (index !== 0) { levelOp = getResourceLabelOp(levelKey, index); } - if ( - levelOp.length === 1 && - !formValues[resourceKey].hasOwnProperty("parent") - ) { + if (levelOp.length === 1 && !formValues[resourceKey]?.parent?.length > 0) { renderLabel = true; } else { if (index !== 0) { @@ -236,34 +184,6 @@ export default function ResourceComp(props) { } }; - const required = (val, formVal) => { - if (!val || val.length == 0) { - return "Required"; - } - if (formVal?.validationRegEx && val?.length > 0) { - var regex = new RegExp(formVal.validationRegEx); - if (formVal.validationRegEx == "^\\*$") { - if (regex.test(val[val.length - 1].value) == false) - return 'Only "*" value is allowed'; - } - if (formVal.validationRegEx == "^[/*]$|^/.*?[^/]$") { - if (regex.test(val[val.length - 1].value) == false) - return "Relative Path start with slash and must not end with a slash"; - } - } - }; - - const onLookupChange = (object, resourceObj, selectedValues, { action }) => { - switch (action) { - case "input-change": - if (object) - fetchDelayResourceLookup(object, resourceObj, selectedValues); - return; - default: - return; - } - }; - return grpResourcesKeys.map((levelKey, index) => { const resourceKey = `resourceName-${levelKey}`; if (index !== 0) { @@ -284,10 +204,6 @@ export default function ResourceComp(props) { }) }; - const rcsValidation = (m) => { - return required(m, formValues[`resourceName-${levelKey}`]); - }; - return ( <FormB.Group as={Row} @@ -329,62 +245,18 @@ export default function ResourceComp(props) { } /> </Col> + {formValues[`resourceName-${levelKey}`] && ( - <Col sm={5}> - <Field - key={formValues[`resourceName-${levelKey}`].name} - className="form-control" - name={`value-${levelKey}`} - validate={ - formValues && - formValues[`resourceName-${levelKey}`]?.mandatory && - rcsValidation - } - render={({ input, meta }) => ( - <> - <CreatableSelect - {...input} - id={ - formValues && - formValues[`resourceName-${levelKey}`]?.mandatory && - meta.error && - meta.touched - ? "isError" - : `value-${levelKey}` - } - isMulti - isDisabled={ - formValues[`resourceName-${levelKey}`].value === - noneOptions.value - } - options={options} - onFocus={() => { - fetchResourceLookup( - "", - formValues[`resourceName-${levelKey}`], - input.value - ); - }} - onInputChange={(inputVal, action) => { - onLookupChange( - inputVal, - formValues[`resourceName-${levelKey}`], - input.value, - action - ); - }} - isLoading={isLoading} - /> - {formValues && - formValues[`resourceName-${levelKey}`]?.mandatory && - meta.touched && - meta.error && ( - <span className="invalid-field">{meta.error}</span> - )} - </> - )} - /> - </Col> + <> + <Col sm={5}> + <ResourceSelectComp + levelKey={levelKey} + formValues={formValues} + grpResourcesKeys={grpResourcesKeys} + serviceDetails={serviceDetails} + /> + </Col> + </> )} {formValues[`resourceName-${levelKey}`] && ( <Col sm={4}> diff --git a/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceSelectComp.jsx b/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceSelectComp.jsx new file mode 100644 index 000000000..1b8903c97 --- /dev/null +++ b/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceSelectComp.jsx @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useRef } from "react"; +import { Field } from "react-final-form"; +import CreatableSelect from "react-select/creatable"; +import { debounce, isArray } from "lodash"; +import { toast } from "react-toastify"; +import { fetchApi } from "Utils/fetchAPI"; + +const noneOptions = { + label: "None", + value: "none" +}; + +export default function ResourceSelectComp(props) { + const { formValues, grpResourcesKeys, levelKey, serviceDetails } = props; + const [options, setOptions] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const toastId = useRef(null); + + const fetchResourceLookup = async ( + inputValue, + resourceObj, + selectedFormValues + ) => { + setIsLoading(true); + let resourceName = resourceObj.name; + let resourceDataLevel = grpResourcesKeys.slice( + 0, + grpResourcesKeys.indexOf(resourceObj.level) + 1 + ); + let resourceData = {}; + if (resourceDataLevel.length > 0) { + resourceDataLevel.map(function (m) { + resourceData[selectedFormValues[`resourceName-${m}`].name] = isArray( + selectedFormValues[`value-${m}`] + ) + ? selectedFormValues[`value-${m}`]?.map?.(({ value }) => value) || [] + : selectedFormValues[`value-${m}`] != undefined + ? [selectedFormValues[`value-${m}`].value] + : []; + }); + } + let data = { + resourceName, + resources: resourceData, + userInput: inputValue || "" + }; + + let op = []; + try { + if (resourceObj.lookupSupported) { + const resourceResp = await fetchApi({ + url: `plugins/services/lookupResource/${serviceDetails.name}`, + method: "POST", + data + }); + op = + resourceResp.data?.map?.((name) => ({ + label: name, + value: name + })) || []; + } + } catch (error) { + setIsLoading(false); + toast.dismiss(toastId.current); + if (error?.response?.data?.msgDesc) { + toastId.current = toast.error(error.response.data.msgDesc); + } else { + toastId.current = toast.error( + "Resouce lookup failed for current resource" + ); + } + } + setOptions(op); + setIsLoading(false); + }; + + const fetchDelayResourceLookup = useRef(debounce(fetchResourceLookup, 1000)); + + const required = (val, formVal) => { + if (!val || val.length == 0) { + return "Required"; + } + if (formVal?.validationRegEx && val?.length > 0) { + var regex = new RegExp(formVal.validationRegEx); + if (formVal.validationRegEx == "^\\*$") { + if (regex.test(val[val.length - 1].value) == false) + return 'Only "*" value is allowed'; + } + if (formVal.validationRegEx == "^[/*]$|^/.*?[^/]$") { + if (regex.test(val[val.length - 1].value) == false) + return "Relative Path start with slash and must not end with a slash"; + } + } + }; + + const onLookupChange = ( + object, + resourceObj, + selectedFormValues, + { action } + ) => { + switch (action) { + case "input-change": + if (object != undefined) { + setIsLoading(true); + setOptions([]); + fetchDelayResourceLookup.current( + object, + resourceObj, + selectedFormValues + ); + } + return; + default: + return; + } + }; + + const rcsValidation = (m) => { + return required(m, formValues[`resourceName-${levelKey}`]); + }; + + const supportMultipleVal = (rscVal) => { + if (rscVal?.uiHint) { + let singleVal = JSON.parse(rscVal.uiHint); + return singleVal.singleValue ? false : true; + } else { + return true; + } + }; + + const customFilterOptions = (option, rawInput) => { + const inputValue = rawInput.trim().toLowerCase(); + + if (!inputValue || inputValue == "*") { + return true; // Show all options when input is empty + } + return true; + }; + + return ( + <Field + key={formValues[`resourceName-${levelKey}`].name} + className="form-control" + name={`value-${levelKey}`} + validate={ + formValues && + formValues[`resourceName-${levelKey}`]?.mandatory && + rcsValidation + } + render={({ input, meta }) => ( + <> + <CreatableSelect + {...input} + id={ + formValues && + formValues[`resourceName-${levelKey}`]?.mandatory && + meta.error && + meta.touched + ? "isError" + : `value-${levelKey}` + } + isMulti={supportMultipleVal(formValues[`resourceName-${levelKey}`])} + isClearable + isDisabled={ + formValues[`resourceName-${levelKey}`].value === noneOptions.value + } + options={options} + onFocus={(e) => { + setOptions([]); + fetchResourceLookup( + "", + formValues[`resourceName-${levelKey}`], + formValues + ); + }} + onInputChange={(inputVal, action) => { + onLookupChange( + inputVal, + formValues[`resourceName-${levelKey}`], + formValues, + action + ); + }} + filterOption={customFilterOptions} + isLoading={isLoading} + /> + {formValues && + formValues[`resourceName-${levelKey}`]?.mandatory && + meta.touched && + meta.error && <span className="invalid-field">{meta.error}</span>} + </> + )} + /> + ); +}
