This is an automated email from the ASF dual-hosted git repository.
madhan pushed a commit to branch RANGER-3923
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/RANGER-3923 by this push:
new d96cdc397 RANGER-4283: [WIP] UI for GDS dashboard, datasets
d96cdc397 is described below
commit d96cdc39793dbf91d0b973a5075ac6fcb255e285
Author: Anand Nadar <[email protected]>
AuthorDate: Fri Sep 1 20:59:26 2023 -0700
RANGER-4283: [WIP] UI for GDS dashboard, datasets
Signed-off-by: Madhan Neethiraj <[email protected]>
---
.../optimized/current/ranger_core_db_mysql.sql | 4 +
.../optimized/current/ranger_core_db_postgres.sql | 2 +
.../src/main/webapp/react-webapp/src/App.jsx | 19 +
.../main/webapp/react-webapp/src/styles/style.css | 91 +++
.../main/webapp/react-webapp/src/utils/XAEnums.js | 7 +
.../views/GovernedData/Dataset/AccessGrantForm.jsx | 631 ++++++++++++++++++++
.../views/GovernedData/Dataset/AddDatasetView.jsx | 640 +++++++++++++++++++++
.../GovernedData/Dataset/DatasetDetailLayout.jsx | 269 +++++++++
.../views/GovernedData/Dataset/DatasetListing.jsx | 580 +++++++++++++++++++
.../GovernedData/Datashare/DatashareListing.jsx | 26 +
.../react-webapp/src/views/SideBar/SideBar.jsx | 23 +
.../react-webapp/src/views/SideBar/SideBarBody.jsx | 35 ++
12 files changed, 2327 insertions(+)
diff --git a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
index db10bc18c..4e0a9a746 100644
--- a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
+++ b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
@@ -1883,6 +1883,7 @@ DECLARE moduleIdUG bigint;
DECLARE moduleIdTagBasedPolicies bigint;
DECLARE moduleIdKeyManager bigint;
DECLARE moduleIdSecurityZone bigint;
+DECLARE moduleIdGovernedDataSharing bigint;
INSERT INTO
x_portal_user(create_time,update_time,added_by_id,upd_by_id,first_name,last_name,pub_scr_name,login_id,password,email,status,user_src,notes)
VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),NULL,NULL,'Admin','','Admin','admin','ceb4f32325eda6142bd65215f4c0f371','',1,0,NULL);
INSERT INTO
x_portal_user(create_time,update_time,added_by_id,upd_by_id,first_name,last_name,pub_scr_name,login_id,password,email,status,user_src,notes)
VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),NULL,NULL,'rangerusersync','','rangerusersync','rangerusersync','70b8374d3dfe0325aaa5002a688c7e3b','rangerusersync',1,0,NULL);
@@ -1896,6 +1897,7 @@ call getXportalUIdByLoginId('rangertagsync',
rangertagsyncID);
INSERT INTO `x_modules_master`
(`create_time`,`update_time`,`added_by_id`,`upd_by_id`,`module`,`url`) VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Resource Based
Policies',''),(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Users/Groups',''),(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Reports',''),(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Audit',''),(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Key
Manager',''),(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,admi [...]
INSERT INTO `x_modules_master`
(`create_time`,`update_time`,`added_by_id`,`upd_by_id`,`module`,`url`) VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Security Zone','');
+INSERT INTO `x_modules_master`
(`create_time`,`update_time`,`added_by_id`,`upd_by_id`,`module`,`url`) VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,'Governed Data Sharing','');
call getModulesIdByName('Reports', moduleIdReports);
call getModulesIdByName('Resource Based Policies',
moduleIdResourceBasedPolicies);
@@ -1904,6 +1906,7 @@ call getModulesIdByName('Users/Groups', moduleIdUG);
call getModulesIdByName('Tag Based Policies', moduleIdTagBasedPolicies);
call getModulesIdByName('Key Manager', moduleIdKeyManager);
call getModulesIdByName('Security Zone', moduleIdSecurityZone);
+call getModulesIdByName('Governed Data Sharing', moduleIdGovernedDataSharing);
INSERT INTO
x_portal_user_role(create_time,update_time,added_by_id,upd_by_id,user_id,user_role,status)
VALUES (UTC_TIMESTAMP(),UTC_TIMESTAMP(),NULL,NULL,adminID,'ROLE_SYS_ADMIN',1);
INSERT INTO x_group (ADDED_BY_ID, CREATE_TIME, DESCR, GROUP_SRC, GROUP_TYPE,
GROUP_NAME, STATUS, UPDATE_TIME, UPD_BY_ID) VALUES (adminID, UTC_TIMESTAMP(),
'public group', 0, 0, 'public', 0, UTC_TIMESTAMP(), adminID);
@@ -1940,6 +1943,7 @@ INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_
INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES
(adminID,moduleIdSecurityZone,UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,1);
INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES
(rangerusersyncID,moduleIdSecurityZone,UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,1);
INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES
(rangertagsyncID,moduleIdSecurityZone,UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,1);
+INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES
(adminID,moduleIdGovernedDataSharing,UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,1);
INSERT INTO x_ranger_global_state
(create_time,update_time,added_by_id,upd_by_id,version,state_name,app_data)
VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,1,'RangerRole','{"Version":"1"}');
INSERT INTO x_ranger_global_state
(create_time,update_time,added_by_id,upd_by_id,version,state_name,app_data)
VALUES
(UTC_TIMESTAMP(),UTC_TIMESTAMP(),adminID,adminID,1,'RangerUserStore','{"Version":"1"}');
diff --git
a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
index a2db70bbf..13fd3724b 100644
--- a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
+++ b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
@@ -2062,6 +2062,7 @@ INSERT INTO
x_user(CREATE_TIME,UPDATE_TIME,user_name,status,descr)VALUES(current
INSERT INTO
x_modules_master(create_time,update_time,added_by_id,upd_by_id,module,url)
VALUES(current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),'Tag
Based Policies','');
INSERT INTO
x_modules_master(create_time,update_time,added_by_id,upd_by_id,module,url)
VALUES(current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),'Security
Zone','');
+INSERT INTO
x_modules_master(create_time,update_time,added_by_id,upd_by_id,module,url)
VALUES(current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),'Governed
Data Sharing','');
INSERT INTO x_security_zone(create_time, update_time, added_by_id, upd_by_id,
version, name, jsonData, description) VALUES (current_timestamp,
current_timestamp, getXportalUIdByLoginId('admin'),
getXportalUIdByLoginId('admin'), 1, ' ', '', 'Unzoned zone');
INSERT INTO x_db_version_h
(version,inst_at,inst_by,updated_at,updated_by,active) VALUES
('CORE_DB_SCHEMA',current_timestamp,'Ranger
1.0.0',current_timestamp,'localhost','Y');
@@ -2155,6 +2156,7 @@ INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_
INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES (getXportalUIdByLoginId('admin'),getModulesIdByName('Security
Zone'),current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1);
INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES (getXportalUIdByLoginId('rangerusersync'),getModulesIdByName('Security
Zone'),current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1);
INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES (getXportalUIdByLoginId('rangertagsync'),getModulesIdByName('Security
Zone'),current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1);
+INSERT INTO x_user_module_perm
(user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed)
VALUES (getXportalUIdByLoginId('admin'),getModulesIdByName('Governed Data
Sharing'),current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1);
INSERT INTO x_ranger_global_state
(create_time,update_time,added_by_id,upd_by_id,version,state_name,app_data)
VALUES
(current_timestamp,current_timestamp,getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1,'RangerRole','{"Version":"1"}');
diff --git a/security-admin/src/main/webapp/react-webapp/src/App.jsx
b/security-admin/src/main/webapp/react-webapp/src/App.jsx
index bc3cf9a7a..750c46946 100644
--- a/security-admin/src/main/webapp/react-webapp/src/App.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/App.jsx
@@ -92,6 +92,18 @@ const AccesLogDetailComp = lazy(() =>
const UserAccessLayoutComp = lazy(() =>
import("Views/Reports/UserAccessLayout")
);
+const DatasetListingComp = lazy(() =>
+ import("Views/GovernedData/Dataset/DatasetListing")
+);
+const CreateDatasetComp = lazy(() =>
+ import("Views/GovernedData/Dataset/AddDatasetView")
+)
+const DatasetDetailLayoutComp = lazy(() =>
+ import("Views/GovernedData/Dataset/DatasetDetailLayout")
+)
+const AccessGrantFormComp = lazy(() =>
+ import("Views/GovernedData/Dataset/AccessGrantForm")
+)
export default class App extends Component {
constructor(props) {
@@ -342,6 +354,13 @@ export default class App extends Component {
<Route path="/locallogin" element={<Loader />} />
{/* NOT FOUND ROUTE */}
<Route path="*" />
+ {/* GDS */}
+ <Route path="/gds">
+ <Route path="datasetlisting"
element={<DatasetListingComp />} />
+ <Route path="create" element={<CreateDatasetComp />} />
+ <Route path="dataset/:datasetId/detail"
element={<DatasetDetailLayoutComp />} />
+ <Route path="dataset/:datasetId/accessGrant"
element={<AccessGrantFormComp />} />
+ </Route>
</Route>
</Routes>
</HashRouter>
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 aaa54a380..cef6d6da8 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
@@ -2550,3 +2550,94 @@ li.list-group-item:hover {
.text-word-break {
word-break: break-all;
}
+.gds-table {
+ margin-left: auto;
+ margin-right: auto;
+ width: 700px;
+}
+
+.gds-tr {
+ text-align: center !important;
+ padding-top: 100px;
+ padding-bottom: 20px;
+ padding-left: 30px;
+ padding-right: 40px;
+}
+
+.gds-header-wrapper {
+ background-color: #f3f8f9 ;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem;
+ border-bottom: 1px solid #d6d6d6;
+ position: sticky;
+ top: 0;
+}
+
+.gds-header {
+ color: #222222;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 20px;
+ margin: 0;
+ padding: 0;
+ text-transform: none;
+ flex: 1;
+}
+
+.gds-header-btn-grp{
+ display: flex;
+ gap: 0.875rem;
+}
+
+.gds-form-wrap {
+ max-width: 880px;
+ margin: 0 auto;
+ background-color: #fff;
+}
+
+.gds-form-header {
+ padding-top: 70px;
+ text-align: center;
+}
+
+.gds-form-step-num {
+ margin-bottom: 1.5rem;
+ text-transform: uppercase;
+ font-size: 1rem;
+}
+
+.gds-form-step-name {
+ font-size: 1.5rem;
+ margin-bottom: 3rem;
+}
+
+.gds-form-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2.5rem;
+}
+
+.gds-form-input {
+ width: 100%;
+}
+
+.gds-add-principle{
+ display: inline-flex;
+ justify-content: space-between;
+ gap: 0.875rem;
+ width: 100%;
+}
+
+.flex-1 {
+ flex: 1;
+}
+
+.gds-text-input {
+ min-height: 40px;
+}
+
+.gds-button{
+ height: 40px;
+}
\ No newline at end of file
diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
index 6e5eb0d5b..2df87d082 100644
--- a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
+++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
@@ -618,6 +618,13 @@ export const PathAssociateWithModule = {
"/roles/create",
"/roles/:roleId"
],
+ "Governed Data Sharing": [
+ "/gds/datasetlisting",
+ "/gds/create",
+ "/gds/dataset/:datasetId/detail",
+ "/gds/dataset/:datasetId/accessGrant",
+ "/gds/datasharelisting"
+ ],
Permission: ["/permissions/models", "/permissions/:permissionId/edit"],
Profile: ["/userprofile"],
KnoxSignOut: ["/knoxSSOWarning"],
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/AccessGrantForm.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/AccessGrantForm.jsx
new file mode 100644
index 000000000..981d32712
--- /dev/null
+++
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/AccessGrantForm.jsx
@@ -0,0 +1,631 @@
+/*
+ * 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, useEffect } from "react";
+import { Form as FormB, Button, Row, Col, Table, InputGroup, FormControl }
from "react-bootstrap";
+import { Form, Field } from "react-final-form";
+import arrayMutators from "final-form-arrays";
+import AsyncSelect from "react-select/async";
+import Select from "react-select";
+import { FieldArray } from "react-final-form-arrays";
+import moment from "moment-timezone";
+import BootstrapSwitchButton from "bootstrap-switch-button-react";
+import Datetime from "react-datetime";
+import PolicyValidityPeriodComp from
"../../PolicyListing/PolicyValidityPeriodComp";
+import { getAllTimeZoneList } from "../../../utils/XAUtils";
+import { isEmpty } from "lodash";
+
+
+function AccessGrantForm(props) {
+
+ const { addPolicyItem } = props;
+ const [validitySchedules, setValiditySchedules] = useState([{name:
"validitySchedules[0]",
+ value: ''}]);
+
+
+ // useEffect(() => {
+ // // Should not ever set state during rendering, so do this in useEffect
instead.
+ // const newObj = {
+ // name: "validitySchedules[0]",
+ // value: ''
+ // };
+ // setValiditySchedules(newObj);
+ // console.log(validitySchedules)
+ // }, []);
+
+ const handleSubmit = async (formData) => {
+ }
+
+ const serviceSelectTheme = (theme) => {
+ return {
+ ...theme,
+ colors: {
+ ...theme.colors,
+ primary: "#0081ab"
+ }
+ };
+ };
+
+ const RenderInput = (props, openCalendar, closeCalendar) => {
+ function clear() {
+ props.dateProps.onChange({ target: { value: "" } });
+ }
+ return (
+ <>
+ <InputGroup className="mb-2">
+ <FormControl {...props.dateProps} readOnly />
+ <InputGroup.Prepend>
+ <InputGroup.Text onClick={clear}> X </InputGroup.Text>
+ </InputGroup.Prepend>
+ </InputGroup>
+ </>
+ );
+ };
+
+ const calStartDate = (sDate, currentDate) => {
+ if (sDate && sDate?.startTime) {
+ return currentDate.isAfter(sDate.startTime);
+ } else {
+ let yesterday = moment().subtract(1, "day");
+ return currentDate.isAfter(yesterday);
+ }
+ };
+
+ const calEndDate = (sDate, currentDate) => {
+ if (sDate && sDate?.endTime) {
+ return currentDate.isBefore(sDate.endTime);
+ } else {
+ return true;
+ }
+ };
+
+ const removeValiditySchedule = (index) => {
+ validitySchedules.splice(index, 1);
+ renderValiditySchedule();
+ }
+
+ const serviceSelectCustomStyles = {
+ option: (provided, state) => ({
+ ...provided,
+ color: state.isSelected ? "white" : "black"
+ }),
+ control: (provided) => ({
+ ...provided,
+ maxHeight: "32px",
+ minHeight: "32px"
+ }),
+ indicatorsContainer: (provided) => ({
+
+ ...provided,
+ maxHeight: "30px"
+ }),
+ dropdownIndicator: (provided) => ({
+ ...provided,
+ padding: "5px"
+ }),
+ clearIndicator: (provided) => ({
+ ...provided,
+ padding: "5px"
+ }),
+ container: (styles) => ({ ...styles, width: "150px" })
+ };
+
+
+ const addValiditySchedule = (e) => {
+ const size = validitySchedules.length == undefined ? 0 :
validitySchedules.length;
+ const newObj = {
+ name: "validitySchedules["+size+"]",
+ value: ''
+ };
+ setValiditySchedules([...validitySchedules, newObj]);
+ }
+
+
+
+ const renderValiditySchedule = () => {
+ return validitySchedules?.map((obj, index) => {
+ return <tr key={obj.name}>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${obj.name}.startTime`}
+ placeholder=" Username"
+ render={({ input, meta }) => (
+ <div>
+ <Datetime
+ {...input}
+ renderInput={(props) => (
+ <RenderInput
dateProps={props} />
+ )}
+ dateFormat="MM-DD-YYYY"
+ timeFormat="HH:mm:ss"
+ closeOnSelect
+ isValidDate={(currentDate,
selectedDate) =>
+
calEndDate(validitySchedules[index], currentDate)
+ }
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${obj.name}.endTime`}
+ render={({ input, meta }) => (
+ <div>
+ <Datetime
+ {...input}
+ renderInput={(props) => (
+ <RenderInput dateProps={props} />
+ )}
+ dateFormat="MM-DD-YYYY"
+ timeFormat="HH:mm:ss"
+ closeOnSelect
+ isValidDate={(currentDate, selectedDate) =>
+ calStartDate(validitySchedules[index],
currentDate)
+ }
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${obj.name}.timeZone`}
+ render={({ input, meta }) => (
+ <div>
+ <Select
+ {...input}
+ options={getAllTimeZoneList()}
+ getOptionLabel={(obj) => obj.text}
+ getOptionValue={(obj) => obj.id}
+ isClearable={true}
+ /*isDisabled={
+
isEmpty(validitySchedules?.[index]?.startTime) &&
+ isEmpty(validitySchedules?.[index]?.endTime)
+ ? true
+ : false
+ }*/
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+ <td className="text-center">
+ <Button
+ variant="danger"
+ size="sm"
+ className="btn-mini"
+ title="Remove"
+ onClick={() => removeValiditySchedule(index)}
+ data-action="delete"
+ data-cy="delete"
+ >
+ <i className="fa-fw fa fa-remove"></i>
+ </Button>
+ </td>
+ </tr>
+ })
+ }
+
+ return (
+ <>
+ <div className="wrap-gds">
+ <Form
+ onSubmit={handleSubmit}
+ mutators={{
+ ...arrayMutators
+ }}
+ render={({
+ handleSubmit,
+ form: {
+ mutators: { push, pop }
+ },
+ form,
+ submitting,
+ invalid,
+ errors,
+ values,
+ fields,
+ pristine,
+ dirty
+ }) => (
+ <div className="wrap user-role-grp-form">
+
+ <form
+ onSubmit={(event) => {
+ handleSubmit(event);
+ }}
+ >
+ <Row className="form-group">
+ <Col sm={5}> <p className="formHeader">Basic
Details</p> </Col>
+ </Row>
+ <Field name="policyName">
+ {({ input, meta }) => (
+ <Row className="form-group">
+ <Col sm={5}>
+ <FormB.Control
+ {...input}
+ placeholder="Policy Name"
+ id={
+ meta.error && meta.touched
+ ? "isError"
+ : "name"
+ }
+ className={
+ meta.error && meta.touched
+ ? "form-control border-danger"
+ : "form-control"
+ }
+ data-cy="policyName"
+ />
+ {meta.touched && meta.error && (
+ <span className="invalid-field">
+ {meta.error.text}
+ </span>
+ )}
+ </Col>
+ <Field
+ className="form-control"
+ name="enableAccessGrant"
+ render={({ input }) => (
+ <>
+ <FormB.Label column sm={3}>
+ <span className="pull-right
fnt-14">
+ Enable Access Grant
+ </span>
+ </FormB.Label>
+ <>
+ <Col sm={1}>
+ <BootstrapSwitchButton
+ {...input}
+ className="abcd"
+ checked={!(input.value ===
false)}
+ onstyle="primary"
+ offstyle="outline-secondary"
+ style="w-100"
+ size="xs"
+ key="isEnabled"
+ />
+ </Col>
+ </>
+ </>
+ )}
+ />
+ </Row>
+ )}
+ </Field>
+
+ <Field name="policyLabel">
+ {({ input, meta }) => (
+ <Row className="form-group">
+ <Col sm={5}>
+ <FormB.Control
+ {...input}
+ placeholder="Policy Label"
+ id={
+ meta.error && meta.touched
+ ? "isError"
+ : "name"
+ }
+ className={
+ meta.error && meta.touched
+ ? "form-control border-danger"
+ : "form-control"
+ }
+ data-cy="policyLabel"
+ />
+ {meta.touched && meta.error && (
+ <span className="invalid-field">
+ {meta.error.text}
+ </span>
+ )}
+ </Col>
+ <Col sm={5}> <p
className="formHeader">Conditions</p> </Col>
+ </Row>
+ )}
+ </Field>
+
+
+ <Field name="policyDscription">
+ {({ input, meta }) => (
+ <Row className="form-group">
+ <Col sm={5}>
+ <FormB.Control
+ {...input}
+ as="textarea"
+ rows={3}
+ placeholder="Policy Description"
+ data-cy="description"
+ />
+ </Col>
+ <Col sm={5}>
+ <FormB.Control
+ {...input}
+ as="textarea"
+ rows={3}
+ placeholder="Enter Boolean Expression"
+ data-cy="policyCondition"
+ />
+ </Col>
+ </Row>
+ )}
+ </Field>
+
+ <Row className="form-group">
+ <Col sm={10}> <p
className="formHeader">Validity Period</p> </Col>
+ </Row>
+ <Row key={name}>
+ <FieldArray name="validitySchedules">
+ {/*validitySchedules.map((obj, index) => (
+ <Col sm={3}>
+ <Field
+ className="form-control"
+ name={`${obj.name}.endTime`}
+ render={({ input, meta }) => (
+ <div>
+ <Datetime
+ {...input}
+ renderInput={(props) => (
+ <RenderInput
dateProps={props} />
+ )}
+ dateFormat="MM-DD-YYYY"
+
timeFormat="HH:mm:ss"
+
closeOnSelect
+
isValidDate={(currentDate, selectedDate) =>
+
calStartDate(validitySchedules.value[index], currentDate)
+ }
+ />
+ {meta.touched
&& meta.error && (
+
<span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </Col>
+ ))*/}
+
+ {/*({ validitySchedules, ...arg }) =>
+ validitySchedules?.map((obj, index) => (
+ <tr key={obj.name}>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${obj.name}.startTime`}
+ placeholder=" Username"
+ render={({ input, meta }) => (
+ <div>
+ <Datetime
+ {...input}
+ renderInput={(props) => (
+ <RenderInput
dateProps={props} />
+ )}
+ dateFormat="MM-DD-YYYY"
+ timeFormat="HH:mm:ss"
+ closeOnSelect
+ isValidDate={(currentDate,
selectedDate) =>
+
calEndDate(validitySchedules.value[index], currentDate)
+ }
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+
+ </tr>
+ ))*/
+
+ }
+ { /*renderValiditySchedule*/}
+
+ {({ fields, ...arg }) =>
+ fields.map((name, index) => (
+ <tr key={name}>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${name}.startTime`}
+ placeholder=" Username"
+ render={({ input, meta }) => (
+ <div>
+ <Datetime
+ {...input}
+ renderInput={(props) => (
+ <RenderInput dateProps={props}
/>
+ )}
+ dateFormat="MM-DD-YYYY"
+ timeFormat="HH:mm:ss"
+ closeOnSelect
+ isValidDate={(currentDate,
selectedDate) =>
+
calEndDate(fields.value[index], currentDate)
+ }
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${name}.endTime`}
+ render={({ input, meta }) => (
+ <div>
+ <Datetime
+ {...input}
+ renderInput={(props) => (
+ <RenderInput dateProps={props}
/>
+ )}
+ dateFormat="MM-DD-YYYY"
+ timeFormat="HH:mm:ss"
+ closeOnSelect
+ isValidDate={(currentDate,
selectedDate) =>
+
calStartDate(fields.value[index], currentDate)
+ }
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+ <td className="text-center">
+ <Field
+ className="form-control"
+ name={`${name}.timeZone`}
+ render={({ input, meta }) => (
+ <div>
+ <Select
+ {...input}
+ options={getAllTimeZoneList()}
+ getOptionLabel={(obj) =>
obj.text}
+ getOptionValue={(obj) => obj.id}
+ isClearable={true}
+ isDisabled={
+
isEmpty(fields?.value?.[index]?.startTime) &&
+
isEmpty(fields?.value?.[index]?.endTime)
+ ? true
+ : false
+ }
+ />
+ {meta.touched && meta.error && (
+ <span>{meta.error}</span>
+ )}
+ </div>
+ )}
+ />
+ </td>
+ <td className="text-center">
+ <Button
+ variant="danger"
+ size="sm"
+ className="btn-mini"
+ title="Remove"
+ onClick={() => fields.remove(index)}
+ data-action="delete"
+ data-cy="delete"
+ >
+ <i className="fa-fw fa fa-remove"></i>
+ </Button>
+ </td>
+ </tr>
+ ))
+ }
+ </FieldArray>
+ </Row>
+ <Row>
+ <Button
+ type="button"
+ className="btn-mini"
+ onClick={addValiditySchedule}
+ data-action="addTime"
+ data-cy="addTime"
+ >
+ Add More
+ </Button>
+ </Row>
+ <div className="mb-4">
+ <PolicyValidityPeriodComp
+ addPolicyItem={push}
+ />
+ </div>
+ <Row className="form-group">
+ <Col sm={10}> <p
className="formHeader">Grants</p> </Col>
+ </Row>
+ <Row>
+ <Col sm={1}>1</Col>
+ <Field name="principle">
+ {({ input }) => (
+ <Col sm={9}>
+ <AsyncSelect
+ placeholder="Select users,
groups, roles"
+ defaultOptions
+ isMulti
+ data-name="usersSelect"
+ data-cy="usersSelect"
+ />
+ </Col>
+ )}
+ </Field>
+ </Row>
+ <Row>
+ <Col sm={1}></Col>
+ <Field name="conditions">
+ {({ input, meta }) => (
+ <Col sm={5}>
+ <FormB.Control
+ {...input}
+ placeholder="consitions"
+ id={
+ meta.error && meta.touched
+ ? "isError"
+ : "name"
+ }
+ className={
+ meta.error && meta.touched
+ ? "form-control
border-danger"
+ : "form-control"
+ }
+ data-cy="consitions"
+ />
+ </Col>
+ )}
+ </Field>
+ <Field name="permissions">
+ {({ input }) => (
+ <Col sm={4}>
+ <AsyncSelect
+ placeholder="Add permissions"
+ defaultOptions
+ isMulti
+ data-name="usersSelect"
+ data-cy="usersSelect"
+ />
+ </Col>
+ )}
+ </Field>
+ </Row>
+ </form>
+ </div>
+ )}
+ />
+ </div>
+ </>
+ );
+}
+
+export default AccessGrantForm;
\ No newline at end of file
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/AddDatasetView.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/AddDatasetView.jsx
new file mode 100644
index 000000000..83043a2aa
--- /dev/null
+++
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/AddDatasetView.jsx
@@ -0,0 +1,640 @@
+/*
+ * 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 { Button, Row, Col, Table, Accordion, Card } from "react-bootstrap";
+import { useNavigate } from "react-router-dom";
+import { serverError } from "../../../utils/XAUtils";
+import { fetchApi } from "Utils/fetchAPI";
+import { toast } from "react-toastify";
+import Select from "react-select";
+import { Form, Field } from "react-final-form";
+import arrayMutators from "final-form-arrays";
+import { FieldArray } from "react-final-form-arrays";
+import AsyncSelect from "react-select/async";
+
+
+const initialState = {
+ dataset: {},
+ loader: false,
+ preventUnBlock: false,
+ blockUI: false,
+ selectedPrinciple: []
+};
+
+
+
+const datasetFormReducer = (state, action) => {
+ switch (action.type) {
+ case "SET_SELECTED_PRINCIPLE":
+ return {
+ ...state,
+ selectedPrinciple: action.selectedPrinciple
+ };
+ case "SET_PREVENT_ALERT":
+ return {
+ ...state,
+ preventUnBlock: action.preventUnBlock
+ };
+ case "SET_BLOCK_UI":
+ return {
+ ...state,
+ blockUI: action.blockUI
+ };
+ default:
+ throw new Error();
+ }
+};
+
+const AddDatasetView = () => {
+
+ const navigate = useNavigate();
+ const [dataSetDetails, dispatch] = useReducer(datasetFormReducer,
initialState);
+ const [step, setStep] = useState(1);
+ const [dataset, setDataset] = useState({
+ name: "",
+ acl: {
+ users: {},
+ groups: {},
+ roles: {}
+ },
+ description: "",
+ termsOfUse: ""
+ });
+ const [datasetName, setName] = useState('');
+ const [datasetDescription, setDatasetDescription] = useState('');
+ const [datasetAcl, setAcl] = useState({});
+ const [datasetTermsAndConditions, setDatasetTermsAndConditions] =
useState('');
+ const [selectedAccess, setSelectedAccess] = useState({ value: 'LIST', label:
'LIST'});
+ const [principals, setPrincipals] = useState([]);
+ const {
+ loader,
+ selectedPrinciple,
+ preventUnBlock,
+ blockUI
+ } = dataSetDetails;
+ const toastId = React.useRef(null);
+ const [userAccordian, setUserAccordian] = useState(false);
+ const [saveButtonText, setSaveButtonText] = useState('Continue');
+
+ const changeUserAccordian = () => {
+ setUserAccordian(!userAccordian);
+ }
+
+ const acl = {
+ "users": {},
+ "groups": {},
+ "roles": {}
+ }
+
+ const aclApiResponse = [
+ {
+ "type": "USER",
+ "name": "GDS_User"
+ },
+ {
+ "type": "GROUP",
+ "name": "GDS_Group"
+ },
+ {
+ "type": "ROLE",
+ "name": "GDS_Role"
+ }
+ ];
+
+ const setACL = (e, input) => {
+ setSelectedAccess(e);
+ input.onChange(e);
+ };
+
+ const datasetNameChange = event => {
+ setName(event.target.value);
+ dataset.name = event.target.value
+ console.log('DatasetName is:', event.target.value);
+ };
+
+ const datasetDescriptionChange = event => {
+ setDatasetDescription(event.target.value);
+ dataset.description = event.target.value;
+ console.log('DatasetDescription is:', event.target.value);
+ }
+
+ const datasetTermsAndConditionsChange = event => {
+ setDatasetTermsAndConditions(event.target.value);
+ dataset.termsOfUse = event.target.value;
+ console.log('datasetTermsAndConditions is:', event.target.value);
+ }
+
+
+ const accessOptions = [
+ { value: 'LIST', label: 'LIST' },
+ { value: 'VIEW', label: 'VIEW' },
+ { value: 'ADMIN', label: 'ADMIN' }
+ ]
+
+ const accessOptionsWithRemove = [
+ { value: 'LIST', label: 'LIST' },
+ { value: 'VIEW', label: 'VIEW' },
+ { value: 'ADMIN', label: 'ADMIN' },
+ { value: 'Remove Access', label: 'Remove Access' }
+ ]
+
+ const formatOptionLabel = ({ label }) => (
+ <div title={label} className="text-truncate">
+ {label}
+ </div>
+ );
+
+ const serviceSelectTheme = (theme) => {
+ return {
+ ...theme,
+ colors: {
+ ...theme.colors,
+ primary: "#0081ab"
+ }
+ };
+ };
+
+ const serviceSelectCustomStyles = {
+ option: (provided, state) => ({
+ ...provided,
+ color: state.isSelected ? "white" : "black"
+ }),
+ control: (provided) => ({
+ ...provided,
+ maxHeight: "32px",
+ minHeight: "32px"
+ }),
+ indicatorsContainer: (provided) => ({
+ ...provided,
+ maxHeight: "30px"
+ }),
+ dropdownIndicator: (provided) => ({
+ ...provided,
+ padding: "5px"
+ }),
+ clearIndicator: (provided) => ({
+ ...provided,
+ padding: "5px"
+ }),
+ container: (styles) => ({ ...styles, width: "150px" })
+ };
+
+ const getAclFromInput = (value, index, array) => {
+
+ }
+
+ const addInSelectedPrincipal = (push, values, input) => {
+ let principle = values.selectedPrinciple;
+ for (let i = 0; i < principle.length; i++) {
+ let acl = { name: '', type: '', perm: ''};
+ acl.name = principle[i].value;
+ acl.perm = selectedAccess.value;
+ acl.type = principle[i].type;
+ principle[i] = acl;
+
+ if (principle[i].type == "USER") {
+ dataset.acl.users[principle[i].name] = selectedAccess.value;
+ } else if (principle[i].type == "GROUP") {
+ dataset.acl.groups[principle[i].name] = selectedAccess.value;
+ } else if (principle[i].type == "ROLE") {
+ dataset.acl.roles[principle[i].name] = selectedAccess.value;
+ }
+ }
+
+ principle.map((principle) => {
+ push("users", principle);
+ });
+ setSelectedAccess({ value: 'LIST', label: 'LIST'});
+
+ //push("users", val);
+ dispatch({
+ type: "SET_SELECTED_PRINCIPLE",
+ selectedPrinciple: []
+ });
+ console.log(selectedPrinciple)
+ /** }*/
+ };
+
+ const customStyles = {
+ control: (provided) => ({
+ ...provided,
+ maxHeight: "40px",
+ width: "172px"
+ }),
+ indicatorsContainer: (provided) => ({
+ ...provided,
+ })
+ };
+
+ const handleTableSelectedValue = (e, input, index, fields) => {
+ if (e.label == "Remove Access") {
+ fields.remove(index)
+ } else {
+ input.onChange(e);
+ }
+ }
+
+ const selectedPrincipal = (e, input) => {
+ dispatch({
+ type: "SET_SELECTED_PRINCIPLE",
+ selectedPrinciple: e
+ });
+ input.onChange(e);
+ }
+
+ const subhmitDatasetDetails = async () => {
+ console.log('Step value :: ' + step)
+
+ let txt = "";
+ for (let x in dataset.acl.users) {
+ txt += dataset.acl.users[x] + " ";
+ };
+
+ if (step == 3) {
+ dispatch({
+ type: "SET_PREVENT_ALERT",
+ preventUnBlock: true
+ });
+ try {
+ dispatch({
+ type: "SET_BLOCK_UI",
+ blockUI: true
+ });
+ const createDatasetResp = await fetchApi({
+ url: `gds/dataset`,
+ method: "post",
+ data: dataset
+ });
+ dispatch({
+ type: "SET_BLOCK_UI",
+ blockUI: false
+ });
+ toast.success("Dataset created successfully!!");
+ self.location.hash = "#/gds/datasetlisting";
+ } catch (error) {
+ dispatch({
+ type: "SET_BLOCK_UI",
+ blockUI: false
+ });
+ serverError(error);
+ console.error(`Error occurred while creating dataset
${error}`);
+ }
+ } else if (step == 2) {
+ setSaveButtonText("Create Dataset");
+ setStep(step + 1);
+ } else {
+ setSaveButtonText("Continue");
+ setStep(step + 1);
+ }
+ }
+
+ const cancelDatasetDetails = () => {
+ if (step == 1) {
+ navigate("/gds/datasetlisting");
+ } else {
+ let txt = "";
+ for (let x in dataset.acl.users) {
+ txt += dataset.acl.users[x] + " ";
+ };
+ console.log('back TExt ::: '+txt)
+ setStep(step - 1);
+ }
+ setSaveButtonText("Continue");
+ }
+
+ const handleSubmit = async (formData) => {
+
+ }
+
+ const handlePrincipleChange = (value) => {
+ console.log(value)
+ dispatch({
+ type: "SET_SELECTED_PRINCIPLE",
+ selectedPrinciple: value
+ });
+ };
+
+ const setPrincipleFormData = () => {
+ let formValueObj = {};
+ /**if (params?.roleID) {**/
+
+ if (aclApiResponse.users != undefined) {
+ formValueObj.users
+ }
+
+ if (aclApiResponse.groups != undefined) {
+
+ }
+
+ if (aclApiResponse.roles != undefined) {
+
+ }
+
+ /**if (aclApiResponse.length > 0) {
+ formValueObj.name = roleInfo.name;
+ formValueObj.description = roleInfo.description;
+ formValueObj.users = roleInfo.users;
+ formValueObj.groups = roleInfo.groups;
+ formValueObj.roles = roleInfo.roles;
+ }**/
+ /*}**/
+ return formValueObj;
+ }
+
+
+
+const fetchPrincipleOp = async (inputValue) => {
+ let params = { name: inputValue || "" };
+ let data = [];
+ const principalResp = await fetchApi({
+ url: "xusers/lookup/principals",
+ params: params
+ });
+ data = principalResp.data;
+ return data.map((obj) => ({
+ label: obj.name,
+ value: obj.name,
+ type: obj.type
+ }));
+ };
+
+ return (
+ <>
+ <div className="gds-header-wrapper">
+ <h3 className="gds-header bold">Create Dataset</h3>
+
+ <div className="gds-header-btn-grp">
+ <Button
+ variant="secondary"
+ type="button"
+ size="sm"
+ onClick={cancelDatasetDetails}
+ data-id="cancel"
+ data-cy="cancel"
+ >
+ Back
+ </Button>
+ <Button
+ variant="primary"
+ onClick={subhmitDatasetDetails}
+ 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">Enter dataset name and
description</h2>
+ </div>
+ <div className="gds-form-content">
+ <div className="gds-form-input">
+ <input
+ type="text"
+ name="datasetName"
+ placeholder="Dataset Name"
+ className="form-control"
+ data-cy="datasetName"
+ onChange={datasetNameChange}
+ value={datasetName}
+ />
+ </div>
+ <div className="gds-form-input">
+ <textarea
+ placeholder="Dataset Description"
+ className="form-control"
+ id="description"
+ data-cy="description"
+ onChange={datasetDescriptionChange}
+ value={datasetDescription}
+ rows={4}
+ />
+ </div>
+ </div>
+
+ </div>
+ )}
+
+
+ {step == 2 && (
+ <Form
+ onSubmit={handleSubmit}
+ initialValues={setPrincipleFormData()}
+ mutators={{
+ ...arrayMutators
+ }}
+ render={({
+ handleSubmit,
+ form: {
+ mutators: { push, pop }
+ },
+ form,
+ submitting,
+ invalid,
+ errors,
+ values,
+ fields,
+ pristine,
+ dirty
+ }) => (
+ <div className="gds-form-wrap">
+ <form
+ onSubmit={(event) => {
+ handleSubmit(event);
+ }}
+ >
+ <div className="gds-form-header">
+ <h6 className="gds-form-step-num">Step 2</h6>
+ <h2 className="gds-form-step-name">Specify
Permissions</h2>
+ </div>
+ <div className="gds-form-content">
+ <div className="gds-form-input">
+
+ <Field
+ className="form-control"
+ name="selectedPrinciple"
+ render={({ input, meta }) => (
+ <div
className="gds-add-principle">
+ {" "}
+ <AsyncSelect className="flex-1
gds-text-input"
+ onChange={(e) =>
selectedPrincipal(e, input)}
+ value={selectedPrinciple}
+
loadOptions={fetchPrincipleOp}
+ components={{
+ DropdownIndicator: () =>
null,
+ IndicatorSeparator: () =>
null
+ }}
+ defaultOptions
+ isMulti
+ placeholder="Select
Principals"
+ data-name="usersSelect"
+ data-cy="usersSelect"
+ />
+
+ <Field
+ name="accessPermList"
+ className="form-control"
+ render={({ input }) => (
+ <Select
+
theme={serviceSelectTheme}
+ styles={customStyles}
+ options={accessOptions}
+ onChange={(e) =>
setACL(e, input)}
+ value={selectedAccess}
+
formatOptionLabel={formatOptionLabel}
+ menuPlacement="auto"
+ isClearable
+ />
+ )}
+ >
+
+ </Field>
+
+
+ <Button
+ type="button"
+ className="gds-button btn
btn-primary"
+ onClick={() => {
+ if (
+
!values.selectedPrinciple ||
+
values.selectedPrinciple.length === 0
+ ) {
+
toast.dismiss(toastId.current);
+ toastId.current =
toast.error(
+ "Please select
principal!!"
+ );
+ return false;
+ }
+
addInSelectedPrincipal(push, values, input);
+ }}
+ size="sm"
+ data-name="usersAddBtn"
+ data-cy="usersAddBtn"
+ >
+ Add Principals
+ </Button>
+
+ </div>
+ )}
+ />
+
+ </div>
+ </div>
+
+ <div>
+ <div className="wrap">
+ <Col sm="12">
+ <FieldArray name="users">
+ {({ fields }) => (
+ <Table >
+ <thead className="thead-light">
+ <tr>
+ <th className="text-center">Selected
Users, Groups, Roles</th>
+ </tr>
+ </thead>
+ <tbody>
+ {fields.value == undefined ? (
+ <tr>
+ <td className="text-center
text-muted" colSpan="3" >
+ No principles found
+ </td>
+ </tr>
+ ) : (
+
+ fields.map((name, index) => (
+
+
+
+ <tr key={index}>
+ <td className="text-center
text-truncate">
+ <span
title={fields.value[index].name}>
+ {fields.value[index].name}
+ </span>
+ </td>
+ <td>
+ <Field
+ name="aclPerms"
+ render={({ input, meta }) => (
+ <Select
+
theme={serviceSelectTheme}
+
styles={serviceSelectCustomStyles}
+
options={accessOptionsWithRemove}
+ onChange={(e) =>
handleTableSelectedValue(e, input, index, fields)}
+
formatOptionLabel={formatOptionLabel}
+ menuPlacement="auto"
+
placeholder={fields.value[index].perm}
+ isClearable
+ />
+ )}
+ />
+ </td>
+ </tr>
+ ))
+ )}
+ </tbody>
+ </Table>
+ )}
+ </FieldArray>
+
+ </Col>
+ </div>
+ </div>
+ </form>
+ </div>
+ )}
+ />
+ )}
+
+
+ {step == 3 && (
+ <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">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={datasetTermsAndConditionsChange}
+ value={datasetTermsAndConditions}
+ rows={8}
+ />
+ </td>
+ </tr>
+ </table>
+ </div>
+ )}
+
+ </>
+ );
+}
+
+export default AddDatasetView;
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/DatasetDetailLayout.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/DatasetDetailLayout.jsx
new file mode 100644
index 000000000..48eaa033b
--- /dev/null
+++
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/DatasetDetailLayout.jsx
@@ -0,0 +1,269 @@
+/*
+ * 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,Row
+ * 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, { Component } from "react";
+import { Tab, Tabs } from "react-bootstrap";
+import withRouter from "Hooks/withRouter";
+import { Outlet } from "react-router-dom";
+import { fetchApi } from "../../../utils/fetchAPI";
+import dateFormat from "dateformat";
+import { Button, Row, Col, Table } from "react-bootstrap";
+import StructuredFilter from
"../../../components/structured-filter/react-typeahead/tokenizer";
+import AccessGrantForm from "./AccessGrantForm";
+
+class DatasetDetailLayout extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ datasetInfo: {
+ description: ""
+ },
+ activeKey: this.activeTab(),
+ dataShareTab: "Active",
+ description: "",
+ termsOfUse: ""
+ };
+ }
+
+ tabChange = (tabName) => {
+ this.setState({ activeKey: tabName });
+ };
+
+ dataShareTabChange = (tabName) => {
+ this.setState({ dataShareTab: tabName });
+ };
+
+ componentDidUpdate(nextProps, prevState) {
+ /*let activeTabVal = this.activeTab();
+
+ if (prevState.activeKey !== activeTabVal) {
+ this.setState({ activeKey: this.activeTab() });
+ }*/
+ }
+
+ componentDidMount() {
+ const datasetId = window.location.href.split('/')[6]
+ this.state.datasetInfo.id = datasetId
+ this.fetchDatasetInfo(datasetId)
+ }
+
+ fetchDatasetInfo = async (datasetId) => {
+ try {
+ const resp = await fetchApi({
+ url: `gds/dataset/${datasetId}`
+ });
+ this.setState({ datasetInfo: resp.data || null });
+ this.setState({ description: resp.data.description || null });
+ this.setState({ termsOfUse: resp.data.termsOfUse || null });
+ } catch (error) {
+ console.error(`Error occurred while fetching dataset details !
${error}`);
+ }
+ };
+
+ addAccessGrant = () => {
+
this.props.navigate(`/gds/dataset/${this.state.datasetInfo.id}/accessGrant`);
+ };
+
+ activeTab = () => {
+ let activeTabVal;
+ /*if (this.props?.location?.pathname) {
+ if (this.props.location.pathname == `/gds/dataset/detail/dataShares`) {
+ activeTabVal = "dataShares";
+ } else if (this.props.location.pathname ==
`/gds/dataset/detail/sharedWith`) {
+ activeTabVal = "sharedWith";
+ } else if (this.props.location.pathname ==
`/gds/dataset/detail/accessGrants`) {
+ activeTabVal = "accessGrants";
+ } else if (this.props.location.pathname ==
`/gds/dataset/detail/history`) {
+ activeTabVal = "history";
+ } else if (this.props.location.pathname ==
`/gds/dataset/detail/termsOfUse`) {
+ activeTabVal = "termsOfUse";
+ } else {
+ activeTabVal = "overview";
+ }
+ }
+ return activeTabVal;*/
+ };
+
+ datasetDescriptionChange = event => {
+ this.setState({ description: event.target.value });
+ console.log('DatasetDescription is:', event.target.value);
+ }
+
+ datasetTermsAndConditionsChange = event => {
+ this.setState({termsOfUse: event.target.value});
+ console.log('datasetTermsAndConditions is:', event.target.value);
+ }
+
+ render() {
+ //const { datasetId } = useParams();
+
+ return (
+ <>
+ <React.Fragment>
+ <div className="gds-header-wrapper">
+ <h3 className="gds-header bold">Dataset :
{this.state.datasetInfo.name}</h3>
+ </div>
+ {this.state.loader ? (
+ <Loader />
+ ) : (
+ <React.Fragment>
+ <div>
+ <Tabs
+ id="DatasetDetailLayout"
+ activeKey={this.state.activeKey}
+ onSelect={(tabKey) => this.tabChange(tabKey)}
+ >
+ <Tab eventKey="overview" title="OVERVIEW" >
+ <div className="wrap-gds">
+ <Row >
+ <Col flex="1" max-width="100%" height="30px">Date
Updated</Col>
+ <Col sm={5}
line-height="30px">{dateFormat(this.state.datasetInfo.updateTime, "mm/dd/yyyy
hh:MM:ss TT")}</Col>
+ </Row>
+ <Row>
+ <Col sm={5} line-height="30px">Date Created</Col>
+ <Col sm={5}
line-height="30px">{dateFormat(this.state.datasetInfo.createTime, "mm/dd/yyyy
hh:MM:ss TT")}</Col>
+ </Row>
+ <Row>
+ <Col sm={10}>Description</Col>
+ </Row>
+ <Row>
+ <Col sm={10}>
+ <textarea
+ placeholder="Dataset Description"
+ className="form-control"
+ id="description"
+ data-cy="description"
+ onChange={this.datasetDescriptionChange}
+ value={this.state.description}
+ />
+ </Col>
+ </Row>
+ <Row>
+ <div className="col-md-9 offset-md-3">
+ <Button
+ variant="primary"
+ onClick={() => {
+ 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[className="invalid-field"]`
+ );
+ scrollToError(selector);
+ }
+ handleSubmit(values);
+ }}
+ size="sm"
+ data-id="save"
+ data-cy="save"
+ >
+ Save
+ </Button>
+ <Button
+ variant="secondary"
+ type="button"
+ size="sm"
+ onClick={() => {
+ form.reset;
+ dispatch({
+ type: "SET_PREVENT_ALERT",
+ preventUnBlock: true
+ });
+ closeForm();
+ }}
+ data-id="cancel"
+ data-cy="cancel"
+ >
+ Cancel
+ </Button>
+ </div>
+ </Row>
+ </div>
+ </Tab>
+ <Tab eventKey="dataShares" title="DATA SHARES" >
+ <div className="wrap-gds">
+ <Row className="mb-4">
+ <Col sm={10} className="usr-grp-role-search-width">
+ <StructuredFilter
+ key="user-listing-search-filter"
+ placeholder="Search datashares..."
+ />
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={10} className="usr-grp-role-search-width">
+ <Tabs id="DatashareTabs"
+ activeKey={this.state.dataShareTab}
+ onSelect={(tabKey) =>
this.dataShareTabChange(tabKey)}>
+ <Tab eventKey="All" title="All" />
+ <Tab eventKey="Active" title="Active" />
+ <Tab eventKey="Requested" title="Requested"
/>
+ <Tab eventKey="Granted" title="Granted" />
+ </Tabs>
+ </Col>
+ </Row>
+ </div>
+ </Tab>
+ <Tab eventKey="sharedWith" title="SHARED WITH" />
+ <Tab eventKey="accessGrants" title="ACCESS GRANTS" >
+ <div className="wrap-gds">
+ <AccessGrantForm />
+ </div>
+ </Tab>
+ <Tab eventKey="history" title="HISTORY" />
+ <Tab eventKey="termsOfUse" title="TERMS OF USE" >
+ <div className="wrap-gds">
+ <Row>
+ <Col sm={10} className="usr-grp-role-search-width">
+ <p className="formHeader">Terms & Conditions</p>
+ <p align="right">Edit</p>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={10}>
+ <textarea
+ placeholder="Terms & Conditions"
+ className="gds-header"
+ id="termsAndConditions"
+ data-cy="termsAndConditions"
+ onChange={this.datasetTermsAndConditionsChange}
+ value={this.state.termsOfUse}
+ />
+ </Col>
+ </Row>
+ </div>
+ </Tab>
+ </Tabs>
+ </div>
+ </React.Fragment>
+ )}
+ </React.Fragment>
+ </>
+ );
+ }
+}
+
+export default withRouter(DatasetDetailLayout);
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/DatasetListing.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/DatasetListing.jsx
new file mode 100644
index 000000000..055db2376
--- /dev/null
+++
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Dataset/DatasetListing.jsx
@@ -0,0 +1,580 @@
+/*
+ * 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, useCallback, useEffect, useRef } from "react";
+import { useSearchParams, useNavigate, useLocation } from "react-router-dom";
+import { Badge, Button, Row, Col, Table, Modal } from "react-bootstrap";
+import XATableLayout from "../../../components/XATableLayout";
+import dateFormat from "dateformat";
+import { fetchApi } from "../../../utils/fetchAPI";
+import {
+ AuditFilterEntries,
+ CustomPopoverOnClick,
+ CustomPopoverTagOnClick
+} from "Components/CommonComponents";
+import moment from "moment-timezone";
+import {
+ isEmpty,
+ isUndefined,
+ pick,
+ indexOf,
+ map,
+ sortBy,
+ toString,
+ toUpper,
+ has,
+ filter
+} from "lodash";
+import CustomBreadcrumb from "../../CustomBreadcrumb";
+import { toast } from "react-toastify";
+import { Link } from "react-router-dom";
+import { AccessMoreLess } from "Components/CommonComponents";
+import StructuredFilter from
"../../../components/structured-filter/react-typeahead/tokenizer";
+import {
+ isKeyAdmin,
+ isKMSAuditor,
+ getTableSortBy,
+ getTableSortType,
+ serverError,
+ isSystemAdmin,
+ requestDataTitle,
+ fetchSearchFilterParams,
+ parseSearchFilter
+} from "../../../utils/XAUtils";
+import { CustomTooltip, Loader, BlockUi } from
"../../../components/CommonComponents";
+import {
+ ServiceRequestDataRangerAcl,
+ ServiceRequestDataHadoopAcl
+} from "../../../utils/XAEnums";
+
+const DatasetListing = () => {
+ const navigate = useNavigate();
+ const { state } = useLocation();
+ const [pageCount, setPageCount] = useState(
+ state && state.showLastPage ? state.addPageData.totalPage : 0
+ );
+ const [currentpageIndex, setCurrentPageIndex] = useState(
+ state && state.showLastPage ? state.addPageData.totalPage - 1 : 0
+ );
+ const [currentpageSize, setCurrentPageSize] = useState(
+ state && state.showLastPage ? state.addPageData.pageSize : 5
+ );
+ const isKMSRole = isKeyAdmin() || isKMSAuditor();
+ const [datasetListData, setDatasetListData] = useState([]);
+ const [serviceDefs, setServiceDefs] = useState([]);
+ const [services, setServices] = useState([]);
+ const [zones, setZones] = useState([]);
+ const [loader, setLoader] = useState(true);
+ const [updateTable, setUpdateTable] = useState(moment.now());
+ const [entries, setEntries] = useState([]);
+ const [showrowmodal, setShowRowModal] = useState(false);
+ const [policyviewmodal, setPolicyViewModal] = useState(false);
+ const [policyParamsData, setPolicyParamsData] = useState(null);
+ const [rowdata, setRowData] = useState([]);
+ const [checked, setChecked] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
+ const fetchIdRef = useRef(0);
+ const [contentLoader, setContentLoader] = useState(true);
+ const [searchFilterParams, setSearchFilterParams] = useState([]);
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [defaultSearchFilterParams, setDefaultSearchFilterParams] = useState(
+ []
+ );
+ const [resetPage, setResetpage] = useState({ page: 0 });
+ const [policyDetails, setPolicyDetails] = useState({});
+ const [blockUI, setBlockUI] = useState(false);
+
+
+const [deleteDatasetModal, setConfirmModal] = useState({
+ datasetDetails: {}
+});
+
+const toggleConfirmModalForDelete = (datasetID, datasetName) => {
+ setConfirmModal({
+ datasetDetails: { datasetID: datasetID, datasetName: datasetName },
+ showPopup: true
+ });
+};
+
+
+ const handleDeleteClick = async (datasetID) => {
+ toggleClose();
+ try {
+ setBlockUI(true);
+ await fetchApi({
+ url: `gds/dataset/${datasetID}`,
+ method: "DELETE"
+ });
+ setBlockUI(false);
+ toast.success(" Success! Dataset deleted successfully");
+ } catch (error) {
+ setBlockUI(false);
+ let errorMsg = "Failed to delete dataset : ";
+ if (error?.response?.data?.msgDesc) {
+ errorMsg += error.response.data.msgDesc;
+ }
+ toast.error(errorMsg);
+ console.error("Error occurred during deleting dataset : " + error);
+ }
+ if (datasetListData.length == 1 && currentpageIndex > 0) {
+ let page = currentpageIndex - currentpageIndex;
+ if (typeof resetPage?.page === "function") {
+ resetPage.page(page);
+ }
+ } else {
+ setUpdateTable(moment.now());
+ }
+ };
+
+const toggleClose = () => {
+ setConfirmModal({
+ datasetDetails: {},
+ showPopup: false
+ });
+ };
+
+ useEffect(() => {
+ if (isEmpty(serviceDefs)) {
+ fetchServiceDefs(), fetchServices();
+
+ if (!isKMSRole) {
+ fetchZones();
+ }
+ }
+ }, [serviceDefs]);
+
+ const fetchDatasetList = useCallback(
+ async ({ pageSize, pageIndex, sortBy, gotoPage }) => {
+ setLoader(true);
+ let resp = [];
+ let datasetList = [];
+ let totalCount = 0;
+ let page =
+ state && state.showLastPage
+ ? state.addPageData.totalPage - 1
+ : pageIndex;
+ let totalPageCount = 0;
+ const fetchId = ++fetchIdRef.current;
+ let params = { ...searchFilterParams };
+ if (fetchId === fetchIdRef.current) {
+ params["pageSize"] = pageSize;
+ params["startIndex"] =
+ state && state.showLastPage
+ ? (state.addPageData.totalPage - 1) * pageSize
+ : pageIndex * pageSize;
+ if (sortBy.length > 0) {
+ params["sortBy"] = getTableSortBy(sortBy);
+ params["sortType"] = getTableSortType(sortBy);
+ }
+ try {
+ resp = await fetchApi({
+ url: "gds/dataset",
+ params: params
+ });
+ datasetList = resp.data.list;
+ totalCount = resp.data.totalCount;
+ } catch (error) {
+ serverError(error);
+ console.error(
+ `Error occurred while fetching Dataset list! ${error}`
+ );
+ }
+ let modifiedDatasetList = [];
+ if (datasetList !== undefined) {
+ for (let i = 0; i < datasetList.length; i++) {
+ let dataset = datasetList[i];
+ if (dataset.acl !== undefined) {
+ if (dataset.acl.users !== undefined) {
+ const map = new
Map(Object.entries(dataset.acl.users));
+ dataset.users = map.size;
+ } else {
+ dataset.users = 0;
+ }
+
+ if (dataset.acl.groups !== undefined) {
+ const map = new
Map(Object.entries(dataset.acl.groups));
+ dataset.groups = map.size;
+ } else {
+ dataset.groups = 0;
+ }
+
+ if (dataset.acl.roles !== undefined) {
+ const map = new
Map(Object.entries(dataset.acl.roles));
+ dataset.roles = map.size;
+ } else {
+ dataset.roles = 0;
+ }
+ } else {
+ dataset.users = dataset.groups = dataset.roles = 0;
+ }
+ modifiedDatasetList[i] = dataset;
+ }
+ }
+ setDatasetListData(modifiedDatasetList);
+ setEntries(resp.data);
+ setPageCount(Math.ceil(totalCount / pageSize));
+ setResetpage({ page: gotoPage });
+ setLoader(false);
+ }
+ },
+ [updateTable, searchFilterParams]
+ );
+
+ const fetchServiceDefs = async () => {
+ let serviceDefsResp = [];
+ try {
+ serviceDefsResp = await fetchApi({
+ url: "plugins/definitions"
+ });
+ } catch (error) {
+ console.error(
+ `Error occurred while fetching Service Definitions or CSRF headers!
${error}`
+ );
+ }
+
+ setServiceDefs(serviceDefsResp.data.serviceDefs);
+ setContentLoader(false);
+ };
+
+ const fetchServices = async () => {
+ let servicesResp = [];
+ try {
+ servicesResp = await fetchApi({
+ url: "plugins/services"
+ });
+ } catch (error) {
+ console.error(
+ `Error occurred while fetching Services or CSRF headers! ${error}`
+ );
+ }
+
+ setServices(servicesResp.data.services);
+ setContentLoader(false);
+ };
+
+ const fetchZones = async () => {
+ let zonesResp;
+ try {
+ zonesResp = await fetchApi({
+ url: "zones/zones"
+ });
+ } catch (error) {
+ console.error(`Error occurred while fetching Zones! ${error}`);
+ }
+
+ setZones(sortBy(zonesResp.data.securityZones, ["name"]));
+ setContentLoader(false);
+ };
+
+ const toggleChange = () => {
+ let currentParams = Object.fromEntries([...searchParams]);
+ currentParams["excludeServiceUser"] = !checked;
+ localStorage.setItem("excludeServiceUser", JSON.stringify(!checked));
+ setSearchParams(currentParams);
+ setAccessLogs([]);
+ setChecked(!checked);
+ setLoader(true);
+ setUpdateTable(moment.now());
+ };
+
+ const handleClosePolicyId = () => setPolicyViewModal(false);
+ const handleClose = () => setShowRowModal(false);
+ const rowModal = (row) => {
+ setShowRowModal(true);
+ setRowData(row.original);
+ };
+
+ const openModal = (policyDetails) => {
+ let policyParams = pick(policyDetails, [
+ "eventTime",
+ "policyId",
+ "policyVersion"
+ ]);
+ setPolicyViewModal(true);
+ setPolicyDetails(policyDetails);
+ setPolicyParamsData(policyParams);
+ fetchVersions(policyDetails.policyId);
+ };
+
+ const addDataset = () => {
+ navigate("/gds/create");
+ };
+
+ const columns = React.useMemo(
+ () => [
+ {
+ Header: "Id",
+ accessor: "id",
+ width: 25,
+ disableResizing: true,
+ disableSortBy: true,
+ getResizerProps: () => { },
+ Cell: (rawValue) => {
+ return (
+ <div className="position-relative text-center">
+ <Link
+ title="Edit"
+ to={`/gds/dataset/${rawValue.value}/detail`}
+ >
+ {rawValue.value}
+ </Link>
+ </div>
+ );
+ }
+ },
+ {
+ Header: "Name",
+ accessor: "name",
+ width: 250,
+ disableResizing: true,
+ disableSortBy: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "Created",
+ accessor: "createTime",
+ Cell: (rawValue) => {
+ return dateFormat(rawValue.value, "mm/dd/yyyy h:MM:ss TT");
+ },
+ width: 170,
+ disableResizing: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "Last Updated",
+ accessor: "updateTime",
+ Cell: (rawValue) => {
+ return dateFormat(rawValue.value, "mm/dd/yyyy h:MM:ss TT");
+ },
+ width: 170,
+ disableResizing: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "DATASHARE",
+ id: "datashareInfo",
+ disableResizing: true,
+ columns: [
+ {
+ Header: "Active",
+ accessor: "active",
+ width: 80,
+ disableResizing: true,
+ disableSortBy: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "Pending",
+ accessor: "pending",
+ width: 80,
+ disableResizing: true,
+ disableSortBy: true,
+ getResizerProps: () => {}
+ }
+ ]
+ },
+ {
+ Header: "SHARED WITH",
+ id: "sharedWithInfo",
+ disableResizing: true,
+ columns: [
+ {
+ Header: "Users",
+ //accessor: "users",
+ width: 60,
+ disableResizing: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "Groups",
+ //accessor: "groups",
+ width: 60,
+ disableResizing: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "Roles",
+ //accessor: "roles",
+ width: 60,
+ disableResizing: true,
+ getResizerProps: () => {}
+ },
+ {
+ Header: "Projects",
+ accessor: "projects",
+ width: 70,
+ disableResizing: true,
+ getResizerProps: () => {}
+ }
+ ]
+ },
+ {
+ Header: "Actions",
+ accessor: "actions",
+ Cell: ({ row: { original } }) => {
+ return (
+ <div>
+ {(isSystemAdmin() || isKeyAdmin() || isUser()) && (
+ <>
+ <Button
+ variant="danger"
+ size="sm"
+ title="Delete"
+ onClick={() =>
+ toggleConfirmModalForDelete(original.id, original.name)
+ }
+ data-name="deletePolicy"
+ data-id={original.id}
+ data-cy={original.id}
+ >
+ <i className="fa-fw fa fa-trash fa-fw fa fa-large"></i>
+ </Button>
+ </>
+ )}
+ </div>
+ );
+ },
+ width: 60,
+ disableSortBy: true
+ }
+ ],
+ []
+ );
+
+ const getDefaultSort = React.useMemo(
+ () => [
+ {
+ id: "eventTime",
+ desc: true
+ }
+ ],
+ []
+ );
+
+ const updateSearchFilter = (filter) => {
+ let { searchFilterParam, searchParam } = parseSearchFilter(
+ filter,
+ searchFilterOptions
+ );
+
+ searchParam["excludeServiceUser"] = checked;
+
+ setSearchFilterParams(searchFilterParam);
+ setSearchParams(searchParam);
+ localStorage.setItem("bigData", JSON.stringify(searchParam));
+
+ if (typeof resetPage?.page === "function") {
+ resetPage.page(0);
+ }
+ };
+
+ const searchFilterOptions = [
+ {
+ category: "datasetName",
+ label: "Dataset Name",
+ urlLabel: "datasetName",
+ type: "text"
+ }
+ ];
+
+ return contentLoader ? (
+ <Loader />
+ ) : (
+ <>
+ <div className="gds-header-wrapper">
+ <h3 className="gds-header bold">My Datasets</h3>
+ </div>
+ <div className="wrap">
+ <React.Fragment>
+ <BlockUi isUiBlock={blockUI} />
+ <Row className="mb-4">
+ <Col sm={10} className="usr-grp-role-search-width">
+ <StructuredFilter
+ key="user-listing-search-filter"
+ placeholder="Search for your users..."
+ options={sortBy(searchFilterOptions, ["label"])}
+ onChange={updateSearchFilter}
+ defaultSelected={defaultSearchFilterParams}
+ />
+ </Col>
+ {isSystemAdmin() && (
+ <Col sm={2} className="gds-button">
+ <Button
+ variant="primary"
+ size="sm"
+ className="btn-sm"
+ onClick={addDataset}
+ >
+ Create Dataset
+ </Button>
+ </Col>
+ )}
+
+ </Row>
+ <XATableLayout
+ data={datasetListData}
+ columns={columns}
+ fetchData={fetchDatasetList}
+ totalCount={entries && entries.totalCount}
+ loading={loader}
+ pageCount={pageCount}
+ getRowProps={(row) => ({
+ onClick: (e) => {
+ e.stopPropagation();
+ rowModal(row);
+ }
+ })}
+ columnHide={false}
+ columnResizable={false}
+ columnSort={true}
+ defaultSort={getDefaultSort}
+ />
+
+ </React.Fragment>
+
+
+
+ <Modal show={deleteDatasetModal.showPopup} onHide={toggleClose}>
+ <Modal.Header closeButton>
+ <span className="text-word-break">
+ Are you sure you want to delete dataset "
+ <b>{deleteDatasetModal?.datasetDetails?.datasetName}</b>" ?
+ </span>
+ </Modal.Header>
+ <Modal.Footer>
+ <Button variant="secondary" size="sm" onClick={toggleClose}>
+ Close
+ </Button>
+ <Button
+ variant="primary"
+ size="sm"
+ onClick={() =>
+
handleDeleteClick(deleteDatasetModal.datasetDetails.datasetID)
+ }
+ >
+ OK
+ </Button>
+ </Modal.Footer>
+ </Modal>
+ </div>
+ </>
+ );
+}
+
+export default DatasetListing;
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Datashare/DatashareListing.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Datashare/DatashareListing.jsx
new file mode 100644
index 000000000..4816f96c3
--- /dev/null
+++
b/security-admin/src/main/webapp/react-webapp/src/views/GovernedData/Datashare/DatashareListing.jsx
@@ -0,0 +1,26 @@
+/*
+ * 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 from "react";
+
+const DatashareListing = () => {
+
+}
+
+export default DatashareListing;
\ No newline at end of file
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
index e0ad55d59..084942b6f 100644
--- a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
+++ b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBar.jsx
@@ -304,6 +304,29 @@ export const SideBar = () => {
</li>
)}
+ {hasAccessToTab("Governed Data Sharing") && (
+ <li
+ className={
+ isActive !== null && isActive === "gdsCollapse"
+ ? "selected"
+ : undefined
+ }
+ >
+ <Button
+ id="tagButton"
+ className={activeClass("GDS")}
+ onClick={() => {
+ setActive("gdsCollapse");
+ setAccountDrawer(false);
+ setDrawer(true);
+ }}
+ >
+ <img src={tagsIcon} />
+ <span>Governed Data Sharing</span>
+ </Button>
+ </li>
+ )}
+
{hasAccessToTab("Reports") && (
<li>
<NavLink
diff --git
a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
index aa4a2d890..ee5be0fc5 100644
---
a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
+++
b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx
@@ -400,6 +400,41 @@ export const SideBarBody = (props) => {
/>
</div>
+ <div
+ id="gdsCollapse"
+ className={
+ activeMenu !== null && activeMenu === "gdsCollapse"
+ ? "show-menu"
+ : "hide-menu"
+ }
+ >
+ <div className="drawer-menu-title">
+ <span>GOVERNED DATA SHARING</span>
+ <span className="drawer-menu-close">
+ <img
+ src={closeIcon}
+ onClick={() => {
+ props.closeCollapse();
+ }}
+ />
+ </span>
+ </div>
+ <ul className="list-group list-group-flush">
+ <React.Fragment>
+ <li className="list-group-item">
+ <NavLink to="/gds/datasetlisting"
+ onClick={() => {
+ props.closeCollapse();
+ }}
+ className="list-group-item"
+ >
+ My Datasets
+ </NavLink>
+ </li>
+ </React.Fragment>
+ </ul>
+ </div>
+
<div
id="auditCollapse"
className={