mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-06 12:36:56 +00:00
Added UI for Messaging services (#290)
* Added UI tabs for different services * Integrated apis and added icons for messaging services
This commit is contained in:
parent
5703a69e8c
commit
8d27ca7ef5
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -16,13 +16,16 @@
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { FunctionComponent, useRef, useState } from 'react';
|
||||
import { ServiceTypes } from 'Models';
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
import { serviceTypes } from '../../../constants/services.const';
|
||||
import { ServiceCategory } from '../../../enums/service.enum';
|
||||
import { fromISOString } from '../../../utils/ServiceUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../../common/editor/MarkdownWithPreview';
|
||||
// import { serviceType } from '../../../constants/services.const';
|
||||
|
||||
export type DatabaseObj = {
|
||||
export type DataObj = {
|
||||
description: string | undefined;
|
||||
ingestionSchedule:
|
||||
| {
|
||||
@ -30,25 +33,36 @@ export type DatabaseObj = {
|
||||
startDate: string;
|
||||
}
|
||||
| undefined;
|
||||
jdbc: {
|
||||
name: string;
|
||||
serviceType: string;
|
||||
jdbc?: {
|
||||
connectionUrl: string;
|
||||
driverClass: string;
|
||||
};
|
||||
name: string;
|
||||
serviceType: string;
|
||||
brokers?: Array<string>;
|
||||
schemaRegistry?: string;
|
||||
};
|
||||
|
||||
type DatabaseService = {
|
||||
connectionUrl: string;
|
||||
driverClass: string;
|
||||
jdbc: { driverClass: string; connectionUrl: string };
|
||||
};
|
||||
|
||||
type MessagingService = {
|
||||
brokers: Array<string>;
|
||||
schemaRegistry: string;
|
||||
};
|
||||
|
||||
export type ServiceDataObj = {
|
||||
connectionUrl: string;
|
||||
description: string;
|
||||
driverClass: string;
|
||||
href: string;
|
||||
id: string;
|
||||
jdbc: { driverClass: string; connectionUrl: string };
|
||||
name: string;
|
||||
serviceType: string;
|
||||
ingestionSchedule?: { repeatFrequency: string; startDate: string };
|
||||
};
|
||||
} & Partial<DatabaseService> &
|
||||
Partial<MessagingService>;
|
||||
|
||||
export type EditObj = {
|
||||
edit: boolean;
|
||||
@ -57,21 +71,22 @@ export type EditObj = {
|
||||
|
||||
type Props = {
|
||||
header: string;
|
||||
serviceName: string;
|
||||
serviceName: ServiceTypes;
|
||||
serviceList: Array<ServiceDataObj>;
|
||||
data?: ServiceDataObj;
|
||||
onSave: (obj: DatabaseObj, text: string, editData: EditObj) => void;
|
||||
onSave: (obj: DataObj, text: string, editData: EditObj) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
type ErrorMsg = {
|
||||
selectService: boolean;
|
||||
name: boolean;
|
||||
url: boolean;
|
||||
url?: boolean;
|
||||
// port: boolean;
|
||||
// userName: boolean;
|
||||
// password: boolean;
|
||||
driverClass: boolean;
|
||||
driverClass?: boolean;
|
||||
broker?: boolean;
|
||||
};
|
||||
type EditorContentRef = {
|
||||
getEditorContent: () => string;
|
||||
@ -137,15 +152,9 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
serviceList,
|
||||
}: Props) => {
|
||||
const [editData] = useState({ edit: !!data, id: data?.id });
|
||||
const [serviceType] = useState([
|
||||
'BigQuery',
|
||||
'MySQL',
|
||||
'Redshift',
|
||||
'Snowflake',
|
||||
'Postgres',
|
||||
'MSSQL',
|
||||
'Hive',
|
||||
]);
|
||||
const [serviceType, setServiceType] = useState(
|
||||
serviceTypes[serviceName] || []
|
||||
);
|
||||
const [parseUrl] = useState(seprateUrl(data?.connectionUrl) || {});
|
||||
const [existingNames] = useState(generateName(serviceList));
|
||||
const [ingestion, setIngestion] = useState(!!data?.ingestionSchedule);
|
||||
@ -158,10 +167,16 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
// const [port, setPort] = useState(parseUrl?.port || '');
|
||||
const [database, setDatabase] = useState(parseUrl?.database || '');
|
||||
const [driverClass, setDriverClass] = useState(data?.driverClass || 'jdbc');
|
||||
const [broker, setBroker] = useState(
|
||||
data?.brokers?.length ? data.brokers[0] : ''
|
||||
);
|
||||
const [schemaRegistry, setSchemaRegistry] = useState(
|
||||
data?.schemaRegistry || ''
|
||||
);
|
||||
const [frequency, setFrequency] = useState(
|
||||
fromISOString(data?.ingestionSchedule?.repeatFrequency)
|
||||
);
|
||||
const [showErrorMsg, setShowErrorMsg] = useState({
|
||||
const [showErrorMsg, setShowErrorMsg] = useState<ErrorMsg>({
|
||||
selectService: false,
|
||||
name: false,
|
||||
url: false,
|
||||
@ -169,6 +184,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
// userName: false,
|
||||
// password: false,
|
||||
driverClass: false,
|
||||
broker: false,
|
||||
});
|
||||
const [sameNameError, setSameNameError] = useState(false);
|
||||
const markdownRef = useRef<EditorContentRef>();
|
||||
@ -235,7 +251,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
};
|
||||
|
||||
const onSaveHelper = (value: ErrorMsg) => {
|
||||
const { selectService, name, url, driverClass } = value;
|
||||
const { selectService, name, url, driverClass, broker } = value;
|
||||
|
||||
return (
|
||||
!sameNameError &&
|
||||
@ -245,25 +261,47 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
// !port &&
|
||||
// !userName &&
|
||||
// !password &&
|
||||
!driverClass
|
||||
!driverClass &&
|
||||
!broker
|
||||
);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const setMsg = {
|
||||
let setMsg: ErrorMsg = {
|
||||
selectService: !selectService,
|
||||
name: !name,
|
||||
url: !url,
|
||||
// port: !port,
|
||||
// userName: !userName,
|
||||
// password: !password,
|
||||
driverClass: !driverClass,
|
||||
};
|
||||
switch (serviceName) {
|
||||
case ServiceCategory.DATABASE_SERVICES:
|
||||
{
|
||||
setMsg = {
|
||||
...setMsg,
|
||||
url: !url,
|
||||
// port: !port,
|
||||
// userName: !userName,
|
||||
// password: !password,
|
||||
driverClass: !driverClass,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case ServiceCategory.MESSAGING_SERVICES:
|
||||
{
|
||||
setMsg = {
|
||||
...setMsg,
|
||||
broker: !broker,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setShowErrorMsg(setMsg);
|
||||
if (onSaveHelper(setMsg)) {
|
||||
const { day, hour, minute } = frequency;
|
||||
const date = new Date();
|
||||
const databaseObj: DatabaseObj = {
|
||||
let dataObj: DataObj = {
|
||||
description: markdownRef.current?.getEditorContent(),
|
||||
ingestionSchedule: ingestion
|
||||
? {
|
||||
@ -271,37 +309,201 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
startDate: date.toISOString(),
|
||||
}
|
||||
: undefined,
|
||||
jdbc: {
|
||||
connectionUrl: `${url}${database && '/' + database}`,
|
||||
driverClass: driverClass,
|
||||
},
|
||||
name: name,
|
||||
serviceType: selectService,
|
||||
};
|
||||
onSave(databaseObj, serviceName, editData);
|
||||
switch (serviceName) {
|
||||
case ServiceCategory.DATABASE_SERVICES:
|
||||
{
|
||||
dataObj = {
|
||||
...dataObj,
|
||||
jdbc: {
|
||||
connectionUrl: `${url}${database && '/' + database}`,
|
||||
driverClass: driverClass,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case ServiceCategory.MESSAGING_SERVICES:
|
||||
{
|
||||
dataObj = {
|
||||
...dataObj,
|
||||
brokers: [broker],
|
||||
schemaRegistry: schemaRegistry,
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
onSave(dataObj, serviceName, editData);
|
||||
}
|
||||
};
|
||||
|
||||
const getDatabaseFields = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<div className="tw-mt-4 tw-grid tw-grid-cols-3 tw-gap-2 ">
|
||||
<div className="tw-col-span-3">
|
||||
<label className="tw-block tw-form-label" htmlFor="url">
|
||||
{requiredField('Connection Url:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="url"
|
||||
name="url"
|
||||
type="text"
|
||||
value={url}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{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:
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="database"
|
||||
name="database"
|
||||
type="text"
|
||||
value={database}
|
||||
onChange={(e) => setDatabase(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="tw-mt-4">
|
||||
<label className="tw-block tw-form-label" htmlFor="driverClass">
|
||||
{requiredField('Driver Class:')}
|
||||
</label>
|
||||
{!editData.edit ? (
|
||||
<select
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="driverClass"
|
||||
name="driverClass"
|
||||
value={driverClass}
|
||||
onChange={handleValidation}>
|
||||
<option value="jdbc">jdbc</option>
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
disabled
|
||||
className="tw-form-inputs tw-px-3 tw-py-1 tw-cursor-not-allowed"
|
||||
id="driverClass"
|
||||
name="driverClass"
|
||||
value={driverClass}
|
||||
/>
|
||||
)}
|
||||
{showErrorMsg.driverClass && errorMsg('Driver class is required')}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getMessagingFields = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<div className="tw-mt-4">
|
||||
<label className="tw-block tw-form-label" htmlFor="broker">
|
||||
{requiredField('Broker Url:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="broker"
|
||||
name="broker"
|
||||
type="text"
|
||||
value={broker}
|
||||
onChange={(e) => setBroker(e.target.value)}
|
||||
/>
|
||||
{showErrorMsg.broker && errorMsg('Broker url is required')}
|
||||
</div>
|
||||
<div className="tw-mt-4">
|
||||
<label className="tw-block tw-form-label" htmlFor="schema-registry">
|
||||
Schema Registry:
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="schema-registry"
|
||||
name="schema-registry"
|
||||
type="text"
|
||||
value={schemaRegistry}
|
||||
onChange={(e) => setSchemaRegistry(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getOptionalFields = (): JSX.Element => {
|
||||
switch (serviceName) {
|
||||
case ServiceCategory.DATABASE_SERVICES:
|
||||
return getDatabaseFields();
|
||||
case ServiceCategory.MESSAGING_SERVICES:
|
||||
return getMessagingFields();
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setServiceType(serviceTypes[serviceName] || []);
|
||||
}, [serviceName]);
|
||||
|
||||
return (
|
||||
<dialog className="tw-modal">
|
||||
<div className="tw-modal-backdrop" />
|
||||
<div className="tw-modal-container tw-max-w-lg">
|
||||
<div className="tw-modal-header">
|
||||
<p className="tw-modal-title">{header}</p>
|
||||
<svg
|
||||
className="tw-w-6 tw-h-6 tw-cursor-pointer"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onCancel}>
|
||||
<path
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="tw-modal-body">
|
||||
<form className="tw-min-w-full">
|
||||
@ -360,106 +562,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
{showErrorMsg.name && errorMsg('Service name is required.')}
|
||||
{sameNameError && errorMsg('Service name already exist.')}
|
||||
</div>
|
||||
<div className="tw-mt-4 tw-grid tw-grid-cols-3 tw-gap-2 ">
|
||||
<div className="tw-col-span-3">
|
||||
<label className="tw-block tw-form-label" htmlFor="url">
|
||||
{requiredField('Connection Url:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="url"
|
||||
name="url"
|
||||
type="text"
|
||||
value={url}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{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:
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="database"
|
||||
name="database"
|
||||
type="text"
|
||||
value={database}
|
||||
onChange={(e) => setDatabase(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="tw-mt-4">
|
||||
<label className="tw-block tw-form-label" htmlFor="driverClass">
|
||||
{requiredField('Driver Class:')}
|
||||
</label>
|
||||
{!editData.edit ? (
|
||||
<select
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="driverClass"
|
||||
name="driverClass"
|
||||
value={driverClass}
|
||||
onChange={handleValidation}>
|
||||
<option value="jdbc">jdbc</option>
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
disabled
|
||||
className="tw-form-inputs tw-px-3 tw-py-1 tw-cursor-not-allowed"
|
||||
id="driverClass"
|
||||
name="driverClass"
|
||||
value={driverClass}
|
||||
/>
|
||||
)}
|
||||
{showErrorMsg.driverClass && errorMsg('Driver class is required')}
|
||||
</div>
|
||||
{getOptionalFields()}
|
||||
<div className="tw-mt-4">
|
||||
<label className="tw-block tw-form-label" htmlFor="description">
|
||||
Description:
|
||||
@ -469,23 +572,6 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
value={data?.description || ''}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="tw-mt-4">
|
||||
<label className="tw-block tw-form-label" htmlFor="tags">
|
||||
Tags:
|
||||
</label>
|
||||
<select
|
||||
className="tw-form-inputs tw-px-3 tw-py-1 "
|
||||
name="tags"
|
||||
id="tags"
|
||||
value={tags}
|
||||
onChange={(e) => setTags(e.target.value)}>
|
||||
<option value="">Select Tags</option>
|
||||
<option value="volvo">Volvo</option>
|
||||
<option value="saab">Saab</option>
|
||||
<option value="opel">Opel</option>
|
||||
<option value="audi">Audi</option>
|
||||
</select>
|
||||
</div> */}
|
||||
<div className="tw-mt-4 tw-flex tw-items-center">
|
||||
<label className="tw-form-label tw-mb-0">Enable Ingestion</label>
|
||||
<div
|
||||
|
||||
@ -15,12 +15,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ServiceTypes } from 'Models';
|
||||
import noService from '../assets/img/no-service.png';
|
||||
import serviceDefault from '../assets/img/service-icon-generic.png';
|
||||
import hive from '../assets/img/service-icon-hive.png';
|
||||
import kafka from '../assets/img/service-icon-kafka.png';
|
||||
import mssql from '../assets/img/service-icon-mssql.png';
|
||||
import oracle from '../assets/img/service-icon-oracle.png';
|
||||
import postgres from '../assets/img/service-icon-post.png';
|
||||
import pulsar from '../assets/img/service-icon-pulsar.png';
|
||||
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';
|
||||
@ -35,17 +38,32 @@ export const HIVE = hive;
|
||||
export const POSTGRES = postgres;
|
||||
export const ORACLE = oracle;
|
||||
export const SNOWFLAKE = snowflakes;
|
||||
export const KAFKA = kafka;
|
||||
export const PULSAR = pulsar;
|
||||
export const SERVICE_DEFAULT = serviceDefault;
|
||||
|
||||
export const PLUS = plus;
|
||||
export const NOSERVICE = noService;
|
||||
|
||||
export const serviceType = [
|
||||
'MySQL',
|
||||
'Postgres',
|
||||
'Redshift',
|
||||
'BigQuery',
|
||||
'Hive',
|
||||
'Oracle',
|
||||
'Snowflake',
|
||||
export const serviceTypes: Record<ServiceTypes, Array<string>> = {
|
||||
databaseServices: [
|
||||
'BigQuery',
|
||||
'MySQL',
|
||||
'Redshift',
|
||||
'Snowflake',
|
||||
'Postgres',
|
||||
'MSSQL',
|
||||
'Hive',
|
||||
],
|
||||
messagingServices: ['Kafka', 'Pulsar'],
|
||||
};
|
||||
|
||||
export const arrServiceTypes: Array<ServiceTypes> = [
|
||||
'databaseServices',
|
||||
'messagingServices',
|
||||
];
|
||||
|
||||
export const servicesDisplayName = {
|
||||
databaseServices: 'Database Service',
|
||||
messagingServices: 'Messaging Service',
|
||||
};
|
||||
|
||||
@ -15,7 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum ServiceType {
|
||||
export enum ServiceCategory {
|
||||
DATABASE_SERVICES = 'databaseServices',
|
||||
MESSAGING_SERVICES = 'messagingServices',
|
||||
}
|
||||
|
||||
export enum DatabaseServiceType {
|
||||
MYSQL = 'MySQL',
|
||||
REDSHIFT = 'Redshift',
|
||||
BIGQUERY = 'BigQuery',
|
||||
@ -25,3 +30,8 @@ export enum ServiceType {
|
||||
SNOWFLAKE = 'Snowflake',
|
||||
MSSQL = 'MSSQL',
|
||||
}
|
||||
|
||||
export enum MessagingServiceType {
|
||||
KAFKA = 'Kafka',
|
||||
PULSAR = 'Pulsar',
|
||||
}
|
||||
|
||||
@ -313,6 +313,10 @@ declare module 'Models' {
|
||||
};
|
||||
};
|
||||
|
||||
export type ServiceTypes = 'databaseServices' | 'messagingServices';
|
||||
|
||||
export type ServiceRecord = Record<ServiceTypes, Array<ServiceDataObj>>;
|
||||
|
||||
export type SampleData = {
|
||||
columns: Array<string>;
|
||||
rows: Array<Array<string>>;
|
||||
|
||||
@ -17,7 +17,12 @@
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { isNull } from 'lodash';
|
||||
import { ServiceCollection, ServiceData } from 'Models';
|
||||
import {
|
||||
ServiceCollection,
|
||||
ServiceData,
|
||||
ServiceRecord,
|
||||
ServiceTypes,
|
||||
} from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
@ -32,12 +37,19 @@ import PageContainer from '../../components/containers/PageContainer';
|
||||
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 {
|
||||
arrServiceTypes,
|
||||
NOSERVICE,
|
||||
PLUS,
|
||||
servicesDisplayName,
|
||||
} from '../../constants/services.const';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { getTabClasses } from '../../utils/CommonUtils';
|
||||
import { getFrequencyTime, serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
|
||||
@ -53,7 +65,12 @@ export type ApiData = {
|
||||
|
||||
const ServicesPage = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [serviceName] = useState('databaseServices');
|
||||
const [serviceName, setServiceName] =
|
||||
useState<ServiceTypes>('databaseServices');
|
||||
const [services, setServices] = useState<ServiceRecord>({
|
||||
databaseServices: [],
|
||||
messagingServices: [],
|
||||
});
|
||||
const [serviceList, setServiceList] = useState<Array<ServiceDataObj>>([]);
|
||||
const [editData, setEditData] = useState<ServiceDataObj>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
@ -61,7 +78,7 @@ const ServicesPage = () => {
|
||||
const updateServiceList = (
|
||||
allServiceCollectionArr: Array<ServiceCollection>
|
||||
) => {
|
||||
let listArr = [];
|
||||
// let listArr = [];
|
||||
// fetch services of all individual collection
|
||||
if (allServiceCollectionArr.length) {
|
||||
let promiseArr = [];
|
||||
@ -71,16 +88,24 @@ const ServicesPage = () => {
|
||||
Promise.all(promiseArr).then((result: AxiosResponse[]) => {
|
||||
if (result.length) {
|
||||
let serviceArr = [];
|
||||
const serviceRecord = {} as ServiceRecord;
|
||||
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 };
|
||||
});
|
||||
setServiceList(listArr);
|
||||
for (let i = 0; i < serviceArr.length; i++) {
|
||||
serviceRecord[allServiceCollectionArr[i].value as ServiceTypes] =
|
||||
serviceArr[i].map((s: ApiData) => {
|
||||
return { ...s, ...s.jdbc };
|
||||
});
|
||||
}
|
||||
// // converted array of arrays to array
|
||||
// const allServices = serviceArr.reduce(
|
||||
// (acc, el) => acc.concat(el),
|
||||
// []
|
||||
// );
|
||||
// listArr = allServices.map((s: ApiData) => {
|
||||
// return { ...s, ...s.jdbc };
|
||||
// });
|
||||
setServices(serviceRecord);
|
||||
setServiceList(serviceRecord[serviceName]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -103,36 +128,43 @@ const ServicesPage = () => {
|
||||
const handleUpdate = (
|
||||
selectedService: string,
|
||||
id: string,
|
||||
dataObj: DatabaseObj
|
||||
dataObj: DataObj
|
||||
) => {
|
||||
updateService(selectedService, id, dataObj).then(
|
||||
({ data }: { data: AxiosResponse['data'] }) => {
|
||||
const updatedData = {
|
||||
...data,
|
||||
...data.jdbc,
|
||||
...data.brokers,
|
||||
...data.schemaRegistry,
|
||||
};
|
||||
const updatedServiceList = serviceList.map((s) =>
|
||||
s.id === updatedData.id ? updatedData : s
|
||||
);
|
||||
setServices({ ...services, [serviceName]: updatedServiceList });
|
||||
setServiceList(updatedServiceList);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleAdd = (selectedService: string, dataObj: DatabaseObj) => {
|
||||
const handleAdd = (selectedService: string, dataObj: DataObj) => {
|
||||
postService(selectedService, dataObj).then(
|
||||
({ data }: { data: AxiosResponse['data'] }) => {
|
||||
const updatedData = {
|
||||
...data,
|
||||
...data.jdbc,
|
||||
...data.brokers,
|
||||
...data.schemaRegistry,
|
||||
};
|
||||
setServiceList([...serviceList, updatedData]);
|
||||
const updatedServiceList = [...serviceList, updatedData];
|
||||
setServices({ ...services, [serviceName]: updatedServiceList });
|
||||
setServiceList(updatedServiceList);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleSave = (
|
||||
dataObj: DatabaseObj,
|
||||
dataObj: DataObj,
|
||||
selectedService: string,
|
||||
isEdit: EditObj
|
||||
) => {
|
||||
@ -149,6 +181,7 @@ const ServicesPage = () => {
|
||||
deleteService(serviceName, id).then((res: AxiosResponse) => {
|
||||
if (res.statusText === 'OK') {
|
||||
const updatedServiceList = serviceList.filter((s) => s.id !== id);
|
||||
setServices({ ...services, [serviceName]: updatedServiceList });
|
||||
setServiceList(updatedServiceList);
|
||||
}
|
||||
});
|
||||
@ -163,6 +196,54 @@ const ServicesPage = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const getServiceTabs = (): Array<{
|
||||
name: ServiceTypes;
|
||||
displayName: string;
|
||||
}> => {
|
||||
const tabs = Object.keys(services);
|
||||
|
||||
return arrServiceTypes
|
||||
.filter((item) => tabs.includes(item))
|
||||
.map((type) => {
|
||||
return {
|
||||
name: type,
|
||||
displayName: servicesDisplayName[type],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getOptionalFields = (service: ServiceDataObj): JSX.Element => {
|
||||
switch (serviceName) {
|
||||
case ServiceCategory.DATABASE_SERVICES: {
|
||||
return (
|
||||
<>
|
||||
<div className="tw-mb-1">
|
||||
<label className="tw-mb-0">Driver Class:</label>
|
||||
<span className=" tw-ml-1 tw-font-normal tw-text-grey-body">
|
||||
{service.driverClass}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case ServiceCategory.MESSAGING_SERVICES: {
|
||||
return (
|
||||
<>
|
||||
<div className="tw-mb-1">
|
||||
<label className="tw-mb-0">Brokers:</label>
|
||||
<span className=" tw-ml-1 tw-font-normal tw-text-grey-body">
|
||||
{service.brokers?.join(', ')}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// fetch all service collection
|
||||
setIsLoading(true);
|
||||
@ -188,6 +269,22 @@ const ServicesPage = () => {
|
||||
{!isLoading ? (
|
||||
<PageContainer>
|
||||
<div className="container-fluid">
|
||||
<div className="tw-bg-transparent tw-mb-4">
|
||||
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4">
|
||||
{getServiceTabs().map((tab, index) => (
|
||||
<button
|
||||
className={getTabClasses(tab.name, serviceName)}
|
||||
data-testid="tab"
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setServiceName(tab.name);
|
||||
setServiceList(services[tab.name]);
|
||||
}}>
|
||||
{tab.displayName}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
{serviceList.length ? (
|
||||
<div className="tw-grid tw-grid-cols-4 tw-gap-4">
|
||||
{serviceList.map((service, index) => (
|
||||
@ -213,19 +310,9 @@ const ServicesPage = () => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* <div className="tw-my-2">
|
||||
<label className="tw-font-semibold ">Tags:</label>
|
||||
<span className="tw-tag tw-ml-3">mysql</span>
|
||||
<span className="tw-tag tw-ml-2">sales</span>
|
||||
</div> */}
|
||||
{getOptionalFields(service)}
|
||||
<div className="tw-mb-1">
|
||||
<label className="tw-mb-0">Driver Class:</label>
|
||||
<span className=" tw-ml-1 tw-font-normal tw-text-grey-body">
|
||||
{service.driverClass}
|
||||
</span>
|
||||
</div>
|
||||
<div className="tw-mb-1">
|
||||
<label className="tw-mb-0">Frequency:</label>
|
||||
<label className="tw-mb-0">Ingestion:</label>
|
||||
<span className=" tw-ml-1 tw-font-normal tw-text-grey-body">
|
||||
{service.ingestionSchedule
|
||||
? getFrequencyTime(
|
||||
@ -275,8 +362,8 @@ const ServicesPage = () => {
|
||||
className="tw-cursor-pointer tw-card tw-flex tw-flex-col tw-justify-center tw-items-center tw-py-2"
|
||||
onClick={() => handleAddService()}>
|
||||
<img alt="" src={PLUS} />
|
||||
<p className="tw-text-base tw-font-normal tw-mt-1">
|
||||
Add new service
|
||||
<p className="tw-text-base tw-font-normal tw-mt-4">
|
||||
Add new {servicesDisplayName[serviceName]}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -289,11 +376,11 @@ const ServicesPage = () => {
|
||||
<p className="tw-text-lg">
|
||||
No services found.{' '}
|
||||
<button
|
||||
className="tw-text-blue-700 tw-cursor-pointer tw-underline"
|
||||
className="link-text tw-underline"
|
||||
onClick={handleAddService}>
|
||||
Click here
|
||||
</button>{' '}
|
||||
to add new services
|
||||
to add new {servicesDisplayName[serviceName]}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -84,3 +84,10 @@ export const getUserTeams = (): Array<UserTeam> => {
|
||||
|
||||
return retVal;
|
||||
};
|
||||
|
||||
export const getTabClasses = (
|
||||
tab: number | string,
|
||||
activeTab: number | string
|
||||
) => {
|
||||
return 'tw-gh-tabs' + (activeTab === tab ? ' active' : '');
|
||||
};
|
||||
|
||||
@ -5,43 +5,54 @@ import { ServiceDataObj } from '../components/Modals/AddServiceModal/AddServiceM
|
||||
import {
|
||||
BIGQUERY,
|
||||
HIVE,
|
||||
KAFKA,
|
||||
MSSQL,
|
||||
MYSQL,
|
||||
ORACLE,
|
||||
POSTGRES,
|
||||
PULSAR,
|
||||
REDSHIFT,
|
||||
SERVICE_DEFAULT,
|
||||
SNOWFLAKE,
|
||||
} from '../constants/services.const';
|
||||
import { ServiceType } from '../enums/service.enum';
|
||||
import {
|
||||
DatabaseServiceType,
|
||||
MessagingServiceType,
|
||||
} from '../enums/service.enum';
|
||||
import { ApiData } from '../pages/services';
|
||||
|
||||
export const serviceTypeLogo = (type: string) => {
|
||||
switch (type) {
|
||||
case ServiceType.MYSQL:
|
||||
case DatabaseServiceType.MYSQL:
|
||||
return MYSQL;
|
||||
|
||||
case ServiceType.REDSHIFT:
|
||||
case DatabaseServiceType.REDSHIFT:
|
||||
return REDSHIFT;
|
||||
|
||||
case ServiceType.BIGQUERY:
|
||||
case DatabaseServiceType.BIGQUERY:
|
||||
return BIGQUERY;
|
||||
|
||||
case ServiceType.HIVE:
|
||||
case DatabaseServiceType.HIVE:
|
||||
return HIVE;
|
||||
|
||||
case ServiceType.POSTGRES:
|
||||
case DatabaseServiceType.POSTGRES:
|
||||
return POSTGRES;
|
||||
|
||||
case ServiceType.ORACLE:
|
||||
case DatabaseServiceType.ORACLE:
|
||||
return ORACLE;
|
||||
|
||||
case ServiceType.SNOWFLAKE:
|
||||
case DatabaseServiceType.SNOWFLAKE:
|
||||
return SNOWFLAKE;
|
||||
|
||||
case ServiceType.MSSQL:
|
||||
case DatabaseServiceType.MSSQL:
|
||||
return MSSQL;
|
||||
|
||||
case MessagingServiceType.KAFKA:
|
||||
return KAFKA;
|
||||
|
||||
case MessagingServiceType.PULSAR:
|
||||
return PULSAR;
|
||||
|
||||
default:
|
||||
return SERVICE_DEFAULT;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user