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,
+ },
+};