mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-02 02:26:00 +00:00
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:
parent
b1a1cd89a7
commit
ef162cec89
@ -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,
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user