Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feathr UI: API spec alignment and ux experience improvement #303

Merged
merged 9 commits into from
Jun 2, 2022
41 changes: 40 additions & 1 deletion ui/src/api/api.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Axios from "axios";
import { Features, IDataSource, IFeature, IFeatureDetail, IFeatureLineage } from "../models/model";
import { Features, IDataSource, IFeature, IFeatureDetail, IFeatureLineage, IUserRole, RoleForm } from "../models/model";
import mockUserRole from "./mock/userrole.json";

const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT + "/api";
const purview = "feathrazuretest3-purview1"
Expand Down Expand Up @@ -101,3 +102,41 @@ export const deleteFeature = async (qualifiedName: string) => {
});
};

export const listUserRole = async () => {
let data:IUserRole[] = mockUserRole
return data
};

export const getUserRole = async (userName: string) => {
return await Axios
.get<IUserRole>(`${ API_ENDPOINT }/user/${userName}/userroles?code=${ token }`, {})
.then((response) => {
return response.data;
})
}

export const addUserRole = async (roleForm: RoleForm) => {
return await Axios
.post(`${ API_ENDPOINT }/user/${roleForm.userName}/userroles/new`, roleForm,
{
headers: { "Content-Type": "application/json;" },
params: {},
}).then((response) => {
return response;
}).catch((error) => {
return error.response;
});
}

export const deleteUserRole = async (roleForm: RoleForm) => {
return await Axios
.post(`${ API_ENDPOINT }/user/${roleForm.userName}/userroles/delete`, roleForm,
{
headers: { "Content-Type": "application/json;" },
params: {},
}).then((response) => {
return response;
}).catch((error) => {
return error.response;
});
}
blrchen marked this conversation as resolved.
Show resolved Hide resolved
63 changes: 63 additions & 0 deletions ui/src/api/mock/userrole.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[
{
"id": 1,
"scope": "Global",
"userName": "edwinc@microsoft.com",
"roleName": "Admin",
"permissions": [
"Read",
"Write",
"Management"
],
"createReason": "Resource Owner",
"createTime": "2022/5/15"
},
{
"id": 1,
"scope": "Global",
"userName": "yuqwe@microsoft.com",
"roleName": "Admin",
"permissions": [
"Read",
"Write",
"Management"
],
"createReason": "Test Purpose",
"createTime": "2022/5/16"
},
{
"id": 2,
"scope": "Project A: Frontend Datasets",
"userName": "blairch@microsoft.com",
"roleName": "Producer",
"permissions": [
"Read",
"Write"
],
"createReason": "Project Owner",
"createTime": "2022/5/16"
},
{
"id": 3,
"scope": "Project B: Backend Datasets",
"userName": "xuchen@microsoft.com",
"roleName": "Producer",
"permissions": [
"Read",
"Write"
],
"createReason": "Project Owner",
"createTime": "2022/5/16"
},
{
"id": 4,
"scope": "Project B: Backend Datasets",
"userName": "yihgu@microsoft.com",
"roleName": "Consumer",
"permissions": [
"Read"
],
"createReason": "Data Engineering",
"createTime": "2022/5/17"
}
]
4 changes: 4 additions & 0 deletions ui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import DataSources from "./pages/dataSource/dataSources";
import Jobs from "./pages/jobs/jobs";
import Monitoring from "./pages/monitoring/monitoring";
import LineageGraph from "./pages/feature/lineageGraph";
import Management from "./pages/management/management";
import RoleManagement from "./pages/management/roleManagement";

