Fix #5551 Feat: Added support to add ML model service via UI and service details page (#5583)

This commit is contained in:
Shailesh Parmar 2022-06-23 02:24:50 +05:30 committed by GitHub
parent 452771c34e
commit 950c3a5992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 341 additions and 19 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.366 9.2C3.1035 9.07325 2.8105 9 2.5 9C1.39725 9 0.5 9.89725 0.5 11C0.5 12.1027 1.39725 13 2.5 13C3.192 13 3.80275 12.6465 4.162 12.1108L6.05125 13.0565C6.01875 13.1993 6 13.3475 6 13.5C6 14.6027 6.89725 15.5 8 15.5C9.10275 15.5 10 14.6027 10 13.5C10 13.0855 9.87325 12.7002 9.65625 12.3805L12.311 9.60475C12.644 9.85175 13.0545 10 13.5 10C14.6027 10 15.5 9.10275 15.5 8C15.5 6.89725 14.6027 6 13.5 6C13.0385 6 12.6145 6.1585 12.2755 6.422L9.578 3.7245C9.8415 3.3855 10 2.9615 10 2.5C10 1.39725 9.10275 0.5 8 0.5C6.89725 0.5 6 1.39725 6 2.5C6 2.652 6.0185 2.79975 6.051 2.94225L4.278 3.58825C3.9455 2.943 3.27425 2.5 2.5 2.5C1.39725 2.5 0.5 3.39725 0.5 4.5C0.5 5.60275 1.39725 6.5 2.5 6.5C2.7845 6.5 3.05475 6.4395 3.3 6.33175L4.22025 7.8805L3.366 9.2ZM2.5 12.5C1.67275 12.5 1 11.8273 1 11C1 10.1727 1.67275 9.5 2.5 9.5C3.32725 9.5 4 10.1727 4 11C4 11.8273 3.32725 12.5 2.5 12.5ZM4.38475 11.6628C4.458 11.455 4.5 11.2325 4.5 11C4.5 10.3867 4.222 9.83775 3.78625 9.47075L4.50525 8.36025L6.673 12.008C6.4905 12.1705 6.33775 12.365 6.2245 12.5837L4.38475 11.6628ZM8 4.5C8.4615 4.5 8.8855 4.3415 9.2245 4.078L11.9222 6.77575C11.7605 6.98375 11.6395 7.224 11.5697 7.4855L9.99925 7.2695C9.999 7.263 10 7.2565 10 7.25C10 6.14725 9.10275 5.25 8 5.25C7.308 5.25 6.697 5.6035 6.338 6.13925L6.03975 5.99025L7.13425 4.3C7.3965 4.42675 7.6895 4.5 8 4.5ZM8.25 11.5173V9.2325C9.0625 9.1305 9.7235 8.54 9.9305 7.7645L11.501 7.9805C11.501 7.987 11.5 7.9935 11.5 8C11.5 8.4775 11.6685 8.91575 11.9487 9.26L9.32375 12.0048C9.0295 11.744 8.65925 11.5688 8.25 11.5173V11.5173ZM8 5.75C8.82725 5.75 9.5 6.42275 9.5 7.25C9.5 8.07725 8.82725 8.75 8 8.75C7.17275 8.75 6.5 8.07725 6.5 7.25C6.5 6.42275 7.17275 5.75 8 5.75V5.75ZM6.11525 6.58725C6.042 6.795 6 7.0175 6 7.25C6 8.268 6.765 9.109 7.75 9.23275V11.5175C7.513 11.5472 7.28925 11.6183 7.0855 11.7235L4.8085 7.89175L5.76625 6.41275L6.11525 6.58725ZM8 15C7.17275 15 6.5 14.3273 6.5 13.5C6.5 12.6727 7.17275 12 8 12C8.82725 12 9.5 12.6727 9.5 13.5C9.5 14.3273 8.82725 15 8 15ZM13.5 6.5C14.3273 6.5 15 7.17275 15 8C15 8.82725 14.3273 9.5 13.5 9.5C12.6727 9.5 12 8.82725 12 8C12 7.17275 12.6727 6.5 13.5 6.5ZM8 1C8.82725 1 9.5 1.67275 9.5 2.5C9.5 3.32725 8.82725 4 8 4C7.17275 4 6.5 3.32725 6.5 2.5C6.5 1.67275 7.17275 1 8 1ZM6.222 3.41175C6.34425 3.649 6.5115 3.85875 6.71375 4.02925L5.58975 5.76525L4.38475 5.16275C4.458 4.955 4.5 4.7325 4.5 4.5C4.5 4.348 4.4815 4.20025 4.449 4.05775L6.222 3.41175ZM1 4.5C1 3.67275 1.67275 3 2.5 3C3.32725 3 4 3.67275 4 4.5C4 5.32725 3.32725 6 2.5 6C1.67275 6 1 5.32725 1 4.5ZM3.72875 6.07475C3.89625 5.94375 4.04325 5.78775 4.162 5.61075L5.31625 6.18775L4.5235 7.412L3.72875 6.07475Z" fill="#37352F" stroke="#37352F" stroke-width="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -12,6 +12,7 @@
*/
import { AxiosResponse } from 'axios';
import { isNil } from 'lodash';
import { Mlmodel } from '../generated/entity/data/mlmodel';
import { getURLWithQueryFields } from '../utils/APIUtils';
import APIClient from './index';
@ -25,6 +26,39 @@ export const getMlModelByFQN: Function = (
return APIClient.get(url);
};
export const getMlmodels = (
serviceName: string,
paging: string,
arrQueryFields: string[]
): Promise<AxiosResponse> => {
const url = `${getURLWithQueryFields(
`/mlmodels`,
arrQueryFields
)}&service=${serviceName}${paging ? paging : ''}`;
return APIClient.get(url);
};
export const getAllMlModal = (
paging: string,
arrQueryFields: string,
limit?: number
): Promise<AxiosResponse> => {
const searchParams = new URLSearchParams();
if (!isNil(limit)) {
searchParams.set('limit', `${limit}`);
}
const url = getURLWithQueryFields(
`/mlmodels`,
arrQueryFields,
`${searchParams.toString()}${paging ? `&${paging}` : ''}`
);
return APIClient.get(url);
};
export const patchMlModelDetails: Function = (
id: string,
data: Mlmodel

View File

@ -14,7 +14,10 @@
import classNames from 'classnames';
import { startCase } from 'lodash';
import React, { useEffect, useState } from 'react';
import { serviceTypes } from '../../../constants/services.const';
import {
excludedService,
serviceTypes,
} from '../../../constants/services.const';
import { ServiceCategory } from '../../../enums/service.enum';
import { errorMsg, getServiceLogo } from '../../../utils/CommonUtils';
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
@ -51,7 +54,11 @@ const SelectServiceType = ({
? serviceCategory
: allCategory[0];
setCategory(selectedCategory);
setSelectedConnectors(serviceTypes[selectedCategory]);
setSelectedConnectors(
serviceTypes[selectedCategory].filter(
(service) => !excludedService.find((e) => e === service)
)
);
}, [serviceCategory]);
return (

View File

@ -621,7 +621,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
};
} else {
const updatedColumnsLineage: ColumnLineage[] =
currentEdge.columnsLineage.map((l) => {
currentEdge.columnsLineage?.map((l) => {
if (l.toColumn === targetHandle) {
return {
...l,
@ -633,7 +633,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
}
return l;
});
}) || [];
if (
!updatedColumnsLineage.find((l) => l.toColumn === targetHandle)
) {

View File

@ -26,14 +26,17 @@ import React, {
import AppState from '../../AppState';
import {
getDashboardDetailsPath,
getServiceDetailsPath,
getTeamAndUserDetailsPath,
} from '../../constants/constants';
import { EntityType } from '../../enums/entity.enum';
import { ServiceCategory } from '../../enums/service.enum';
import { OwnerType } from '../../enums/user.enum';
import { Mlmodel } from '../../generated/entity/data/mlmodel';
import { EntityReference } from '../../generated/type/entityReference';
import { LabelType, State, TagLabel } from '../../generated/type/tagLabel';
import { getEntityName, getEntityPlaceHolder } from '../../utils/CommonUtils';
import { serviceTypeLogo } from '../../utils/ServiceUtils';
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
import Description from '../common/description/Description';
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
@ -83,8 +86,19 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
const mlModelTags = useMemo(() => {
return getTagsWithoutTier(mlModelDetail.tags || []);
}, [mlModelDetail.tags]);
const slashedMlModelName: TitleBreadcrumbProps['titleLinks'] = [
{
name: mlModelDetail.service.name || '',
url: mlModelDetail.service.name
? getServiceDetailsPath(
mlModelDetail.service.name,
ServiceCategory.ML_MODAL_SERVICES
)
: '',
imgSrc: mlModelDetail.serviceType
? serviceTypeLogo(mlModelDetail.serviceType || '')
: undefined,
},
{
name: getEntityName(mlModelDetail as unknown as EntityReference),
url: '',

View File

@ -30,6 +30,7 @@ type Props = {
countDashboards: number;
countPipelines: number;
countServices: number;
countMlModal: number;
countTables: number;
countTopics: number;
countTeams: number;
@ -47,6 +48,7 @@ type Summary = {
const MyAssetStats: FunctionComponent<Props> = ({
countDashboards,
countPipelines,
countMlModal,
countServices,
countTables,
countTopics,
@ -85,6 +87,13 @@ const MyAssetStats: FunctionComponent<Props> = ({
link: getExplorePathWithSearch(undefined, 'pipelines'),
dataTestId: 'pipelines',
},
mlModal: {
icon: Icons.MLMODAL,
data: 'ML Models',
count: countMlModal,
link: getExplorePathWithSearch(undefined, 'mlmodels'),
dataTestId: 'mlmodels',
},
service: {
icon: Icons.SERVICE,
data: 'Services',

View File

@ -38,6 +38,7 @@ const mockProp = {
countTeams: 7,
countTopics: 13,
countUsers: 100,
countMlModal: 2,
};
describe('Test MyDataHeader Component', () => {
@ -51,14 +52,14 @@ describe('Test MyDataHeader Component', () => {
expect(myDataHeader).toBeInTheDocument();
});
it('Should have 7 data summary details', () => {
it('Should have 8 data summary details', () => {
const { container } = render(<MyAssetStats {...mockProp} />, {
wrapper: MemoryRouter,
});
const dataSummary = getAllByTestId(container, /-summary$/);
expect(dataSummary.length).toBe(7);
expect(dataSummary.length).toBe(8);
});
it('OnClick it should redirect to respective page', () => {
@ -72,6 +73,7 @@ describe('Test MyDataHeader Component', () => {
const topics = getByTestId(container, 'topics');
const dashboards = getByTestId(container, 'dashboards');
const pipelines = getByTestId(container, 'pipelines');
const mlmodel = getByTestId(container, 'mlmodels');
const service = getByTestId(container, 'service');
const user = getByTestId(container, 'user');
const terms = getByTestId(container, 'terms');
@ -80,6 +82,7 @@ describe('Test MyDataHeader Component', () => {
expect(topics).toHaveAttribute('href', '/explore/topics/');
expect(dashboards).toHaveAttribute('href', '/explore/dashboards/');
expect(pipelines).toHaveAttribute('href', '/explore/pipelines/');
expect(mlmodel).toHaveAttribute('href', '/explore/mlmodels/');
expect(service).toHaveAttribute('href', '/services');
expect(user).toHaveAttribute(
'href',

View File

@ -51,6 +51,7 @@ const MyData: React.FC<MyDataProps> = ({
countTeams,
countUsers,
ownedData,
countMlModal,
followedData,
feedData,
feedFilter,
@ -108,6 +109,7 @@ const MyData: React.FC<MyDataProps> = ({
<div className="tw-mt-4">
<MyAssetStats
countDashboards={countDashboards}
countMlModal={countMlModal}
countPipelines={countPipelines}
countServices={countServices}
countTables={countTables}

View File

@ -25,6 +25,7 @@ export interface MyDataProps {
countTopics: number;
countTeams: number;
countUsers: number;
countMlModal: number;
countDashboards: number;
followedDataCount: number;
ownedDataCount: number;

View File

@ -328,6 +328,7 @@ const mockProp: MyDataProps = {
countTopics: 5,
countTeams: 7,
countUsers: 100,
countMlModal: 2,
followedDataCount: 5,
ownedDataCount: 5,
error: '',

View File

@ -17,6 +17,7 @@ import { LoadingState } from 'Models';
import React, { Fragment, FunctionComponent, useMemo } from 'react';
import { TestConnection } from '../../axiosAPIs/serviceAPI';
import { ServiceCategory } from '../../enums/service.enum';
import { MlModelServiceType } from '../../generated/api/services/createMlModelService';
import {
DashboardService,
DashboardServiceType,
@ -29,6 +30,7 @@ import {
MessagingService,
MessagingServiceType,
} from '../../generated/entity/services/messagingService';
import { MlmodelService } from '../../generated/entity/services/mlmodelService';
import {
PipelineService,
PipelineServiceType,
@ -39,6 +41,7 @@ import { getDashboardConfig } from '../../utils/DashboardServiceUtils';
import { getDatabaseConfig } from '../../utils/DatabaseServiceUtils';
import { formatFormDataForSubmit } from '../../utils/JSONSchemaFormUtils';
import { getMessagingConfig } from '../../utils/MessagingServiceUtils';
import { getMlmodelConfig } from '../../utils/MlmodelServiceUtils';
import { getPipelineConfig } from '../../utils/PipelineServiceUtils';
import {
getTestConnectionType,
@ -52,7 +55,8 @@ interface Props {
| DatabaseService
| MessagingService
| DashboardService
| PipelineService;
| PipelineService
| MlmodelService;
okText?: string;
cancelText?: string;
serviceType: string;
@ -142,6 +146,11 @@ const ConnectionConfigForm: FunctionComponent<Props> = ({
case ServiceCategory.PIPELINE_SERVICES: {
connSch = getPipelineConfig(serviceType as PipelineServiceType);
break;
}
case ServiceCategory.ML_MODAL_SERVICES: {
connSch = getMlmodelConfig(serviceType as MlModelServiceType);
break;
}
}

View File

@ -23,11 +23,13 @@ import { EntityType } from '../../enums/entity.enum';
import { DashboardServiceType } from '../../generated/entity/services/dashboardService';
import { DatabaseServiceType } from '../../generated/entity/services/databaseService';
import { MessagingServiceType } from '../../generated/entity/services/messagingService';
import { MlModelServiceType } from '../../generated/entity/services/mlmodelService';
import { PipelineServiceType } from '../../generated/entity/services/pipelineService';
import { ConfigData } from '../../interface/service.interface';
import { getDashboardConfig } from '../../utils/DashboardServiceUtils';
import { getDatabaseConfig } from '../../utils/DatabaseServiceUtils';
import { getMessagingConfig } from '../../utils/MessagingServiceUtils';
import { getMlmodelConfig } from '../../utils/MlmodelServiceUtils';
import { getPipelineConfig } from '../../utils/PipelineServiceUtils';
import PopOver from '../common/popover/PopOver';
@ -143,6 +145,10 @@ const ServiceConnectionDetails = ({
case EntityType.PIPELINE_SERVICE:
setSchema(getPipelineConfig(serviceFQN as PipelineServiceType).schema);
break;
case EntityType.MLMODEL_SERVICE:
setSchema(getMlmodelConfig(serviceFQN as MlModelServiceType).schema);
break;
}
}, [serviceCategory, serviceFQN]);

View File

@ -30,6 +30,7 @@ import kafka from '../assets/img/service-icon-kafka.png';
import looker from '../assets/img/service-icon-looker.png';
import mariadb from '../assets/img/service-icon-mariadb.png';
import metabase from '../assets/img/service-icon-metabase.png';
import mlflow from '../assets/img/service-icon-mlflow.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';
@ -41,6 +42,7 @@ import query from '../assets/img/service-icon-query.png';
import redash from '../assets/img/service-icon-redash.png';
import redshift from '../assets/img/service-icon-redshift.png';
import salesforce from '../assets/img/service-icon-salesforce.png';
import scikit from '../assets/img/service-icon-scikit.png';
import singlestore from '../assets/img/service-icon-singlestore.png';
import snowflakes from '../assets/img/service-icon-snowflakes.png';
import mysql from '../assets/img/service-icon-sql.png';
@ -57,6 +59,7 @@ import topicDefault from '../assets/svg/topic.svg';
import { DashboardServiceType } from '../generated/entity/services/dashboardService';
import { DatabaseServiceType } from '../generated/entity/services/databaseService';
import { MessagingServiceType } from '../generated/entity/services/messagingService';
import { MlModelServiceType } from '../generated/entity/services/mlmodelService';
import { PipelineServiceType } from '../generated/entity/services/pipelineService';
export const NoDataFoundPlaceHolder = noDataFound;
@ -90,6 +93,8 @@ export const DRUID = druid;
export const DYNAMODB = dynamodb;
export const SINGLESTORE = singlestore;
export const SALESFORCE = salesforce;
export const MLFLOW = mlflow;
export const SCIKIT = scikit;
export const DELTALAKE = deltalake;
export const DEFAULT_SERVICE = iconDefaultService;
@ -103,12 +108,13 @@ export const PIPELINE_DEFAULT = pipelineDefault;
export const PLUS = plus;
export const NOSERVICE = noService;
export const excludedService = [MlModelServiceType.Sklearn];
export const serviceTypes: Record<ServiceTypes, Array<string>> = {
databaseServices: Object.values(DatabaseServiceType),
messagingServices: Object.values(MessagingServiceType),
dashboardServices: Object.values(DashboardServiceType),
pipelineServices: Object.values(PipelineServiceType),
mlmodelServices: Object.values(MlModelServiceType),
};
export const arrServiceTypes: Array<ServiceTypes> = [
@ -116,6 +122,7 @@ export const arrServiceTypes: Array<ServiceTypes> = [
'messagingServices',
'dashboardServices',
'pipelineServices',
'mlmodelServices',
];
export const servicesDisplayName = {
@ -123,6 +130,7 @@ export const servicesDisplayName = {
messagingServices: 'Messaging Service',
dashboardServices: 'Dashboard Service',
pipelineServices: 'Pipeline Service',
mlmodelServices: 'ML Model Service',
};
export const STEPS_FOR_ADD_SERVICE: Array<StepperStepType> = [

View File

@ -25,6 +25,7 @@ export enum EntityType {
MESSAGING_SERVICE = 'messagingService',
DASHBOARD_SERVICE = 'dashboardService',
PIPELINE_SERVICE = 'pipelineService',
MLMODEL_SERVICE = 'mlmodelService',
WEBHOOK = 'webhook',
MLMODEL = 'mlmodel',
TYPE = 'type',

View File

@ -16,6 +16,7 @@ export enum ServiceCategory {
MESSAGING_SERVICES = 'messagingServices',
DASHBOARD_SERVICES = 'dashboardServices',
PIPELINE_SERVICES = 'pipelineServices',
ML_MODAL_SERVICES = 'mlmodelServices',
}
export enum IngestionType {

View File

@ -15,6 +15,7 @@ import { DynamicObj, Paging } from 'Models';
import { DashboardService } from '../generated/entity/services/dashboardService';
import { DatabaseService } from '../generated/entity/services/databaseService';
import { MessagingService } from '../generated/entity/services/messagingService';
import { MlmodelService } from '../generated/entity/services/mlmodelService';
import { PipelineService } from '../generated/entity/services/pipelineService';
export interface IngestionSchedule {
@ -60,13 +61,15 @@ export interface EditObj {
export type ServiceDataObj = { name: string } & Partial<DatabaseService> &
Partial<MessagingService> &
Partial<DashboardService> &
Partial<PipelineService>;
Partial<PipelineService> &
Partial<MlmodelService>;
export type DataService =
| DatabaseService
| MessagingService
| DashboardService
| PipelineService;
| PipelineService
| MlmodelService;
export interface ServiceResponse {
data: Array<ServiceDataObj>;
@ -76,4 +79,5 @@ export interface ServiceResponse {
export type ConfigData = Partial<DatabaseService['connection']> &
Partial<MessagingService['connection']> &
Partial<DashboardService['connection']> &
Partial<PipelineService['connection']>;
Partial<PipelineService['connection']> &
Partial<MlmodelService['connection']>;

View File

@ -399,7 +399,8 @@ declare module 'Models' {
| 'databaseServices'
| 'messagingServices'
| 'dashboardServices'
| 'pipelineServices';
| 'pipelineServices'
| 'mlmodelServices';
export type SampleData = {
columns: Array<string>;

View File

@ -41,9 +41,46 @@ export const mockServiceDetails = {
href: 'http://pipelineServices',
},
},
{
collection: {
name: 'mlmodelServices',
documentation: 'MlModel service collection',
href: 'http://localhost:8585/api/v1/services/mlmodelServices',
},
},
],
};
export const mockMlmodelService = {
data: {
data: [
{
id: 'b59a9acb-6c90-481e-afd9-ec0f208c4f35',
name: 'mlflow_svc',
fullyQualifiedName: 'mlflow_svc',
serviceType: 'Mlflow',
description: 'description for mlflow_svc',
version: 0.4,
updatedAt: 1655890983668,
updatedBy: 'anonymous',
connection: {
config: {
type: 'Mlflow',
registryUri: 'http://localhost:8088',
trackingUri: 'http://localhost:8088',
supportsMetadataExtraction: null,
},
},
href: 'http://localhost:8585/api/v1/services/mlmodelServices/b59a9acb-6c90-481e-afd9-ec0f208c4f35',
deleted: false,
},
],
paging: {
total: 1,
},
},
};
export const mockDatabaseService = {
data: {
data: [

View File

@ -22,6 +22,7 @@ import AppState from '../../AppState';
import { getAllDashboards } from '../../axiosAPIs/dashboardAPI';
import { getFeedsWithFilter, postFeedById } from '../../axiosAPIs/feedsAPI';
import { fetchSandboxConfig, searchData } from '../../axiosAPIs/miscAPI';
import { getAllMlModal } from '../../axiosAPIs/mlModelAPI';
import { getAllPipelines } from '../../axiosAPIs/pipelineAPI';
import { getAllTables } from '../../axiosAPIs/tableAPI';
import { getTeams } from '../../axiosAPIs/teamsAPI';
@ -60,6 +61,7 @@ const MyDataPage = () => {
const [countTopics, setCountTopics] = useState<number>();
const [countDashboards, setCountDashboards] = useState<number>();
const [countPipelines, setCountPipelines] = useState<number>();
const [countMlModal, setCountMlModal] = useState<number>();
const [countUsers, setCountUsers] = useState<number>();
const [countTeams, setCountTeams] = useState<number>();
@ -171,6 +173,23 @@ const MyDataPage = () => {
);
setCountDashboards(0);
});
// limit=0 will fetch empty data list with total count
getAllMlModal('', '', 0)
.then((res) => {
if (res.data) {
setCountMlModal(res.data.paging.total);
} else {
throw jsonData['api-error-messages']['unexpected-server-response'];
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['unexpected-server-response']
);
setCountMlModal(0);
});
};
const fetchTeamsAndUsersCount = () => {
@ -415,10 +434,12 @@ const MyDataPage = () => {
!isUndefined(countDashboards) &&
!isUndefined(countPipelines) &&
!isUndefined(countTeams) &&
!isUndefined(countMlModal) &&
!isUndefined(countUsers) ? (
<Fragment>
<MyData
countDashboards={countDashboards}
countMlModal={countMlModal}
countPipelines={countPipelines}
countServices={countServices}
countTables={countTables}

View File

@ -111,6 +111,16 @@ jest.mock('../../axiosAPIs/pipelineAPI', () => ({
),
}));
jest.mock('../../axiosAPIs/mlModelAPI', () => ({
getAllMlModal: jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
data: [],
},
})
),
}));
jest.mock('../../axiosAPIs/userAPI', () => ({
getUsers: jest.fn().mockImplementation(() =>
Promise.resolve({

View File

@ -28,6 +28,7 @@ import {
triggerIngestionPipelineById,
} from '../../axiosAPIs/ingestionPipelineAPI';
import { fetchAirflowConfig } from '../../axiosAPIs/miscAPI';
import { getMlmodels } from '../../axiosAPIs/mlModelAPI';
import { getPipelines } from '../../axiosAPIs/pipelineAPI';
import { getServiceByFQN, updateService } from '../../axiosAPIs/serviceAPI';
import { getTopics } from '../../axiosAPIs/topicsAPI';
@ -57,6 +58,7 @@ import { ServiceCategory } from '../../enums/service.enum';
import { OwnerType } from '../../enums/user.enum';
import { Dashboard } from '../../generated/entity/data/dashboard';
import { Database } from '../../generated/entity/data/database';
import { Mlmodel } from '../../generated/entity/data/mlmodel';
import { Pipeline } from '../../generated/entity/data/pipeline';
import { Topic } from '../../generated/entity/data/topic';
import { DatabaseService } from '../../generated/entity/services/databaseService';
@ -129,6 +131,8 @@ const ServicePage: FunctionComponent = () => {
return 'Topics';
case ServiceCategory.PIPELINE_SERVICES:
return 'Pipelines';
case ServiceCategory.ML_MODAL_SERVICES:
return 'Models';
case ServiceCategory.DATABASE_SERVICES:
default:
return 'Databases';
@ -420,6 +424,26 @@ const ServicePage: FunctionComponent = () => {
});
};
const fetchMlModal = (paging = '') => {
setIsloading(true);
getMlmodels(serviceFQN, paging, ['owner', 'tags'])
.then((res: AxiosResponse) => {
if (res.data.data) {
setData(res.data.data);
setPaging(res.data.paging);
setInstanceCount(res.data.paging.total);
setIsloading(false);
} else {
setData([]);
setPaging(pagingObject);
setIsloading(false);
}
})
.catch(() => {
setIsloading(false);
});
};
const getAirflowStatus = () => {
return new Promise<void>((resolve, reject) => {
checkAirflowStatus()
@ -456,6 +480,11 @@ const ServicePage: FunctionComponent = () => {
break;
}
case ServiceCategory.ML_MODAL_SERVICES: {
fetchMlModal(paging);
break;
}
default:
break;
}
@ -472,6 +501,9 @@ const ServicePage: FunctionComponent = () => {
case ServiceCategory.PIPELINE_SERVICES:
return getEntityLink(SearchIndex.PIPELINE, fqn);
case ServiceCategory.ML_MODAL_SERVICES:
return getEntityLink(SearchIndex.MLMODEL, fqn);
case ServiceCategory.DATABASE_SERVICES:
default:
return `/database/${fqn}`;
@ -520,6 +552,16 @@ const ServicePage: FunctionComponent = () => {
</>
);
}
case ServiceCategory.ML_MODAL_SERVICES: {
return (
<>
<th className="tableHead-cell">Model Name</th>
<th className="tableHead-cell">Description</th>
<th className="tableHead-cell">Owner</th>
<th className="tableHead-cell">Tags</th>
</>
);
}
default:
return <></>;
}
@ -594,6 +636,24 @@ const ServicePage: FunctionComponent = () => {
</td>
);
}
case ServiceCategory.ML_MODAL_SERVICES: {
const mlmodal = data as Mlmodel;
return (
<td className="tableBody-cell">
{mlmodal.tags && mlmodal.tags?.length > 0 ? (
<TagsViewer
showStartWith={false}
sizeCap={-1}
tags={mlmodal.tags}
type="border"
/>
) : (
'--'
)}
</td>
);
}
default:
return <></>;
}

View File

@ -37,6 +37,7 @@ import {
mockLookerService,
mockMessagingService,
mockMetabaseService,
mockMlmodelService,
mockPipelineService,
mockPowerBIService,
mockPulsarService,
@ -81,6 +82,9 @@ jest.mock('../../axiosAPIs/serviceAPI', () => ({
case 'pipelineServices':
return Promise.resolve(mockPipelineService);
case 'mlmodelServices':
return Promise.resolve(mockMlmodelService);
default:
return Promise.resolve(mockDashboardService);
}

View File

@ -45,6 +45,7 @@ import { ServiceCategory } from '../../enums/service.enum';
import { DashboardService } from '../../generated/entity/services/dashboardService';
import { DatabaseService } from '../../generated/entity/services/databaseService';
import { MessagingService } from '../../generated/entity/services/messagingService';
import { MlmodelService } from '../../generated/entity/services/mlmodelService';
import { PipelineService } from '../../generated/entity/services/pipelineService';
import { EntityReference } from '../../generated/type/entityReference';
import { Paging } from '../../generated/type/paging';
@ -71,6 +72,7 @@ type ServiceRecord = {
messagingServices: Array<MessagingService>;
dashboardServices: Array<DashboardService>;
pipelineServices: Array<PipelineService>;
mlmodelServices: Array<MlmodelService>;
};
type ServicePagingRecord = {
@ -78,6 +80,7 @@ type ServicePagingRecord = {
messagingServices: Paging;
dashboardServices: Paging;
pipelineServices: Paging;
mlmodelServices: Paging;
};
export type ApiData = {
@ -104,12 +107,14 @@ const ServicesPage = () => {
messagingServices: pagingObject,
dashboardServices: pagingObject,
pipelineServices: pagingObject,
mlmodelServices: pagingObject,
});
const [services, setServices] = useState<ServiceRecord>({
databaseServices: [],
messagingServices: [],
dashboardServices: [],
pipelineServices: [],
mlmodelServices: [],
});
const [serviceList, setServiceList] = useState<Array<ServiceDataObj>>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
@ -121,6 +126,7 @@ const ServicesPage = () => {
messagingServices: 0,
dashboardServices: 0,
pipelineServices: 0,
mlmodelServices: 0,
});
const [currentPage, setCurrentPage] = useState(1);
@ -170,6 +176,7 @@ const ServicesPage = () => {
messagingServices: servicePaging.messagingServices.total || 0,
dashboardServices: servicePaging.dashboardServices.total || 0,
pipelineServices: servicePaging.pipelineServices.total || 0,
mlmodelServices: servicePaging.mlmodelServices.total || 0,
});
setServiceList(
serviceRecord[serviceName] as unknown as Array<ServiceDataObj>
@ -190,14 +197,14 @@ const ServicesPage = () => {
}
}
}
setIsLoading(false);
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-services-error']
);
});
})
.finally(() => setIsLoading(false));
}
};
@ -255,7 +262,7 @@ const ServicesPage = () => {
onClick={() => {
handleTabChange(tab.name);
}}>
<p className="tw-text-center tw-self-center label-category">
<p className="tw-text-center tw-mb-0 tw-self-center label-category">
{tab.displayName}
</p>
@ -322,6 +329,31 @@ const ServicesPage = () => {
</>
);
}
case ServiceCategory.ML_MODAL_SERVICES: {
const mlmodel = service as unknown as MlmodelService;
return (
<>
<div className="tw-mb-1 tw-truncate" data-testid="additional-field">
<label className="tw-mb-0">Registry:</label>
<span
className=" tw-ml-1 tw-font-normal tw-text-grey-body"
data-testid="pipeline-url">
{mlmodel.connection.config?.registryUri}
</span>
</div>
<div className="tw-mb-1 tw-truncate" data-testid="additional-field">
<label className="tw-mb-0">Tracking:</label>
<span
className=" tw-ml-1 tw-font-normal tw-text-grey-body"
data-testid="pipeline-url">
{mlmodel.connection.config?.trackingUri}
</span>
</div>
</>
);
}
default: {
return <></>;
}
@ -416,7 +448,7 @@ const ServicesPage = () => {
{serviceList.map((service, index) => (
<Card key={index} style={leftPanelAntCardStyle}>
<div
className="tw-flex tw-py-2 tw-px-3 tw-justify-between tw-text-grey-muted"
className="tw-flex tw-justify-between tw-text-grey-muted"
data-testid="service-card">
<div className="tw-flex tw-flex-col tw-justify-between tw-truncate">
<div>

View File

@ -127,6 +127,7 @@ const TourPage = () => {
return (
<MyData
countDashboards={10}
countMlModal={2}
countPipelines={8}
countServices={4}
countTables={21}

View File

@ -216,7 +216,7 @@
}
.tw-form-container {
@apply tw-bg-white tw-py-7 tw-px-5 tw-border tw-rounded-lg tw-shadow-form;
@apply tw-bg-white tw-py-7 tw-px-5 tw-border tw-rounded-lg tw-shadow-form tw-w-700;
}
/* Dropdown CSS start */

View File

@ -186,7 +186,7 @@ export const getLineageDataV1 = (
const targetType = nodes.find((n) => edge.toEntity === n.id);
if (!isUndefined(edge.lineageDetails)) {
edge.lineageDetails.columnsLineage.forEach((e) => {
edge.lineageDetails.columnsLineage?.forEach((e) => {
const toColumn = e.toColumn || '';
if (e.fromColumns && e.fromColumns.length > 0) {
e.fromColumns.forEach((fromColumn) => {

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep } from 'lodash';
import { COMMON_UI_SCHEMA } from '../constants/services.const';
import { MlModelServiceType } from '../generated/entity/services/mlmodelService';
import mlflowConnection from '../jsons/connectionSchemas/connections/mlmodel/mlflowConnection.json';
import sklearnConnection from '../jsons/connectionSchemas/connections/mlmodel/sklearnConnection.json';
export const getMlmodelConfig = (type: MlModelServiceType) => {
let schema = {};
const uiSchema = { ...COMMON_UI_SCHEMA };
switch (type) {
case MlModelServiceType.Mlflow: {
schema = mlflowConnection;
break;
}
case MlModelServiceType.Sklearn: {
schema = sklearnConnection;
break;
}
}
return cloneDeep({ schema, uiSchema });
};

View File

@ -52,6 +52,7 @@ import {
LOOKER,
MARIADB,
METABASE,
MLFLOW,
MSSQL,
MYSQL,
ORACLE,
@ -64,6 +65,7 @@ import {
REDASH,
REDSHIFT,
SALESFORCE,
SCIKIT,
serviceTypes,
SINGLESTORE,
SNOWFLAKE,
@ -76,6 +78,7 @@ import {
} from '../constants/services.const';
import { ServiceCategory } from '../enums/service.enum';
import { ConnectionType } from '../generated/api/services/ingestionPipelines/testServiceConnection';
import { MlModelServiceType } from '../generated/entity/data/mlmodel';
import { DashboardServiceType } from '../generated/entity/services/dashboardService';
import { DatabaseServiceType } from '../generated/entity/services/databaseService';
import { PipelineType as IngestionPipelineType } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
@ -186,6 +189,12 @@ export const serviceTypeLogo = (type: string) => {
case PipelineServiceType.Prefect:
return PREFECT;
case MlModelServiceType.Mlflow:
return MLFLOW;
case MlModelServiceType.Sklearn:
return SCIKIT;
default: {
let logo;
if (serviceTypes.messagingServices.includes(type)) {

View File

@ -110,6 +110,7 @@ import LogoMonogram from '../assets/svg/logo-monogram.svg';
import Logo from '../assets/svg/logo.svg';
import IconManageColor from '../assets/svg/manage-color.svg';
import IconMinus from '../assets/svg/minus.svg';
import IconMlModal from '../assets/svg/mlmodal.svg';
import IconPaperPlanePrimary from '../assets/svg/paper-plane-primary.svg';
import IconPaperPlane from '../assets/svg/paper-plane.svg';
import IconPendingBadge from '../assets/svg/pending-badge.svg';
@ -238,6 +239,7 @@ export const Icons = {
EXTERNAL_LINK_GREY: 'external-link-grey',
PROFILER: 'icon-profiler',
PIPELINE: 'pipeline',
MLMODAL: 'mlmodal',
PIPELINE_GREY: 'pipeline-grey',
DBTMODEL_GREY: 'dbtmodel-grey',
DBTMODEL_LIGHT_GREY: 'dbtmodel-light-grey',
@ -570,6 +572,10 @@ const SVGIcons: FunctionComponent<Props> = ({
case Icons.TOPIC:
IconComponent = IconTopic;
break;
case Icons.MLMODAL:
IconComponent = IconMlModal;
break;
case Icons.DASHBOARD:
IconComponent = IconDashboard;

View File

@ -173,6 +173,7 @@ module.exports = {
'screen-xxl': '2160px',
'full-hd': '1080px',
600: '600px',
700: '700px',
},
minWidth: {
badgeCount: '30px',