From b269e7a6aa91a98142d9a2b9f9b0a9bf424904a9 Mon Sep 17 00:00:00 2001 From: darth-coder00 <86726556+darth-coder00@users.noreply.github.com> Date: Thu, 12 Aug 2021 21:13:59 +0530 Subject: [PATCH] Updated landing page with stats data (#110) * Updated landing page * Minor change --- .../ui/src/assets/svg/data-assets.svg | 4 + .../resources/ui/src/assets/svg/service.svg | 4 + .../resources/ui/src/assets/svg/terms.svg | 3 + .../main/resources/ui/src/assets/svg/user.svg | 4 + .../ui/src/auth-provider/AuthProvider.tsx | 9 +- .../resources/ui/src/axiosAPIs/userAPI.ts | 9 +- .../AddServiceModal/AddServiceModal.tsx | 8 +- .../src/components/my-data/MyDataHeader.tsx | 105 ++++++++++++++++++ .../resources/ui/src/constants/constants.ts | 1 + .../resources/ui/src/interface/types.d.ts | 13 +++ .../resources/ui/src/pages/my-data/index.tsx | 18 ++- .../resources/ui/src/pages/services/index.tsx | 18 +-- .../resources/ui/src/pages/tags/index.tsx | 4 +- .../resources/ui/src/utils/ServiceUtils.ts | 67 ++++++++++- .../main/resources/ui/src/utils/SvgUtils.tsx | 24 ++++ 15 files changed, 264 insertions(+), 27 deletions(-) create mode 100644 catalog-rest-service/src/main/resources/ui/src/assets/svg/data-assets.svg create mode 100644 catalog-rest-service/src/main/resources/ui/src/assets/svg/service.svg create mode 100644 catalog-rest-service/src/main/resources/ui/src/assets/svg/terms.svg create mode 100644 catalog-rest-service/src/main/resources/ui/src/assets/svg/user.svg create mode 100644 catalog-rest-service/src/main/resources/ui/src/components/my-data/MyDataHeader.tsx diff --git a/catalog-rest-service/src/main/resources/ui/src/assets/svg/data-assets.svg b/catalog-rest-service/src/main/resources/ui/src/assets/svg/data-assets.svg new file mode 100644 index 00000000000..d8ff11ec64f --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/assets/svg/data-assets.svg @@ -0,0 +1,4 @@ + + + + diff --git a/catalog-rest-service/src/main/resources/ui/src/assets/svg/service.svg b/catalog-rest-service/src/main/resources/ui/src/assets/svg/service.svg new file mode 100644 index 00000000000..8cb3fc6cff6 --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/assets/svg/service.svg @@ -0,0 +1,4 @@ + + + + diff --git a/catalog-rest-service/src/main/resources/ui/src/assets/svg/terms.svg b/catalog-rest-service/src/main/resources/ui/src/assets/svg/terms.svg new file mode 100644 index 00000000000..315ba95587f --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/assets/svg/terms.svg @@ -0,0 +1,3 @@ + + + diff --git a/catalog-rest-service/src/main/resources/ui/src/assets/svg/user.svg b/catalog-rest-service/src/main/resources/ui/src/assets/svg/user.svg new file mode 100644 index 00000000000..a5b35445b3e --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/assets/svg/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/catalog-rest-service/src/main/resources/ui/src/auth-provider/AuthProvider.tsx b/catalog-rest-service/src/main/resources/ui/src/auth-provider/AuthProvider.tsx index 255bb849d2f..6e319d33dc1 100644 --- a/catalog-rest-service/src/main/resources/ui/src/auth-provider/AuthProvider.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/auth-provider/AuthProvider.tsx @@ -38,7 +38,12 @@ import { getUserByName, getUsers, } from '../axiosAPIs/userAPI'; -import { oidcTokenKey, ROUTES, TIMEOUT } from '../constants/constants'; +import { + API_RES_MAX_SIZE, + oidcTokenKey, + ROUTES, + TIMEOUT, +} from '../constants/constants'; import { ClientErrors } from '../enums/axios.enum'; import { useAuth } from '../hooks/authHooks'; import useToastContext from '../hooks/useToastContext'; @@ -94,7 +99,7 @@ const AuthProvider: FunctionComponent = ({ // Moving this code here from App.tsx const getAllUsersList = (): void => { - getUsers().then((res) => { + getUsers('', API_RES_MAX_SIZE).then((res) => { appState.users = res.data.data; }); }; diff --git a/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/userAPI.ts b/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/userAPI.ts index 012a0393e88..2270975aa42 100644 --- a/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/userAPI.ts +++ b/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/userAPI.ts @@ -20,8 +20,13 @@ import { UserProfile } from 'Models'; import { getURLWithQueryFields } from '../utils/APIUtils'; import APIClient from './index'; -export const getUsers = (arrQueryFields?: string): Promise => { - const url = getURLWithQueryFields('/users', arrQueryFields); +export const getUsers = ( + arrQueryFields?: string, + limit?: number +): Promise => { + const url = + `${getURLWithQueryFields('/users', arrQueryFields)}` + + (limit ? `${arrQueryFields?.length ? '&' : '?'}limit=${limit}` : ''); return APIClient.get(url); }; diff --git a/catalog-rest-service/src/main/resources/ui/src/components/Modals/AddServiceModal/AddServiceModal.tsx b/catalog-rest-service/src/main/resources/ui/src/components/Modals/AddServiceModal/AddServiceModal.tsx index 68acf55d0db..7f260da92d9 100644 --- a/catalog-rest-service/src/main/resources/ui/src/components/Modals/AddServiceModal/AddServiceModal.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/components/Modals/AddServiceModal/AddServiceModal.tsx @@ -36,7 +36,7 @@ export type DatabaseObj = { serviceType: string; }; -export type DataObj = { +export type ServiceDataObj = { connectionUrl: string; description: string; driverClass: string; @@ -56,8 +56,8 @@ export type EditObj = { type Props = { header: string; serviceName: string; - serviceList: Array; - data?: DataObj; + serviceList: Array; + data?: ServiceDataObj; onSave: (obj: DatabaseObj, text: string, editData: EditObj) => void; onCancel: () => void; }; @@ -88,7 +88,7 @@ const generateOptions = (count: number, initialValue = 0) => { )); }; -const generateName = (data: Array) => { +const generateName = (data: Array) => { const newArr: string[] = []; data.forEach((d) => { newArr.push(d.name); diff --git a/catalog-rest-service/src/main/resources/ui/src/components/my-data/MyDataHeader.tsx b/catalog-rest-service/src/main/resources/ui/src/components/my-data/MyDataHeader.tsx new file mode 100644 index 00000000000..fb46534a73f --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/components/my-data/MyDataHeader.tsx @@ -0,0 +1,105 @@ +import { observer } from 'mobx-react'; +import React, { FunctionComponent, useEffect, useState } from 'react'; +import AppState from '../../AppState'; +import SVGIcons, { Icons } from '../../utils/SvgUtils'; + +type Props = { + countServices: number; + countAssets: number; +}; + +const LANDING_STATES = [ + { + title: 'Explore Assets', + description: + 'OpenMetadata has {countAssets} Assets. Click Explore on top menu to search, claim or follow your Data Assets', + }, + { + title: 'Register Services', + description: + 'Create a service to bring in metadata. Click Settings -> Services to explore available services.', + }, + { + title: 'Knowledgebase', + description: + 'Donec tempus eu dolor non vehicula. Etiam malesuada, sapien ac euismod condimentum.', + }, +]; + +const MyDataHeader: FunctionComponent = ({ + countAssets, + countServices, +}: Props) => { + const { users, userTeams } = AppState; + const [dataSummary, setdataSummary] = useState({ + asstes: { + icon: Icons.ASSETS, + data: `${countAssets} of Assets`, + }, + service: { + icon: Icons.SERVICE, + data: `${countServices} of Services`, + }, + user: { + icon: Icons.USERS, + data: `${users.length} of Users`, + }, + terms: { + icon: Icons.TERMS, + data: `${userTeams.length} of Teams`, + }, + }); + + const getFormattedDescription = (description: string) => { + return description.replaceAll('{countAssets}', countAssets.toString()); + }; + + useEffect(() => { + setdataSummary({ + asstes: { + icon: Icons.ASSETS, + data: `${countAssets} of Assets`, + }, + service: { + icon: Icons.SERVICE, + data: `${countServices} of Services`, + }, + user: { + icon: Icons.USERS, + data: `${users.length} of Users`, + }, + terms: { + icon: Icons.TERMS, + data: `${userTeams.length} of Teams`, + }, + }); + }, [userTeams, users, countAssets, countServices]); + + return ( + + + Open + Metadata + + + {Object.values(dataSummary).map((data, index) => ( + + + + {data.data} + + ))} + + + {LANDING_STATES.map((d, i) => ( + + {d.title} + {getFormattedDescription(d.description)} + + ))} + + + ); +}; + +export default observer(MyDataHeader); diff --git a/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts b/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts index 5c0e8cf0b6c..2a4d2c230ff 100644 --- a/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts +++ b/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts @@ -16,6 +16,7 @@ */ export const PAGE_SIZE = 10; +export const API_RES_MAX_SIZE = 100000; export const LIST_SIZE = 5; export const SIDEBAR_WIDTH_COLLAPSED = 290; export const SIDEBAR_WIDTH_EXPANDED = 290; diff --git a/catalog-rest-service/src/main/resources/ui/src/interface/types.d.ts b/catalog-rest-service/src/main/resources/ui/src/interface/types.d.ts index 1d817d9a952..0f37be79829 100644 --- a/catalog-rest-service/src/main/resources/ui/src/interface/types.d.ts +++ b/catalog-rest-service/src/main/resources/ui/src/interface/types.d.ts @@ -281,4 +281,17 @@ declare module 'Models' { aggregations: Record; }; }; + + export type ServiceCollection = { + name: string; + value: string; + }; + + export type ServiceData = { + collection: { + documentation: string; + href: string; + name: string; + }; + }; } diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/my-data/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/my-data/index.tsx index c56addd210f..5a5839908e2 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/my-data/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/my-data/index.tsx @@ -21,12 +21,14 @@ import React, { useEffect, useState } from 'react'; import { searchData } from '../../axiosAPIs/miscAPI'; import Error from '../../components/common/error/Error'; import Loader from '../../components/Loader/Loader'; +import MyDataHeader from '../../components/my-data/MyDataHeader'; import SearchedData from '../../components/searched-data/SearchedData'; import { ERROR404, ERROR500, PAGE_SIZE } from '../../constants/constants'; import { Ownership } from '../../enums/mydata.enum'; import useToastContext from '../../hooks/useToastContext'; import { formatDataResponse } from '../../utils/APIUtils'; import { getCurrentUserId } from '../../utils/CommonUtils'; +import { getAllServices } from '../../utils/ServiceUtils'; const MyDataPage: React.FC = (): React.ReactElement => { const showToast = useToastContext(); @@ -37,6 +39,8 @@ const MyDataPage: React.FC = (): React.ReactElement => { const [currentTab, setCurrentTab] = useState(1); const [error, setError] = useState(''); const [filter, setFilter] = useState(''); + const [countServices, setCountServices] = useState(0); + const [countAssets, setCountAssets] = useState(0); const getActiveTabClass = (tab: number) => { return tab === currentTab ? 'active' : ''; @@ -52,9 +56,11 @@ const MyDataPage: React.FC = (): React.ReactElement => { ) .then((res: SearchResponse) => { const hits = res.data.hits.hits; + const total = res.data.hits.total.value; if (hits.length > 0) { setTotalNumberOfValues(res.data.hits.total.value); setData(formatDataResponse(hits)); + setCountAssets(total); setIsLoading(false); } else { setData([]); @@ -93,7 +99,7 @@ const MyDataPage: React.FC = (): React.ReactElement => { setFilter(Ownership.OWNER); setCurrentPage(1); }}> - Owned + My Data { fetchTableData(); }, [currentPage, filter]); + useEffect(() => { + getAllServices() + .then((res) => setCountServices(res.length)) + .catch(() => setCountServices(0)); + }, []); + return ( <> {isLoading ? ( @@ -134,6 +146,10 @@ const MyDataPage: React.FC = (): React.ReactElement => { searchText="*" showResultCount={filter && data.length > 0 ? true : false} totalValue={totalNumberOfValue}> + {getTabs()} )} diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/services/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/services/index.tsx index 840add1d4a9..8725f367ff5 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/services/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/services/index.tsx @@ -16,6 +16,7 @@ */ import { AxiosResponse } from 'axios'; +import { ServiceCollection, ServiceData } from 'Models'; import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { @@ -30,21 +31,14 @@ import Loader from '../../components/Loader/Loader'; import { AddServiceModal, DatabaseObj, - DataObj, EditObj, + ServiceDataObj, } from '../../components/Modals/AddServiceModal/AddServiceModal'; import { getServiceDetailsPath } from '../../constants/constants'; import { NOSERVICE, PLUS } from '../../constants/services.const'; import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { stringToHTML } from '../../utils/StringsUtils'; import SVGIcons from '../../utils/SvgUtils'; -type ServiceData = { - collection: { - documentation: string; - href: string; - name: string; - }; -}; export type ApiData = { description: string; @@ -56,13 +50,11 @@ export type ApiData = { ingestionSchedule?: { repeatFrequency: string; startDate: string }; }; -type ServiceCollection = { name: string; value: string }; - const ServicesPage = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [serviceName] = useState('databaseServices'); - const [serviceList, setServiceList] = useState>([]); - const [editData, setEditData] = useState(); + const [serviceList, setServiceList] = useState>([]); + const [editData, setEditData] = useState(); const [isLoading, setIsLoading] = useState(false); const updateServiceList = ( @@ -102,7 +94,7 @@ const ServicesPage = () => { setEditData(undefined); }; - const handleEdit = (value: DataObj) => { + const handleEdit = (value: ServiceDataObj) => { setEditData(value); setIsModalOpen(true); }; diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/tags/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/tags/index.tsx index 0d4918a032c..4958382a373 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/tags/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/tags/index.tsx @@ -164,12 +164,12 @@ const TagsPage = () => { Tag Categories setIsAddingCategory((prevState) => !prevState)}> - + + {categories && diff --git a/catalog-rest-service/src/main/resources/ui/src/utils/ServiceUtils.ts b/catalog-rest-service/src/main/resources/ui/src/utils/ServiceUtils.ts index 85bbf2330ef..6859ac86567 100644 --- a/catalog-rest-service/src/main/resources/ui/src/utils/ServiceUtils.ts +++ b/catalog-rest-service/src/main/resources/ui/src/utils/ServiceUtils.ts @@ -1,13 +1,18 @@ +import { AxiosResponse } from 'axios'; +import { ServiceCollection, ServiceData } from 'Models'; +import { getServiceDetails, getServices } from '../axiosAPIs/serviceAPI'; +import { ServiceDataObj } from '../components/Modals/AddServiceModal/AddServiceModal'; import { - MYSQL, - REDSHIFT, BIGQUERY, HIVE, - POSTGRES, + MYSQL, ORACLE, + POSTGRES, + REDSHIFT, SNOWFLAKE, } from '../constants/services.const'; import { ServiceType } from '../enums/service.enum'; +import { ApiData } from '../pages/services'; export const serviceTypeLogo = (type: string) => { switch (type) { @@ -36,3 +41,59 @@ export const serviceTypeLogo = (type: string) => { return MYSQL; } }; + +const getAllServiceList = ( + allServiceCollectionArr: Array +): Promise> => { + let listArr = []; + + // fetch services of all individual collection + return new Promise>((resolve, reject) => { + if (allServiceCollectionArr.length) { + let promiseArr = []; + promiseArr = allServiceCollectionArr.map((obj) => { + return getServices(obj.value); + }); + Promise.all(promiseArr) + .then((result: AxiosResponse[]) => { + if (result.length) { + let serviceArr = []; + serviceArr = result.map((service) => service?.data?.data || []); + // converted array of arrays to array + const allServices = serviceArr.reduce( + (acc, el) => acc.concat(el), + [] + ); + listArr = allServices.map((s: ApiData) => { + return { ...s, ...s.jdbc }; + }); + resolve(listArr); + } else { + resolve([]); + } + }) + .catch((err) => reject(err)); + } else { + resolve([]); + } + }); +}; + +export const getAllServices = (): Promise> => { + return new Promise>((resolve, reject) => { + getServiceDetails().then((res: AxiosResponse) => { + let allServiceCollectionArr: Array = []; + if (res.data.data?.length) { + allServiceCollectionArr = res.data.data.map((service: ServiceData) => { + return { + name: service.collection.name, + value: service.collection.name, + }; + }); + } + getAllServiceList(allServiceCollectionArr) + .then((res) => resolve(res)) + .catch((err) => reject(err)); + }); + }); +}; diff --git a/catalog-rest-service/src/main/resources/ui/src/utils/SvgUtils.tsx b/catalog-rest-service/src/main/resources/ui/src/utils/SvgUtils.tsx index 43d80ed7ec1..79031b04048 100644 --- a/catalog-rest-service/src/main/resources/ui/src/utils/SvgUtils.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/utils/SvgUtils.tsx @@ -1,5 +1,6 @@ import React, { FunctionComponent } from 'react'; import IconSuccess from '../assets/svg/check.svg'; +import IconAsstest from '../assets/svg/data-assets.svg'; import IconError from '../assets/svg/error.svg'; import IconCheckCircle from '../assets/svg/ic-check-circle.svg'; import IconDelete from '../assets/svg/ic-delete.svg'; @@ -39,6 +40,9 @@ import IconUnique from '../assets/svg/icon-unique.svg'; import IconInfo from '../assets/svg/info.svg'; import LogoMonogram from '../assets/svg/logo-monogram.svg'; import Logo from '../assets/svg/logo.svg'; +import IconSetting from '../assets/svg/service.svg'; +import IconTerns from '../assets/svg/terms.svg'; +import IconUser from '../assets/svg/user.svg'; import IconWarning from '../assets/svg/warning.svg'; type Props = { @@ -90,6 +94,10 @@ export const Icons = { KEY: 'key', NOT_NULL: 'not-null', UNIQUE: 'unique', + ASSETS: 'assets', + SERVICE: 'service', + USERS: 'users', + TERMS: 'terms', }; const SVGIcons: FunctionComponent = ({ @@ -263,10 +271,26 @@ const SVGIcons: FunctionComponent = ({ case Icons.UNIQUE: IconComponent = IconUnique; + break; + case Icons.ASSETS: + IconComponent = IconAsstest; + break; case Icons.TOAST_INFO: IconComponent = IconInfo; + break; + case Icons.SERVICE: + IconComponent = IconSetting; + + break; + case Icons.USERS: + IconComponent = IconUser; + + break; + case Icons.TERMS: + IconComponent = IconTerns; + break; default:
{data.data}
{d.title}
{getFormattedDescription(d.description)}