type Props = {};
blrchen marked this conversation as resolved.
Show resolved Hide resolved
const queryClient = new QueryClient();
Expand Down Expand Up @@ -43,6 +45,8 @@ const App: React.FC<Props> = () => {
<Route path="/projects/:project/lineage" element={ <LineageGraph /> } />
<Route path="/jobs" element={ <Jobs /> } />
<Route path="/monitoring" element={ <Monitoring /> } />
<Route path="/management" element={ <Management /> } />
<Route path="/role-management" element={ <RoleManagement /> } />
</Routes>
</Layout>
</Layout>
Expand Down
88 changes: 88 additions & 0 deletions ui/src/components/roleManagementForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { CSSProperties, useEffect, useState } from 'react';
import { BackTop, Button, Form, Input, Select, Space } from 'antd';
import { Navigate } from "react-router-dom";
import { addUserRole} from '../api';
import { UpCircleOutlined } from '@ant-design/icons'
import { RoleForm, IUserRole } from "../models/model";

type RoleManagementFormProps = {
isNew: boolean;
editMode: boolean;
userRole?: IUserRole;
};

const Admin = "Admin"
const Producer = "Producer"
const Consumer = "Consumer"

const RoleManagementForm: React.FC<RoleManagementFormProps> = ({ isNew, editMode, userRole }) => {
const [fireRedirect, setRedirect] = useState<boolean>(false);
const [createLoading, setCreateLoading] = useState<boolean>(false);

const [form] = Form.useForm();
const { Option } = Select;

useEffect(() => {
if (userRole !== undefined) {
form.setFieldsValue(userRole);
}
}, [userRole, form]);

const onClickSave = async () => {
setCreateLoading(true);
const roleForm: RoleForm = form.getFieldsValue();
await addUserRole(roleForm);
setCreateLoading(false);
}

const styling: CSSProperties = { width: "92%" }
return (
<>
<Form
form={form}
style={styling}
labelCol={{ span: 4 }}
wrapperCol={{ span: 24 }}
layout="horizontal"
initialValues={{ remember: true }}
>
<Space direction="vertical" size="large" style={styling}>
<Form.Item name="scope" label="Scope" rules={[{ required: true }]}>
<Input disabled={!editMode} />
</Form.Item>
<Form.Item name="userName" label="User Name" rules={[{ required: true }]}>
<Input disabled={!editMode} />
</Form.Item>
<Form.Item name="roleName" label="Role Name" rules={[{ required: true }]}>
<Select
placeholder="Select a role to assign:"
allowClear
>
<Option value={Admin}>{Admin}</Option>
<Option value={Producer}>{Producer}</Option>
<Option value={Consumer}>{Consumer}</Option>
</Select>
</Form.Item>
<Form.Item name="Reason" label="Reason" rules={[{ required: true }]}>
<Input disabled={!editMode} />
</Form.Item>
</Space>
<Form.Item wrapperCol={{ offset: 11 }}>
<Button type="primary" htmlType="button" title="submit and go back to list"
style={{ float: 'inline-start' }}
onClick={onClickSave}
loading={createLoading}
disabled={!editMode}
>
Submit
</Button>
</Form.Item>
<BackTop style={{ marginBottom: '5%', marginRight: '20px' }}><UpCircleOutlined
style={{ fontSize: '400%', color: '#3F51B5' }} /></BackTop>
</Form>
{ fireRedirect && (<Navigate to={ '/features' }></Navigate>) }
</>
);
};

export default RoleManagementForm
3 changes: 1 addition & 2 deletions ui/src/components/sidemenu/siteMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { Layout, Menu } from 'antd';
import { CopyOutlined, DatabaseOutlined, EyeOutlined, RocketOutlined } from '@ant-design/icons';
import { CopyOutlined, DatabaseOutlined, EyeOutlined, RocketOutlined} from '@ant-design/icons';
import { Link } from 'react-router-dom';

const { Sider } = Layout;
Expand Down
150 changes: 150 additions & 0 deletions ui/src/components/userRoles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Modal, PageHeader, Row, Space, Table, Tag } from "antd";
import { IUserRole } from "../models/model";
import { listUserRole } from "../api";
import { useMsal } from '@azure/msal-react';

const UserRoles: React.FC = () => {
const navigate = useNavigate();
const [visible, setVisible] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [modalText, setModalText] = React.useState('Content of the modal');

const { accounts, instance } = useMsal();
const showModal = () => {
setVisible(true);
setModalText("This Role Assignment will be deleted.");
};
const handleOk = () => {
setModalText('The modal will be closed after two seconds');
setConfirmLoading(true);
setTimeout(() => {
setVisible(false);
setConfirmLoading(false);
}, 2000);
};

const handleCancel = () => {
console.log('Clicked cancel button');
setVisible(false);
};
const columns = [
{
title: <div>Scope</div>,
dataIndex: 'scope',
key: 'scope',
align: 'center' as 'center',
},
{
title: <div>User</div>,
dataIndex: 'userName',
key: 'userName',
align: 'center' as 'center',
},
{
title: <div style={{ userSelect: "none" }}>Role</div>,
dataIndex: 'roleName',
key: 'roleName',
align: 'center' as 'center',
},
{
title: <div>Permissions</div>,
key: 'permissions',
dataIndex: 'permissions',
render: (tags: any[]) => (
<>
{tags.map(tag => {
let color = tag.length > 5 ? 'red' : 'green';
if (tag === 'Write') color = 'blue'
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</>
),
},
{
title: <div>Create Reason</div>,
dataIndex: 'createReason',
key: 'createReason',
align: 'center' as 'center',
},
{
title: <div>Create Time</div>,
dataIndex: 'createTime',
key: 'createTime',
align: 'center' as 'center',
},
{
title: 'Action',
key: 'action',
render: (text: any) => (
<Space size="middle">
<Button type="primary" onClick={showModal}>
Delete
</Button>
<Modal
title="Please Confirm"
visible={visible}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
>
<p>{modalText}</p>
</Modal>
</Space>
),
},
];
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [tableData, setTableData] = useState<IUserRole[]>();

const fetchData = useCallback(async () => {
setLoading(true);
const result = await listUserRole();
console.log(result);
setPage(page);
setTableData(result);
setLoading(false);
}, [page])

const onClickRoleAssign = () => {
navigate('/role-management');
return;
}

useEffect(() => {
fetchData()
}, [])

return (
<div>
<PageHeader
title={`Role Managements`}
style={{ backgroundColor: "white", paddingLeft: "50px", paddingRight: "50px" }}>
<Row>
<div style={{ flex: 1 }}>
<>
<p>
Below is the mock data for now. Will connect with Management APIs.
</p>
</>
</div>
</Row>
</PageHeader>
<Space style={{ marginBottom: 16 }}>
<Button type="primary" onClick={onClickRoleAssign}
style={{ position: "absolute", right: "12px", top: "56px" }}>
+ Create Role Assignment
</Button>
</Space>
<Table dataSource={tableData} columns={columns} />;
</div>
);
}

export default UserRoles;
Loading