blob: 30be71988e7f8a40f1697f3e52ec869c68926fca [file] [log] [blame]
/*
* 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, useReducer } from "react";
import { useNavigate } from "react-router-dom";
import { Button, Form as FormB, Card } from "react-bootstrap";
import Select from "react-select";
import { groupBy, filter } from "lodash";
import AsyncSelect from "react-select/async";
import { Form, Field } from "react-final-form";
import { fetchApi } from "Utils/fetchAPI";
import PrinciplePermissionComp from "../Dataset/PrinciplePermissionComp";
import { FieldArray } from "react-final-form-arrays";
import { toast } from "react-toastify";
import arrayMutators from "final-form-arrays";
import { BlockUi } from "../../../components/CommonComponents";
const initialState = {};
const datashareFormReducer = (state, action) => {
switch (action.type) {
case "SET_SELECTED_SERVICE":
return {
...state,
selectedService: action.selectedService
};
case "SET_SELECTED_ZONE":
return {
...state,
selectedZone: action.selectedZone
};
default:
throw new Error();
}
};
const AddDatashareView = () => {
const [step, setStep] = useState(1);
const [saveButtonText, setSaveButtonText] = useState("Continue");
const [datashareName, setName] = useState();
const [datashareDescription, setDatashareDescription] = useState();
const [datashareDetails, dispatch] = useReducer(
datashareFormReducer,
initialState
);
const { selectedService, selectedZone } = datashareDetails;
const [serviceDef, setServiceDef] = useState({});
const [blockUI, setBlockUI] = useState(false);
const [userList, setUserList] = useState([]);
const [groupList, setGroupList] = useState([]);
const [roleList, setRoleList] = useState([]);
const [maskDef, setMaskDef] = useState(false);
const [rowFilter, setRowFilterDef] = useState(false);
const [accessTypeOptions, setAccessTypeOptions] = useState([]);
const [accessType, setAccessType] = useState([]);
const [maskTypeOptions, setMaskTypeOptions] = useState([]);
const [selectedShareMask, setSelectedShareMask] = useState({});
const [datashareTermsAndConditions, setDatashareTermsAndConditions] =
useState();
const navigate = useNavigate();
const [defaultZoneOptions, setDefaultZoneOptions] = useState([]);
const [defaultServiceOptions, setDefaultServiceOptions] = useState([]);
const cancelDatashareDetails = () => {
if (step == 1) {
navigate("/gds/mydatasharelisting");
} else {
setStep(step - 1);
}
setSaveButtonText("Continue");
};
const datashareTermsAndConditionsChange = (event) => {
setDatashareTermsAndConditions(event.target.value);
};
const noneOptions = {
label: "None",
value: "none"
};
const toggleMoreOptionsModalClose = () => {
setShowMoreOptions(false);
};
const toggleClose = () => {
setShowMoreOptions(false);
};
const subhmitDatashareDetails = async (values) => {
if (step == 4) {
let dataShareInfo = {
name: datashareName,
acl: {
users: {},
groups: {},
roles: {}
},
service: selectedService.label,
description: datashareDescription,
termsOfUse: datashareTermsAndConditions,
defaultAccessTypes:
values.permission != undefined
? Object.entries(values.permission).map(([key, obj]) => {
return obj.value;
})
: []
};
if (selectedZone != undefined) {
dataShareInfo.zone = selectedZone.label;
}
dataShareInfo.defaultTagMasks = [];
if (values.tagMaskInfo != undefined) {
for (let i = 0; i < values.tagMaskInfo.length; i++) {
let data = {};
data.tagName = values.tagMaskInfo[i].tagName;
data.maskInfo = {};
data.maskInfo.dataMaskType = values.tagMaskInfo[i].masking.value;
data.maskInfo.conditionExpr =
values.tagMaskInfo[i].shareDataMaskInfo?.valueExpr;
dataShareInfo.defaultTagMasks.push(data);
}
}
userList.forEach((user) => {
dataShareInfo.acl.users[user.name] = user.perm;
});
groupList.forEach((group) => {
dataShareInfo.acl.groups[group.name] = group.perm;
});
roleList.forEach((role) => {
dataShareInfo.acl.roles[role.name] = role.perm;
});
try {
setBlockUI(true);
const createDatashareResp = await fetchApi({
url: `gds/datashare`,
method: "post",
data: dataShareInfo,
skipNavigate: true
});
toast.success("Datashare created successfully!!");
navigate("/gds/mydatasharelisting");
} catch (error) {
let errorMsg = `Failed to create datashare`;
if (error?.response?.data?.msgDesc) {
errorMsg = `Error! ${error.response.data.msgDesc}`;
}
toast.error(errorMsg);
console.error(`Error occurred while creating datashare ${error}`);
}
setBlockUI(false);
} else if (step == 3) {
setSaveButtonText("Create Datashare");
setStep(step + 1);
} else if (step == 1) {
if (datashareName == undefined) {
toast.error("Please add Datashare Name");
return;
} else if (selectedService == undefined) {
toast.error("Please add Service Name");
return;
} else {
if (selectedService.def != undefined) {
fetchServiceDef(selectedService.def);
} else {
let serviceType = await fetchServiceTypeByName(selectedService.label);
fetchServiceDef(serviceType);
}
setStep(step + 1);
}
setStep(step + 1);
} else {
setSaveButtonText("Continue");
setStep(step + 1);
}
};
const fetchServiceDef = async (serviceDefName) => {
let serviceDefsResp = [];
try {
serviceDefsResp = await fetchApi({
url: `plugins/definitions/name/${serviceDefName}`,
skipNavigate: true
});
} catch (error) {
console.error(
`Error occurred while fetching Service Definition or CSRF headers! ${error}`
);
}
let modifiedServiceDef = serviceDefsResp.data;
for (const obj of modifiedServiceDef.resources) {
obj.recursiveSupported = false;
obj.excludesSupported = false;
}
if (Object.keys(modifiedServiceDef.rowFilterDef).length !== 0) {
setRowFilterDef(true);
}
if (Object.keys(modifiedServiceDef.dataMaskDef).length !== 0) {
setMaskDef(true);
setMaskTypeOptions(
modifiedServiceDef.dataMaskDef.maskTypes.map(
({ label, name: value }) => ({
label,
value
})
)
);
}
setAccessTypeOptions(
modifiedServiceDef.accessTypes.map(({ label, name: value }) => ({
label,
value
}))
);
setServiceDef(modifiedServiceDef);
};
const datashareNameChange = (event) => {
setName(event.target.value);
console.log("DatashareName is:", event.target.value);
};
const datashareDescriptionChange = (event) => {
setDatashareDescription(event.target.value);
console.log("Datashare Description is:", event.target.value);
};
const onSubmit = () => {
console.log("Submitting");
};
const fetchServiceTypeByName = async (name) => {
let params = {};
params["serviceName"] = name;
try {
let serviceResp = await fetchApi({
url: "plugins/services",
params: params,
skipNavigate: true
});
return serviceResp.data.services[0].type;
} catch (error) {
console.error(`Error occurred while fetching service details ! ${error}`);
}
};
const filterServiceByName = async (inputValue) => {
let params = { serviceNamePrefix: inputValue || "" };
let op = [];
let serviceResp = [];
if (selectedZone?.id) {
serviceResp = await fetchApi({
url: `public/v2/api/zones/${selectedZone?.id}/service-headers`,
params: params
});
} else {
serviceResp = await fetchApi({
url: "/public/v2/api/service-headers",
params: params
});
}
op = filter(serviceResp.data, ["isTagService", false]);
return op.map((obj) => ({
label: obj.name,
id: obj.id,
def: obj.type
}));
};
const filterZoneByName = async (inputValue) => {
let params = { zoneNamePrefix: inputValue || "" };
let op = [];
let zoneResp = [];
if (selectedService?.id) {
zoneResp = await fetchApi({
url: `public/v2/api/zones/zone-headers/for-service/${selectedService?.id}`,
params: params
});
} else {
zoneResp = await fetchApi({
url: "/public/v2/api/zone-headers",
params: params
});
}
op = zoneResp.data;
return op.map((obj) => ({
label: obj.name,
id: obj.id
}));
};
const fetchService = async (zoneId) => {
let serviceResp = [];
try {
if (zoneId == undefined) {
serviceResp = await fetchApi({
url: "public/v2/api/service-headers",
skipNavigate: true
});
} else {
serviceResp = await fetchApi({
url: `public/v2/api/zones/${zoneId}/service-headers`,
skipNavigate: true
});
}
let data = filter(serviceResp.data, ["isTagService", false]);
return data.map(({ name, id, type }) => ({
label: name,
id: id,
def: type
}));
} catch (error) {
console.error(`Error occurred while fetching service details ! ${error}`);
}
};
const onFocusServiceSelect = () => {
fetchService(selectedZone?.id).then((opts) => {
setDefaultServiceOptions(opts);
});
};
const onFocusZoneSelect = () => {
fetchSecurityZone(selectedService?.id).then((opts) => {
setDefaultZoneOptions(opts);
});
};
const fetchSecurityZone = async (serviceId) => {
let zoneResp = [];
try {
if (serviceId == undefined) {
zoneResp = await fetchApi({
url: "public/v2/api/zone-headers",
skipNavigate: true
});
console.log("test");
} else {
zoneResp = await fetchApi({
url: `public/v2/api/zones/zone-headers/for-service/${serviceId}`,
skipNavigate: true
});
}
} catch (e) {
console.log(e);
}
console.log(zoneResp);
// const zoneResp = await fetchApi({
// url: "zones/zones",
// params: params
// });
// return zoneResp.data.securityZones.map(({ name, id }) => ({
// label: name,
// value: id
// }));
return zoneResp.data.map(({ name, id }) => ({
label: name,
id: id
}));
};
const onServiceChange = (e, input) => {
dispatch({
type: "SET_SELECTED_SERVICE",
selectedService: e
});
input.onChange(e);
};
const setZone = (e, input) => {
dispatch({
type: "SET_SELECTED_ZONE",
selectedZone: e
});
input.onChange(e);
};
const resourceErrorCheck = (errors, values) => {
let serviceCompResourcesDetails = serviceDef.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;
}
}
};
const onAccessTypeChange = (event, input) => {
setAccessType(event);
input.onChange(event);
};
const getAccessTypeOptions = () => {
let srcOp = serviceDef.accessTypes;
return srcOp.map(({ label, name: value }) => ({
label,
value
}));
};
const getMaskingAccessTypeOptions = () => {
return serviceDef.dataMaskDef.maskTypes.map(({ label, name: value }) => ({
label,
value
}));
};
const onShareMaskChange = (event, input) => {
setSelectedShareMask(event);
input.onChange(event);
};
const handleDataChange = (userList, groupList, roleList) => {
setUserList(userList);
setGroupList(groupList);
setRoleList(roleList);
};
const MaskingConfig = (props) => {
const { addTagMaskingConfig } = props;
return (
<div>
<FieldArray name="tagMaskInfo">
{({ fields }) =>
fields.map((name, index) => (
<div>
<span>Tag Name : </span>
<div className="gds-form-input">
<Field
name={`${name}.tagName`}
render={({ input, meta }) => (
<input
{...input}
type="text"
//name={`${name}.tagName`}
className="form-control"
data-cy="tagName"
//onChange={tagNameChange}
//value={tagName}
/>
)}
/>
</div>
<span>Masking Type : </span>
<Field
className="form-control"
name={`${name}.shareDataMaskInfo`}
render={({ input, meta }) => (
<div>
<Field
name={`maskType`}
render={({ input, meta }) => (
<Field
name={`${name}.masking`}
render={({ input, meta }) => (
<div className="d-flex ">
<div className="w-50">
<Select
{...input}
defaultOptions
//value={selectedShareMask}
options={maskTypeOptions}
onChange={(e) =>
onShareMaskChange(e, input)
}
isClearable={false}
width="500px"
/>
</div>
{fields?.value[index]?.masking?.label ==
"Custom" && (
<div className="pl-2 w-50">
<Field
className="form-control"
name={`${name}.shareDataMaskInfo.valueExpr`}
//validate={required}
render={({ input, meta }) => (
<>
<FormB.Control
type="text"
className="gds-input"
{...input}
placeholder="Enter masked value or expression..."
/>
{meta.error && (
<span className="invalid-field">
{meta.error}
</span>
)}
</>
)}
/>
</div>
)}
</div>
)}
/>
)}
/>
</div>
)}
/>
<Button
variant="danger"
size="sm"
title="Remove"
onClick={() => {
fields.remove(index);
}}
data-action="delete"
data-cy="delete"
>
<i className="fa-fw fa fa-remove"></i>
</Button>
</div>
))
}
</FieldArray>
<Button
className="btn btn-mini mt-2"
type="button"
onClick={() => addTagMaskingConfig("tagMaskInfo", undefined)}
data-action="addTagMaskInfo"
data-cy="addTagMaskInfo"
title="Add"
>
Add More
</Button>
</div>
);
};
return (
<>
<Form
id="myform2"
name="myform2"
onSubmit={onSubmit}
mutators={{
...arrayMutators
}}
render={({
handleSubmit,
submitting,
required,
values,
form: {
mutators: { push: addTagMaskingConfig, pop }
}
}) => (
<div>
<div className="gds-form-header-wrapper">
<h3 className="gds-header bold">Create Datashare</h3>
<div className="gds-header-btn-grp">
<Button
variant="secondary"
type="button"
size="sm"
onClick={cancelDatashareDetails}
data-id="cancel"
data-cy="cancel"
>
Back
</Button>
<Button
variant="primary"
onClick={() => subhmitDatashareDetails(values)}
size="sm"
data-id="save"
data-cy="save"
>
{saveButtonText}
</Button>
</div>
</div>
{step == 1 && (
<div className="gds-form-wrap">
<div className="gds-form-header">
<h6 className="gds-form-step-num">Step 1</h6>
<h2 className="gds-form-step-name">Basic details</h2>
</div>
<div className="gds-form-content">
<div className="gds-form-input">
<input
type="text"
name="datashareName"
placeholder="Datashare Name"
className="form-control"
data-cy="datashareName"
onChange={datashareNameChange}
value={datashareName}
/>
</div>
<div className="gds-form-content">
<Field
className="form-control"
name="selectService"
render={({ input }) => (
<div className="gds-form-input">
<AsyncSelect
defaultOptions={defaultServiceOptions}
onFocus={() => {
onFocusServiceSelect();
}}
components={{
DropdownIndicator: () => null,
IndicatorSeparator: () => null
}}
value={selectedService}
loadOptions={filterServiceByName}
onChange={(e) => onServiceChange(e, input)}
isClearable={true}
placeholder="Select Service"
width="500px"
data-name="serviceSelect"
data-cy="serviceSelect"
/>
</div>
)}
/>
<div className="gds-form-input">
<Field
className="form-control"
name="selectSecurityZone"
render={({ input }) => (
<div className="gds-form-input">
<AsyncSelect
{...input}
value={selectedZone}
loadOptions={filterZoneByName}
onFocus={() => {
onFocusZoneSelect();
}}
defaultOptions={defaultZoneOptions}
onChange={(e) => setZone(e, input)}
isClearable={true}
placeholder="Select Security Zone (Optional)"
width="500px"
/>
</div>
)}
/>
</div>
</div>
<div className="gds-form-input">
<textarea
placeholder="Datashare Description"
className="form-control"
id="description"
data-cy="description"
onChange={datashareDescriptionChange}
value={datashareDescription}
rows={4}
/>
</div>
</div>
</div>
)}
{step == 2 && (
<div className="gds-form-wrap">
<div className="gds-form-header">
<h6 className="gds-form-step-num">Step 3</h6>
<h2 className="gds-form-step-name">
Select default access types
</h2>
</div>
<div>
<div className="gds-section-title">
<p className="gds-card-heading">Access Configuration</p>
</div>
<div className="gds-flex mg-b-10">
<div className="gds-flex">
<div>
<Field
name={`permission`}
render={({ input, meta }) => (
<div className="flex-1">
<span> Default Access Types : </span>
<Select
{...input}
options={accessTypeOptions}
onChange={(e) => onAccessTypeChange(e, input)}
menuPortalTarget={document.body}
value={accessType}
menuPlacement="auto"
placeholder="All Permissions"
isClearable
isMulti
/>
</div>
)}
/>
</div>
</div>
</div>
</div>
{false && maskDef ? (
<div className="gds-section-title">
<p className="gds-card-heading">
Tag Masking Configuration
</p>
<MaskingConfig addTagMaskingConfig={addTagMaskingConfig} />
{/* <div>
<FieldArray name="tagMaskInfo">
{({ fields }) =>
fields.map((name, index) => (
<div>
<span>Tag Name : </span>
<div className="gds-form-input">
<input
type="text"
name="tagName"
className="form-control"
data-cy="tagName"
onChange={tagNameChange}
value={tagName}
/>
</div>
<span>Masking Type : </span>
<Field
className="form-control"
name={`shareDataMaskInfo`}
render={({ input, meta }) => (
<div>
<Field
name={`maskType`}
render={({ input, meta }) => (
<Field
name="masking"
render={({ input, meta }) => (
<div className="d-flex ">
<div className="w-50">
<Select
{...input}
defaultOptions
value={selectedShareMask}
options={maskTypeOptions}
onChange={(e) =>
onShareMaskChange(e, input)
}
isClearable={false}
width="500px"
/>
</div>
{selectedShareMask?.label ==
"Custom" && (
<div className="pl-2 w-50">
<Field
className="form-control"
name={`shareDataMaskInfo.valueExpr`}
validate={required}
render={({
input,
meta
}) => (
<>
<FormB.Control
type="text"
className="gds-input"
{...input}
placeholder="Enter masked value or expression..."
/>
{meta.error && (
<span className="invalid-field">
{meta.error}
</span>
)}
</>
)}
/>
</div>
)}
</div>
)}
/>
)}
/>
</div>
)}
/>
<Button
variant="danger"
size="sm"
title="Remove"
onClick={() => {
fields.remove(index);
}}
data-action="delete"
data-cy="delete"
>
<i className="fa-fw fa fa-remove"></i>
</Button>
</div>
))
}
</FieldArray>
<Button
className="btn btn-mini mt-2"
type="button"
onClick={() => push("tagMaskInfo", undefined)}
data-action="addTagMaskInfo"
data-cy="addTagMaskInfo"
title="Add"
>
Add More
</Button>
</div> */}
</div>
) : (
<div></div>
)}
</div>
)}
{step == 3 && (
<div className="gds-form-wrap">
<div className="gds-form-header">
<h6 className="gds-form-step-num">Step 4</h6>
<h2 className="gds-form-step-name">Datashare Visibility</h2>
</div>
<PrinciplePermissionComp
userList={userList}
groupList={groupList}
roleList={roleList}
type="datashare"
onDataChange={handleDataChange}
/>
</div>
)}
{step == 4 && (
<div className="gds-form-wrap">
<div className="gds-form-header">
<h6 className="gds-form-step-num">Step 5</h6>
<h2 className="gds-form-step-name">
Specify terms and conditions
</h2>
</div>
<table className="gds-table">
<tr>
<td>
<textarea
placeholder="Terms & Conditions"
className="form-control"
id="termsAndConditions"
data-cy="termsAndConditions"
onChange={datashareTermsAndConditionsChange}
value={datashareTermsAndConditions}
rows={16}
/>
</td>
</tr>
</table>
</div>
)}
<BlockUi isUiBlock={blockUI} />
</div>
)}
/>
</>
);
};
export default AddDatashareView;