diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts index f15ee99b9f1..338a00843ec 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts @@ -161,11 +161,17 @@ test.describe('Bulk Edit Entity', () => { failed: '0', }); + const updateButtonResponse = page.waitForResponse( + `/api/v1/services/databaseServices/name/*/importAsync?*dryRun=false&recursive=false*` + ); + await page.getByRole('button', { name: 'Update' }).click(); + await page .locator('.inovua-react-toolkit-load-mask__background-layer') .waitFor({ state: 'detached' }); - + await updateButtonResponse; + await page.waitForEvent('framenavigated'); await toastNotification(page, /details updated successfully/); await page.click('[data-testid="databases"]'); @@ -311,12 +317,15 @@ test.describe('Bulk Edit Entity', () => { await page.waitForSelector('.InovuaReactDataGrid__header-layout', { state: 'visible', }); - + const updateButtonResponse = page.waitForResponse( + `/api/v1/databases/name/*/importAsync?*dryRun=false&recursive=false*` + ); await page.getByRole('button', { name: 'Update' }).click(); await page .locator('.inovua-react-toolkit-load-mask__background-layer') .waitFor({ state: 'detached' }); - + await updateButtonResponse; + await page.waitForEvent('framenavigated'); await toastNotification(page, /details updated successfully/); // Verify Details updated @@ -443,8 +452,13 @@ test.describe('Bulk Edit Entity', () => { processed: '2', failed: '0', }); - + const updateButtonResponse = page.waitForResponse( + `/api/v1/databaseSchemas/name/*/importAsync?*dryRun=false&recursive=false*` + ); await page.getByRole('button', { name: 'Update' }).click(); + + await updateButtonResponse; + await page.waitForEvent('framenavigated'); await toastNotification(page, /details updated successfully/); // Verify Details updated @@ -561,6 +575,9 @@ test.describe('Bulk Edit Entity', () => { failed: '0', }); + const updateButtonResponse = page.waitForResponse( + `/api/v1/tables/name/*/importAsync?*dryRun=false&recursive=false*` + ); await page.click('[type="button"] >> text="Update"', { force: true }); await page .locator('.inovua-react-toolkit-load-mask__background-layer') @@ -569,7 +586,7 @@ test.describe('Bulk Edit Entity', () => { await page.waitForSelector('.message-banner-wrapper', { state: 'detached', }); - + await updateButtonResponse; await toastNotification(page, /details updated successfully/); // Verify Details updated diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/GlossaryVersionPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/GlossaryVersionPage.spec.ts index 18294767db3..1804b3173e4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/GlossaryVersionPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/GlossaryVersionPage.spec.ts @@ -204,7 +204,7 @@ test('GlossaryTerm', async ({ page }) => { const glossaryTermsRes = page.waitForResponse( '/api/v1/glossaryTerms/name/**' ); - await page.click('[data-testid="version-button"]'); + await page.getByRole('dialog').getByRole('img').click(); await page.waitForLoadState('networkidle'); await glossaryTermsRes; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/MySqlIngestionClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/MySqlIngestionClass.ts index 55d728ca925..a587dbfb609 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/MySqlIngestionClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ingestion/MySqlIngestionClass.ts @@ -37,6 +37,7 @@ class MysqlIngestionClass extends ServiceBaseClass { name = ''; defaultFilters = ['^information_schema$', '^performance_schema$']; tableFilter: string[]; + excludeSchemas: string[]; profilerTable = 'alert_entity'; constructor(tableFilter?: string[]) { const serviceName = `pw-mysql-with-%-${uuid()}`; @@ -47,6 +48,7 @@ class MysqlIngestionClass extends ServiceBaseClass { 'alert_entity', 'chart_entity', ]; + this.excludeSchemas = ['openmetadata']; } async createService(page: Page) { @@ -77,6 +79,12 @@ class MysqlIngestionClass extends ServiceBaseClass { .locator('#root\\/tableFilterPattern\\/includes') .press('Enter'); } + for (const schema of this.excludeSchemas) { + await page.fill('#root\\/schemaFilterPattern\\/excludes', schema); + await page + .locator('#root\\/schemaFilterPattern\\/excludes') + .press('Enter'); + } } async runAdditionalTests( @@ -187,7 +195,7 @@ class MysqlIngestionClass extends ServiceBaseClass { await page.waitForSelector('.ant-select-selection-item-content'); await expect(page.locator('.ant-select-selection-item-content')).toHaveText( - this.defaultFilters.concat(this.tableFilter) + this.defaultFilters.concat([...this.excludeSchemas, ...this.tableFilter]) ); } } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts index 79027a61eae..7e5b7618271 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts @@ -1106,11 +1106,10 @@ export const approveTagsTask = async ( await selectActiveGlossary(page, entity.data.displayName); await page.waitForLoadState('networkidle'); - const tagVisibility = await page.isVisible( - `[data-testid="tag-${value.tag}"]` - ); + const tagVisibility = page.locator(`[data-testid="tag-${value.tag}"]`); + await tagVisibility.scrollIntoViewIfNeeded(); - expect(tagVisibility).toBe(true); + await expect(tagVisibility).toBeVisible(); }; export async function openColumnDropdown(page: Page): Promise { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts index 1a094907f6d..adba35a88a9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts @@ -39,7 +39,7 @@ export const createGlossaryTermRowDetails = () => { }; export const fillTextInputDetails = async (page: Page, text: string) => { - await page.locator('.InovuaReactDataGrid__cell--cell-active').press('Enter'); + await page.keyboard.press('Enter'); await page.locator('.ant-layout-content').getByRole('textbox').fill(text); await page @@ -69,6 +69,8 @@ export const fillOwnerDetails = async (page: Page, owners: string[]) => { .locator('.InovuaReactDataGrid__cell--cell-active') .press('Enter', { delay: 100 }); + await expect(page.getByTestId('select-owner-tabs')).toBeVisible(); + await page.waitForLoadState('networkidle'); await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.test.tsx index 3749c08201f..8979ce88b9a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.test.tsx @@ -13,6 +13,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { NodeViewProps } from '@tiptap/react'; import React from 'react'; +import { UPLOADED_ASSETS_URL } from '../../../../../constants/BlockEditor.constants'; import ImageAttachment from './ImageAttachment'; describe('ImageAttachment', () => { @@ -28,24 +29,7 @@ describe('ImageAttachment', () => { jest.clearAllMocks(); }); - it('should render loading state when isMediaLoading is true and needs authentication', () => { - const authenticatedNode = { - ...mockNode, - attrs: { - ...mockNode.attrs, - url: '/api/v1/attachments/123', - }, - } as unknown as NodeViewProps['node']; - - render( - - ); - - expect(screen.getByTestId('loader')).toBeInTheDocument(); - expect(screen.getByText('label.loading')).toBeInTheDocument(); - }); - - it('should render uploading state when isUploading is true', () => { + it('should render loading state when isUploading is true', () => { const uploadingNode = { ...mockNode, attrs: { @@ -62,8 +46,29 @@ describe('ImageAttachment', () => { /> ); - expect(screen.getByTestId('loader')).toBeInTheDocument(); - expect(screen.getByText('label.uploading')).toBeInTheDocument(); + const imageContainer = screen.getByTestId('image-container'); + + expect(imageContainer).toHaveClass('loading-state'); + expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument(); + }); + + it('should render loading state when media is loading and needs authentication', () => { + const authenticatedNode = { + ...mockNode, + attrs: { + ...mockNode.attrs, + url: `${UPLOADED_ASSETS_URL}/123`, + }, + } as unknown as NodeViewProps['node']; + + render( + + ); + + const imageContainer = screen.getByTestId('image-container'); + + expect(imageContainer).toHaveClass('loading-state'); + expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument(); }); it('should render image when mediaSrc is provided', async () => { @@ -85,20 +90,13 @@ describe('ImageAttachment', () => { it('should show error state when image fails to load', async () => { render( - + ); - const image = screen.getByTestId('uploaded-image-node'); - fireEvent.error(image); - await waitFor(() => { - expect(screen.getByTestId('uploaded-image-node')).toHaveStyle({ - visibility: 'hidden', - }); + expect( + screen.queryByTestId('uploaded-image-node') + ).not.toBeInTheDocument(); }); }); @@ -107,23 +105,82 @@ describe('ImageAttachment', () => { ...mockNode, attrs: { ...mockNode.attrs, - url: '/api/v1/attachments/123', + url: `${UPLOADED_ASSETS_URL}/123`, }, } as unknown as NodeViewProps['node']; + render( + + ); + + const imageContainer = screen.getByTestId('image-container'); + + expect(imageContainer).toHaveClass('loading-state'); + expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument(); + }); + + it('should display authenticated image when mediaSrc is provided', async () => { + const authenticatedNode = { + ...mockNode, + attrs: { + ...mockNode.attrs, + url: `${UPLOADED_ASSETS_URL}/123`, + }, + } as unknown as NodeViewProps['node']; + + const mediaSrc = 'https://example.com/authenticated-image.jpg'; render( ); - expect(screen.getByTestId('loader')).toBeInTheDocument(); - expect(screen.getByText('label.loading')).toBeInTheDocument(); + const image = screen.getByTestId('uploaded-image-node'); + + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute('src', mediaSrc); }); - it('should reset states when url or mediaSrc changes', async () => { + it('should reset states when url changes', async () => { + const { rerender } = render( + + ); + + // Simulate image load + const image = screen.getByTestId('uploaded-image-node'); + fireEvent.load(image); + + // Rerender with new url + const newNode = { + ...mockNode, + attrs: { + ...mockNode.attrs, + url: 'https://example.com/new-image.jpg', + }, + } as unknown as NodeViewProps['node']; + + rerender( + + ); + + // Image should be hidden again until it loads + expect(screen.getByTestId('uploaded-image-node')).toHaveAttribute( + 'src', + 'https://example.com/image1.jpg' + ); + }); + + it('should reset states when mediaSrc changes', async () => { const { rerender } = render( { ); // Image should be hidden again until it loads - expect(image).toHaveStyle({ visibility: 'hidden' }); + expect(screen.getByTestId('uploaded-image-node')).toHaveAttribute( + 'src', + 'https://example.com/image2.jpg' + ); + }); + + it('should handle empty alt text', () => { + const nodeWithEmptyAlt = { + ...mockNode, + attrs: { + ...mockNode.attrs, + alt: '', + }, + } as unknown as NodeViewProps['node']; + + render( + + ); + + const image = screen.getByTestId('uploaded-image-node'); + + expect(image).toHaveAttribute('alt', ''); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.tsx index ef84b1d74ae..a624642be1e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/ImageAttachment.tsx @@ -11,13 +11,10 @@ * limitations under the License. */ import { NodeViewProps } from '@tiptap/react'; -import { Spin } from 'antd'; import classNames from 'classnames'; import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { ReactComponent as IconFormatImage } from '../../../../../assets/svg/ic-format-image.svg'; import { UPLOADED_ASSETS_URL } from '../../../../../constants/BlockEditor.constants'; -import Loader from '../../../../common/Loader/Loader'; const ImageAttachment = ({ node, @@ -32,7 +29,6 @@ const ImageAttachment = ({ const [imageError, setImageError] = useState(false); const [imageLoaded, setImageLoaded] = useState(false); const needsAuthentication = url?.includes(UPLOADED_ASSETS_URL); - const { t } = useTranslation(); // Reset states when url changes useEffect(() => { @@ -56,34 +52,25 @@ const ImageAttachment = ({ return (
- } - spinning={showLoadingOverlay} - tip={isUploading ? t('label.uploading') : t('label.loading')}> -
- {(showLoadingOverlay || imageError) && ( -
- -
- )} - {displaySrc && ( - {alt - )} -
-
+
+ {displaySrc ? ( + {alt + ) : ( +
+ +
+ )} +
); };