From bce4bcd32fcfd4dcc52caab3c1cb335f04093ea3 Mon Sep 17 00:00:00 2001 From: Aniket Katkar Date: Fri, 8 Aug 2025 07:55:55 +0530 Subject: [PATCH] Fix service insight live update bugs (#22822) * Fix live update bugs * support automator status * fix query search index * Update the logic to display the AutoPilot status and add playwright test * Fix tests --------- Co-authored-by: ulixius9 --- .../DataInsightSystemChartRepository.java | 140 ++++- .../DataInsightSystemChartResource.java | 7 +- .../messages/ChartDataStreamMessage.java | 7 +- .../elasticsearch/en/query_index_mapping.json | 8 +- .../elasticsearch/jp/query_index_mapping.json | 8 +- .../elasticsearch/zh/query_index_mapping.json | 8 +- .../playwright/e2e/Features/AutoPilot.spec.ts | 61 ++- .../entity/ingestion/ServiceBaseClass.ts | 2 +- .../CuratedAssetsWidget.test.tsx | 4 + .../AgentsStatusWidget.interface.ts | 2 + .../AgentsStatusWidget/AgentsStatusWidget.tsx | 27 +- .../agents-status-widget.less | 13 + .../PlatformInsightsWidget.test.tsx | 506 ++++++++++++++++++ .../PlatformInsightsWidget.tsx | 42 +- .../platform-insights-widget.less | 10 + .../ServiceInsightsTab.interface.ts | 1 - .../ServiceInsights/ServiceInsightsTab.tsx | 50 +- .../TotalDataAssetsWidget.tsx | 14 +- .../total-data-assets-widget.less | 7 +- .../TotalDataAssetsWidget.constants.ts | 35 -- .../ui/src/locale/languages/de-de.json | 1 + .../ui/src/locale/languages/en-us.json | 1 + .../ui/src/locale/languages/es-es.json | 1 + .../ui/src/locale/languages/fr-fr.json | 1 + .../ui/src/locale/languages/gl-es.json | 1 + .../ui/src/locale/languages/he-he.json | 1 + .../ui/src/locale/languages/ja-jp.json | 1 + .../ui/src/locale/languages/ko-kr.json | 1 + .../ui/src/locale/languages/mr-in.json | 1 + .../ui/src/locale/languages/nl-nl.json | 1 + .../ui/src/locale/languages/pr-pr.json | 1 + .../ui/src/locale/languages/pt-br.json | 1 + .../ui/src/locale/languages/pt-pt.json | 1 + .../ui/src/locale/languages/ru-ru.json | 1 + .../ui/src/locale/languages/th-th.json | 1 + .../ui/src/locale/languages/tr-tr.json | 1 + .../ui/src/locale/languages/zh-cn.json | 1 + .../resources/ui/src/rest/DataInsightAPI.ts | 1 + .../ui/src/utils/AgentsStatusWidgetUtils.tsx | 30 +- .../ui/src/utils/ServiceInsightsTabUtils.tsx | 29 +- 40 files changed, 908 insertions(+), 121 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.test.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/constants/TotalDataAssetsWidget.constants.ts diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataInsightSystemChartRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataInsightSystemChartRepository.java index 7c557ff2588..1a1229c38bf 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataInsightSystemChartRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataInsightSystemChartRepository.java @@ -25,6 +25,7 @@ import org.openmetadata.schema.entity.app.AppType; import org.openmetadata.schema.entity.services.DatabaseService; import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline; import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatus; +import org.openmetadata.schema.governance.workflows.WorkflowInstance; import org.openmetadata.schema.type.EntityReference; import org.openmetadata.schema.type.Include; import org.openmetadata.schema.utils.JsonUtils; @@ -33,6 +34,7 @@ import org.openmetadata.service.search.SearchClient; import org.openmetadata.service.socket.WebSocketManager; import org.openmetadata.service.socket.messages.ChartDataStreamMessage; import org.openmetadata.service.util.EntityUtil; +import org.openmetadata.service.util.FullyQualifiedName; import org.openmetadata.service.util.ResultList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -238,6 +240,67 @@ public class DataInsightSystemChartRepository extends EntityRepository getWorkflowInstances(String entityLink, long startTime, long endTime) { + List workflowInstances = new ArrayList<>(); + + try { + if (entityLink == null || entityLink.trim().isEmpty()) { + return workflowInstances; + } + + // Get the workflow instance repository + WorkflowInstanceRepository workflowInstanceRepository = + (WorkflowInstanceRepository) + Entity.getEntityTimeSeriesRepository(Entity.WORKFLOW_INSTANCE); + + if (workflowInstanceRepository == null) { + LOG.warn("WorkflowInstanceRepository not available"); + return workflowInstances; + } + + // Create filter for workflow instances + ListFilter filter = new ListFilter(null); + filter.addQueryParam("entityFQNHash", FullyQualifiedName.buildHash("AutoPilotWorkflow")); + filter.addQueryParam("entityLink", entityLink); + + // Fetch workflow instances + ResultList instances = + workflowInstanceRepository.list(null, startTime, endTime, 100, filter, false); + + if (instances != null && instances.getData() != null) { + for (WorkflowInstance instance : instances.getData()) { + Map instanceData = new HashMap<>(); + instanceData.put("id", instance.getId().toString()); + instanceData.put("workflowDefinitionId", instance.getWorkflowDefinitionId().toString()); + instanceData.put("startedAt", instance.getStartedAt()); + instanceData.put("endedAt", instance.getEndedAt()); + instanceData.put("status", instance.getStatus().toString()); + instanceData.put("timestamp", instance.getTimestamp()); + instanceData.put("variables", instance.getVariables()); + instanceData.put("entityLink", entityLink); + instanceData.put("type", "workflow"); + + workflowInstances.add(instanceData); + } + } + + LOG.info( + "Found {} workflow instances for entity link: {}", workflowInstances.size(), entityLink); + + } catch (Exception e) { + LOG.error("Error fetching workflow instances for entity link: {}", entityLink, e); + } + + return workflowInstances; + } + /** * Get the app repository */ @@ -661,9 +724,11 @@ public class DataInsightSystemChartRepository extends EntityRepository session.getChartNames().equals(chartNames)) .filter( @@ -677,6 +742,17 @@ public class DataInsightSystemChartRepository extends EntityRepository { + // Handle null entityLink comparison + if (entityLink == null && session.getEntityLink() == null) { + return true; + } + if (entityLink != null && session.getEntityLink() != null) { + return entityLink.equals(session.getEntityLink()); + } + return false; + }) .findFirst() .orElse(null); } @@ -695,6 +771,7 @@ public class DataInsightSystemChartRepository extends EntityRepository response = new HashMap<>(); response.put("sessionId", sessionId); @@ -803,22 +884,24 @@ public class DataInsightSystemChartRepository extends EntityRepository future = @@ -862,7 +946,8 @@ public class DataInsightSystemChartRepository extends EntityRepository ingestionPipelineStatus = getIngestionPipelineStatus(session.getServiceName()); + // Fetch workflow instances for the entity link + List workflowInstances = + getWorkflowInstances(session.getEntityLink(), startTime, endTime); + // Send the data to all users in the session sendMessageToAllUsers( session, @@ -969,7 +1061,8 @@ public class DataInsightSystemChartRepository extends EntityRepository ingestionPipelineStatus, - List appStatus) { + List appStatus, + List workflowInstances) { ChartDataStreamMessage message = new ChartDataStreamMessage( sessionId, @@ -1023,7 +1119,8 @@ public class DataInsightSystemChartRepository extends EntityRepository ingestionPipelineStatus, - List appStatus) { + List appStatus, + List workflowInstances) { for (UUID userId : session.getUserIds()) { sendMessageToUser( userId, @@ -1055,7 +1153,8 @@ public class DataInsightSystemChartRepository extends EntityRepository userIds; // Multiple users can share the same session private final long startTime; // Session start time private final long dataStartTime; // Data range start time @@ -1097,6 +1197,7 @@ public class DataInsightSystemChartRepository extends EntityRepository getUserIds() { return userIds; } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java index 49348ca67ef..dbb6468ee46 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/datainsight/system/DataInsightSystemChartResource.java @@ -209,6 +209,11 @@ public class DataInsightSystemChartResource schema = @Schema(type = "string", example = "{\"query\":{...}}")) @QueryParam("filter") String filter, + @Parameter( + description = "Entity link for workflow instances filtering", + schema = @Schema(type = "String", example = "<#E::databaseService::sample_data>")) + @QueryParam("entityLink") + String entityLink, @Parameter( description = "Start time for data fetching (unix timestamp in milliseconds)", schema = @Schema(type = "long", example = "1426349294842")) @@ -229,7 +234,7 @@ public class DataInsightSystemChartResource // Call repository method to handle streaming Map response = repository.startChartDataStreaming( - chartNames, serviceName, filter, user.getId(), startTime, endTime); + chartNames, serviceName, filter, entityLink, user.getId(), startTime, endTime); // Check if there's an error in the response if (response.containsKey("error")) { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/socket/messages/ChartDataStreamMessage.java b/openmetadata-service/src/main/java/org/openmetadata/service/socket/messages/ChartDataStreamMessage.java index da5df682f49..9ef46d01551 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/socket/messages/ChartDataStreamMessage.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/socket/messages/ChartDataStreamMessage.java @@ -21,7 +21,8 @@ public class ChartDataStreamMessage { Long remainingTime, Long nextUpdate, List ingestionPipelineStatus, - List appStatus) { + List appStatus, + List workflowInstances) { this.sessionId = sessionId; this.status = status; this.serviceName = serviceName; @@ -32,6 +33,7 @@ public class ChartDataStreamMessage { this.nextUpdate = nextUpdate; this.ingestionPipelineStatus = ingestionPipelineStatus; this.appStatus = appStatus; + this.workflowInstances = workflowInstances; } @JsonProperty("sessionId") @@ -63,4 +65,7 @@ public class ChartDataStreamMessage { @JsonProperty("appStatus") private List appStatus; + + @JsonProperty("workflowInstances") + private List workflowInstances; } diff --git a/openmetadata-spec/src/main/resources/elasticsearch/en/query_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/en/query_index_mapping.json index 1bf822d6186..ec2b41bdc86 100644 --- a/openmetadata-spec/src/main/resources/elasticsearch/en/query_index_mapping.json +++ b/openmetadata-spec/src/main/resources/elasticsearch/en/query_index_mapping.json @@ -76,7 +76,13 @@ "mappings": { "properties": { "id": { - "type": "keyword" + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } }, "checksum": { "type": "text", diff --git a/openmetadata-spec/src/main/resources/elasticsearch/jp/query_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/jp/query_index_mapping.json index 189141786aa..c7b48290021 100644 --- a/openmetadata-spec/src/main/resources/elasticsearch/jp/query_index_mapping.json +++ b/openmetadata-spec/src/main/resources/elasticsearch/jp/query_index_mapping.json @@ -53,7 +53,13 @@ "mappings": { "properties": { "id": { - "type": "keyword" + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } }, "name": { "type": "text", diff --git a/openmetadata-spec/src/main/resources/elasticsearch/zh/query_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/zh/query_index_mapping.json index 92cfcb57b48..0e2c3046009 100644 --- a/openmetadata-spec/src/main/resources/elasticsearch/zh/query_index_mapping.json +++ b/openmetadata-spec/src/main/resources/elasticsearch/zh/query_index_mapping.json @@ -58,7 +58,13 @@ "mappings": { "properties": { "id": { - "type": "keyword" + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } }, "name": { "type": "text", diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AutoPilot.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AutoPilot.spec.ts index 237ea660784..581e3d54329 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AutoPilot.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AutoPilot.spec.ts @@ -26,7 +26,6 @@ import { createNewPage, getApiContext, redirectToHomePage, - reloadAndWaitForNetworkIdle, } from '../../utils/common'; import { getServiceCategoryFromService } from '../../utils/serviceIngestion'; import { settingClick, SettingOptionsType } from '../../utils/sidebar'; @@ -115,19 +114,67 @@ services.forEach((ServiceClass) => { state: 'detached', }); - // Reload the page and wait for the network to be idle - await reloadAndWaitForNetworkIdle(page); - // Check the auto pilot status await checkAutoPilotStatus(page, service); - // Reload the page and wait for the network to be idle - await reloadAndWaitForNetworkIdle(page); - // Wait for the auto pilot status banner to be visible await expect( page.getByText('AutoPilot agents run completed successfully.') ).toBeVisible(); + + if (service.serviceType === 'Mysql') { + await page.getByTestId('agent-status-widget-view-more').click(); + + await page.waitForSelector( + '[data-testid="agent-status-card-Metadata"]', + { + state: 'visible', + } + ); + + // Check the agents statuses + await expect( + page.getByTestId('agent-status-card-Lineage') + ).toBeVisible(); + await expect( + page.getByTestId('agent-status-card-Usage') + ).toBeVisible(); + await expect( + page.getByTestId('agent-status-card-Auto Classification') + ).toBeVisible(); + await expect( + page.getByTestId('agent-status-card-Profiler') + ).toBeVisible(); + + // Check the agents summary + await expect( + page + .getByTestId('agent-status-summary-item-Successful') + .getByTestId('pipeline-count') + ).toHaveText('3'); + await expect( + page + .getByTestId('agent-status-summary-item-Pending') + .getByTestId('pipeline-count') + ).toHaveText('2'); + + // Check the total data assets count + await expect( + page + .getByTestId('total-data-assets-widget') + .getByTestId('Database-count') + ).toHaveText('1'); + await expect( + page + .getByTestId('total-data-assets-widget') + .getByTestId('Database Schema-count') + ).toHaveText('2'); + await expect( + page + .getByTestId('total-data-assets-widget') + .getByTestId('Table-count') + ).toHaveText('3'); + } }); test('Agents created by AutoPilot should be deleted', async ({ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/ServiceBaseClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/ServiceBaseClass.ts index 9160c42d6f3..2a2ea79e676 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/ServiceBaseClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/ServiceBaseClass.ts @@ -46,7 +46,7 @@ class ServiceBaseClass { protected entityName: string; protected shouldTestConnection: boolean; protected shouldAddIngestion: boolean; - protected shouldAddDefaultFilters: boolean; + public shouldAddDefaultFilters: boolean; protected entityFQN: string | null; public serviceResponseData: ResponseDataType = {} as ResponseDataType; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/CuratedAssetsWidget/CuratedAssetsWidget.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/CuratedAssetsWidget/CuratedAssetsWidget.test.tsx index 8198235c770..a79c5e271ff 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/CuratedAssetsWidget/CuratedAssetsWidget.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/CuratedAssetsWidget/CuratedAssetsWidget.test.tsx @@ -78,6 +78,10 @@ jest.mock('../../../../utils/ServiceUtilClassBase', () => ({ getServiceTypeLogo: jest.fn().mockReturnValue('test-logo.png'), })); +jest.mock('../../../common/RichTextEditor/RichTextEditorPreviewerV1', () => + jest.fn().mockImplementation(({ markdown }) =>
{markdown}
) +); + jest.mock('../../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => jest.fn().mockImplementation(({ children, icon, type, className }) => (
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.interface.ts index f1d190b4b22..139a7686192 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.interface.ts @@ -12,12 +12,14 @@ */ import { ReactNode } from 'react'; import { AgentStatus } from '../../../enums/ServiceInsights.enum'; +import { WorkflowInstance } from '../../../generated/governance/workflows/workflowInstance'; import { ServiceInsightWidgetCommonProps } from '../ServiceInsightsTab.interface'; export interface AgentsStatusWidgetProps extends ServiceInsightWidgetCommonProps { isLoading: boolean; agentsInfo: AgentsInfo[]; + liveAutoPilotStatusData?: WorkflowInstance; } export interface AgentsInfo { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.tsx index 93065056466..56c649e06af 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.tsx @@ -28,7 +28,7 @@ import './agents-status-widget.less'; import { AgentsStatusWidgetProps } from './AgentsStatusWidget.interface'; function AgentsStatusWidget({ - workflowStatesData, + liveAutoPilotStatusData, isLoading, agentsInfo, }: Readonly) { @@ -36,8 +36,12 @@ function AgentsStatusWidget({ const agentsRunningStatusMessage = useMemo( () => - getAgentRunningStatusMessage(isLoading, agentsInfo, workflowStatesData), - [workflowStatesData, isLoading, agentsInfo] + getAgentRunningStatusMessage( + isLoading, + agentsInfo, + liveAutoPilotStatusData + ), + [liveAutoPilotStatusData, isLoading, agentsInfo] ); const agentStatusSummary = useMemo(() => { @@ -47,8 +51,11 @@ function AgentsStatusWidget({ return ( ( -
+
{isLoading ? ( ) : ( @@ -56,15 +63,20 @@ function AgentsStatusWidget({ {Object.entries(agentStatusSummary).map(([key, value]) => (
{getIconFromStatus(key)} - {value} + + {value} + {key}
))}
)} - + {t('label.view-more')} @@ -112,7 +124,8 @@ function AgentsStatusWidget({ className={classNames( 'agent-status-card', agent.isCollateAgent ? 'collate-agent' : '' - )}> + )} + data-testid={`agent-status-card-${agent.label}`}> {agent.agentIcon} {agent.label} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/agents-status-widget.less b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/agents-status-widget.less index 20fa7a718b8..8fb4164236c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/agents-status-widget.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/AgentsStatusWidget/agents-status-widget.less @@ -85,6 +85,19 @@ } } + .RUNNING { + color: @blue-31; + } + + .FINISHED { + color: @green-10; + } + + .EXCEPTION, + .FAILURE { + color: @red-10; + } + .ant-collapse-arrow.expand-icon-container { display: flex; align-items: center; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.test.tsx new file mode 100644 index 00000000000..9f22f69e837 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.test.tsx @@ -0,0 +1,506 @@ +/* + * Copyright 2025 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 { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { SystemChartType } from '../../../enums/DataInsight.enum'; +import { ServiceCategory } from '../../../enums/service.enum'; +import { getTitleByChartType } from '../../../utils/ServiceInsightsTabUtils'; +import { getReadableCountString } from '../../../utils/ServiceUtils'; +import { useRequiredParams } from '../../../utils/useRequiredParams'; +import PlatformInsightsWidget from './PlatformInsightsWidget'; +import { PlatformInsightsWidgetProps } from './PlatformInsightsWidget.interface'; + +// Mock dependencies +jest.mock('../../../utils/useRequiredParams', () => ({ + useRequiredParams: jest.fn(), +})); + +jest.mock('../../../utils/ServiceInsightsTabUtils', () => ({ + getTitleByChartType: jest.fn(), +})); + +jest.mock('../../../utils/ServiceUtils', () => ({ + getReadableCountString: jest.fn(), +})); + +// Mock SVG components +jest.mock('../../../assets/svg/ic-arrow-down.svg', () => { + return { + __esModule: true, + ReactComponent: () =>
, + }; +}); + +jest.mock('../../../assets/svg/ic-trend-up.svg', () => { + return { + __esModule: true, + ReactComponent: () =>
, + }; +}); + +const mockUseRequiredParams = useRequiredParams as jest.MockedFunction; + +const mockGetTitleByChartType = getTitleByChartType as jest.MockedFunction< + typeof getTitleByChartType +>; + +const mockGetReadableCountString = + getReadableCountString as jest.MockedFunction; + +describe('PlatformInsightsWidget', () => { + const mockServiceDetails = { + id: 'test-service-id', + name: 'test-service', + serviceType: 'Mysql' as any, + fullyQualifiedName: 'test-service-fqn', + } as any; + + const mockChartsData = [ + { + chartType: SystemChartType.DescriptionCoverage, + currentPercentage: 85, + percentageChange: 5, + isIncreased: true, + numberOfDays: 7, + }, + { + chartType: SystemChartType.PIICoverage, + currentPercentage: 60, + percentageChange: -2, + isIncreased: false, + numberOfDays: 7, + }, + { + chartType: SystemChartType.TierCoverage, + currentPercentage: 75, + percentageChange: 0, + isIncreased: true, + numberOfDays: 1, + }, + { + chartType: SystemChartType.OwnersCoverage, + currentPercentage: 90, + percentageChange: 10, + isIncreased: true, + numberOfDays: 30, + }, + ]; + + const defaultProps: PlatformInsightsWidgetProps = { + chartsData: mockChartsData, + isLoading: false, + serviceDetails: mockServiceDetails, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseRequiredParams.mockReturnValue({ + serviceCategory: ServiceCategory.DATABASE_SERVICES, + }); + mockGetTitleByChartType.mockImplementation( + (chartType) => `Title for ${chartType}` + ); + mockGetReadableCountString.mockImplementation((value) => value.toString()); + }); + + const renderComponent = (props = {}) => { + return render( + + + + ); + }; + + describe('Component Rendering', () => { + it('should render the component with correct header', () => { + renderComponent(); + + expect( + screen.getByText('label.entity-insight-plural') + ).toBeInTheDocument(); + expect( + screen.getByText('message.platform-insight-description') + ).toBeInTheDocument(); + }); + + it('should render expand/collapse functionality', () => { + renderComponent(); + + const expandIcon = screen.getByText('label.view-more'); + + expect(expandIcon).toBeInTheDocument(); + }); + + it('should render the export class for platform insights chart', () => { + renderComponent(); + + const exportContainer = document.querySelector( + '.export-platform-insights-chart' + ); + + expect(exportContainer).toBeInTheDocument(); + }); + }); + + describe('Loading State', () => { + it('should render skeleton loaders when isLoading is true', () => { + renderComponent({ isLoading: true }); + + // Should render 5 skeleton cards for database services (includes HealthyDataAssets) + const skeletonCards = document.querySelectorAll('.ant-skeleton'); + + expect(skeletonCards).toHaveLength(5); + }); + + it('should render skeleton loaders for non-database services', () => { + mockUseRequiredParams.mockReturnValue({ + serviceCategory: ServiceCategory.MESSAGING_SERVICES, + }); + renderComponent({ isLoading: true }); + + // Should render 4 skeleton cards (excludes HealthyDataAssets for non-database services) + const skeletonCards = document.querySelectorAll('.ant-skeleton'); + + expect(skeletonCards).toHaveLength(4); + }); + }); + + describe('Chart Data Rendering', () => { + it('should render all chart cards when data is available', () => { + renderComponent(); + + // Should render 4 chart cards for database services + const chartCards = screen.getAllByText(/Title for/); + + expect(chartCards).toHaveLength(4); + }); + + it('should render chart cards for non-database services (excluding HealthyDataAssets)', () => { + mockUseRequiredParams.mockReturnValue({ + serviceCategory: ServiceCategory.MESSAGING_SERVICES, + }); + renderComponent(); + + // Should render 4 chart cards (excludes HealthyDataAssets for non-database services) + const chartCards = screen.getAllByText(/Title for/); + + expect(chartCards).toHaveLength(4); + }); + + it('should render chart titles correctly', () => { + renderComponent(); + + expect(mockGetTitleByChartType).toHaveBeenCalledWith( + SystemChartType.DescriptionCoverage + ); + expect(mockGetTitleByChartType).toHaveBeenCalledWith( + SystemChartType.PIICoverage + ); + expect(mockGetTitleByChartType).toHaveBeenCalledWith( + SystemChartType.TierCoverage + ); + expect(mockGetTitleByChartType).toHaveBeenCalledWith( + SystemChartType.OwnersCoverage + ); + }); + + it('should render current percentage values', () => { + renderComponent(); + + expect(screen.getByText('85%')).toBeInTheDocument(); + expect(screen.getByText('60%')).toBeInTheDocument(); + expect(screen.getByText('75%')).toBeInTheDocument(); + expect(screen.getByText('90%')).toBeInTheDocument(); + }); + }); + + describe('Percentage Change Display', () => { + it('should render positive percentage change with green color and up arrow', () => { + renderComponent(); + + const positiveChange = screen.getByText('5%'); + + expect(positiveChange).toBeInTheDocument(); + expect(positiveChange).toHaveStyle({ color: 'rgb(6, 118, 71)' }); // GREEN_1 + }); + + it('should render negative percentage change with red color and down arrow', () => { + renderComponent(); + + const negativeChange = screen.getByText('-2%'); + + expect(negativeChange).toBeInTheDocument(); + expect(negativeChange).toHaveStyle({ color: 'rgb(240, 68, 56)' }); // RED_1 + }); + + it('should render percentage change text but not icon when value is 0', () => { + const chartsDataWithZeroChange = [ + { + chartType: SystemChartType.DescriptionCoverage, + currentPercentage: 85, + percentageChange: 0, + isIncreased: true, + numberOfDays: 7, + }, + ]; + + renderComponent({ chartsData: chartsDataWithZeroChange }); + + // Should show the percentage change text + expect(screen.getByText('0%')).toBeInTheDocument(); + + // Should not show the icon (since showIcon = false when percentageChange === 0) + const icons = screen.getAllByTestId('arrow-up-icon'); + + expect(icons).toHaveLength(1); // Only the one from the header + }); + + it('should render percentage change icon correctly', () => { + renderComponent(); + + const upIcons = screen.getAllByTestId('arrow-up-icon'); + + expect(upIcons.length).toBeGreaterThan(0); + }); + }); + + describe('Time Period Display', () => { + it('should render "in the last day" for single day', () => { + renderComponent(); + + expect(screen.getByText('label.in-the-last-day')).toBeInTheDocument(); + }); + + it('should render "in last X days" for multiple days', () => { + renderComponent(); + + expect(screen.getAllByText('label.in-last-number-of-days')).toHaveLength( + 3 + ); // Multiple charts have this text + }); + }); + + describe('Service Category Filtering', () => { + it('should include HealthyDataAssets chart for database services', () => { + mockUseRequiredParams.mockReturnValue({ + serviceCategory: ServiceCategory.DATABASE_SERVICES, + }); + + const chartsDataWithHealthyAssets = [ + ...mockChartsData, + { + chartType: SystemChartType.HealthyDataAssets, + currentPercentage: 95, + percentageChange: 3, + isIncreased: true, + numberOfDays: 7, + }, + ]; + + renderComponent({ chartsData: chartsDataWithHealthyAssets }); + + expect(mockGetTitleByChartType).toHaveBeenCalledWith( + SystemChartType.HealthyDataAssets + ); + }); + + it('should exclude HealthyDataAssets chart for non-database services', () => { + mockUseRequiredParams.mockReturnValue({ + serviceCategory: ServiceCategory.MESSAGING_SERVICES, + }); + + const chartsDataWithHealthyAssets = [ + ...mockChartsData, + { + chartType: SystemChartType.HealthyDataAssets, + currentPercentage: 95, + percentageChange: 3, + isIncreased: true, + numberOfDays: 7, + }, + ]; + + renderComponent({ chartsData: chartsDataWithHealthyAssets }); + + expect(mockGetTitleByChartType).not.toHaveBeenCalledWith( + SystemChartType.HealthyDataAssets + ); + }); + }); + + describe('Empty State', () => { + it('should handle empty charts data', () => { + renderComponent({ chartsData: [] }); + + const chartCards = screen.queryAllByText(/Title for/); + + expect(chartCards).toHaveLength(0); + }); + + it('should handle undefined percentage change', () => { + const chartsDataWithUndefinedChange = [ + { + chartType: SystemChartType.DescriptionCoverage, + currentPercentage: 85, + percentageChange: undefined, + isIncreased: true, + numberOfDays: 7, + }, + ]; + + renderComponent({ chartsData: chartsDataWithUndefinedChange }); + + // Should not render percentage change section + expect( + screen.queryByText('label.in-last-number-of-days') + ).not.toBeInTheDocument(); + }); + }); + + describe('CSS Classes and Styling', () => { + it('should apply correct container class for 5 charts (database services)', () => { + renderComponent(); + + const colElement = document.querySelector('.other-charts-container'); + + expect(colElement).toBeInTheDocument(); + expect(colElement).not.toHaveClass('four-chart-container'); + }); + + it('should apply correct container class for non-4 charts', () => { + const limitedChartsData = mockChartsData.slice(0, 2); + renderComponent({ chartsData: limitedChartsData }); + + const colElement = document.querySelector('.four-chart-container'); + + expect(colElement).not.toBeInTheDocument(); + }); + + it('should apply correct CSS classes to chart cards', () => { + renderComponent(); + + const chartCards = screen.getAllByText(/Title for/); + chartCards.forEach((card) => { + const cardElement = card.closest('.widget-info-card'); + + expect(cardElement).toHaveClass('other-charts-card'); + }); + }); + }); + + describe('Utility Function Calls', () => { + it('should call getReadableCountString for current percentage', () => { + renderComponent(); + + expect(mockGetReadableCountString).toHaveBeenCalledWith(85); + expect(mockGetReadableCountString).toHaveBeenCalledWith(60); + expect(mockGetReadableCountString).toHaveBeenCalledWith(75); + expect(mockGetReadableCountString).toHaveBeenCalledWith(90); + }); + + it('should call getReadableCountString for percentage change', () => { + renderComponent(); + + expect(mockGetReadableCountString).toHaveBeenCalledWith(5); + expect(mockGetReadableCountString).toHaveBeenCalledWith(-2); + expect(mockGetReadableCountString).toHaveBeenCalledWith(0); + expect(mockGetReadableCountString).toHaveBeenCalledWith(10); + }); + + it('should call getTitleByChartType for each chart', () => { + renderComponent(); + + expect(mockGetTitleByChartType).toHaveBeenCalledTimes(4); + }); + }); + + describe('Edge Cases', () => { + it('should handle charts data with noRecords flag', () => { + const chartsDataWithNoRecords = [ + { + chartType: SystemChartType.DescriptionCoverage, + currentPercentage: 85, + percentageChange: 5, + isIncreased: true, + numberOfDays: 7, + noRecords: true, + }, + ]; + + renderComponent({ chartsData: chartsDataWithNoRecords }); + + // Should still render the chart card + expect( + screen.getByText('Title for assets_with_description') + ).toBeInTheDocument(); + }); + + it('should handle very large percentage values', () => { + const chartsDataWithLargeValues = [ + { + chartType: SystemChartType.DescriptionCoverage, + currentPercentage: 999999, + percentageChange: 999999, + isIncreased: true, + numberOfDays: 7, + }, + ]; + + renderComponent({ chartsData: chartsDataWithLargeValues }); + + expect(screen.getAllByText('999999%')).toHaveLength(2); // Both current and percentage change + }); + + it('should handle zero current percentage', () => { + const chartsDataWithZeroPercentage = [ + { + chartType: SystemChartType.DescriptionCoverage, + currentPercentage: 0, + percentageChange: 5, + isIncreased: true, + numberOfDays: 7, + }, + ]; + + renderComponent({ chartsData: chartsDataWithZeroPercentage }); + + expect(screen.getByText('0%')).toBeInTheDocument(); + }); + }); + + describe('Component Integration', () => { + it('should integrate with useRequiredParams hook', () => { + renderComponent(); + + expect(mockUseRequiredParams).toHaveBeenCalled(); + }); + + it('should handle route parameter changes', () => { + const { rerender } = renderComponent(); + + // Change service category + mockUseRequiredParams.mockReturnValue({ + serviceCategory: ServiceCategory.MESSAGING_SERVICES, + }); + + rerender( + + + + ); + + expect(mockUseRequiredParams).toHaveBeenCalled(); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.tsx index 5c4c7c4f865..718542e727b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/PlatformInsightsWidget.tsx @@ -11,14 +11,20 @@ * limitations under the License. */ import { Card, Col, Collapse, Row, Skeleton, Typography } from 'antd'; +import classNames from 'classnames'; import { isUndefined } from 'lodash'; +import { ServiceTypes } from 'Models'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as ArrowSvg } from '../../../assets/svg/ic-arrow-down.svg'; import { ReactComponent as ArrowUp } from '../../../assets/svg/ic-trend-up.svg'; import { GREEN_1, RED_1 } from '../../../constants/Color.constants'; import { PLATFORM_INSIGHTS_CHARTS } from '../../../constants/ServiceInsightsTab.constants'; +import { SystemChartType } from '../../../enums/DataInsight.enum'; +import { ServiceCategory } from '../../../enums/service.enum'; import { getTitleByChartType } from '../../../utils/ServiceInsightsTabUtils'; import { getReadableCountString } from '../../../utils/ServiceUtils'; +import { useRequiredParams } from '../../../utils/useRequiredParams'; import './platform-insights-widget.less'; import { PlatformInsightsWidgetProps } from './PlatformInsightsWidget.interface'; @@ -26,8 +32,28 @@ function PlatformInsightsWidget({ chartsData, isLoading, }: Readonly) { + const { serviceCategory } = + useRequiredParams<{ serviceCategory: ServiceTypes }>(); const { t } = useTranslation(); + const { filteredCharts, filteredChartsData, containerClassName } = + useMemo(() => { + const filteredCharts = PLATFORM_INSIGHTS_CHARTS.filter((chart) => + chart === SystemChartType.HealthyDataAssets + ? serviceCategory === ServiceCategory.DATABASE_SERVICES + : true + ); + + return { + filteredCharts, + filteredChartsData: chartsData.filter((chart) => + filteredCharts.includes(chart.chartType) + ), + containerClassName: + filteredCharts.length === 4 ? 'four-chart-container' : '', + }; + }, [serviceCategory, chartsData]); + return ( {/* Don't remove this class name, it is used for exporting the platform insights chart */} - + {isLoading - ? PLATFORM_INSIGHTS_CHARTS.map((chartType) => ( + ? filteredCharts.map((chartType) => ( @@ -70,7 +98,7 @@ function PlatformInsightsWidget({ /> )) - : chartsData.map((chart) => { + : filteredChartsData.map((chart) => { const icon = chart.isIncreased ? ( ) : ( @@ -91,13 +119,13 @@ function PlatformInsightsWidget({ {getTitleByChartType(chart.chartType)} - + - + {`${getReadableCountString( chart.currentPercentage )}%`} - + {!isUndefined(chart.percentageChange) && ( {showIcon && icon} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/platform-insights-widget.less b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/platform-insights-widget.less index 26677839614..f8328a6032e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/platform-insights-widget.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/PlatformInsightsWidget/platform-insights-widget.less @@ -19,6 +19,10 @@ gap: 16px; } + .other-charts-container.four-chart-container { + grid-template-columns: repeat(4, 1fr); + } + .percent-change-tag { display: flex; align-items: center; @@ -43,6 +47,12 @@ &::after { display: none; } + + .current-percentage { + line-height: 20px; + font-size: 20px; + font-weight: 600; + } } } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.interface.ts index b0cdf7e39be..9468c41b480 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.interface.ts @@ -73,6 +73,5 @@ export interface CollateAgentLiveInfo export interface TotalAssetsCount { name: string; value: number; - fill: string; icon: JSX.Element; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.tsx index d5967d6290f..82314f47543 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/ServiceInsightsTab.tsx @@ -14,7 +14,7 @@ import { Col, Row } from 'antd'; import { AxiosError } from 'axios'; import { isEmpty, isUndefined } from 'lodash'; -import { ServiceTypes } from 'Models'; +import { Bucket, ServiceTypes } from 'Models'; import { useCallback, useEffect, useRef, useState } from 'react'; import { SOCKET_EVENTS } from '../../constants/constants'; import { @@ -22,12 +22,14 @@ import { PLATFORM_INSIGHTS_CHARTS, PLATFORM_INSIGHTS_LIVE_CHARTS, } from '../../constants/ServiceInsightsTab.constants'; -import { totalDataAssetsWidgetColors } from '../../constants/TotalDataAssetsWidget.constants'; import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocketProvider'; import { SystemChartType } from '../../enums/DataInsight.enum'; import { SearchIndex } from '../../enums/search.enum'; import { AppRunRecord } from '../../generated/entity/applications/appRunRecord'; -import { WorkflowStatus } from '../../generated/governance/workflows/workflowInstance'; +import { + WorkflowInstance, + WorkflowStatus, +} from '../../generated/governance/workflows/workflowInstance'; import { getAgentRuns } from '../../rest/applicationAPI'; import { getMultiChartsPreviewByName, @@ -44,7 +46,7 @@ import { getCurrentMillis, getDayAgoStartGMTinMillis, } from '../../utils/date-time/DateTimeUtils'; -import { getEntityNameLabel } from '../../utils/EntityUtils'; +import { getEntityFeedLink, getEntityNameLabel } from '../../utils/EntityUtils'; import { filterDistributionChartItem, getAssetsByServiceType, @@ -53,7 +55,10 @@ import { getPlatformInsightsChartDataFormattingMethod, } from '../../utils/ServiceInsightsTabUtils'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; -import { getServiceNameQueryFilter } from '../../utils/ServiceUtils'; +import { + getEntityTypeFromServiceCategory, + getServiceNameQueryFilter, +} from '../../utils/ServiceUtils'; import { getEntityIcon } from '../../utils/TableUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import { useRequiredParams } from '../../utils/useRequiredParams'; @@ -82,6 +87,9 @@ const ServiceInsightsTab = ({ const [isLoading, setIsLoading] = useState(false); const [totalAssetsCount, setTotalAssetsCount] = useState>(); + const [liveAutoPilotStatusData, setLiveAutoPilotStatusData] = useState< + WorkflowInstance | undefined + >(workflowStatesData?.mainInstanceState); const sessionIdRef = useRef(); const serviceName = serviceDetails.name; @@ -97,14 +105,22 @@ const ServiceInsightsTab = ({ const assets = getAssetsByServiceType(serviceCategory); - const buckets = response.aggregations['entityType'].buckets.filter( - (bucket) => assets.includes(bucket.key) - ); + // Arrange the buckets in the order of the assets + const buckets = assets.reduce((acc, curr) => { + const bucket = response.aggregations['entityType'].buckets.find( + (bucket) => bucket.key === curr + ); - const entityCountsArray = buckets.map((bucket, index) => ({ + if (!isUndefined(bucket)) { + return [...acc, bucket]; + } + + return acc; + }, [] as Bucket[]); + + const entityCountsArray = buckets.map((bucket) => ({ name: getEntityNameLabel(bucket.key), value: bucket.doc_count ?? 0, - fill: totalDataAssetsWidgetColors[index], icon: getEntityIcon(bucket.key, '', { height: 16, width: 16 }) ?? <>, })); @@ -178,11 +194,16 @@ const ServiceInsightsTab = ({ const triggerSocketConnection = useCallback(async () => { if (isUndefined(sessionIdRef.current)) { + const entityType = getEntityTypeFromServiceCategory(serviceCategory); const { sessionId } = await setChartDataStreamConnection({ chartNames: LIVE_CHARTS_LIST, serviceName, startTime: getCurrentDayStartGMTinMillis(), endTime: getCurrentDayStartGMTinMillis() + 360000000, + entityLink: getEntityFeedLink( + entityType, + serviceDetails.fullyQualifiedName + ), }); sessionIdRef.current = sessionId; @@ -251,10 +272,13 @@ const ServiceInsightsTab = ({ setTotalAssetsCount( getFormattedTotalAssetsDataFromSocketData( - data?.data?.total_data_assets_live + data?.data?.total_data_assets_live, + serviceCategory ) ); + setLiveAutoPilotStatusData(data.workflowInstances?.[0]); + setChartsResults((prev) => ({ platformInsightsChart, piiDistributionChart: prev?.piiDistributionChart ?? [], @@ -263,7 +287,7 @@ const ServiceInsightsTab = ({ } } }, - [serviceDetails] + [serviceDetails, serviceCategory] ); useEffect(() => { @@ -317,7 +341,7 @@ const ServiceInsightsTab = ({ isCollateAIagentsLoading || isIngestionPipelineLoading } - workflowStatesData={workflowStatesData} + liveAutoPilotStatusData={liveAutoPilotStatusData} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/TotalDataAssetsWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/TotalDataAssetsWidget.tsx index 361921d6295..43b91e76306 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/TotalDataAssetsWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/TotalDataAssetsWidget.tsx @@ -49,7 +49,9 @@ function TotalDataAssetsWidget({ ); return ( - +
@@ -69,18 +71,14 @@ function TotalDataAssetsWidget({ className="flex items-center justify-between" key={entity.name}>
-
{entity.icon}
{entity.name}
- + {getReadableCountString(entity.value)}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/total-data-assets-widget.less b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/total-data-assets-widget.less index 2df2f08e049..8b6c6905ec3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/total-data-assets-widget.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/ServiceInsights/TotalDataAssetsWidget/total-data-assets-widget.less @@ -15,11 +15,6 @@ .total-data-assets-widget.ant-card { height: 100%; - .bullet { - width: 8px; - height: 8px; - border-radius: 4px; - } .icon-container { display: flex; align-items: center; @@ -35,7 +30,7 @@ display: flex; flex-direction: column; gap: 8px; - background-color: @grey-1; + background-color: @grey-50; border-radius: @border-rad-xs; height: 100%; } diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/TotalDataAssetsWidget.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/TotalDataAssetsWidget.constants.ts deleted file mode 100644 index 6899d792934..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/constants/TotalDataAssetsWidget.constants.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2025 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 { - BLUE_2, - DESERT, - ELECTRIC_VIOLET, - LEMON_ZEST, - MY_SIN, - PINK_SALMON, - RIPTIDE, - SAN_MARINO, - SILVER_TREE, -} from './Color.constants'; - -export const totalDataAssetsWidgetColors = [ - BLUE_2, - PINK_SALMON, - SILVER_TREE, - SAN_MARINO, - RIPTIDE, - MY_SIN, - DESERT, - ELECTRIC_VIOLET, - LEMON_ZEST, -]; diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index ec29e6deefa..6350a7574bf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Donnerstag", "tier": "Stufe", + "tier-label-type": "Stufen-Etikettentyp", "tier-number": "Stufe {{tier}}", "tier-plural": "Tiers", "tier-plural-lowercase": "stufen", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 84a8c29bc20..42e7bd48ed6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Thursday", "tier": "Tier", + "tier-label-type": "Tier Label Type", "tier-number": "Tier{{tier}}", "tier-plural": "Tiers", "tier-plural-lowercase": "tiers", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index 68c9ca71942..ed11904fc98 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Jueves", "tier": "Nivel", + "tier-label-type": "Tipo de Etiqueta de Nivel", "tier-number": "Nivel {{tier}}", "tier-plural": "Niveles", "tier-plural-lowercase": "niveles", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index e23f6e9547c..8ac2d2fb402 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Jeudi", "tier": "Niveau", + "tier-label-type": "Type d'Étiquette de Niveau", "tier-number": "Niveau {{tier}}", "tier-plural": "Niveaux", "tier-plural-lowercase": "niveaux", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json index 2c90c758487..bff5891be21 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Xoves", "tier": "Nivel", + "tier-label-type": "Tipo de Etiqueta de Nivel", "tier-number": "Nivel{{tier}}", "tier-plural": "Niveles", "tier-plural-lowercase": "niveis", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index b8738d529c5..81d343de291 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "יום חמישי", "tier": "שכבת מידע", + "tier-label-type": "סוג תווית שכבת מידע", "tier-number": "שכבת מידע {{tier}}", "tier-plural": "רמות", "tier-plural-lowercase": "שכבות", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 92922b5c14d..3755ee07306 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "木曜日", "tier": "ティア", + "tier-label-type": "ティアラベルタイプ", "tier-number": "ティア{{tier}}", "tier-plural": "ティア", "tier-plural-lowercase": "ティア", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json index 3f340983197..e7c5395b161 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "목요일", "tier": "계층", + "tier-label-type": "계층 라벨 유형", "tier-number": "계층{{tier}}", "tier-plural": "계층들", "tier-plural-lowercase": "계층들", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json index e73b59223cf..7c6c11de72d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "गुरुवार", "tier": "स्तर", + "tier-label-type": "स्तर लेबल प्रकार", "tier-number": "स्तर{{tier}}", "tier-plural": "टियर्स", "tier-plural-lowercase": "स्तर", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 8d2e82249bb..e055d5d4876 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "donderdag", "tier": "Niveau", + "tier-label-type": "Niveau-etikettype", "tier-number": "Niveau {{tier}}", "tier-plural": "Lagen", "tier-plural-lowercase": "niveaus", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json index c3997db8986..32278fa522e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "پنج‌شنبه", "tier": "سطح", + "tier-label-type": "نوع برچسب سطح", "tier-number": "سطح {{tier}}", "tier-plural": "طبقات", "tier-plural-lowercase": "سطوح", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index a8d4760f4da..db3b59e222e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Quinta-feira", "tier": "Camada", + "tier-label-type": "Tipo de Rótulo de Camada", "tier-number": "Camada{{tier}}", "tier-plural": "Camadas", "tier-plural-lowercase": "camadas", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json index df872a8520b..59ad538879c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Quinta-feira", "tier": "Camada", + "tier-label-type": "Tipo de Etiqueta de Camada", "tier-number": "Camada{{tier}}", "tier-plural": "Camadas", "tier-plural-lowercase": "camadas", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 3a85679ab54..64fad06baaa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Четверг", "tier": "Уровень", + "tier-label-type": "Тип метки уровня", "tier-number": "Уровень{{tier}}", "tier-plural": "Уровни", "tier-plural-lowercase": "уровни", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json index 61d1d35b316..1e9bce9e28d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "วันพฤหัสบดี", "tier": "ระดับ", + "tier-label-type": "ประเภทป้ายระดับ", "tier-number": "ระดับ {{tier}}", "tier-plural": "ชั้น", "tier-plural-lowercase": "ระดับหลายรายการ", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json index 92ac31a3195..b80d9eac45c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "Perşembe", "tier": "Katman", + "tier-label-type": "Katman Etiketi Türü", "tier-number": "Katman{{tier}}", "tier-plural": "Katmanlar", "tier-plural-lowercase": "katmanlar", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 3bfa869cbc6..367232fc8d0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -1610,6 +1610,7 @@ "three-dots-symbol": "•••", "thursday": "星期四", "tier": "分级", + "tier-label-type": "分级标签类型", "tier-number": "{{tier}}级", "tier-plural": "层", "tier-plural-lowercase": "分级", diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/DataInsightAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/DataInsightAPI.ts index b9590c6c5c9..84c175097dd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/DataInsightAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/DataInsightAPI.ts @@ -76,6 +76,7 @@ export const setChartDataStreamConnection = async (params: { serviceName: string; startTime: number; endTime: number; + entityLink: string; }) => { const response = await APIClient.post( `/analytics/dataInsights/system/charts/stream`, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AgentsStatusWidgetUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AgentsStatusWidgetUtils.tsx index 9a0dd3ca134..a7c61e75e8d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AgentsStatusWidgetUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AgentsStatusWidgetUtils.tsx @@ -29,7 +29,6 @@ import { AgentsInfo } from '../components/ServiceInsights/AgentsStatusWidget/Age import { AgentsLiveInfo, CollateAgentLiveInfo, - WorkflowStatesData, } from '../components/ServiceInsights/ServiceInsightsTab.interface'; import { AUTOPILOT_AGENTS_ORDERED_LIST, @@ -53,7 +52,10 @@ import { PipelineType, ProviderType, } from '../generated/entity/services/ingestionPipelines/ingestionPipeline'; -import { WorkflowStatus } from '../generated/governance/workflows/workflowInstance'; +import { + WorkflowInstance, + WorkflowStatus, +} from '../generated/governance/workflows/workflowInstance'; import { t } from './i18next/LocalUtil'; export const getAgentLabelFromType = (agentType: string) => { @@ -300,7 +302,7 @@ export const getIconFromStatus = (status?: string) => { export const getAgentRunningStatusMessage = ( isLoading: boolean, agentsInfo: AgentsInfo[], - workflowStatesData?: WorkflowStatesData + liveAutoPilotStatusData?: WorkflowInstance ) => { if (isLoading) { return ( @@ -309,22 +311,28 @@ export const getAgentRunningStatusMessage = ( } let message = ''; + let Icon: SvgComponent = () => null; + const status = liveAutoPilotStatusData?.status ?? ''; - switch (workflowStatesData?.mainInstanceState?.status) { + switch (status) { case WorkflowStatus.Running: message = t('message.auto-pilot-agents-running-message'); + Icon = RunningIcon; break; case WorkflowStatus.Failure: message = t('message.auto-pilot-agents-failed-message'); + Icon = ErrorIcon; break; case WorkflowStatus.Finished: message = t('message.auto-pilot-agents-finished-message'); + Icon = CheckIcon; break; case WorkflowStatus.Exception: message = t('message.auto-pilot-agents-exception-message'); + Icon = ErrorIcon; break; } @@ -334,10 +342,14 @@ export const getAgentRunningStatusMessage = ( } return ( - - {message} - +
+ + + + {message} + +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx index c096d9cdb33..c544e4c9359 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceInsightsTabUtils.tsx @@ -11,7 +11,15 @@ * limitations under the License. */ import { Typography } from 'antd'; -import { first, isEmpty, last, round, sortBy, toLower } from 'lodash'; +import { + first, + isEmpty, + isUndefined, + last, + round, + sortBy, + toLower, +} from 'lodash'; import { ServiceTypes } from 'Models'; import { ReactComponent as DescriptionPlaceholderIcon } from '../assets/svg/ic-flat-doc.svg'; import { ReactComponent as TablePlaceholderIcon } from '../assets/svg/ic-large-table.svg'; @@ -22,7 +30,6 @@ import { ReactComponent as PiiPlaceholderIcon } from '../assets/svg/security-saf import ErrorPlaceHolder from '../components/common/ErrorWithPlaceholder/ErrorPlaceHolder'; import { ChartsResults } from '../components/ServiceInsights/ServiceInsightsTab.interface'; import { SERVICE_AUTOPILOT_AGENT_TYPES } from '../constants/Services.constant'; -import { totalDataAssetsWidgetColors } from '../constants/TotalDataAssetsWidget.constants'; import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../enums/common.enum'; import { SystemChartType } from '../enums/DataInsight.enum'; import { EntityType } from '../enums/entity.enum'; @@ -173,12 +180,24 @@ export const getPlatformInsightsChartDataFormattingMethod = }; export const getFormattedTotalAssetsDataFromSocketData = ( - socketData: DataInsightCustomChartResult + socketData: DataInsightCustomChartResult, + serviceCategory: ServiceTypes ) => { - const entityCountsArray = socketData.results.map((result, index) => ({ + const assets = getAssetsByServiceType(serviceCategory); + + const buckets = assets.reduce((acc, curr) => { + const bucket = socketData.results.find((bucket) => bucket.group === curr); + + if (!isUndefined(bucket)) { + return [...acc, bucket]; + } + + return acc; + }, [] as DataInsightCustomChartResult['results']); + + const entityCountsArray = buckets.map((result) => ({ name: getEntityNameLabel(result.group), value: result.count ?? 0, - fill: totalDataAssetsWidgetColors[index], icon: getEntityIcon(result.group, '', { height: 16, width: 16 }) ?? <>, }));