Added Dashboard services UI and api integration (#422)

Co-authored-by: aashitk <aashit.kothari@deuexsolutions.com>
This commit is contained in:
darth-coder00 2021-09-07 20:14:58 +05:30 committed by GitHub
parent 5c01546daf
commit f48e34be9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 251 additions and 71 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -44,6 +44,9 @@ export type DataObj = {
}; };
brokers?: Array<string>; brokers?: Array<string>;
schemaRegistry?: string; schemaRegistry?: string;
dashboardUrl?: string;
username?: string;
password?: string;
}; };
type DatabaseService = { type DatabaseService = {
@ -57,6 +60,12 @@ type MessagingService = {
schemaRegistry: string; schemaRegistry: string;
}; };
type DashboardService = {
dashboardUrl: string;
username: string;
password: string;
};
export type ServiceDataObj = { export type ServiceDataObj = {
description: string; description: string;
href: string; href: string;
@ -65,7 +74,8 @@ export type ServiceDataObj = {
serviceType: string; serviceType: string;
ingestionSchedule?: { repeatFrequency: string; startDate: string }; ingestionSchedule?: { repeatFrequency: string; startDate: string };
} & Partial<DatabaseService> & } & Partial<DatabaseService> &
Partial<MessagingService>; Partial<MessagingService> &
Partial<DashboardService>;
export type EditObj = { export type EditObj = {
edit: boolean; edit: boolean;
@ -86,10 +96,11 @@ type ErrorMsg = {
name: boolean; name: boolean;
url?: boolean; url?: boolean;
// port: boolean; // port: boolean;
// userName: boolean;
// password: boolean;
driverClass?: boolean; driverClass?: boolean;
broker?: boolean; broker?: boolean;
dashboardUrl?: boolean;
username?: boolean;
password?: boolean;
}; };
type EditorContentRef = { type EditorContentRef = {
getEditorContent: () => string; getEditorContent: () => string;
@ -176,6 +187,9 @@ export const AddServiceModal: FunctionComponent<Props> = ({
const [schemaRegistry, setSchemaRegistry] = useState( const [schemaRegistry, setSchemaRegistry] = useState(
data?.schemaRegistry || '' data?.schemaRegistry || ''
); );
const [dashboardUrl, setDashboardUrl] = useState(data?.dashboardUrl || '');
const [username, setUsername] = useState(data?.username || '');
const [password, setPassword] = useState(data?.password || '');
const [frequency, setFrequency] = useState( const [frequency, setFrequency] = useState(
fromISOString(data?.ingestionSchedule?.repeatFrequency) fromISOString(data?.ingestionSchedule?.repeatFrequency)
); );
@ -261,18 +275,27 @@ export const AddServiceModal: FunctionComponent<Props> = ({
}; };
const onSaveHelper = (value: ErrorMsg) => { const onSaveHelper = (value: ErrorMsg) => {
const { selectService, name, url, driverClass, broker } = value; const {
selectService,
name,
url,
driverClass,
broker,
dashboardUrl,
username,
password,
} = value;
return ( return (
!sameNameError && !sameNameError &&
!selectService && !selectService &&
!name && !name &&
!url && !url &&
// !port &&
// !userName &&
// !password &&
!driverClass && !driverClass &&
!broker !broker &&
!dashboardUrl &&
!username &&
!password
); );
}; };
@ -287,9 +310,6 @@ export const AddServiceModal: FunctionComponent<Props> = ({
setMsg = { setMsg = {
...setMsg, ...setMsg,
url: !url, url: !url,
// port: !port,
// userName: !userName,
// password: !password,
driverClass: !driverClass, driverClass: !driverClass,
}; };
} }
@ -303,6 +323,17 @@ export const AddServiceModal: FunctionComponent<Props> = ({
}; };
} }
break;
case ServiceCategory.DASHBOARD_SERVICES:
{
setMsg = {
...setMsg,
dashboardUrl: !dashboardUrl,
username: !username,
password: !password,
};
}
break; break;
default: default:
break; break;
@ -347,6 +378,17 @@ export const AddServiceModal: FunctionComponent<Props> = ({
}; };
} }
break;
case ServiceCategory.DASHBOARD_SERVICES:
{
dataObj = {
...dataObj,
dashboardUrl: dashboardUrl,
username: username,
password: password,
};
}
break; break;
default: default:
break; break;
@ -374,54 +416,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
/> />
{showErrorMsg.url && errorMsg('Connection url is required')} {showErrorMsg.url && errorMsg('Connection url is required')}
</div> </div>
{/* didn't removed below code as it will be need in future relase */}
{/* <div>
<label className="tw-block tw-form-label" htmlFor="port">
{requiredField('Connection Port:')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
id="port"
name="port"
type="number"
value={port}
onChange={handleValidation}
/>
{showErrorMsg.port && errorMsg('Port is required')}
</div> */}
</div> </div>
{/* <div className="tw-mt-4 tw-grid tw-grid-cols-2 tw-gap-2 ">
<div>
<label className="tw-block tw-form-label" htmlFor="userName">
{requiredField('Username:')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
id="userName"
name="userName"
type="text"
value={userName}
onChange={handleValidation}
/>
{showErrorMsg.userName && errorMsg('Username is required')}
</div>
<div>
<label className="tw-block tw-form-label" htmlFor="password">
{requiredField('Password:')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
id="password"
name="password"
type="password"
value={password}
onChange={handleValidation}
/>
{showErrorMsg.password && errorMsg('Password is required')}
</div>
</div> */}
<div className="tw-mt-4"> <div className="tw-mt-4">
<label className="tw-block tw-form-label" htmlFor="database"> <label className="tw-block tw-form-label" htmlFor="database">
Database: Database:
@ -500,12 +495,66 @@ export const AddServiceModal: FunctionComponent<Props> = ({
); );
}; };
const getDashboardFields = (): JSX.Element => {
return (
<>
<div className="tw-mt-4">
<label className="tw-block tw-form-label" htmlFor="dashboard-url">
{requiredField('Dashboard Url:')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
id="dashboard-url"
name="dashboard-url"
placeholder="http(s)://hostname:port"
type="text"
value={dashboardUrl}
onChange={(e) => setDashboardUrl(e.target.value)}
/>
{showErrorMsg.dashboardUrl && errorMsg('Dashboard url is required')}
</div>
<div className="tw-mt-4">
<label className="tw-block tw-form-label" htmlFor="username">
{requiredField('Username:')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
id="username"
name="username"
placeholder="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
{showErrorMsg.username && errorMsg('Username is required')}
</div>
<div className="tw-mt-4">
<label className="tw-block tw-form-label" htmlFor="password">
{requiredField('Password:')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
id="password"
name="password"
placeholder="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{showErrorMsg.password && errorMsg('Password is required')}
</div>
</>
);
};
const getOptionalFields = (): JSX.Element => { const getOptionalFields = (): JSX.Element => {
switch (serviceName) { switch (serviceName) {
case ServiceCategory.DATABASE_SERVICES: case ServiceCategory.DATABASE_SERVICES:
return getDatabaseFields(); return getDatabaseFields();
case ServiceCategory.MESSAGING_SERVICES: case ServiceCategory.MESSAGING_SERVICES:
return getMessagingFields(); return getMessagingFields();
case ServiceCategory.DASHBOARD_SERVICES:
return getDashboardFields();
default: default:
return <></>; return <></>;
} }

View File

@ -30,6 +30,7 @@ import query from '../assets/img/service-icon-query.png';
import redshift from '../assets/img/service-icon-redshift.png'; import redshift from '../assets/img/service-icon-redshift.png';
import snowflakes from '../assets/img/service-icon-snowflakes.png'; import snowflakes from '../assets/img/service-icon-snowflakes.png';
import mysql from '../assets/img/service-icon-sql.png'; import mysql from '../assets/img/service-icon-sql.png';
import superset from '../assets/img/service-icon-superset.png';
import plus from '../assets/svg/plus.svg'; import plus from '../assets/svg/plus.svg';
import { ServiceCategory } from '../enums/service.enum'; import { ServiceCategory } from '../enums/service.enum';
@ -45,6 +46,7 @@ export const ATHENA = athena;
export const PRESTO = presto; export const PRESTO = presto;
export const KAFKA = kafka; export const KAFKA = kafka;
export const PULSAR = pulsar; export const PULSAR = pulsar;
export const SUPERSET = superset;
export const SERVICE_DEFAULT = serviceDefault; export const SERVICE_DEFAULT = serviceDefault;
export const PLUS = plus; export const PLUS = plus;
@ -64,16 +66,19 @@ export const serviceTypes: Record<ServiceTypes, Array<string>> = {
'Presto', 'Presto',
], ],
messagingServices: ['Kafka', 'Pulsar'], messagingServices: ['Kafka', 'Pulsar'],
dashboardServices: ['Superset'],
}; };
export const arrServiceTypes: Array<ServiceTypes> = [ export const arrServiceTypes: Array<ServiceTypes> = [
'databaseServices', 'databaseServices',
'messagingServices', 'messagingServices',
'dashboardServices',
]; ];
export const servicesDisplayName = { export const servicesDisplayName = {
databaseServices: 'Database Service', databaseServices: 'Database Service',
messagingServices: 'Messaging Service', messagingServices: 'Messaging Service',
dashboardServices: 'Dashboard Service',
}; };
export const routeServiceTypes = [ export const routeServiceTypes = [

View File

@ -18,6 +18,7 @@
export enum ServiceCategory { export enum ServiceCategory {
DATABASE_SERVICES = 'databaseServices', DATABASE_SERVICES = 'databaseServices',
MESSAGING_SERVICES = 'messagingServices', MESSAGING_SERVICES = 'messagingServices',
DASHBOARD_SERVICES = 'dashboardServices',
} }
export enum DatabaseServiceType { export enum DatabaseServiceType {
@ -37,3 +38,7 @@ export enum MessagingServiceType {
KAFKA = 'Kafka', KAFKA = 'Kafka',
PULSAR = 'Pulsar', PULSAR = 'Pulsar',
} }
export enum DashboardServiceType {
SUPERSET = 'Superset',
}

View File

@ -52,6 +52,7 @@ declare module 'Models' {
id: string; id: string;
brokers?: Array<string>; brokers?: Array<string>;
description: string; description: string;
dashboardUrl?: string;
ingestionSchedule?: { ingestionSchedule?: {
repeatFrequency: string; repeatFrequency: string;
startDate: string; startDate: string;
@ -253,6 +254,7 @@ declare module 'Models' {
export type Database = { export type Database = {
description: string; description: string;
displayName?: string;
fullyQualifiedName: string; fullyQualifiedName: string;
href: string; href: string;
id: string; id: string;
@ -317,7 +319,10 @@ declare module 'Models' {
}; };
}; };
export type ServiceTypes = 'databaseServices' | 'messagingServices'; export type ServiceTypes =
| 'databaseServices'
| 'messagingServices'
| 'dashboardServices';
export type ServiceRecord = Record<ServiceTypes, Array<ServiceDataObj>>; export type ServiceRecord = Record<ServiceTypes, Array<ServiceDataObj>>;

View File

@ -18,7 +18,7 @@
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isNull } from 'lodash'; import { isNil } from 'lodash';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Database, Paging, TableDetail } from 'Models'; import { Database, Paging, TableDetail } from 'Models';
import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
@ -366,7 +366,7 @@ const DatabaseDetails: FunctionComponent = () => {
)} )}
</tbody> </tbody>
</table> </table>
{Boolean(!isNull(paging.after) || !isNull(paging.before)) && ( {Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
<NextPrevious paging={paging} pagingHandler={pagingHandler} /> <NextPrevious paging={paging} pagingHandler={pagingHandler} />
)} )}
</div> </div>

View File

@ -17,10 +17,11 @@
import { AxiosError, AxiosResponse } from 'axios'; import { AxiosError, AxiosResponse } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { isNull, isUndefined } from 'lodash'; import { isNil, isUndefined } from 'lodash';
import { Database, Paging, ServiceOption } from 'Models'; import { Database, Paging, ServiceOption } from 'Models';
import React, { Fragment, FunctionComponent, useEffect, useState } from 'react'; import React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { getDashboards } from '../../axiosAPIs/dashboardAPI';
import { getDatabases } from '../../axiosAPIs/databaseAPI'; import { getDatabases } from '../../axiosAPIs/databaseAPI';
import { getServiceByFQN, updateService } from '../../axiosAPIs/serviceAPI'; import { getServiceByFQN, updateService } from '../../axiosAPIs/serviceAPI';
import { getTopics } from '../../axiosAPIs/topicsAPI'; import { getTopics } from '../../axiosAPIs/topicsAPI';
@ -36,6 +37,7 @@ import Tags from '../../components/tags/tags';
import { pagingObject } from '../../constants/constants'; import { pagingObject } from '../../constants/constants';
import { SearchIndex } from '../../enums/search.enum'; import { SearchIndex } from '../../enums/search.enum';
import { ServiceCategory } from '../../enums/service.enum'; import { ServiceCategory } from '../../enums/service.enum';
import { Dashboard } from '../../generated/entity/data/dashboard';
import { Topic } from '../../generated/entity/data/topic'; import { Topic } from '../../generated/entity/data/topic';
import useToastContext from '../../hooks/useToastContext'; import useToastContext from '../../hooks/useToastContext';
import { isEven } from '../../utils/CommonUtils'; import { isEven } from '../../utils/CommonUtils';
@ -101,6 +103,30 @@ const ServicePage: FunctionComponent = () => {
}); });
}; };
const fetchDashboards = (paging?: string) => {
setIsloading(true);
getDashboards(serviceFQN, paging, [
'owner',
'service',
'usageSummary',
'tags',
])
.then((res: AxiosResponse) => {
if (res.data.data) {
setData(res.data.data);
setPaging(res.data.paging);
setIsloading(false);
} else {
setData([]);
setPaging(pagingObject);
setIsloading(false);
}
})
.catch(() => {
setIsloading(false);
});
};
const getOtherDetails = (paging?: string) => { const getOtherDetails = (paging?: string) => {
switch (serviceName) { switch (serviceName) {
case ServiceCategory.DATABASE_SERVICES: { case ServiceCategory.DATABASE_SERVICES: {
@ -113,6 +139,11 @@ const ServicePage: FunctionComponent = () => {
break; break;
} }
case ServiceCategory.DASHBOARD_SERVICES: {
fetchDashboards(paging);
break;
}
default: default:
break; break;
} }
@ -123,9 +154,12 @@ const ServicePage: FunctionComponent = () => {
case ServiceCategory.MESSAGING_SERVICES: case ServiceCategory.MESSAGING_SERVICES:
return getEntityLink(SearchIndex.TOPIC, fqn); return getEntityLink(SearchIndex.TOPIC, fqn);
case ServiceCategory.DASHBOARD_SERVICES:
return getEntityLink(SearchIndex.DASHBOARD, fqn);
case ServiceCategory.DATABASE_SERVICES: case ServiceCategory.DATABASE_SERVICES:
default: default:
return `/database/${fqn}`; return getEntityLink(SearchIndex.TABLE, fqn);
} }
}; };
@ -205,6 +239,29 @@ const ServicePage: FunctionComponent = () => {
</> </>
); );
} }
case ServiceCategory.DASHBOARD_SERVICES: {
return (
<span>
<span className="tw-text-grey-muted tw-font-normal">
Dashboard Url :
</span>{' '}
<span className="tw-pl-1tw-font-normal ">
{serviceDetails?.dashboardUrl ? (
<a
className="link-text"
href={serviceDetails.dashboardUrl}
rel="noopener noreferrer"
target="_blank">
{serviceDetails.dashboardUrl}
</a>
) : (
'--'
)}
</span>
<span className="tw-mx-3 tw-text-grey-muted"></span>
</span>
);
}
default: { default: {
return <></>; return <></>;
} }
@ -233,6 +290,16 @@ const ServicePage: FunctionComponent = () => {
</> </>
); );
} }
case ServiceCategory.DASHBOARD_SERVICES: {
return (
<>
<th className="tableHead-cell">Dashboard Name</th>
<th className="tableHead-cell">Description</th>
<th className="tableHead-cell">Owner</th>
<th className="tableHead-cell">Tags</th>
</>
);
}
default: default:
return <></>; return <></>;
} }
@ -280,6 +347,33 @@ const ServicePage: FunctionComponent = () => {
</td> </td>
); );
} }
case ServiceCategory.DASHBOARD_SERVICES: {
const dashboard = data as Dashboard;
return (
<td className="tableBody-cell">
{dashboard.tags && dashboard.tags?.length > 0
? dashboard.tags.map((tag, tagIndex) => (
<PopOver
key={tagIndex}
position="top"
size="small"
title={tag.labelType}
trigger="mouseenter">
<Tags
className="tw-bg-gray-200"
tag={`#${
tag.tagFQN?.startsWith('Tier.Tier')
? tag.tagFQN.split('.')[1]
: tag.tagFQN
}`}
/>
</PopOver>
))
: '--'}
</td>
);
}
default: default:
return <></>; return <></>;
} }
@ -427,7 +521,7 @@ const ServicePage: FunctionComponent = () => {
</thead> </thead>
<tbody className="tableBody"> <tbody className="tableBody">
{data.length > 0 ? ( {data.length > 0 ? (
data.map((database, index) => ( data.map((dataObj, index) => (
<tr <tr
className={classNames( className={classNames(
'tableBody-row', 'tableBody-row',
@ -436,14 +530,18 @@ const ServicePage: FunctionComponent = () => {
data-testid="column" data-testid="column"
key={index}> key={index}>
<td className="tableBody-cell"> <td className="tableBody-cell">
<Link to={getLinkForFqn(database.fullyQualifiedName)}> <Link to={getLinkForFqn(dataObj.fullyQualifiedName)}>
{database.name} {serviceName ===
ServiceCategory.DASHBOARD_SERVICES &&
dataObj.displayName
? dataObj.displayName
: dataObj.name}
</Link> </Link>
</td> </td>
<td className="tableBody-cell"> <td className="tableBody-cell">
{database.description ? ( {dataObj.description ? (
<RichTextEditorPreviewer <RichTextEditorPreviewer
markdown={database.description} markdown={dataObj.description}
/> />
) : ( ) : (
<span className="tw-no-description"> <span className="tw-no-description">
@ -452,9 +550,9 @@ const ServicePage: FunctionComponent = () => {
)} )}
</td> </td>
<td className="tableBody-cell"> <td className="tableBody-cell">
<p>{database?.owner?.name || '--'}</p> <p>{dataObj?.owner?.name || '--'}</p>
</td> </td>
{getOptionalTableCells(database)} {getOptionalTableCells(dataObj)}
</tr> </tr>
)) ))
) : ( ) : (
@ -467,7 +565,7 @@ const ServicePage: FunctionComponent = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
{Boolean(!isNull(paging.after) || !isNull(paging.before)) && ( {Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
<NextPrevious paging={paging} pagingHandler={pagingHandler} /> <NextPrevious paging={paging} pagingHandler={pagingHandler} />
)} )}
</div> </div>

View File

@ -70,6 +70,7 @@ const ServicesPage = () => {
const [services, setServices] = useState<ServiceRecord>({ const [services, setServices] = useState<ServiceRecord>({
databaseServices: [], databaseServices: [],
messagingServices: [], messagingServices: [],
dashboardServices: [],
}); });
const [serviceList, setServiceList] = useState<Array<ServiceDataObj>>([]); const [serviceList, setServiceList] = useState<Array<ServiceDataObj>>([]);
const [editData, setEditData] = useState<ServiceDataObj>(); const [editData, setEditData] = useState<ServiceDataObj>();
@ -238,6 +239,18 @@ const ServicesPage = () => {
</> </>
); );
} }
case ServiceCategory.DASHBOARD_SERVICES: {
return (
<>
<div className="tw-mb-1">
<label className="tw-mb-0">Dashboard URL:</label>
<span className=" tw-ml-1 tw-font-normal tw-text-grey-body">
{service.dashboardUrl}
</span>
</div>
</>
);
}
default: { default: {
return <></>; return <></>;
} }

View File

@ -17,8 +17,10 @@ import {
serviceTypes, serviceTypes,
SERVICE_DEFAULT, SERVICE_DEFAULT,
SNOWFLAKE, SNOWFLAKE,
SUPERSET,
} from '../constants/services.const'; } from '../constants/services.const';
import { import {
DashboardServiceType,
DatabaseServiceType, DatabaseServiceType,
MessagingServiceType, MessagingServiceType,
} from '../enums/service.enum'; } from '../enums/service.enum';
@ -62,6 +64,9 @@ export const serviceTypeLogo = (type: string) => {
case MessagingServiceType.PULSAR: case MessagingServiceType.PULSAR:
return PULSAR; return PULSAR;
case DashboardServiceType.SUPERSET:
return SUPERSET;
default: default:
return SERVICE_DEFAULT; return SERVICE_DEFAULT;
} }