diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataInsight.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataInsight.spec.ts index 4e8fcb9e0f4..8190bf9a189 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataInsight.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataInsight.spec.ts @@ -22,6 +22,11 @@ test.use({ storageState: 'playwright/.auth/admin.json' }); test.describe.configure({ mode: 'serial' }); +const DESCRIPTION_WITH_PERCENTAGE = + 'playwright-description-with-percentage-percentage'; + +const DESCRIPTION_WITH_OWNER = 'playwright-owner-with-percentage-percentage'; + test.describe('Data Insight Page', { tag: '@data-insight' }, () => { test.beforeAll(async ({ browser }) => { const { apiContext } = await createNewPage(browser); @@ -137,14 +142,10 @@ test.describe('Data Insight Page', { tag: '@data-insight' }, () => { await expect(page.getByTestId('kpi-card')).toBeVisible(); await expect( - page.locator( - '[data-row-key="playwright-description-with-percentage-percentage"]' - ) + page.locator(`[data-row-key=${DESCRIPTION_WITH_PERCENTAGE}]`) ).toBeVisible(); await expect( - page.locator( - '[data-row-key="playwright-owner-with-percentage-percentage"]' - ) + page.locator(`[data-row-key=${DESCRIPTION_WITH_OWNER}]`) ).toBeVisible(); }); @@ -162,6 +163,20 @@ test.describe('Data Insight Page', { tag: '@data-insight' }, () => { } }); + test('Verify KPI widget in Landing page', async ({ page }) => { + const kpiResponse = page.waitForResponse('/api/v1/kpi/*/kpiResult?*'); + + await redirectToHomePage(page); + + await kpiResponse; + + expect(page.locator('[data-testid="kpi-widget"]')).toBeVisible(); + + // description and owner data to be visible + expect(page.getByTestId(DESCRIPTION_WITH_PERCENTAGE)).toBeVisible(); + expect(page.getByTestId(DESCRIPTION_WITH_OWNER)).toBeVisible(); + }); + test('Delete Kpi', async ({ page }) => { await page.waitForResponse( '/api/v1/kpi/playwright-owner-with-percentage-percentage/latestKpiResult' diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.component.tsx index 116d5cf322d..7f3b7981934 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.component.tsx @@ -33,12 +33,11 @@ import { } from '../../../../constants/constants'; import { DATA_INSIGHT_GRAPH_COLORS } from '../../../../constants/DataInsight.constants'; import { DATA_INSIGHT_DOCS } from '../../../../constants/docs.constants'; -import { SIZE } from '../../../../enums/common.enum'; +import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../../../enums/common.enum'; import { WidgetWidths } from '../../../../enums/CustomizablePage.enum'; import { TabSpecificField } from '../../../../enums/entity.enum'; import { Kpi, KpiResult } from '../../../../generated/dataInsight/kpi/kpi'; import { UIKpiResult } from '../../../../interface/data-insight.interface'; -import { useDataInsightProvider } from '../../../../pages/DataInsightPage/DataInsightProvider'; import { DataInsightCustomChartResult } from '../../../../rest/DataInsightAPI'; import { getLatestKpiResult, @@ -46,42 +45,17 @@ import { getListKPIs, } from '../../../../rest/KpiAPI'; import { Transi18next } from '../../../../utils/CommonUtils'; -import { customFormatDateTime } from '../../../../utils/date-time/DateTimeUtils'; +import { + customFormatDateTime, + getCurrentMillis, + getEpochMillisForPastDays, +} from '../../../../utils/date-time/DateTimeUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; +import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import KPILatestResultsV1 from '../../../DataInsight/KPILatestResultsV1'; import './kpi-widget.less'; import { KPIWidgetProps } from './KPIWidget.interface'; -const EmptyPlaceholder = () => { - const { t } = useTranslation(); - - return ( -
- -
- - {t('message.no-kpi')} - - - - } - values={{ - doc: t('label.doc-plural-lowercase'), - }} - /> - -
-
- ); -}; - const KPIWidget = ({ isEditView = false, selectedDays = CHART_WIDGET_DAYS_DURATION, @@ -98,12 +72,11 @@ const KPIWidget = ({ const [kpiLatestResults, setKpiLatestResults] = useState>(); const [isLoading, setIsLoading] = useState(false); - const { chartFilter } = useDataInsightProvider(); const getKPIResult = async (kpi: Kpi) => { const response = await getListKpiResult(kpi.fullyQualifiedName ?? '', { - startTs: chartFilter.startTs, - endTs: chartFilter.endTs, + startTs: getEpochMillisForPastDays(selectedDays), + endTs: getCurrentMillis(), }); return { name: kpi.name, data: response.results }; @@ -253,7 +226,28 @@ const KPIWidget = ({ {isEmpty(kpiList) || isEmpty(kpiResults) ? ( - + } + type={ERROR_PLACEHOLDER_TYPE.CUSTOM}> + + {t('message.no-kpi')} + + + + } + values={{ + doc: t('label.doc-plural-lowercase'), + }} + /> + + ) : ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.test.tsx new file mode 100644 index 00000000000..035614cb89b --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/Widgets/KPIWidget/KPIWidget.test.tsx @@ -0,0 +1,145 @@ +/* + * Copyright 2024 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 { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { act } from 'react-test-renderer'; +import { WidgetWidths } from '../../../../enums/CustomizablePage.enum'; +import { MOCK_KPI_LIST_RESPONSE } from '../../../../pages/KPIPage/KPIMock.mock'; +import { getListKPIs } from '../../../../rest/KpiAPI'; +import KPIWidget from './KPIWidget.component'; + +jest.mock('../../../../constants/DataInsight.constants', () => ({ + DATA_INSIGHT_GRAPH_COLORS: ['#E7B85D'], +})); + +jest.mock('../../../../constants/constants', () => ({ + CHART_WIDGET_DAYS_DURATION: 14, + GRAPH_BACKGROUND_COLOR: '#000000', +})); + +jest.mock('../../../../utils/date-time/DateTimeUtils', () => ({ + customFormatDateTime: jest.fn().mockReturnValue('Dec 05, 11:54'), + getCurrentMillis: jest.fn().mockReturnValue(1711583974000), + getEpochMillisForPastDays: jest.fn().mockReturnValue(1709424034000), +})); + +jest.mock('../../../../rest/DataInsightAPI', () => ({ + DataInsightCustomChartResult: jest + .fn() + .mockImplementation(() => Promise.resolve()), +})); + +jest.mock('../../../../rest/KpiAPI', () => ({ + getLatestKpiResult: jest.fn().mockImplementation(() => + Promise.resolve({ + timestamp: 1724760319723, + kpiFqn: 'description-percentage', + targetResult: [ + { + value: '23.52941176470588', + targetMet: false, + }, + ], + }) + ), + getListKpiResult: jest.fn().mockImplementation(() => + Promise.resolve({ + results: [ + { + count: 23.52941176470588, + day: 1724716800000, + }, + ], + }) + ), + getListKPIs: jest + .fn() + .mockImplementation(() => Promise.resolve(MOCK_KPI_LIST_RESPONSE)), +})); + +jest.mock('../../../../utils/ToastUtils', () => ({ + showErrorToast: jest.fn(), +})); + +jest.mock('../../../../utils/CommonUtils', () => ({ + Transi18next: jest.fn().mockReturnValue('text'), +})); + +jest.mock('../../../DataInsight/KPILatestResultsV1', () => + jest.fn().mockReturnValue(

KPILatestResultsV1.Component

) +); + +jest.mock('../../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => + jest.fn().mockReturnValue(

ErrorPlaceHolder.Component

) +); + +const mockHandleRemoveWidget = jest.fn(); + +const widgetProps = { + selectedGridSize: WidgetWidths.medium, + isEditView: true, + widgetKey: 'testWidgetKey', + handleRemoveWidget: mockHandleRemoveWidget, +}; + +describe('KPIWidget', () => { + it('should fetch kpi list api initially', async () => { + render(); + + expect(getListKPIs).toHaveBeenCalledWith({ fields: 'dataInsightChart' }); + }); + + it('should handle close click when in edit view', async () => { + await act(async () => { + render(); + }); + + fireEvent.click(screen.getByTestId('remove-widget-button')); + + expect(mockHandleRemoveWidget).toHaveBeenCalledWith(widgetProps.widgetKey); + }); + + it('should render charts and data if present', async () => { + await act(async () => { + render(); + }); + + expect(screen.getByText('label.kpi-title')).toBeInTheDocument(); + expect( + screen.getByText('KPILatestResultsV1.Component') + ).toBeInTheDocument(); + }); + + it('should not render data if selectedGridSize is small', async () => { + await act(async () => { + render( + + ); + }); + + expect(screen.getByText('label.kpi-title')).toBeInTheDocument(); + expect( + screen.queryByText('KPILatestResultsV1.Component') + ).not.toBeInTheDocument(); + }); + + it('should render ErrorPlaceholder if no data there', async () => { + (getListKPIs as jest.Mock).mockImplementation(() => Promise.resolve()); + + await act(async () => { + render(); + }); + + expect(screen.getByText('ErrorPlaceHolder.Component')).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/KPIMock.mock.ts b/openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/KPIMock.mock.ts index 325cf221c65..9717a0a1a2d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/KPIMock.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/KPIMock.mock.ts @@ -442,3 +442,36 @@ export const DESCRIPTION_CHART = { href: 'http://localhost:8585/api/v1/analytics/dataInsights/charts/7dc794d3-1881-408c-92fc-6182aa453bc8', deleted: false, }; + +export const MOCK_KPI_LIST_RESPONSE = { + data: [ + { + id: '17651e36-2350-414a-a68d-bea58cc96d02', + name: 'description-percentage', + displayName: 'description', + fullyQualifiedName: 'description-percentage', + description: 'this is description', + metricType: 'PERCENTAGE', + dataInsightChart: { + id: 'e10d1bef-0d6b-42cd-a215-5771f40803eb', + type: 'dataInsightCustomChart', + name: 'percentage_of_data_asset_with_description_kpi', + fullyQualifiedName: 'percentage_of_data_asset_with_description_kpi', + displayName: 'percentage_of_data_asset_with_description_kpi', + deleted: false, + href: 'http://localhost:8585/api/v1/analytics/dataInsights/system/charts/e10d1bef-0d6b-42cd-a215-5771f40803eb', + }, + targetValue: 58, + startDate: 1724697000000, + endDate: 1725128999999, + version: 0.1, + updatedAt: 1724760086039, + updatedBy: 'admin', + href: 'http://localhost:8585/api/v1/kpi/17651e36-2350-414a-a68d-bea58cc96d02', + deleted: false, + }, + ], + paging: { + total: 1, + }, +};