feat(ui): allow toggle to see both deleted and non-deleted chart on dashboard page (#22963)

* add deleted/non-deleted charts functionality

* add e2e test

* fix test

* minor refactor
This commit is contained in:
Pranita Fulsundar 2025-08-19 14:17:11 +05:30 committed by GitHub
parent b1a1cd89a7
commit ef162cec89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 171 additions and 10 deletions

View File

@ -11,17 +11,22 @@
* limitations under the License.
*/
import { expect } from '@playwright/test';
import { BIG_ENTITY_DELETE_TIMEOUT } from '../../constant/delete';
import { DashboardClass } from '../../support/entity/DashboardClass';
import { EntityTypeEndpoint } from '../../support/entity/Entity.interface';
import { DashboardServiceClass } from '../../support/entity/service/DashboardServiceClass';
import { performAdminLogin } from '../../utils/admin';
import { redirectToHomePage } from '../../utils/common';
import { redirectToHomePage, toastNotification } from '../../utils/common';
import {
assignTagToChildren,
generateEntityChildren,
removeTagsFromChildren,
restoreEntity,
} from '../../utils/entity';
import { test } from '../fixtures/pages';
const dashboardEntity = new DashboardServiceClass();
const dashboard = new DashboardClass();
test.describe('Dashboards', () => {
test.slow(true);
@ -84,6 +89,111 @@ test.describe('Dashboards', () => {
});
});
test.describe('Dashboard and Charts deleted toggle', () => {
test.slow(true);
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await dashboard.create(apiContext);
await afterAction();
});
test.afterAll('Clean up', async ({ browser }) => {
const { afterAction, apiContext } = await performAdminLogin(browser);
await dashboard.delete(apiContext);
await afterAction();
});
test.beforeEach('Visit home page', async ({ page }) => {
await redirectToHomePage(page);
});
test('should be able to toggle between deleted and non-deleted charts', async ({
page,
}) => {
await dashboard.visitEntityPage(page);
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
await page.click('[data-testid="manage-button"]');
await page.click('[data-testid="delete-button"]');
await page.waitForSelector('[role="dialog"].ant-modal');
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
const deleteResponse = page.waitForResponse(
`/api/v1/${EntityTypeEndpoint.Dashboard}/async/*?hardDelete=false&recursive=true`
);
await page.click('[data-testid="confirm-button"]');
await deleteResponse;
await page.waitForLoadState('networkidle');
await toastNotification(
page,
/(deleted successfully!|Delete operation initiated)/,
BIG_ENTITY_DELETE_TIMEOUT
);
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
// Retry mechanism for checking deleted badge
let deletedBadge = page.locator('[data-testid="deleted-badge"]');
let attempts = 0;
const maxAttempts = 5;
while (attempts < maxAttempts) {
const isVisible = await deletedBadge.isVisible();
if (isVisible) {
break;
}
attempts++;
if (attempts < maxAttempts) {
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
deletedBadge = page.locator('[data-testid="deleted-badge"]');
}
}
await expect(deletedBadge).toHaveText('Deleted');
await expect(
page.getByTestId('charts-table').getByTestId('no-data-placeholder')
).toBeVisible();
await page.getByTestId('show-deleted').click();
await expect(
page.getByTestId('charts-table').getByTestId('no-data-placeholder')
).not.toBeVisible();
await restoreEntity(page);
await page.reload();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
await expect(
page.getByTestId('charts-table').getByTestId('no-data-placeholder')
).toBeVisible();
await page.getByTestId('show-deleted').click();
await expect(
page.getByTestId('charts-table').getByTestId('no-data-placeholder')
).not.toBeVisible();
});
});
test.describe('Data Model', () => {
test('expand / collapse should not appear after updating nested fields for dashboardDataModels', async ({
page,

View File

@ -11,23 +11,27 @@
* limitations under the License.
*/
import { Switch, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import { compare, Operation } from 'fast-json-patch';
import { groupBy, isEmpty, isUndefined, uniqBy } from 'lodash';
import { groupBy, isUndefined, uniqBy } from 'lodash';
import { EntityTags, TagFilterOptions } from 'Models';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { INITIAL_CHART_FILTERS } from '../../../constants/constants';
import {
DEFAULT_DASHBOARD_CHART_VISIBLE_COLUMNS,
TABLE_COLUMNS_KEYS,
} from '../../../constants/TableKeys.constants';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface';
import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
import { EntityType } from '../../../enums/entity.enum';
import { TagLabel, TagSource } from '../../../generated/entity/data/chart';
import { Dashboard } from '../../../generated/entity/data/dashboard';
import { useTableFilters } from '../../../hooks/useTableFilters';
import { ChartType } from '../../../pages/DashboardDetailsPage/DashboardDetailsPage.component';
import { updateChart } from '../../../rest/chartAPI';
import { fetchCharts } from '../../../utils/DashboardDetailsUtils';
@ -72,6 +76,9 @@ export const DashboardChartTable = ({
chart: ChartType;
index: number;
}>();
const { filters: chartFilters, setFilters } = useTableFilters(
INITIAL_CHART_FILTERS
);
const fetchChartPermissions = useCallback(async (id: string) => {
try {
@ -120,7 +127,10 @@ export const DashboardChartTable = ({
const initializeCharts = useCallback(async () => {
try {
const res = await fetchCharts(listChartIds);
const res = await fetchCharts(
listChartIds,
chartFilters.showDeletedCharts
);
setCharts(res);
} catch (error) {
showErrorToast(
@ -130,7 +140,7 @@ export const DashboardChartTable = ({
})
);
}
}, [listChartIds]);
}, [listChartIds, chartFilters.showDeletedCharts]);
const handleUpdateChart = (chart: ChartType, index: number) => {
setEditChart({ chart, index });
@ -261,6 +271,13 @@ export const DashboardChartTable = ({
}
};
const handleShowDeletedCharts = useCallback(
(value: boolean) => {
setFilters({ showDeletedCharts: value });
},
[setFilters, chartFilters]
);
const tableColumn: ColumnsType<ChartType> = useMemo(
() => [
{
@ -390,11 +407,14 @@ export const DashboardChartTable = ({
}
initializeCharts();
}, [listChartIds, isCustomizationPage]);
}, [listChartIds, isCustomizationPage, initializeCharts]);
if (isEmpty(charts)) {
return <ErrorPlaceHolder className="border-default border-radius-sm" />;
}
useEffect(() => {
setFilters({
showDeletedCharts:
chartFilters.showDeletedCharts ?? dashboardDetails?.deleted,
});
}, [dashboardDetails?.deleted, chartFilters.showDeletedCharts, setFilters]);
return (
<>
@ -404,6 +424,26 @@ export const DashboardChartTable = ({
data-testid="charts-table"
dataSource={charts}
defaultVisibleColumns={DEFAULT_DASHBOARD_CHART_VISIBLE_COLUMNS}
extraTableFilters={
<span>
<Switch
checked={chartFilters.showDeletedCharts}
data-testid="show-deleted"
onClick={handleShowDeletedCharts}
/>
<Typography.Text className="m-l-xs">
{t('label.deleted')}
</Typography.Text>
</span>
}
locale={{
emptyText: (
<ErrorPlaceHolder
className="border-none mt-0-important"
type={ERROR_PLACEHOLDER_TYPE.NO_DATA}
/>
),
}}
pagination={false}
rowKey="fullyQualifiedName"
scroll={{ x: 1200 }}

View File

@ -434,6 +434,10 @@ export const INITIAL_TABLE_FILTERS = {
showDeletedTables: false,
};
export const INITIAL_CHART_FILTERS = {
showDeletedCharts: false,
};
export const MAX_VISIBLE_OWNERS_FOR_FEED_TAB = 4;
export const MAX_VISIBLE_OWNERS_FOR_FEED_CARD = 2;

View File

@ -27,6 +27,7 @@ import { DetailPageWidgetKeys } from '../enums/CustomizeDetailPage.enum';
import { EntityTabs, EntityType, TabSpecificField } from '../enums/entity.enum';
import { Dashboard } from '../generated/entity/data/dashboard';
import { PageType } from '../generated/system/ui/page';
import { Include } from '../generated/type/include';
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
import { ChartType } from '../pages/DashboardDetailsPage/DashboardDetailsPage.component';
import { getChartById } from '../rest/chartAPI';
@ -36,13 +37,19 @@ import { t } from './i18next/LocalUtil';
// eslint-disable-next-line max-len
export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`;
export const fetchCharts = async (charts: Dashboard['charts']) => {
export const fetchCharts = async (
charts: Dashboard['charts'],
showDeleted = false
) => {
let chartsData: ChartType[] = [];
let promiseArr: Array<Promise<ChartType>> = [];
try {
if (charts?.length) {
promiseArr = charts.map((chart) =>
getChartById(chart.id, { fields: TabSpecificField.TAGS })
getChartById(chart.id, {
fields: TabSpecificField.TAGS,
include: showDeleted ? Include.Deleted : Include.NonDeleted,
})
);
const res = await Promise.allSettled(promiseArr);