mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-19 15:31:59 +00:00
Added Dashboard services UI and api integration (#422)
Co-authored-by: aashitk <aashit.kothari@deuexsolutions.com>
This commit is contained in:
parent
5c01546daf
commit
f48e34be9e
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
@ -44,6 +44,9 @@ export type DataObj = {
|
||||
};
|
||||
brokers?: Array<string>;
|
||||
schemaRegistry?: string;
|
||||
dashboardUrl?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
type DatabaseService = {
|
||||
@ -57,6 +60,12 @@ type MessagingService = {
|
||||
schemaRegistry: string;
|
||||
};
|
||||
|
||||
type DashboardService = {
|
||||
dashboardUrl: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type ServiceDataObj = {
|
||||
description: string;
|
||||
href: string;
|
||||
@ -65,7 +74,8 @@ export type ServiceDataObj = {
|
||||
serviceType: string;
|
||||
ingestionSchedule?: { repeatFrequency: string; startDate: string };
|
||||
} & Partial<DatabaseService> &
|
||||
Partial<MessagingService>;
|
||||
Partial<MessagingService> &
|
||||
Partial<DashboardService>;
|
||||
|
||||
export type EditObj = {
|
||||
edit: boolean;
|
||||
@ -86,10 +96,11 @@ type ErrorMsg = {
|
||||
name: boolean;
|
||||
url?: boolean;
|
||||
// port: boolean;
|
||||
// userName: boolean;
|
||||
// password: boolean;
|
||||
driverClass?: boolean;
|
||||
broker?: boolean;
|
||||
dashboardUrl?: boolean;
|
||||
username?: boolean;
|
||||
password?: boolean;
|
||||
};
|
||||
type EditorContentRef = {
|
||||
getEditorContent: () => string;
|
||||
@ -176,6 +187,9 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
const [schemaRegistry, setSchemaRegistry] = useState(
|
||||
data?.schemaRegistry || ''
|
||||
);
|
||||
const [dashboardUrl, setDashboardUrl] = useState(data?.dashboardUrl || '');
|
||||
const [username, setUsername] = useState(data?.username || '');
|
||||
const [password, setPassword] = useState(data?.password || '');
|
||||
const [frequency, setFrequency] = useState(
|
||||
fromISOString(data?.ingestionSchedule?.repeatFrequency)
|
||||
);
|
||||
@ -261,18 +275,27 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
};
|
||||
|
||||
const onSaveHelper = (value: ErrorMsg) => {
|
||||
const { selectService, name, url, driverClass, broker } = value;
|
||||
const {
|
||||
selectService,
|
||||
name,
|
||||
url,
|
||||
driverClass,
|
||||
broker,
|
||||
dashboardUrl,
|
||||
username,
|
||||
password,
|
||||
} = value;
|
||||
|
||||
return (
|
||||
!sameNameError &&
|
||||
!selectService &&
|
||||
!name &&
|
||||
!url &&
|
||||
// !port &&
|
||||
// !userName &&
|
||||
// !password &&
|
||||
!driverClass &&
|
||||
!broker
|
||||
!broker &&
|
||||
!dashboardUrl &&
|
||||
!username &&
|
||||
!password
|
||||
);
|
||||
};
|
||||
|
||||
@ -287,9 +310,6 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
setMsg = {
|
||||
...setMsg,
|
||||
url: !url,
|
||||
// port: !port,
|
||||
// userName: !userName,
|
||||
// password: !password,
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
@ -347,6 +378,17 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case ServiceCategory.DASHBOARD_SERVICES:
|
||||
{
|
||||
dataObj = {
|
||||
...dataObj,
|
||||
dashboardUrl: dashboardUrl,
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -374,54 +416,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
/>
|
||||
{showErrorMsg.url && errorMsg('Connection url is required')}
|
||||
</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 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">
|
||||
<label className="tw-block tw-form-label" htmlFor="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 => {
|
||||
switch (serviceName) {
|
||||
case ServiceCategory.DATABASE_SERVICES:
|
||||
return getDatabaseFields();
|
||||
case ServiceCategory.MESSAGING_SERVICES:
|
||||
return getMessagingFields();
|
||||
case ServiceCategory.DASHBOARD_SERVICES:
|
||||
return getDashboardFields();
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import query from '../assets/img/service-icon-query.png';
|
||||
import redshift from '../assets/img/service-icon-redshift.png';
|
||||
import snowflakes from '../assets/img/service-icon-snowflakes.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 { ServiceCategory } from '../enums/service.enum';
|
||||
|
||||
@ -45,6 +46,7 @@ export const ATHENA = athena;
|
||||
export const PRESTO = presto;
|
||||
export const KAFKA = kafka;
|
||||
export const PULSAR = pulsar;
|
||||
export const SUPERSET = superset;
|
||||
export const SERVICE_DEFAULT = serviceDefault;
|
||||
|
||||
export const PLUS = plus;
|
||||
@ -64,16 +66,19 @@ export const serviceTypes: Record<ServiceTypes, Array<string>> = {
|
||||
'Presto',
|
||||
],
|
||||
messagingServices: ['Kafka', 'Pulsar'],
|
||||
dashboardServices: ['Superset'],
|
||||
};
|
||||
|
||||
export const arrServiceTypes: Array<ServiceTypes> = [
|
||||
'databaseServices',
|
||||
'messagingServices',
|
||||
'dashboardServices',
|
||||
];
|
||||
|
||||
export const servicesDisplayName = {
|
||||
databaseServices: 'Database Service',
|
||||
messagingServices: 'Messaging Service',
|
||||
dashboardServices: 'Dashboard Service',
|
||||
};
|
||||
|
||||
export const routeServiceTypes = [
|
||||
|
@ -18,6 +18,7 @@
|
||||
export enum ServiceCategory {
|
||||
DATABASE_SERVICES = 'databaseServices',
|
||||
MESSAGING_SERVICES = 'messagingServices',
|
||||
DASHBOARD_SERVICES = 'dashboardServices',
|
||||
}
|
||||
|
||||
export enum DatabaseServiceType {
|
||||
@ -37,3 +38,7 @@ export enum MessagingServiceType {
|
||||
KAFKA = 'Kafka',
|
||||
PULSAR = 'Pulsar',
|
||||
}
|
||||
|
||||
export enum DashboardServiceType {
|
||||
SUPERSET = 'Superset',
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ declare module 'Models' {
|
||||
id: string;
|
||||
brokers?: Array<string>;
|
||||
description: string;
|
||||
dashboardUrl?: string;
|
||||
ingestionSchedule?: {
|
||||
repeatFrequency: string;
|
||||
startDate: string;
|
||||
@ -253,6 +254,7 @@ declare module 'Models' {
|
||||
|
||||
export type Database = {
|
||||
description: string;
|
||||
displayName?: string;
|
||||
fullyQualifiedName: string;
|
||||
href: 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>>;
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { isNull } from 'lodash';
|
||||
import { isNil } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Database, Paging, TableDetail } from 'Models';
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
@ -366,7 +366,7 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
{Boolean(!isNull(paging.after) || !isNull(paging.before)) && (
|
||||
{Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
|
||||
<NextPrevious paging={paging} pagingHandler={pagingHandler} />
|
||||
)}
|
||||
</div>
|
||||
|
@ -17,10 +17,11 @@
|
||||
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { isNull, isUndefined } from 'lodash';
|
||||
import { isNil, isUndefined } from 'lodash';
|
||||
import { Database, Paging, ServiceOption } from 'Models';
|
||||
import React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { getDashboards } from '../../axiosAPIs/dashboardAPI';
|
||||
import { getDatabases } from '../../axiosAPIs/databaseAPI';
|
||||
import { getServiceByFQN, updateService } from '../../axiosAPIs/serviceAPI';
|
||||
import { getTopics } from '../../axiosAPIs/topicsAPI';
|
||||
@ -36,6 +37,7 @@ import Tags from '../../components/tags/tags';
|
||||
import { pagingObject } from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { Topic } from '../../generated/entity/data/topic';
|
||||
import useToastContext from '../../hooks/useToastContext';
|
||||
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) => {
|
||||
switch (serviceName) {
|
||||
case ServiceCategory.DATABASE_SERVICES: {
|
||||
@ -113,6 +139,11 @@ const ServicePage: FunctionComponent = () => {
|
||||
|
||||
break;
|
||||
}
|
||||
case ServiceCategory.DASHBOARD_SERVICES: {
|
||||
fetchDashboards(paging);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -123,9 +154,12 @@ const ServicePage: FunctionComponent = () => {
|
||||
case ServiceCategory.MESSAGING_SERVICES:
|
||||
return getEntityLink(SearchIndex.TOPIC, fqn);
|
||||
|
||||
case ServiceCategory.DASHBOARD_SERVICES:
|
||||
return getEntityLink(SearchIndex.DASHBOARD, fqn);
|
||||
|
||||
case ServiceCategory.DATABASE_SERVICES:
|
||||
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: {
|
||||
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:
|
||||
return <></>;
|
||||
}
|
||||
@ -280,6 +347,33 @@ const ServicePage: FunctionComponent = () => {
|
||||
</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:
|
||||
return <></>;
|
||||
}
|
||||
@ -427,7 +521,7 @@ const ServicePage: FunctionComponent = () => {
|
||||
</thead>
|
||||
<tbody className="tableBody">
|
||||
{data.length > 0 ? (
|
||||
data.map((database, index) => (
|
||||
data.map((dataObj, index) => (
|
||||
<tr
|
||||
className={classNames(
|
||||
'tableBody-row',
|
||||
@ -436,14 +530,18 @@ const ServicePage: FunctionComponent = () => {
|
||||
data-testid="column"
|
||||
key={index}>
|
||||
<td className="tableBody-cell">
|
||||
<Link to={getLinkForFqn(database.fullyQualifiedName)}>
|
||||
{database.name}
|
||||
<Link to={getLinkForFqn(dataObj.fullyQualifiedName)}>
|
||||
{serviceName ===
|
||||
ServiceCategory.DASHBOARD_SERVICES &&
|
||||
dataObj.displayName
|
||||
? dataObj.displayName
|
||||
: dataObj.name}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="tableBody-cell">
|
||||
{database.description ? (
|
||||
{dataObj.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
markdown={database.description}
|
||||
markdown={dataObj.description}
|
||||
/>
|
||||
) : (
|
||||
<span className="tw-no-description">
|
||||
@ -452,9 +550,9 @@ const ServicePage: FunctionComponent = () => {
|
||||
)}
|
||||
</td>
|
||||
<td className="tableBody-cell">
|
||||
<p>{database?.owner?.name || '--'}</p>
|
||||
<p>{dataObj?.owner?.name || '--'}</p>
|
||||
</td>
|
||||
{getOptionalTableCells(database)}
|
||||
{getOptionalTableCells(dataObj)}
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
@ -467,7 +565,7 @@ const ServicePage: FunctionComponent = () => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{Boolean(!isNull(paging.after) || !isNull(paging.before)) && (
|
||||
{Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
|
||||
<NextPrevious paging={paging} pagingHandler={pagingHandler} />
|
||||
)}
|
||||
</div>
|
||||
|
@ -70,6 +70,7 @@ const ServicesPage = () => {
|
||||
const [services, setServices] = useState<ServiceRecord>({
|
||||
databaseServices: [],
|
||||
messagingServices: [],
|
||||
dashboardServices: [],
|
||||
});
|
||||
const [serviceList, setServiceList] = useState<Array<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: {
|
||||
return <></>;
|
||||
}
|
||||
|
@ -17,8 +17,10 @@ import {
|
||||
serviceTypes,
|
||||
SERVICE_DEFAULT,
|
||||
SNOWFLAKE,
|
||||
SUPERSET,
|
||||
} from '../constants/services.const';
|
||||
import {
|
||||
DashboardServiceType,
|
||||
DatabaseServiceType,
|
||||
MessagingServiceType,
|
||||
} from '../enums/service.enum';
|
||||
@ -62,6 +64,9 @@ export const serviceTypeLogo = (type: string) => {
|
||||
case MessagingServiceType.PULSAR:
|
||||
return PULSAR;
|
||||
|
||||
case DashboardServiceType.SUPERSET:
|
||||
return SUPERSET;
|
||||
|
||||
default:
|
||||
return SERVICE_DEFAULT;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user