Skip to content

Commit

Permalink
UI: Add data source detail page (feathr-ai#620)
Browse files Browse the repository at this point in the history
UI: Add data source detail page
  • Loading branch information
ahlag authored and hyingyang-linkedin committed Oct 25, 2022
1 parent 1544999 commit 8497219
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 12 deletions.
12 changes: 12 additions & 0 deletions registry/access_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ def get_project_datasources(project: str, requestor: User = Depends(project_read
return json.loads(response)


@router.get("/projects/{project}/datasources/{datasource}", name="Get a single data source by datasource Id [Read Access Required]")
def get_project_datasource(project: str, datasource: str, requestor: User = Depends(project_read_access)) -> list:
response = requests.get(url=f"{registry_url}/projects/{project}/datasources/{datasource}",
headers=get_api_header(requestor)).content.decode('utf-8')
ret = json.loads(response)

datasource_qualifiedName = ret['attributes']['qualifiedName']
validate_project_access_for_feature(
datasource_qualifiedName, requestor, AccessType.READ)
return ret


@router.get("/projects/{project}/features", name="Get features under my project [Read Access Required]")
def get_project_features(project: str, keyword: Optional[str] = None, requestor: User = Depends(project_read_access)) -> list:
response = requests.get(url=f"{registry_url}/projects/{project}/features",
Expand Down
11 changes: 11 additions & 0 deletions registry/purview-registry/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ def get_project_datasources(project: str) -> list:
return list([to_camel(e.to_dict()) for e in sources])


@router.get("/projects/{project}/datasources/{datasource}",tags=["Project"])
def get_datasource(project: str, datasource: str) -> dict:
p = registry.get_entity(project,True)
for s in p.attributes.sources:
if str(s.id) == datasource:
return s
# If datasource is not found, raise 404 error
raise HTTPException(
status_code=404, detail=f"Data Source {datasource} not found")


@router.get("/projects/{project}/features",tags=["Project"])
def get_project_features(project: str, keyword: Optional[str] = None) -> list:
atlasEntities = registry.get_project_features(project, keywords=keyword)
Expand Down
13 changes: 12 additions & 1 deletion registry/sql-registry/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,25 @@ def get_project_datasources(project: str) -> list:
return list([e.to_dict() for e in sources])


@router.get("/projects/{project}/datasources/{datasource}")
def get_datasource(project: str, datasource: str) -> dict:
p = registry.get_entity(project)
for s in p.attributes.sources:
if str(s.id) == datasource:
return s
# If datasource is not found, raise 404 error
raise HTTPException(
status_code=404, detail=f"Data Source {datasource} not found")


@router.get("/projects/{project}/features")
def get_project_features(project: str, keyword: Optional[str] = None, page: Optional[int] = None, limit: Optional[int] = None) -> list:
if keyword:
start = None
size = None
if page is not None and limit is not None:
start = (page - 1) * limit
size = limit
size = limit
efs = registry.search_entity(
keyword, [EntityType.AnchorFeature, EntityType.DerivedFeature], project=project, start=start, size=size)
feature_ids = [ef.id for ef in efs]
Expand Down
3 changes: 3 additions & 0 deletions ui/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
REACT_APP_AZURE_TENANT_ID=common
REACT_APP_API_ENDPOINT=http://127.0.0.1:8000
REACT_APP_ENABLE_RBAC=false
17 changes: 17 additions & 0 deletions ui/src/api/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ export const fetchDataSources = async (project: string) => {
});
};

export const fetchDataSource = async (
project: string,
dataSourceId: string
) => {
const axios = await authAxios(msalInstance);
return axios
.get<DataSource>(
`${getApiBaseUrl()}/projects/${project}/datasources/${dataSourceId}`,
{
params: { project: project, datasource: dataSourceId },
}
)
.then((response) => {
return response.data;
});
};

export const fetchProjects = async () => {
const axios = await authAxios(msalInstance);
return axios
Expand Down
5 changes: 5 additions & 0 deletions ui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Features from "./pages/feature/features";
import NewFeature from "./pages/feature/newFeature";
import FeatureDetails from "./pages/feature/featureDetails";
import DataSources from "./pages/dataSource/dataSources";
import DataSourceDetails from "./pages/dataSource/dataSourceDetails";
import Jobs from "./pages/jobs/jobs";
import Monitoring from "./pages/monitoring/monitoring";
import LineageGraph from "./pages/feature/lineageGraph";
Expand Down Expand Up @@ -44,6 +45,10 @@ const App = () => {
path="/projects/:project/features/:featureId"
element={<FeatureDetails />}
/>
<Route
path="/projects/:project/dataSources/:dataSourceId"
element={<DataSourceDetails />}
/>
<Route
path="/projects/:project/lineage"
element={<LineageGraph />}
Expand Down
64 changes: 60 additions & 4 deletions ui/src/components/dataSourceList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import React, { useCallback, useEffect, useState } from "react";
import { Form, Select, Table } from "antd";
import { useNavigate, Link } from "react-router-dom";
import { Form, Select, Table, Button, Menu, Dropdown, Tooltip } from "antd";
import { DownOutlined } from "@ant-design/icons";
import { DataSource } from "../models/model";
import { fetchDataSources, fetchProjects } from "../api";

const DataSourceList = () => {
type Props = {
projectProp: string;
keywordProp: string;
};

const DataSourceList = ({ projectProp, keywordProp }: Props) => {
const navigate = useNavigate();
const columns = [
{
title: <div style={{ userSelect: "none" }}>Name</div>,
key: "name",
align: "center" as "center",
width: 120,
render: (row: DataSource) => {
return row.attributes.name;
return (
<Button
type="link"
onClick={() => {
navigate(`/projects/${project}/dataSources/${row.guid}`);
}}
>
{row.displayText}
</Button>
);
},
onCell: () => {
return {
Expand Down Expand Up @@ -54,7 +71,7 @@ const DataSourceList = () => {
},
},
{
title: <div>Pre Processing</div>,
title: <div>Preprocessing</div>,
key: "preprocessing",
align: "center" as "center",
width: 190,
Expand Down Expand Up @@ -101,6 +118,45 @@ const DataSourceList = () => {
};
},
},
{
title: (
<div>
Action{" "}
<Tooltip
title={
<Link style={{ color: "cyan" }} to="/help">
Learn more
</Link>
}
></Tooltip>
</div>
),
key: "action",
align: "center" as "center",
width: 120,
render: (name: string, row: DataSource) => (
<Dropdown
overlay={() => {
return (
<Menu>
<Menu.Item key="view">
<Button
type="link"
onClick={() => {
navigate(`/projects/${project}/dataSources/${row.guid}`);
}}
>
View Details
</Button>
</Menu.Item>
</Menu>
);
}}
>
<Button icon={<DownOutlined />}>action</Button>
</Dropdown>
),
},
];
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
Expand Down
12 changes: 7 additions & 5 deletions ui/src/components/userRoles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const UserRoles = () => {
sorter: {
compare: (a: UserRole, b: UserRole) => a.scope.localeCompare(b.scope),
multiple: 3,
}
},
},
{
title: <div style={{ userSelect: "none" }}>Role</div>,
Expand All @@ -53,9 +53,10 @@ const UserRoles = () => {
key: "userName",
align: "center" as "center",
sorter: {
compare: (a: UserRole, b: UserRole) => a.userName.localeCompare(b.userName),
compare: (a: UserRole, b: UserRole) =>
a.userName.localeCompare(b.userName),
multiple: 1,
}
},
},
{
title: <div>Permissions</div>,
Expand Down Expand Up @@ -93,9 +94,10 @@ const UserRoles = () => {
key: "createTime",
align: "center" as "center",
sorter: {
compare: (a: UserRole, b: UserRole) => a.createTime.localeCompare(b.createTime),
compare: (a: UserRole, b: UserRole) =>
a.createTime.localeCompare(b.createTime),
multiple: 2,
}
},
},
{
title: "Action",
Expand Down
112 changes: 112 additions & 0 deletions ui/src/pages/dataSource/dataSourceDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React from "react";
import { LoadingOutlined } from "@ant-design/icons";
import { useNavigate, useParams } from "react-router-dom";
import { Alert, Button, Card, Col, Row, Spin, Typography } from "antd";
import { QueryStatus, useQuery } from "react-query";
import { AxiosError } from "axios";
import { fetchDataSource } from "../../api";
import { DataSource, DataSourceAttributes } from "../../models/model";

const { Title } = Typography;

type DataSourceKeyProps = { dataSource: DataSource };
const DataSourceKey = ({ dataSource }: DataSourceKeyProps) => {
const keys = dataSource.attributes;
return (
<>
{keys && (
<Col span={24}>
<Card className="card">
<Title level={4}>Data Source Attributes</Title>
<div className="dataSource-container">
<p>Name: {keys.name}</p>
<p>Type: {keys.type}</p>
<p>Path: {keys.path}</p>
<p>Preprocessing: {keys.preprocessing}</p>
<p>Event Timestamp Column: {keys.eventTimestampColumn}</p>
<p>Timestamp Format: {keys.timestampFormat}</p>
<p>Qualified Name: {keys.qualifiedName}</p>
<p>Tags: {JSON.stringify(keys.tags)}</p>
</div>
</Card>
</Col>
)}
</>
);
};

type Params = {
project: string;
dataSourceId: string;
};

const DataSourceDetails = () => {
const { project, dataSourceId } = useParams() as Params;
const navigate = useNavigate();
const loadingIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
const { status, error, data } = useQuery<DataSource, AxiosError>(
["dataSourceId", dataSourceId],
() => fetchDataSource(project, dataSourceId)
);

const render = (status: QueryStatus): JSX.Element => {
switch (status) {
case "error":
return (
<Card>
<Alert
message="Error"
description={error?.message}
type="error"
showIcon
/>
</Card>
);
case "idle":
return (
<Card>
<Spin indicator={loadingIcon} />
</Card>
);
case "loading":
return (
<Card>
<Spin indicator={loadingIcon} />
</Card>
);
case "success":
if (data === undefined) {
return (
<Card>
<Alert
message="Error"
description="Data does not exist..."
type="error"
showIcon
/>
</Card>
);
} else {
return (
<>
<Button type="link" onClick={() => navigate(-1)}>
dataSource list {">"}
</Button>
<Card>
<Title level={3}>{data.attributes.name}</Title>
<div>
<Row>
<DataSourceKey dataSource={data} />
</Row>
</div>
</Card>
</>
);
}
}
};

return <div className="page">{render(status)}</div>;
};

export default DataSourceDetails;
8 changes: 6 additions & 2 deletions ui/src/pages/dataSource/dataSources.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import React from "react";
import { Card, Typography } from "antd";
import { useSearchParams } from "react-router-dom";
import DataSourceList from "../../components/dataSourceList";

const { Title } = Typography;

const DataSources = () => {
const [searchParams] = useSearchParams();
const project = (searchParams.get("project") as string) ?? "";
const keyword = (searchParams.get("keyword") as string) ?? "";

return (
<div className="page">
<Card>
<Title level={3}>Data Sources</Title>
<DataSourceList />
<DataSourceList projectProp={project} keywordProp={keyword} />
</Card>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions ui/src/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@
.feature-container p {
break-inside: avoid-column;
}

.dataSource-container {
column-count: 1;
}

0 comments on commit 8497219

Please sign in to comment.