diff --git a/openmetadata-ui/src/main/resources/ui/eslint.config.mjs b/openmetadata-ui/src/main/resources/ui/eslint.config.mjs index 1ff5ac18ef9e..f2272bed3a1e 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint.config.mjs +++ b/openmetadata-ui/src/main/resources/ui/eslint.config.mjs @@ -454,16 +454,20 @@ export default [ }, }, - // Test setup files + // Test, spec, and mock files: these contain no user-facing strings, so the + // i18n literal-string rule does not apply to them. { files: [ 'src/setupTests.js', 'src/**/*.test.{js,jsx,ts,tsx}', 'src/**/*.spec.{js,jsx,ts,tsx}', + 'src/**/*.mock.{js,jsx,ts,tsx}', + 'src/mocks/**/*.{js,jsx,ts,tsx}', 'playwright/**/*.spec.{js,jsx,ts,tsx}', ], rules: { '@typescript-eslint/no-require-imports': 'off', + 'i18next/no-literal-string': 'off', }, }, ]; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOAuthentication.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOAuthentication.spec.ts index 18bced359b15..d8bf2ed80e8f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOAuthentication.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/SSOAuthentication.spec.ts @@ -264,6 +264,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // Navigate again — app should handle the error gracefully await page.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(5000); const url = page.url(); @@ -306,6 +307,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { }); await page.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(5000); const refreshFlag = await page.evaluate(() => { @@ -325,8 +327,10 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { localStorage.setItem('refreshInProgress', 'true'); }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(2000); await page.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(5000); const url = page.url(); @@ -352,6 +356,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // WebKit processes page.route() 401 interceptions with different event // loop timing — the async logout chain doesn't complete before the page // settles, so the redirect to /signin doesn't happen reliably. + // eslint-disable-next-line playwright/no-skipped-test -- intentionally skipped test.skip( browserName === 'webkit', 'WebKit handles route interception timing differently' @@ -422,6 +427,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // Navigate to a page that makes multiple parallel API calls await page.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(10000); // The refresh should have happened at most once despite multiple 401s @@ -439,6 +445,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { }) => { // WebKit processes page.route() 401 interceptions with different event // loop timing — the forced logout redirect doesn't happen reliably. + // eslint-disable-next-line playwright/no-skipped-test -- intentionally skipped test.skip( browserName === 'webkit', 'WebKit handles route interception timing differently' @@ -493,6 +500,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // Navigate — app should not crash even if silent renewal cannot use // a refresh token (it falls back to iframe/popup) await page.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(5000); const url = page.url(); @@ -533,6 +541,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // Allow async IndexedDB cleanup to complete (WebKit needs more time // because the OIDC logout redirect chain can interrupt pending writes) + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(2000); // Verify auth state is cleared @@ -568,6 +577,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // Open a second tab const page2 = await context.newPage(); await page2.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page2.waitForTimeout(3000); const tab2TokenBefore = await getStoredToken(page2); @@ -596,10 +606,12 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { // Trigger the 401 in tab 1 await page.goto('/activity-feed'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(10000); // Check that tab 2 can still access the app await page2.goto('/'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page2.waitForTimeout(5000); const tab2TokenAfter = await getStoredToken(page2); @@ -638,10 +650,12 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { await resetMetrics(request); // Wait for the token to expire and proactive renewal to trigger + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(8000); // Hard reload — this forces the app to read from IndexedDB (no in-memory cache) await page.reload({ waitUntil: 'networkidle' }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(3000); const url = page.url(); @@ -681,6 +695,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { await setTokenExpiry(request, 5); await resetMetrics(request); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(8000); // Observe the Authorization header on the next API call using waitForRequest @@ -736,6 +751,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { }) => { await performOidcLogin(page); await verifyAuthenticated(page); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(2000); // Set refreshInProgress BEFORE writing expired token to block the @@ -789,6 +805,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { }); // Wait for TOKEN_UPDATE broadcast to be handled (blocked by flag) + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(1000); // Remove the lock so visibilitychange handler can trigger refreshToken() @@ -805,6 +822,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { }); // Wait for the async handler to complete + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(3000); // Verify the handler detected the expired token and attempted refresh. @@ -832,6 +850,7 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { document.dispatchEvent(new Event('visibilitychange')); }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(2000); // App should still be authenticated (handler didn't break anything) @@ -860,10 +879,12 @@ test.describe('SSO Authentication with Mock OIDC Provider', () => { expect(initialToken.length).toBeGreaterThan(0); // Wait 10 seconds to simulate idle period + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(10000); // Navigate to a different page await page.goto('/explore/tables'); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(5000); // Should still be authenticated diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditImportPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditImportPermissions.spec.ts index 57d3ced581a3..d8dd490f031f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditImportPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditImportPermissions.spec.ts @@ -61,6 +61,7 @@ const table = new TableClass(); const test = base.extend<{ bulkEditorPage: Page }>({ bulkEditorPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await editorUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts index 8827d6b3e9d6..5569eb65eae6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts @@ -636,6 +636,7 @@ test.describe('Context Center Articles', () => { url.pathname.includes('/context-center/articles/') ); await waitForAllLoadersToDisappear(page); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(500); await navigateToArticles(page); @@ -1190,6 +1191,7 @@ test.describe('Context Center Articles', () => { .click(); await page.getByTestId('save').click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector( '[role="dialog"].description-markdown-editor', { state: 'hidden' } @@ -1437,6 +1439,7 @@ test.describe('Context Center Articles', () => { .getByTestId('entity-header-display-name') .fill(newDisplayName); await page.getByText('Unsaved').waitFor({ state: 'visible' }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(400); await page.getByRole('link', { name: 'Articles' }).click(); }); @@ -1494,6 +1497,7 @@ test.describe('Context Center Articles', () => { await navigateToArticle(page, draftArticleA.fullyQualifiedName); await page.fill('.om-block-editor', reloadDescription); await page.getByText('Unsaved').waitFor({ state: 'visible' }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(400); }); @@ -1573,6 +1577,7 @@ test.describe('Context Center Articles', () => { await navigateToArticle(page, articleToDelete.fullyQualifiedName); await page.fill('.om-block-editor', 'This draft should be deleted'); await page.getByText('Unsaved').waitFor({ state: 'visible' }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(400); }); @@ -1621,6 +1626,7 @@ test.describe('Context Center Articles', () => { await navigateToArticle(page, draftArticleA.fullyQualifiedName); await page.fill('.om-block-editor', contentA); await page.getByText('Unsaved').waitFor({ state: 'visible' }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(400); }); @@ -1628,6 +1634,7 @@ test.describe('Context Center Articles', () => { await navigateToArticle(page, draftArticleB.fullyQualifiedName); await page.fill('.om-block-editor', contentB); await page.getByText('Unsaved').waitFor({ state: 'visible' }); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(400); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterDocument.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterDocument.spec.ts index baa2b052b04d..5eae29b15bb7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterDocument.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterDocument.spec.ts @@ -897,6 +897,7 @@ test.describe('Context Center - Documents Page', () => { const clipboardText = await copyAndGetClipboardText(page, copyBtn); expect(clipboardText).toContain(`document=${doc.id}`); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const newTab = await browser.newPage(); await newTab.goto(clipboardText); await newTab diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts index eb460b91acbe..55400637d09d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts @@ -63,6 +63,7 @@ const entityTypeToTestEntity: Record = { const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts index 6e88c8dc1858..6200cb3f4178 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts @@ -60,12 +60,14 @@ const test = base.extend<{ userPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); await adminPage.close(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeNavigationNewItems.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeNavigationNewItems.spec.ts index c2f41bbb5bf7..26351bc5f858 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeNavigationNewItems.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeNavigationNewItems.spec.ts @@ -36,12 +36,14 @@ const test = base.extend<{ userPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await use(page); await page.close(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts index 22f05a44b8a8..3a1451eabaaa 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts @@ -44,12 +44,14 @@ const test = base.extend<{ userPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); await adminPage.close(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts index 75471c913c5b..5acf9d043855 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/DataQualityPermissions.spec.ts @@ -78,72 +78,84 @@ const test = base.extend<{ await afterAction(); }, createPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await createUser.login(page); await use(page); await page.close(); }, deletePage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await deleteUser.login(page); await use(page); await page.close(); }, suitePage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await suiteUser.login(page); await use(page); await page.close(); }, viewBasicPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await viewBasicUser.login(page); await use(page); await page.close(); }, consumerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataConsumerUser.login(page); await use(page); await page.close(); }, stewardPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataStewardUser.login(page); await use(page); await page.close(); }, tableCreateTestsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await tableCreateTestsUser.login(page); await use(page); await page.close(); }, editPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await editTestCaseUser.login(page); await use(page); await page.close(); }, tableEditPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await tableEditTestsUser.login(page); await use(page); await page.close(); }, editTestsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await editTestsOnTcUser.login(page); await use(page); await page.close(); }, viewAllPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await viewAllTcUser.login(page); await use(page); await page.close(); }, suiteEditOnlyPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await suiteEditOnlyUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportBasic.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportBasic.spec.ts index e2e7fdb5aae1..e8c52f842c35 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportBasic.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportBasic.spec.ts @@ -74,6 +74,7 @@ const test = base.extend<{ testCaseEditPage: Page; }>({ testCaseEditPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await testCaseEditUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts index fcd0d92ac5d1..3e16b7af3cbf 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts @@ -51,6 +51,7 @@ const test = base.extend<{ testCaseEditPage: Page; }>({ testCaseEditPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await testCaseEditUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseIncidentPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseIncidentPermissions.spec.ts index e525eb218965..55f774304a45 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseIncidentPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseIncidentPermissions.spec.ts @@ -69,30 +69,35 @@ const test = base.extend<{ await afterAction(); }, viewIncidentsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await viewIncidentsUser.login(page); await use(page); await page.close(); }, editIncidentsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await editIncidentsUser.login(page); await use(page); await page.close(); }, tableEditIncidentsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await tableEditIncidentsUser.login(page); await use(page); await page.close(); }, tableViewIncidentsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await tableViewIncidentsUser.login(page); await use(page); await page.close(); }, consumerLikePage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await consumerLikeUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts index 1df70885a369..f12c1d04b6b1 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts @@ -56,36 +56,42 @@ const test = base.extend<{ await afterAction(); }, viewResultsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await viewResultsUser.login(page); await use(page); await page.close(); }, editResultsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await editResultsUser.login(page); await use(page); await page.close(); }, tableEditResultsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await tableEditResultsUser.login(page); await use(page); await page.close(); }, deleteResultsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await deleteResultsUser.login(page); await use(page); await page.close(); }, partialDeleteTcPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await partialDeleteTcUser.login(page); await use(page); await page.close(); }, partialDeleteTablePage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await partialDeleteTableUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestDefinitionPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestDefinitionPermissions.spec.ts index e570b49872f1..88c5789cc8a6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestDefinitionPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestDefinitionPermissions.spec.ts @@ -93,18 +93,21 @@ const test = base.extend<{ await afterAction(); }, dataConsumerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataConsumerUser.login(page); await use(page); await page.close(); }, dataStewardPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataStewardUser.login(page); await use(page); await page.close(); }, viewOnlyPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await viewOnlyUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DescriptionSuggestion.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DescriptionSuggestion.spec.ts index 757e1297553d..1496005a955e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DescriptionSuggestion.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DescriptionSuggestion.spec.ts @@ -171,6 +171,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -227,6 +228,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -290,6 +292,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -349,6 +352,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -409,6 +413,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -467,6 +472,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -544,6 +550,7 @@ test.describe.serial( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainDropdownIsolation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainDropdownIsolation.spec.ts index fc9df0fb50c6..20cd95a9d20d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainDropdownIsolation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainDropdownIsolation.spec.ts @@ -28,6 +28,7 @@ const foreignDomain = new Domain(); const test = base.extend<{ adminPage: Page; restrictedUserPage: Page }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await adminUser.login(page); @@ -37,6 +38,7 @@ const test = base.extend<{ adminPage: Page; restrictedUserPage: Page }>({ } }, restrictedUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await restrictedUser.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainLineageIsolation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainLineageIsolation.spec.ts index 3f7b6aed9fb0..5f8b48150fa3 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainLineageIsolation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainLineageIsolation.spec.ts @@ -43,6 +43,7 @@ const test = base.extend<{ userBPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await adminUser.login(page); @@ -52,6 +53,7 @@ const test = base.extend<{ } }, userAPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userA.login(page); @@ -61,6 +63,7 @@ const test = base.extend<{ } }, userBPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userB.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainListingIsolation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainListingIsolation.spec.ts index f5f5fea310f0..8a10931e16b0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainListingIsolation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainListingIsolation.spec.ts @@ -40,6 +40,7 @@ const test = base.extend<{ userBPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await adminUser.login(page); @@ -49,6 +50,7 @@ const test = base.extend<{ } }, userAPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userA.login(page); @@ -58,6 +60,7 @@ const test = base.extend<{ } }, userBPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userB.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainSearchIsolation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainSearchIsolation.spec.ts index 7a2c39abeb14..e53dcb6bd0c6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainSearchIsolation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainSearchIsolation.spec.ts @@ -41,6 +41,7 @@ const test = base.extend<{ userBPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await adminUser.login(page); @@ -50,6 +51,7 @@ const test = base.extend<{ } }, userAPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userA.login(page); @@ -59,6 +61,7 @@ const test = base.extend<{ } }, userBPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userB.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainTaskIsolation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainTaskIsolation.spec.ts index e722f064f964..c23b90ca7722 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainTaskIsolation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DomainIsolation/DomainTaskIsolation.spec.ts @@ -39,6 +39,7 @@ const test = base.extend<{ userBPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await adminUser.login(page); @@ -48,6 +49,7 @@ const test = base.extend<{ } }, userAPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userA.login(page); @@ -57,6 +59,7 @@ const test = base.extend<{ } }, userBPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await userB.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryAdvancedOperations.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryAdvancedOperations.spec.ts index 4cdcae9c6049..c9c6b6d006c9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryAdvancedOperations.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryAdvancedOperations.spec.ts @@ -323,6 +323,7 @@ test.describe('Glossary Advanced Operations', () => { }); // G-U12: Remove domain from glossary + // eslint-disable-next-line playwright/no-skipped-test -- intentionally skipped test.skip('should remove domain from glossary', async ({ page }) => { test.slow(true); const { apiContext, afterAction } = await getApiContext(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryMutualExclusivity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryMutualExclusivity.spec.ts index 16b04842ff63..6cd5267e548a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryMutualExclusivity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryMutualExclusivity.spec.ts @@ -83,6 +83,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .click(); // Wait for dropdown to open + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -168,6 +169,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -277,6 +279,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -370,6 +373,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -468,6 +472,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -595,6 +600,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -784,6 +790,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { const termRow = page.locator(`[data-row-key="${escapedFqn}"]`); await termRow.getByTestId('edit-button').click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[role="dialog"].edit-glossary-modal'); // Toggle ME to true @@ -809,6 +816,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -872,6 +880,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -993,6 +1002,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); @@ -1138,6 +1148,7 @@ test.describe('Glossary Mutual Exclusivity Feature', () => { .getByTestId('add-tag') .click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.async-tree-select-list-dropdown', { state: 'visible', }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts index 9ff2863d6072..8b8d36a051a7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts @@ -44,12 +44,14 @@ const test = base.extend<{ userPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); await adminPage.close(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts index d2bff377ea8b..2746a5cceaa7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts @@ -39,18 +39,21 @@ const test = base.extend<{ reviewer2Page: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); await adminPage.close(); }, reviewer1Page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await reviewer1.login(page); await use(page); await page.close(); }, reviewer2Page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await reviewer2.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/IncidentManager.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/IncidentManager.spec.ts index b7d245e7c11a..0f3498832a9b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/IncidentManager.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/IncidentManager.spec.ts @@ -610,6 +610,7 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => { const testCasePageUrl = `/test-case/${encodeURIComponent( testCase.fullyQualifiedName )}/test-case-results`; + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern actorPage = await browser.newPage(); await user1.login(actorPage); const testCaseResponse = actorPage.waitForResponse( @@ -739,6 +740,7 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => { */ await test.step('Resolve incident', async () => { const currentUrl = actorPage.url(); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern actorPage = await browser.newPage(); await user3.login(actorPage); const testCaseResponse = actorPage.waitForResponse( diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/DomainDataProductsWidgets.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/DomainDataProductsWidgets.spec.ts index 1e0f0064fbc2..d9dd6f1f08b9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/DomainDataProductsWidgets.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/DomainDataProductsWidgets.spec.ts @@ -59,6 +59,7 @@ const topic = new TopicClass(); const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/FollowingWidget.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/FollowingWidget.spec.ts index a6db6e6b1322..8828036c4812 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/FollowingWidget.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LandingPageWidgets/FollowingWidget.spec.ts @@ -48,6 +48,7 @@ const adminUser = new UserClass(); const test = base.extend<{ adminPage: Page }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts index 849945b9291c..25261db4d7c8 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts @@ -1354,6 +1354,7 @@ test.describe( test('Custom metric editor role can import export and bulk edit metrics', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const metricEditorPage = await browser.newPage(); await metricEditorUser.login(metricEditorPage); @@ -1393,6 +1394,7 @@ test.describe( viewOnlyPage, }) => { test.slow(); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const customViewOnlyPage = await browser.newPage(); await viewOnlyUser.login(customViewOnlyPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MultipleRename.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MultipleRename.spec.ts index 9c64a0cd14c0..7f3435f7dca2 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MultipleRename.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MultipleRename.spec.ts @@ -91,6 +91,7 @@ test.describe('Multiple Rename Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { const glossary = new Glossary(); await glossary.create(apiContext); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); let currentName = glossary.data.name; @@ -152,6 +153,7 @@ test.describe('Multiple Rename Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { const glossaryTerm = new GlossaryTerm(glossary); await glossaryTerm.create(apiContext); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { @@ -203,6 +205,7 @@ test.describe('Multiple Rename Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { const classification = new ClassificationClass(); await classification.create(apiContext); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); let currentName = classification.data.name; @@ -275,6 +278,7 @@ test.describe('Multiple Rename Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { const tag = new TagClass({ classification: classification.data.name }); await tag.create(apiContext); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/NavigationBlocker.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/NavigationBlocker.spec.ts index 59be7f5d4a8d..5711463fdb9a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/NavigationBlocker.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/NavigationBlocker.spec.ts @@ -27,6 +27,7 @@ const persona = new PersonaClass(); const test = base.extend<{ adminPage: Page; userPage: Page }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts index efa9cbdeb7dd..913d58505d9d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts @@ -101,6 +101,7 @@ test.describe('Online Users Feature', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { browser, page, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const userPage = await browser.newPage(); try { await testUser.login(userPage); @@ -233,6 +234,7 @@ test.describe('Online Users Feature', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { }) => { test.slow(); // Mark this test as slow since it involves multiple logins and navigation await test.step('Visit Explore Page as New User', async () => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const userPage = await browser.newPage(); await testUser.login(userPage); await redirectToHomePage(userPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyExplorerRdf.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyExplorerRdf.spec.ts index 2ea89b190e37..01244e86b285 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyExplorerRdf.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyExplorerRdf.spec.ts @@ -83,6 +83,7 @@ test.describe('Ontology Explorer — RDF exports @ontology-rdf', () => { test('Turtle (.ttl) option appears in the export menu when RDF is enabled', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -104,6 +105,7 @@ test.describe('Ontology Explorer — RDF exports @ontology-rdf', () => { test('RDF/XML (.rdf) option appears in the export menu when RDF is enabled', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -122,6 +124,7 @@ test.describe('Ontology Explorer — RDF exports @ontology-rdf', () => { }); test('Turtle export triggers a .ttl file download', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -154,6 +157,7 @@ test.describe('Ontology Explorer — RDF exports @ontology-rdf', () => { }); test('RDF/XML export triggers a .rdf file download', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -187,6 +191,7 @@ test.describe('Ontology Explorer — RDF exports @ontology-rdf', () => { test('Turtle and RDF/XML options are NOT shown when RDF is disabled', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -216,6 +221,7 @@ test.describe('Ontology Explorer — RDF graph data loading @ontology-rdf', () = test('term Relations Graph requests /rdf/glossary/graph scoped to the selected term (glossaryTermId) when RDF is enabled', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -271,6 +277,7 @@ test.describe('Ontology Explorer — RDF graph data loading @ontology-rdf', () = test('glossary Relations Graph calls /rdf/glossary/graph when RDF is enabled and renders nodes from the response', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -308,6 +315,7 @@ test.describe('Ontology Explorer — RDF graph data loading @ontology-rdf', () = test('renders without crashing when /rdf/glossary/graph returns duplicate nodes and dangling edges', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -371,6 +379,7 @@ test.describe('Ontology Explorer — RDF graph data loading @ontology-rdf', () = browser, }) => { test.slow(); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyImportRdf.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyImportRdf.spec.ts index a3a5e4b62a80..52f4147e9adc 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyImportRdf.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyImportRdf.spec.ts @@ -149,6 +149,7 @@ test.describe('Ontology RDF Import', { tag: ['@ontology-rdf'] }, () => { test('hides Import Ontology from a user without glossary edit permission', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const userPage = await browser.newPage(); await consumerUser.login(userPage); await redirectToHomePage(userPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permission.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permission.spec.ts index 35da1a625ba3..d89366098b86 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permission.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permission.spec.ts @@ -78,6 +78,7 @@ const test = base.extend<{ await afterAction(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DataProductPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DataProductPermissions.spec.ts index 15c6d0612704..c8453fab9d61 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DataProductPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DataProductPermissions.spec.ts @@ -39,6 +39,7 @@ const test = base.extend<{ testUserPage: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); try { await adminUser.login(adminPage); @@ -48,6 +49,7 @@ const test = base.extend<{ } }, testUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const userPage = await browser.newPage(); try { await testUser.login(userPage); @@ -226,6 +228,7 @@ test.describe('Data Product Permissions', () => { } ); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const expertPage = await browser.newPage(); await expertUser.login(expertPage); await redirectToHomePage(expertPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DomainPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DomainPermissions.spec.ts index 559cce0495df..a823e8af5a24 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DomainPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/DomainPermissions.spec.ts @@ -32,6 +32,7 @@ const test = base.extend<{ testUserPage: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); try { await adminUser.login(adminPage); @@ -41,6 +42,7 @@ const test = base.extend<{ } }, testUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await testUser.login(page); @@ -72,6 +74,7 @@ test('Domain allow operations', async ({ testUserPage, browser }) => { test.slow(true); // Setup allow permissions + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); const { apiContext } = await getApiContext(page); @@ -143,6 +146,7 @@ test('Domain deny operations', async ({ testUserPage, browser }) => { test.slow(true); // Setup deny permissions + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); const { apiContext } = await getApiContext(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts index c286ff83828b..24b5bcf2d935 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts @@ -106,6 +106,7 @@ const test = base.extend<{ testUserPage: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); try { await adminUser.login(adminPage); @@ -115,6 +116,7 @@ const test = base.extend<{ } }, testUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await testUser.login(page); @@ -156,6 +158,7 @@ const headerPermTest = base.extend<{ denyAllPage: Page; }>({ editAllPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await editAllUser.login(page); @@ -165,6 +168,7 @@ const headerPermTest = base.extend<{ } }, specificEditsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await specificEditsUser.login(page); @@ -267,6 +271,7 @@ Object.entries(entityConfig).forEach(([, config]) => { // Allow permissions tests test.describe('Allow permissions', () => { test.beforeAll('Initialize allow permissions', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await initializePermissions(page, 'allow', ALL_OPERATIONS); @@ -303,6 +308,7 @@ Object.entries(entityConfig).forEach(([, config]) => { } test.afterAll('Cleanup allow permissions', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); const { apiContext } = await getApiContext(page); @@ -314,6 +320,7 @@ Object.entries(entityConfig).forEach(([, config]) => { // Deny permissions tests test.describe('Deny permissions', () => { test.beforeAll('Initialize deny permissions', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await initializePermissions(page, 'deny', ALL_OPERATIONS); @@ -350,6 +357,7 @@ Object.entries(entityConfig).forEach(([, config]) => { } test.afterAll('Cleanup deny permissions', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); const { apiContext } = await getApiContext(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/GlossaryPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/GlossaryPermissions.spec.ts index 3edcb82f9206..cacc04d4f500 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/GlossaryPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/GlossaryPermissions.spec.ts @@ -33,6 +33,7 @@ const test = base.extend<{ testUserPage: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); try { await adminUser.login(adminPage); @@ -42,6 +43,7 @@ const test = base.extend<{ } }, testUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); try { await testUser.login(page); @@ -412,6 +414,7 @@ test.describe('Glossary Permissions', () => { }, }); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const teamUserPage = await browser.newPage(); try { await teamUser.login(teamUserPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SettingsNavigationPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SettingsNavigationPage.spec.ts index c1ea59a223b8..8edae83440df 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SettingsNavigationPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SettingsNavigationPage.spec.ts @@ -26,6 +26,7 @@ const persona = new PersonaClass(); const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TagsSuggestion.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TagsSuggestion.spec.ts index c91a79c4ea91..eda967a04115 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TagsSuggestion.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TagsSuggestion.spec.ts @@ -176,6 +176,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -235,6 +236,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -300,6 +302,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -361,6 +364,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -424,6 +428,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -485,6 +490,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); @@ -560,6 +566,7 @@ describeTagTaskWorkflowsInParallel( }); createdTaskIds.push(task.id); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const reviewerPage = await browser.newPage(); try { await reviewerUser.login(reviewerPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks.spec.ts index bb251b8fbdcf..82fe8af26e30 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks.spec.ts @@ -95,6 +95,7 @@ test.describe('Task Workflow Tests', () => { await requestDescBtn.click(); // Wait for task form page to load (navigates to separate page, not modal) + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="form-container"]', { state: 'visible', }); @@ -123,6 +124,7 @@ test.describe('Task Workflow Tests', () => { await requestDescBtn.click(); // Wait for task form page to load + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="form-container"]', { state: 'visible', }); @@ -151,6 +153,7 @@ test.describe('Task Workflow Tests', () => { await requestTagsBtn.click(); // Wait for task form page to load + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="form-container"]', { state: 'visible', }); @@ -199,6 +202,7 @@ test.describe('Task Workflow Tests', () => { }, }); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); @@ -270,6 +274,7 @@ test.describe('Task Workflow Tests', () => { const task = await taskResponse.json(); // Login as regular user (who is the assignee) + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await regularUser.login(page); @@ -307,6 +312,7 @@ test.describe('Task Workflow Tests', () => { await nonAssignee.create(apiContext); try { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await nonAssignee.login(page); @@ -451,6 +457,7 @@ test.describe('Task Workflow Tests', () => { }); expect(taskResponse.ok()).toBe(true); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await tableWithOwner.visitEntityPage(page); @@ -500,6 +507,7 @@ test.describe('Task Workflow Tests', () => { }, }); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await regularUser.login(page); await redirectToHomePage(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts index afb84c98ebeb..849f48d09e14 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/ActivityFeed.spec.ts @@ -694,6 +694,7 @@ test.describe('Activity Feed - Real-time Updates', () => { expect(patchResponse.ok()).toBe(true); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await table.visitEntityPage(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskDashboardEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskDashboardEntity.spec.ts index ae591bb375e0..4da4d660a3cb 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskDashboardEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskDashboardEntity.spec.ts @@ -469,6 +469,7 @@ test.describe('Dashboard Task UI Flow', () => { await waitForAllLoadersToDisappear(page); // Wait for the activity feed content to load + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page .waitForSelector('[data-testid="activity-feed-tab"]', { state: 'visible', diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskNavigation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskNavigation.spec.ts index 879a7f93c600..389278fa7674 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskNavigation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskNavigation.spec.ts @@ -506,6 +506,7 @@ test.describe('Task Navigation - URL Validation', () => { }); const task = await taskResponse.json(); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskPermissions.spec.ts index 727f39b4bbab..c4a97c3355d4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskPermissions.spec.ts @@ -42,6 +42,7 @@ const createTaskAsAdmin = async ( }; const getUserApiContext = async (browser: Browser, user: UserClass) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); const { apiContext, afterAction } = await getApiContext(page); @@ -619,6 +620,7 @@ test.describe('Task Permissions - Task Creator', () => { await afterAction(); // Try to close as creator user (who did NOT create this task) + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await creatorUser.login(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TeamActivity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TeamActivity.spec.ts index 2fdcc3964725..5b5852b0ef3f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TeamActivity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TeamActivity.spec.ts @@ -96,6 +96,7 @@ test.describe('Team Activity - Membership Changes', () => { await afterAction(); // Login as existing team member + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await teamMember.login(page); await redirectToHomePage(page); @@ -143,6 +144,7 @@ test.describe('Team Activity - Membership Changes', () => { await afterAction(); // Login as existing team member and check feed + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await teamMember.login(page); await redirectToHomePage(page); @@ -239,6 +241,7 @@ test.describe('Team Activity - Team Owned Entities', () => { await afterAction(); // Login as team member and check they can see the change + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await teamMember1.login(page); await redirectToHomePage(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamSubscriptions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamSubscriptions.spec.ts index f1589e38e0de..a31771131fe7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamSubscriptions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamSubscriptions.spec.ts @@ -353,6 +353,7 @@ test.describe( test('team owner can manage subscriptions', async ({ browser }) => { test.slow(); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const ownerPage = await browser.newPage(); try { @@ -436,6 +437,7 @@ test.describe( await afterAction(); await test.step('Verify member cannot edit subscriptions', async () => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const memberPage = await browser.newPage(); await memberUser.login(memberPage); await redirectToHomePage(memberPage); @@ -487,6 +489,7 @@ test.describe( test('data consumer cannot edit team subscriptions', async ({ browser, }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await test.step('Login as data consumer and visit team page', async () => { @@ -536,6 +539,7 @@ test.describe( }); test('data steward cannot edit team subscriptions', async ({ browser }) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await test.step('Login as data steward and visit team page', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts index 15965848cfe0..0b7263326ed1 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts @@ -30,12 +30,14 @@ const test = base.extend<{ user2Page: Page; }>({ user1Page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await userWithOwnerPermission.login(page); await use(page); await page.close(); }, user2Page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await userWithTagPermission.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeLandingPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeLandingPage.spec.ts index 40be28361c91..592a9c1308eb 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeLandingPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeLandingPage.spec.ts @@ -37,6 +37,7 @@ const persona2 = new PersonaClass(); const test = base.extend<{ adminPage: Page; userPage: Page }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeWidgets.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeWidgets.spec.ts index ab7627240eaf..1ff4dbf6b8e9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeWidgets.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/CustomizeWidgets.spec.ts @@ -54,6 +54,7 @@ let testDataProducts: DataProduct[] = []; const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await adminUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/IngestionBot.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/IngestionBot.spec.ts index 5e9b0c8519e4..ce7625584ab0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/IngestionBot.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/IngestionBot.spec.ts @@ -41,6 +41,7 @@ const test = base.extend<{ ingestionBotPage: async ({ browser }, use) => { const { apiContext, afterAction } = await performAdminLogin(browser); + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await page.goto('/'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Metric.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Metric.spec.ts index 37b3a1322387..72527ff1b16f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Metric.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Metric.spec.ts @@ -42,6 +42,7 @@ const adminUser = new UserClass(); const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/NotificationAlerts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/NotificationAlerts.spec.ts index 13300efaf755..08174fc38936 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/NotificationAlerts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/NotificationAlerts.spec.ts @@ -70,18 +70,21 @@ const test = base.extend<{ userWithoutPermissionsPage: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await admin.login(page); await use(page); await page.close(); }, userWithPermissionsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user1.login(page); await use(page); await page.close(); }, userWithoutPermissionsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user2.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ObservabilityAlerts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ObservabilityAlerts.spec.ts index 226ef85b7be3..c66def55e16a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ObservabilityAlerts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ObservabilityAlerts.spec.ts @@ -68,12 +68,14 @@ const test = base.extend<{ userWithoutPermissionsPage: Page; }>({ userWithPermissionsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user1.login(page); await use(page); await page.close(); }, userWithoutPermissionsPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user2.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/SearchRBAC.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/SearchRBAC.spec.ts index 52e24321e61f..6ca285639cda 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/SearchRBAC.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/SearchRBAC.spec.ts @@ -28,6 +28,7 @@ import { } from '../../utils/searchRBAC'; const newStrippedPage = async (browser: Browser) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await disableEtagConditionalReads(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ServiceCreationPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ServiceCreationPermissions.spec.ts index 66b5dbc2ecb7..8cdcf7cfb964 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ServiceCreationPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ServiceCreationPermissions.spec.ts @@ -66,24 +66,28 @@ const test = base.extend<{ pipelineEditPage: Page; }>({ serviceOwnerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await serviceOwnerUser.login(page); await use(page); await page.close(); }, anotherUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await anotherUser.login(page); await use(page); await page.close(); }, pipelineTriggerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await pipelineTriggerUser.login(page); await use(page); await page.close(); }, pipelineEditPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await pipelineEditUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Http2/SmokeH2.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Http2/SmokeH2.spec.ts index dcc0ded42758..c6c4b70e71c2 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Http2/SmokeH2.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Http2/SmokeH2.spec.ts @@ -34,6 +34,7 @@ type ResponseRecord = { contentEncoding: string | undefined; }; +// eslint-disable-next-line playwright/no-skipped-test -- intentionally skipped test.skip( process.env.PW_PROTOCOL !== 'h2', 'Opt-in: requires PW_PROTOCOL=h2 and the h2 server config.' diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/PageObject/Explore/CustomPropertiesPageObject.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/PageObject/Explore/CustomPropertiesPageObject.ts index 7edeaa62c960..8f9dedf0e32a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/PageObject/Explore/CustomPropertiesPageObject.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/PageObject/Explore/CustomPropertiesPageObject.ts @@ -117,7 +117,7 @@ export class CustomPropertiesPageObject extends RightPanelBase { */ async verifyPropertyValue( propertyName: string, - expectedValue: any + expectedValue: unknown ): Promise { const propertyCard = this.page.getByTestId(propertyName); await propertyCard.waitFor({ state: 'visible' }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts index 0347c083f812..90579bead2c3 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts @@ -2258,6 +2258,7 @@ entitiesWithDataContracts.forEach((EntityClass) => { const testPersona = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplacePermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplacePermissions.spec.ts index f35f1a159f08..8e0e7f38f927 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplacePermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplacePermissions.spec.ts @@ -38,6 +38,7 @@ const test = base.extend<{ await page.close(); }, consumerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await consumerUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProducts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProducts.spec.ts index 542849336f37..c4fc8ef2816b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProducts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataProducts.spec.ts @@ -62,6 +62,7 @@ const test = base.extend<{ await afterAction(); }, userPage: async ({ browser }, setPage) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await setPage(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts index 0539037ea7d5..bccaab03a69e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts @@ -337,6 +337,7 @@ test.describe( browser, }) => { // Admin: Customize Table detail page for persona + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await redirectToHomePage(adminPage); @@ -403,6 +404,7 @@ test.describe( await adminPage.close(); // User: Validate long description in custom tab + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const userPage = await browser.newPage(); await regularUser.login(userPage); await redirectToHomePage(userPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts index 71c4356aa44b..e4921bf332f9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts @@ -110,6 +110,7 @@ const test = base.extend<{ await afterAction(); }, userPage: async ({ browser }, setPage) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await setPage(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataConsumer.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataConsumer.spec.ts index 6400826e63a8..026762ef21ca 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataConsumer.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataConsumer.spec.ts @@ -59,6 +59,7 @@ const test = base.extend<{ page: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataSteward.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataSteward.spec.ts index 48f578b52739..178f9da2b625 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataSteward.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityDataSteward.spec.ts @@ -58,6 +58,7 @@ const test = base.extend<{ page: Page; }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityHeaderBreadcrumb.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityHeaderBreadcrumb.spec.ts index f9cc72bb83e5..fce62019f5f8 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityHeaderBreadcrumb.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/EntityHeaderBreadcrumb.spec.ts @@ -62,6 +62,7 @@ const adminUser = new UserClass(); const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts index 27120b4df830..c456e3710396 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts @@ -1375,6 +1375,7 @@ test.describe('Glossary tests', () => { browser, }) => { // Create page and set up mocked WebSocket BEFORE navigation + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await setupMockedWebSocket(page); @@ -1462,6 +1463,7 @@ test.describe('Glossary tests', () => { test.slow(true); // Create page and set up mocked WebSocket BEFORE navigation + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await setupMockedWebSocket(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LiveIndexingTab.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LiveIndexingTab.spec.ts index a931fd8447d1..723685872d90 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LiveIndexingTab.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LiveIndexingTab.spec.ts @@ -50,6 +50,7 @@ test.describe( () => { test.beforeAll('Seed retry queue records', async ({ browser }) => { const { apiContext, afterAction } = await getApiContext( + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern await browser.newPage() ); @@ -68,6 +69,7 @@ test.describe( test.afterAll('Clean up retry queue records', async ({ browser }) => { const { apiContext, afterAction } = await getApiContext( + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern await browser.newPage() ); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ODCSImportExportPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ODCSImportExportPermissions.spec.ts index 437a30a7110a..07b16211ab88 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ODCSImportExportPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ODCSImportExportPermissions.spec.ts @@ -75,12 +75,14 @@ const test = base.extend<{ dataContractViewPage: Page; }>({ dataContractEditPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataContractEditUser.login(page); await use(page); await page.close(); }, dataContractViewPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataContractViewUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ProfilerConfigurationPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ProfilerConfigurationPage.spec.ts index 4b497e2d4615..0bc82ed898e9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ProfilerConfigurationPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ProfilerConfigurationPage.spec.ts @@ -32,12 +32,14 @@ const admin = new AdminClass(); // Create 2 page and authenticate 1 with admin and another with normal user const test = base.extend<{ adminPage: Page; userPage: Page }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await admin.login(page); await use(page); await page.close(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts index f90ace6b9e76..1fec5f03b6bc 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts @@ -37,6 +37,7 @@ let adminUser: AdminClass; // toast notifications for search settings update in tests. const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts index 792168f4d770..051c4e44f78f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/ServiceEntity.spec.ts @@ -65,6 +65,7 @@ const adminUser = new UserClass(); const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tag.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tag.spec.ts index 90a4081b399f..9a3932a28537 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tag.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tag.spec.ts @@ -54,24 +54,28 @@ const test = base.extend<{ limitedAccessPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); await adminPage.close(); }, dataConsumerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataConsumerUser.login(page); await use(page); await page.close(); }, dataStewardPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataStewardUser.login(page); await use(page); await page.close(); }, limitedAccessPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await limitedAccessUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts index cd662f61dc73..d11a77343a45 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TasksUIFlow.spec.ts @@ -72,6 +72,7 @@ const createDescriptionTaskViaUI = async ( ) => { await page.getByTestId('request-description').click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('#title', { state: 'visible' }); expect(await page.locator('#title').inputValue()).toContain( @@ -100,6 +101,7 @@ const createTagTaskViaUI = async ( ) => { await page.getByTestId('request-entity-tags').click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('#title', { state: 'visible' }); expect(await page.locator('#title').inputValue()).toContain( @@ -321,6 +323,7 @@ test.describe('Task Workflow - Table Column Tasks', () => { }); await test.step('Fill task form and submit', async () => { + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('#title', { state: 'visible' }); expect(await page.locator('#title').inputValue()).toContain('columns'); @@ -370,6 +373,7 @@ test.describe('Task Workflow - Table Column Tasks', () => { }); await test.step('Fill tag task form and submit', async () => { + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('#title', { state: 'visible' }); expect(await page.locator('#title').inputValue()).toContain('columns'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Teams.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Teams.spec.ts index 41525bc4d7bc..ee14f0130246 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Teams.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Teams.spec.ts @@ -130,24 +130,28 @@ const test = base.extend<{ scopedUserPage: Page; }>({ editOnlyUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await editOnlyUser.login(page); await use(page); await page.close(); }, dataConsumerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataConsumerUser.login(page); await use(page); await page.close(); }, ownerUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await ownerUser.login(page); await use(page); await page.close(); }, scopedUserPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuite.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuite.spec.ts index 1a5bd6da8f11..cbb6f3ff3197 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuite.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuite.spec.ts @@ -206,6 +206,7 @@ test( .locator('[data-testid="test-suite-name"] input') .fill(NEW_TEST_SUITE.name); await page.locator(descriptionBox).fill(NEW_TEST_SUITE.description); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector( "[data-testid='test-case-selection-card'] [data-testid='loader']", { state: 'detached' } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuiteDetailsPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuiteDetailsPage.spec.ts index 587a2a59a1bd..977030d95be2 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuiteDetailsPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TestSuiteDetailsPage.spec.ts @@ -70,6 +70,7 @@ test( .locator('[data-testid="test-suite-name"] input') .fill(NEW_TEST_SUITE.name); await page.locator(descriptionBox).fill(NEW_TEST_SUITE.description); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector( "[data-testid='test-case-selection-card'] [data-testid='loader']", { state: 'detached' } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts index 2ffbc16ed83b..143f1e906e57 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts @@ -42,12 +42,14 @@ const test = base.extend<{ userPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await admin.login(page); await use(page); await page.close(); }, userPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await user1.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts index 93d8eb272f03..02a4f45b47df 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts @@ -118,18 +118,21 @@ const test = base.extend<{ dataStewardPage: Page; }>({ adminPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); await adminPage.close(); }, dataConsumerPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataConsumerUser.login(page); await use(page); await page.close(); }, dataStewardPage: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const page = await browser.newPage(); await dataStewardUser.login(page); await use(page); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts index 06cb72194694..1676781cb428 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts @@ -64,6 +64,7 @@ let entities: InstanceType<(typeof entityClasses)[number]>[]; const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/ServiceEntityVersionPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/ServiceEntityVersionPage.spec.ts index 454d14b62627..5f3df3ca2938 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/ServiceEntityVersionPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/ServiceEntityVersionPage.spec.ts @@ -86,6 +86,7 @@ const adminUser = new UserClass(); const test = base.extend<{ page: Page }>({ page: async ({ browser }, use) => { + // eslint-disable-next-line no-restricted-syntax -- existing multi-context test pattern const adminPage = await browser.newPage(); await adminUser.login(adminPage); await use(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts index 18814ef512c0..3bd37551d8a7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts @@ -95,7 +95,7 @@ export class EntityClass { return {}; } - public set(_data: any) { + public set(_data: unknown) { // handle in parent component } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/ContextCenterUtil.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/ContextCenterUtil.ts index 94b47ebc48f9..66a994ab8aaa 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/ContextCenterUtil.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/ContextCenterUtil.ts @@ -752,6 +752,7 @@ export const scrollHierarchyToNode = async ( if (lastNode === previousLastNode) { staleCount += 1; + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(1000); if (staleCount >= 5) { break; @@ -860,6 +861,7 @@ export const scrollListingToCard = async (page: Page, displayName: string) => { if (lastCard === previousLastCard) { staleCount += 1; + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(1000); if (staleCount >= 5) { break; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/KnowledgeCenter.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/KnowledgeCenter.ts index 52669788fb39..856869cbf015 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/KnowledgeCenter.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/KnowledgeCenter.ts @@ -112,6 +112,7 @@ export const updateTags = async ( await editTagBtn.click(); } + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="tag-selector"] input', { state: 'visible', }); @@ -148,6 +149,7 @@ export const updateDataAsset = async ( ); await page.getByTestId('add-data-assets-container').click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector( '[data-testid="asset-select-list"] > .ant-select-selector input', { state: 'visible' } @@ -166,6 +168,7 @@ export const updateDataAsset = async ( const response = await updateKnowledgePage; expect(response.status()).toBe(200); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector(`[data-testid="${dataAsset.entity.name}"]`, { state: 'visible', }); @@ -353,6 +356,7 @@ export const readArticleInHierarchy = async ( await hierarchyElement.hover(); await page.mouse.wheel(0, -9999); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(500); // Retry mechanism for pagination @@ -363,6 +367,7 @@ export const readArticleInHierarchy = async ( while (elementCount === 0 && retryCount < maxRetries) { await page.locator('[data-testid="knowledge-pages-hierarchy"]').hover(); await page.mouse.wheel(0, 500); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(500); // Create fresh locator and check if the article is now visible after this retry @@ -394,6 +399,7 @@ export const createMentionInConversation = async ( // Click on Conversations tab await page.getByRole('tab', { name: 'Conversations' }).click(); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="editor-wrapper"]'); // Create message with mention @@ -437,6 +443,7 @@ export const verifyNotificationAndClick = async ( await page.locator('[data-testid="task-notifications"]').click(); // Wait for notification dropdown to appear + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="notification-heading"]', { state: 'visible', }); @@ -535,11 +542,13 @@ export const createNewKnowledgePageArticle = async ( export const getEditor = async (page: Page, waitForWrapper = false) => { if (waitForWrapper) { await waitForAllLoadersToDisappear(page); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('#block-editor-wrapper', { state: 'visible', }); } + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.ProseMirror[contenteditable="true"]', { state: 'visible', }); @@ -654,6 +663,7 @@ export const clearCodeFormatting = async ( if (isInCode) { await page.keyboard.press(SHORTCUTS.selectWord); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.menu-wrapper', { state: 'visible', }); @@ -678,6 +688,7 @@ export const createLink = async ( await selectLastWord(page, linkText.split(' ').length, editor); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('.menu-wrapper', { state: 'visible', }); @@ -838,6 +849,7 @@ export const toggleTask = async ( const taskItem = editor.locator('li').filter({ hasText: taskText }); const checkbox = taskItem.locator('input[type="checkbox"]'); await checkbox.click(); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(100); }; @@ -846,6 +858,7 @@ export const createCallout = async ( text: string ): Promise => { await executeSlashCommand(page, SLASH_COMMANDS.callout); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(200); await page.keyboard.type(text); }; @@ -862,6 +875,7 @@ export const verifyCallout = async ( export const createTable = async (page: Page): Promise => { await executeSlashCommand(page, SLASH_COMMANDS.table); + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait await page.waitForTimeout(300); }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/activityFeed.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/activityFeed.ts index 5cd7a92a7a73..8e870b2792d3 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/activityFeed.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/activityFeed.ts @@ -233,6 +233,7 @@ export const reactOnActivity = async ( export const navigateToActivityFeedTab = async (page: Page) => { await page.getByTestId('activity_feed').click(); await waitForPageLoaded(page); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts index 837127e719d5..2dca8c2db0bd 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts @@ -607,6 +607,7 @@ export const verifyWidgetEntityNavigation = async ( return false; }), + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait page.waitForTimeout(10000), ]); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPermissionUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPermissionUtils.ts index 46e8a56cffed..e74bd375ef52 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPermissionUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPermissionUtils.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import { expect, Page } from '@playwright/test'; +import { Browser, expect, Page } from '@playwright/test'; import { ContainerClass } from '../support/entity/ContainerClass'; import { DashboardClass } from '../support/entity/DashboardClass'; import { DashboardDataModelClass } from '../support/entity/DashboardDataModelClass'; @@ -676,7 +676,7 @@ export const serviceEntityConfig = { // Function to create custom properties for different entity types export const createCustomPropertyForEntity = async ( - browser: any, + browser: Browser, entityType: string, customPropertyName: string, adminUser: UserClass 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 144c6e3694db..216ad95be4ef 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts @@ -1970,6 +1970,7 @@ export const openGlossaryTagSelector = async (page: Page) => { await page.click( '[data-testid="entity-right-panel"] [data-testid="glossary-container"] [data-testid="add-tag"]' ); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[role="presentation"]', { state: 'visible' }); }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/nestedColumnUpdatesUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/nestedColumnUpdatesUtils.ts index 3faa7c785355..d0d048fb17f0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/nestedColumnUpdatesUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/nestedColumnUpdatesUtils.ts @@ -27,6 +27,7 @@ type EntityTypes = InstanceType< >; export const getNestedColumnDetails = (type: string, data: EntityTypes) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- response shape varies across entity types const entityData = data.entityResponseData as any; const fqn = entityData.fullyQualifiedName; @@ -996,6 +997,7 @@ export const createWorksheetEntity = async (apiContext: APIRequestContext) => { entity, service, deleteService: () => + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- coercing empty object to delete-response contract worksheetClass.delete(apiContext).then(() => ({} as any)), visitPage: async (page: Page) => { await worksheetClass.visitEntityPage(page); @@ -1064,7 +1066,9 @@ export const createFileEntity = async (apiContext: APIRequestContext) => { return { entity, service, - deleteService: () => fileClass.delete(apiContext).then(() => ({} as any)), + deleteService: () => + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- coercing empty object to delete-response contract + fileClass.delete(apiContext).then(() => ({} as any)), visitPage: async (page: Page) => { await fileClass.visitEntityPage(page); await page.getByTestId('schema').click(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/reviewerWorkflow.utils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/reviewerWorkflow.utils.ts index 308250455f9c..76b2687007d9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/reviewerWorkflow.utils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/reviewerWorkflow.utils.ts @@ -215,6 +215,7 @@ export const addReviewerToEntity = async ( await page.getByTestId('Add').click(); } + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector( '[data-testid="select-owner-tabs"] [data-testid="loader"]', { state: 'detached' } @@ -228,6 +229,7 @@ export const addReviewerToEntity = async ( ); await page.fill('[data-testid="owner-select-users-search-bar"]', name); await searchOwner; + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector( '[data-testid="select-owner-tabs"] [data-testid="loader"]', { state: 'detached' } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/sso.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/sso.ts index 6cac0c5cf1fd..788d97b64e3b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/sso.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/sso.ts @@ -28,7 +28,7 @@ export interface SSOConfig { enableSelfSignup: boolean; clientType?: string; secret?: string; - oidcConfiguration?: Record; + oidcConfiguration?: Record; }; authorizerConfiguration: { className: string; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts index 594c6c8892d3..62df6ae5bab9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts @@ -80,6 +80,7 @@ const selectTagSuggestion = async ({ logTaskDebug('selectTagSuggestion:start', searchText, tagTestId); if (!(await tagsInput.isVisible().catch(() => false))) { + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await tagSelector.click({ force: true }).catch(() => undefined); } @@ -142,11 +143,13 @@ const clickDropdownMenuItem = async ({ if (await isMenuItemVisible()) { if (await roleMenuItem.isVisible().catch(() => false)) { + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await roleMenuItem.click({ force: true }); return; } + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await cssMenuItem.click({ force: true }); return; @@ -172,6 +175,7 @@ const clickDropdownMenuItem = async ({ for (let attempt = 0; attempt < 3; attempt++) { logTaskDebug('clickDropdownMenuItem:openAttempt', attempt + 1); + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await resolvedTrigger.click({ force: true }).catch(() => undefined); if (await waitForMenuItem()) { @@ -191,6 +195,7 @@ const clickDropdownMenuItem = async ({ break; } + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await fallbackTrigger.click({ force: true }).catch(() => undefined); if (await waitForMenuItem()) { @@ -199,12 +204,14 @@ const clickDropdownMenuItem = async ({ } if (await roleMenuItem.isVisible().catch(() => false)) { + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await roleMenuItem.click({ force: true }); return; } await expect(cssMenuItem).toBeVisible(); + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await cssMenuItem.click({ force: true }); }; @@ -251,6 +258,7 @@ export const buildTaskRoute = ({ export const openTaskForm = async (page: Page, route: string) => { await page.goto(route); + // eslint-disable-next-line playwright/no-wait-for-selector -- waiting on dynamic element await page.waitForSelector('[data-testid="form-container"]', { state: 'visible', }); @@ -613,6 +621,7 @@ export const addCommentToTask = async (page: Page, comment: string) => { await expect(commentInput).toBeVisible({ timeout: 5000 }); await commentInput.scrollIntoViewIfNeeded().catch(() => undefined); logTaskDebug('addCommentToTask:openingEditor'); + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await commentInput.click({ force: true }).catch(() => undefined); const editorAppearedAfterClick = await editor @@ -627,6 +636,7 @@ export const addCommentToTask = async (page: Page, comment: string) => { await expect(editor).toBeVisible({ timeout: 15000 }); logTaskDebug('addCommentToTask:editorVisible'); + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await editor.click({ force: true }); await editor.type(comment); logTaskDebug('addCommentToTask:commentEntered'); @@ -685,6 +695,7 @@ export const closeTaskFromDetails = async (page: Page) => { if (primaryLabel?.match(/reject|decline|close/i)) { const taskActionResponse = waitForTaskActionResponse(page); + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await workflowPrimaryButton.click({ force: true }); await taskActionResponse; await waitForPageLoaded(page); @@ -755,6 +766,7 @@ export const approveTaskFromDetails = async (page: Page) => { const clickAndWait = async (button: Locator) => { const taskActionResponse = waitForTaskActionResponse(page); await button.scrollIntoViewIfNeeded().catch(() => undefined); + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await button.click({ force: true }); await visibleTaskModal diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts index a0a765c0d847..80111db04077 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts @@ -256,8 +256,10 @@ export const addTeamHierarchy = async ( // Fetching the add button and clicking on it if (index && index > 0) { + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await page.click('[data-testid="add-placeholder-button"]', { force: true }); } else { + // eslint-disable-next-line playwright/no-force-option -- overlay/animation workaround await page.click('[data-testid="add-team"]', { force: true }); } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/widgetFilters.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/widgetFilters.ts index fa406977ee9e..234ce8c479bf 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/widgetFilters.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/widgetFilters.ts @@ -48,6 +48,7 @@ export const verifyActivityFeedFilters = async ( response.url().includes('/api/v1/activities')) && response.url().includes('filterType=OWNER') ), + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait page.waitForTimeout(5000), ]); await page.getByRole('menuitem', { name: 'My Data' }).click(); @@ -65,6 +66,7 @@ export const verifyActivityFeedFilters = async ( response.url().includes('/api/v1/activities')) && response.url().includes('filterType=FOLLOWS') ), + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait page.waitForTimeout(5000), ]); await page.getByRole('menuitem', { name: 'Following' }).click(); @@ -81,6 +83,7 @@ export const verifyActivityFeedFilters = async ( response.url().includes('/api/v1/feed') || response.url().includes('/api/v1/activities') ), + // eslint-disable-next-line playwright/no-wait-for-timeout -- deliberate stabilization wait page.waitForTimeout(5000), ]); await page.getByRole('menuitem', { name: 'All Activity' }).click(); diff --git a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx index b4bf6db72004..2181d1429605 100644 --- a/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/AppRoot.tsx @@ -64,6 +64,7 @@ const AppRoot: FC = () => { useEffect(() => { fetchApplicationConfig(); initializeAuthState(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, []); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.test.tsx index 741f6711a6f0..c41c965b6bde 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.test.tsx @@ -29,7 +29,9 @@ const mockApiEndpointDetails: APIEndpoint = { version: 0.1, updatedAt: 1234567890, updatedBy: 'test-user', + // eslint-disable-next-line sonarjs/no-clear-text-protocols -- test fixture URL, not a real network call href: 'http://test.com', + // eslint-disable-next-line sonarjs/no-clear-text-protocols -- test fixture URL, not a real network call endpointURL: 'http://api.test.com/endpoint', requestMethod: APIRequestMethod.Get, service: { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.tsx b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.tsx index 495293826b7d..8dde9049657e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointDetails/APIEndpointDetails.tsx @@ -158,6 +158,7 @@ const APIEndpointDetails: React.FC = ({ }; await onApiEndpointUpdate(updatedApiEndpointDetails, 'owners'); }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [owners] ); @@ -175,6 +176,7 @@ const APIEndpointDetails: React.FC = ({ setFeedCount(data); }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const getEntityFeedCount = () => getFeedCounts( EntityType.API_ENDPOINT, @@ -230,6 +232,7 @@ const APIEndpointDetails: React.FC = ({ useEffect(() => { fetchTaskCounts(); fetchActivityCount(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [apiEndpointPermissions, decodedApiEndpointFqn]); const tabs = useMemo(() => { @@ -287,6 +290,7 @@ const APIEndpointDetails: React.FC = ({ const isExpandViewSupported = useMemo( () => checkIfExpandViewSupported(tabs[0], activeTab, PageType.APIEndpoint), + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [tabs[0], activeTab] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointSchema/APIEndpointSchema.tsx b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointSchema/APIEndpointSchema.tsx index a752fde49f5e..eb592cfce07a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointSchema/APIEndpointSchema.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointSchema/APIEndpointSchema.tsx @@ -468,6 +468,7 @@ const APIEndpointSchema: FC = ({ filteredValue: tagFilterState[TABLE_COLUMNS_KEYS.GLOSSARY] ?? null, }, ], + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [ apiEndpointDetails, editFieldDescription, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointVersion/APIEndpointVersion.tsx b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointVersion/APIEndpointVersion.tsx index 3a17f81bad38..f4fe027cac92 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointVersion/APIEndpointVersion.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/APIEndpoint/APIEndpointVersion/APIEndpointVersion.tsx @@ -181,6 +181,7 @@ const APIEndpointVersion: FC = ({ ), }, ], + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [description, currentVersionData, viewCustomPropertiesPermission, tags] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.test.tsx index 85663d9320c2..c8ecdeeaeb41 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.test.tsx @@ -20,6 +20,8 @@ import { import { MemoryRouter } from 'react-router-dom'; import FeedCardBody from './FeedCardBody'; +const SAVE_BUTTON = 'save-button'; + const mockCancel = jest.fn(); const mockUpdate = jest.fn(); @@ -78,7 +80,7 @@ describe('Test FeedCardBody component', () => { const cancelButton = await findByTestId(container, 'cancel-button'); - const saveButton = await findByTestId(container, 'save-button'); + const saveButton = await findByTestId(container, SAVE_BUTTON); expect(editor).toBeInTheDocument(); @@ -118,7 +120,7 @@ describe('Test FeedCardBody component', () => { const editor = await findByTestId(container, 'editor'); - const saveButton = await findByTestId(container, 'save-button'); + const saveButton = await findByTestId(container, SAVE_BUTTON); expect(editor).toBeInTheDocument(); @@ -139,7 +141,7 @@ describe('Test FeedCardBody component', () => { const editor = await findByTestId(container, 'editor'); - const saveButton = await findByTestId(container, 'save-button'); + const saveButton = await findByTestId(container, SAVE_BUTTON); expect(editor).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.tsx index 3f937915947e..c5f3a35a5c88 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBody.tsx @@ -100,6 +100,7 @@ const FeedCardBody: FC = ({ markdown={getFrontEndFormat(postMessage)} /> ), + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [isEditPost, message, postMessage] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBodyNew.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBodyNew.tsx index 5cb320f53d1d..6b5ca85b5c9c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBodyNew.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardBody/FeedCardBodyNew.tsx @@ -115,6 +115,7 @@ const FeedCardBodyNew = ({ return MarkdownToHTMLConverter.makeHtml(getFrontEndFormat(defaultMessage)); }; + // eslint-disable-next-line sonarjs/cognitive-complexity, sonarjs/cyclomatic-complexity -- preserve behavior const feedBodyStyleCardsRender = useMemo(() => { if (isActivityEvent && activity) { const eventType = activity.eventType; @@ -185,6 +186,7 @@ const FeedCardBodyNew = ({ markdown={getFrontEndFormat(feed?.message ?? message)} /> ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [ isPost, message, @@ -234,6 +236,7 @@ const FeedCardBodyNew = ({ } return feedBodyStyleCardsRender; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [isEditPost, message, feedBodyStyleCardsRender]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.test.tsx index 2b5eae67b838..01596bd89f2d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.test.tsx @@ -20,6 +20,10 @@ import { import { MemoryRouter } from 'react-router-dom'; import FeedCardFooter from './FeedCardFooter'; +const REPLIED_USER = 'replied-user'; +const REPLY_COUNT = 'reply-count'; +const LAST_REPLY = 'last-reply'; + jest.mock('../../../../utils/FeedUtilsPure', () => ({ getReplyText: jest.fn(), })); @@ -27,7 +31,7 @@ jest.mock('../../../../utils/FeedUtilsPure', () => ({ jest.mock('../../../common/ProfilePicture/ProfilePicture', () => { return jest .fn() - .mockReturnValue(

ProfilePicture

); + .mockReturnValue(

ProfilePicture

); }); const mockFeedCardFooterPorps = { @@ -48,7 +52,7 @@ describe('Test FeedCardFooter component', () => { } ); - const replyCount = await findByTestId(container, 'reply-count'); + const replyCount = await findByTestId(container, REPLY_COUNT); expect(replyCount).toBeInTheDocument(); }); @@ -61,9 +65,9 @@ describe('Test FeedCardFooter component', () => { } ); - const repliedUsers = queryAllByTestId(container, 'replied-user'); - const replyCount = queryByTestId(container, 'reply-count'); - const lastReply = queryByTestId(container, 'last-reply'); + const repliedUsers = queryAllByTestId(container, REPLIED_USER); + const replyCount = queryByTestId(container, REPLY_COUNT); + const lastReply = queryByTestId(container, LAST_REPLY); expect(repliedUsers).toHaveLength(0); expect(replyCount).not.toBeInTheDocument(); @@ -78,9 +82,9 @@ describe('Test FeedCardFooter component', () => { } ); - const repliedUsers = queryAllByTestId(container, 'replied-user'); - const replyCount = queryByTestId(container, 'reply-count'); - const lastReply = queryByTestId(container, 'last-reply'); + const repliedUsers = queryAllByTestId(container, REPLIED_USER); + const replyCount = queryByTestId(container, REPLY_COUNT); + const lastReply = queryByTestId(container, LAST_REPLY); expect(repliedUsers).toHaveLength(0); expect(replyCount).not.toBeInTheDocument(); @@ -95,9 +99,9 @@ describe('Test FeedCardFooter component', () => { } ); - const repliedUsers = queryAllByTestId(container, 'replied-user'); - const replyCount = queryByTestId(container, 'reply-count'); - const lastReply = queryByTestId(container, 'last-reply'); + const repliedUsers = queryAllByTestId(container, REPLIED_USER); + const replyCount = queryByTestId(container, REPLY_COUNT); + const lastReply = queryByTestId(container, LAST_REPLY); expect(repliedUsers).toHaveLength(0); expect(replyCount).not.toBeInTheDocument(); @@ -116,8 +120,8 @@ describe('Test FeedCardFooter component', () => { } ); - const replyCount = queryByTestId(container, 'reply-count'); - const lastReply = queryByTestId(container, 'last-reply'); + const replyCount = queryByTestId(container, REPLY_COUNT); + const lastReply = queryByTestId(container, LAST_REPLY); expect(replyCount).toBeInTheDocument(); expect(lastReply).not.toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.tsx index 3dfbc6c616ae..888049855662 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardFooter/FeedCardFooter.tsx @@ -40,12 +40,12 @@ const FeedCardFooter: FC = ({ size="small" type="link" onClick={() => onThreadSelect?.(threadId as string)}> - {repliedUsers?.map((u, i) => ( + {repliedUsers?.map((u) => ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx index b873e34ea3d0..9167dc9345fe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx @@ -45,6 +45,7 @@ const FeedCardHeader: FC = ({ isEntityFeed, feedType, task, + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- inherent presentational branching }) => { const [, , user] = useUserProfile({ permission: true, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/ActivityFeedcardNew.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/ActivityFeedcardNew.component.tsx index 623e24cdac44..be080f38aa67 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/ActivityFeedcardNew.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/ActivityFeedcardNew.component.tsx @@ -82,7 +82,8 @@ const ActivityFeedCardNew = ({ isFeedWidget = false, isFullSizeWidget = false, onActivityClick, -}: ActivityFeedCardNewProps) => { +}: // eslint-disable-next-line sonarjs/cyclomatic-complexity -- top-level card component +ActivityFeedCardNewProps) => { const isActivityEvent = !isUndefined(activity); const { entityFQN, entityType } = useMemo(() => { @@ -151,6 +152,7 @@ const ActivityFeedCardNew = ({ entityCheck: !isUndefined(entityFQN) && !isUndefined(entityType), isUserOrTeam: [EntityType.USER, EntityType.TEAM].includes(entityType), }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [entityFQN, entityType, feed?.cardStyle]); const entityRef = feed?.entityRef ?? activity?.entity; @@ -208,6 +210,7 @@ const ActivityFeedCardNew = ({ ); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [ feed?.cardStyle, entityType, @@ -248,6 +251,7 @@ const ActivityFeedCardNew = ({ ) : null; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const closeFeedEditor = () => { setShowFeedEditor(false); }; @@ -514,6 +518,7 @@ const ActivityFeedCardNew = ({ onUpdate={onUpdate} /> + {/* eslint-disable-next-line sonarjs/expression-complexity -- preserve short-circuit order */} {(isPost || (!showThread && !isPost)) && !isActivityEvent && feed && ( { const updatedPost = { ...feed, message }; const patch = compare(feed, updatedPost); @@ -124,6 +125,7 @@ const CommentCard = ({ markdown={getFrontEndFormat(post.message)} /> ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [isEditPost, postMessage, handleSave]); return ( @@ -132,6 +134,7 @@ const CommentCard = ({ 'reply-card-border-bottom': !isLastReply, })} data-testid="feed-reply-card" + role="presentation" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/CommentCard.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/CommentCard.test.tsx index 258ea30301ec..a5206bcc1976 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/CommentCard.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/CommentCard.test.tsx @@ -20,6 +20,11 @@ import { } from '../../../generated/entity/feed/thread'; import CommentCard from './CommentCard.component'; +const MOCK_FEED_EDITOR = 'feed-editor'; +const MOCK_FEED_ACTIONS = 'feed-actions'; +const MOCK_EDIT_BUTTON = 'edit-button'; +const FEED_REPLY_CARD = 'feed-reply-card'; + const mockUpdateFeed = jest.fn(); jest.mock('../ActivityFeedProvider/ActivityFeedProvider', () => ({ @@ -65,8 +70,9 @@ jest.mock('../ActivityFeedCardV2/FeedCardFooter/FeedCardFooterNew', () => { jest.mock('../ActivityFeedEditor/ActivityFeedEditorNew', () => { return jest.fn(({ onSave, onTextChange }) => ( -
+
onTextChange(e.target.value)} /> @@ -81,8 +87,8 @@ jest.mock('../ActivityFeedEditor/ActivityFeedEditorNew', () => { jest.mock('../Shared/ActivityFeedActions', () => { return jest.fn(({ onEditPost }) => ( -
- @@ -106,6 +112,7 @@ const createMockPost = (from: string, message: string): Post => ({ const createMockFeed = (): Thread => ({ id: 'thread-123', + // eslint-disable-next-line sonarjs/no-clear-text-protocols -- test fixture URL, not a network call href: 'http://test', threadTs: 1234567890, about: '<#E::table::test>', @@ -151,7 +158,7 @@ describe('CommentCard', () => { it('should render comment card with post message', () => { renderCommentCard(); - expect(screen.getByTestId('feed-reply-card')).toBeInTheDocument(); + expect(screen.getByTestId(FEED_REPLY_CARD)).toBeInTheDocument(); expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( 'Test comment message' ); @@ -188,27 +195,27 @@ describe('CommentCard', () => { it('should show feed actions on hover', async () => { renderCommentCard(); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('feed-actions')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_FEED_ACTIONS)).toBeInTheDocument(); }); }); it('should hide feed actions when not hovering', async () => { renderCommentCard(); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('feed-actions')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_FEED_ACTIONS)).toBeInTheDocument(); }); fireEvent.mouseLeave(card); await waitFor(() => { - expect(screen.queryByTestId('feed-actions')).not.toBeInTheDocument(); + expect(screen.queryByTestId(MOCK_FEED_ACTIONS)).not.toBeInTheDocument(); }); }); }); @@ -217,17 +224,17 @@ describe('CommentCard', () => { it('should show editor when edit button is clicked', async () => { renderCommentCard(); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_EDIT_BUTTON)).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('edit-button')); + fireEvent.click(screen.getByTestId(MOCK_EDIT_BUTTON)); await waitFor(() => { - expect(screen.getByTestId('feed-editor')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_FEED_EDITOR)).toBeInTheDocument(); }); }); @@ -235,14 +242,14 @@ describe('CommentCard', () => { const closeFeedEditor = jest.fn(); renderCommentCard({ closeFeedEditor }); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_EDIT_BUTTON)).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('edit-button')); + fireEvent.click(screen.getByTestId(MOCK_EDIT_BUTTON)); expect(closeFeedEditor).toHaveBeenCalled(); }); @@ -250,17 +257,17 @@ describe('CommentCard', () => { it('should call updateFeed when saving edited message', async () => { renderCommentCard(); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_EDIT_BUTTON)).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('edit-button')); + fireEvent.click(screen.getByTestId(MOCK_EDIT_BUTTON)); await waitFor(() => { - expect(screen.getByTestId('feed-editor')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_FEED_EDITOR)).toBeInTheDocument(); }); fireEvent.change(screen.getByTestId('editor-input'), { @@ -277,23 +284,23 @@ describe('CommentCard', () => { it('should hide editor and show preview after update', async () => { renderCommentCard(); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_EDIT_BUTTON)).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('edit-button')); + fireEvent.click(screen.getByTestId(MOCK_EDIT_BUTTON)); await waitFor(() => { - expect(screen.getByTestId('feed-editor')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_FEED_EDITOR)).toBeInTheDocument(); }); fireEvent.click(screen.getByTestId('save-button')); await waitFor(() => { - expect(screen.queryByTestId('feed-editor')).not.toBeInTheDocument(); + expect(screen.queryByTestId(MOCK_FEED_EDITOR)).not.toBeInTheDocument(); expect(screen.getByTestId('rich-text-preview')).toBeInTheDocument(); }); }); @@ -303,7 +310,7 @@ describe('CommentCard', () => { it('should apply border class when not last reply', () => { renderCommentCard({ isLastReply: false }); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); expect(card).toHaveClass('reply-card-border-bottom'); }); @@ -311,7 +318,7 @@ describe('CommentCard', () => { it('should not apply border class when last reply', () => { renderCommentCard({ isLastReply: true }); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); expect(card).not.toHaveClass('reply-card-border-bottom'); }); @@ -321,23 +328,23 @@ describe('CommentCard', () => { it('should close edit mode when clicking outside', async () => { renderCommentCard(); - const card = screen.getByTestId('feed-reply-card'); + const card = screen.getByTestId(FEED_REPLY_CARD); fireEvent.mouseEnter(card); await waitFor(() => { - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_EDIT_BUTTON)).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('edit-button')); + fireEvent.click(screen.getByTestId(MOCK_EDIT_BUTTON)); await waitFor(() => { - expect(screen.getByTestId('feed-editor')).toBeInTheDocument(); + expect(screen.getByTestId(MOCK_FEED_EDITOR)).toBeInTheDocument(); }); fireEvent.mouseDown(document.body); await waitFor(() => { - expect(screen.queryByTestId('feed-editor')).not.toBeInTheDocument(); + expect(screen.queryByTestId(MOCK_FEED_EDITOR)).not.toBeInTheDocument(); }); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/DescriptionFeed/ActivityDescriptionFeed.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/DescriptionFeed/ActivityDescriptionFeed.test.tsx index ec4c9bd4f9f3..2b5a896de608 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/DescriptionFeed/ActivityDescriptionFeed.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/DescriptionFeed/ActivityDescriptionFeed.test.tsx @@ -15,6 +15,11 @@ import { render, screen } from '@testing-library/react'; import { ActivityEvent } from '../../../../../generated/entity/activity/activityEvent'; import ActivityDescriptionFeed from './ActivityDescriptionFeed'; +const RICH_TEXT_PREVIEW = 'rich-text-preview' as const; +const UPDATED_DESCRIPTION = 'Updated description' as const; +const NEW_DESCRIPTION = 'New description' as const; +const OLD_DESCRIPTION = 'Old description' as const; + jest.mock('../../../../../utils/FeedUtilsPure', () => ({ getFrontEndFormat: jest.fn((text) => text), })); @@ -35,7 +40,7 @@ const createMockActivity = ( actor: { id: 'user-1', type: 'user', name: 'testuser' }, entity: { id: 'entity-1', type: 'table', name: 'testTable' }, about: '<#E::table::test>', - summary: 'Updated description', + summary: UPDATED_DESCRIPTION, oldValue, newValue, }); @@ -47,71 +52,68 @@ describe('ActivityDescriptionFeed', () => { render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent( 'New description added' ); }); it('should render rich text preview when description is newly added', () => { - const activity = createMockActivity('', 'New description'); + const activity = createMockActivity('', NEW_DESCRIPTION); render(); - expect(screen.getByTestId('rich-text-preview')).toBeInTheDocument(); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toBeInTheDocument(); }); it('should render when oldValue is undefined', () => { - const activity = createMockActivity(undefined, 'New description'); + const activity = createMockActivity(undefined, NEW_DESCRIPTION); render(); - expect(screen.getByTestId('rich-text-preview')).toBeInTheDocument(); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toBeInTheDocument(); }); }); describe('Description Updated', () => { it('should display new description when description is updated', () => { - const activity = createMockActivity( - 'Old description', - 'Updated description' - ); + const activity = createMockActivity(OLD_DESCRIPTION, UPDATED_DESCRIPTION); render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( - 'Updated description' + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent( + UPDATED_DESCRIPTION ); }); it('should render when description is updated', () => { - const activity = createMockActivity('Old description', 'New description'); + const activity = createMockActivity(OLD_DESCRIPTION, NEW_DESCRIPTION); render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( - 'New description' + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent( + NEW_DESCRIPTION ); }); }); describe('Description Removed', () => { it('should display old description when description is removed', () => { - const activity = createMockActivity('Old description', ''); + const activity = createMockActivity(OLD_DESCRIPTION, ''); render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( - 'Old description' + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent( + OLD_DESCRIPTION ); }); it('should display old description when newValue is undefined', () => { - const activity = createMockActivity('Old description', undefined); + const activity = createMockActivity(OLD_DESCRIPTION, undefined); render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( - 'Old description' + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent( + OLD_DESCRIPTION ); }); }); @@ -122,7 +124,7 @@ describe('ActivityDescriptionFeed', () => { render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent(''); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent(''); }); it('should handle both values being undefined', () => { @@ -130,7 +132,7 @@ describe('ActivityDescriptionFeed', () => { render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent(''); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent(''); }); it('should handle whitespace-only descriptions', () => { @@ -138,7 +140,7 @@ describe('ActivityDescriptionFeed', () => { render(); - expect(screen.getByTestId('rich-text-preview')).toBeInTheDocument(); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toBeInTheDocument(); }); it('should handle markdown content', () => { @@ -148,12 +150,8 @@ describe('ActivityDescriptionFeed', () => { render(); // Text content collapses newlines, so we just verify the core content is present - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( - 'Header' - ); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent( - 'Item 1' - ); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent('Header'); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent('Item 1'); }); it('should handle HTML content in description', () => { @@ -162,7 +160,7 @@ describe('ActivityDescriptionFeed', () => { render(); - expect(screen.getByTestId('rich-text-preview')).toHaveTextContent('bold'); + expect(screen.getByTestId(RICH_TEXT_PREVIEW)).toHaveTextContent('bold'); }); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.test.tsx index a2c5c8c66a2c..b900f2f68cf5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.test.tsx @@ -17,6 +17,10 @@ import { ActivityEvent } from '../../../../../generated/entity/activity/activity import { EntityReference } from '../../../../../generated/entity/type'; import ActivityOwnersFeed from './ActivityOwnersFeed'; +const LABEL_OWNER_PLURAL_WITH_COLON = 'label.owner-plural-with-colon'; +const PROFILE_PICTURE_NEWOWNER = 'profile-picture-newowner'; +const PROFILE_PICTURE_OLDOWNER = 'profile-picture-oldowner'; + jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, @@ -101,7 +105,7 @@ describe('ActivityOwnersFeed', () => { render(); expect( - screen.getByText('label.owner-plural-with-colon') + screen.getByText(LABEL_OWNER_PLURAL_WITH_COLON) ).toBeInTheDocument(); }); @@ -115,9 +119,7 @@ describe('ActivityOwnersFeed', () => { render(); - expect( - screen.getByTestId('profile-picture-newowner') - ).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_NEWOWNER)).toBeInTheDocument(); expect( screen.queryByTestId('profile-picture-existing') ).not.toBeInTheDocument(); @@ -167,12 +169,8 @@ describe('ActivityOwnersFeed', () => { render(); - expect( - screen.getByTestId('profile-picture-oldowner') - ).toBeInTheDocument(); - expect( - screen.getByTestId('profile-picture-newowner') - ).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_OLDOWNER)).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_NEWOWNER)).toBeInTheDocument(); }); it('should show two owner labels when both add and remove', () => { @@ -185,7 +183,7 @@ describe('ActivityOwnersFeed', () => { render(); - const ownerLabels = screen.getAllByText('label.owner-plural-with-colon'); + const ownerLabels = screen.getAllByText(LABEL_OWNER_PLURAL_WITH_COLON); expect(ownerLabels).toHaveLength(2); }); @@ -243,7 +241,7 @@ describe('ActivityOwnersFeed', () => { render(); expect( - screen.queryByText('label.owner-plural-with-colon') + screen.queryByText(LABEL_OWNER_PLURAL_WITH_COLON) ).not.toBeInTheDocument(); }); }); @@ -258,9 +256,7 @@ describe('ActivityOwnersFeed', () => { render(); - expect( - screen.getByTestId('profile-picture-newowner') - ).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_NEWOWNER)).toBeInTheDocument(); }); it('should handle undefined newValue', () => { @@ -272,9 +268,7 @@ describe('ActivityOwnersFeed', () => { render(); - expect( - screen.getByTestId('profile-picture-oldowner') - ).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_OLDOWNER)).toBeInTheDocument(); }); it('should handle invalid JSON in oldValue', () => { @@ -286,9 +280,7 @@ describe('ActivityOwnersFeed', () => { render(); - expect( - screen.getByTestId('profile-picture-newowner') - ).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_NEWOWNER)).toBeInTheDocument(); }); it('should handle invalid JSON in newValue', () => { @@ -300,9 +292,7 @@ describe('ActivityOwnersFeed', () => { render(); - expect( - screen.getByTestId('profile-picture-oldowner') - ).toBeInTheDocument(); + expect(screen.getByTestId(PROFILE_PICTURE_OLDOWNER)).toBeInTheDocument(); }); it('should handle single owner object instead of array', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.tsx index c45b1dfcad90..149a3f07e782 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/OwnerFeed/ActivityOwnersFeed.tsx @@ -50,7 +50,8 @@ function ActivityOwnersFeed({ try { if (activity.oldValue) { const parsed = JSON.parse(activity.oldValue); - oldOwners = Array.isArray(parsed) ? parsed : parsed ? [parsed] : []; + const parsedAsList = parsed ? [parsed] : []; + oldOwners = Array.isArray(parsed) ? parsed : parsedAsList; } } catch { oldOwners = []; @@ -59,7 +60,8 @@ function ActivityOwnersFeed({ try { if (activity.newValue) { const parsed = JSON.parse(activity.newValue); - newOwners = Array.isArray(parsed) ? parsed : parsed ? [parsed] : []; + const parsedAsList = parsed ? [parsed] : []; + newOwners = Array.isArray(parsed) ? parsed : parsedAsList; } } catch { newOwners = []; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/TagsFeed/ActivityTagsFeed.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/TagsFeed/ActivityTagsFeed.test.tsx index e44a48b56adf..af9bdbce5922 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/TagsFeed/ActivityTagsFeed.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardBody/TagsFeed/ActivityTagsFeed.test.tsx @@ -16,6 +16,10 @@ import { ActivityEvent } from '../../../../../generated/entity/activity/activity import { TagLabel } from '../../../../../generated/type/tagLabel'; import ActivityTagsFeed from './ActivityTagsFeed'; +const PII_SENSITIVE = 'PII.Sensitive'; +const TAGS_VIEWER = 'tags-viewer'; +const TAG_NEWTAG = 'tag-NewTag'; +const TAG_OLDTAG = 'tag-OldTag'; jest.mock('../../../../Tag/TagsViewer/TagsViewer', () => { return jest.fn(({ tags }) => (
@@ -59,7 +63,7 @@ describe('ActivityTagsFeed', () => { it('should display added tags with add icon', () => { const activity = createMockActivity( createTagsJson([]), - createTagsJson(['PII.Sensitive', 'Tier.Tier1']) + createTagsJson([PII_SENSITIVE, 'Tier.Tier1']) ); render(); @@ -71,12 +75,12 @@ describe('ActivityTagsFeed', () => { it('should show tags viewer when tags are added', () => { const activity = createMockActivity( createTagsJson([]), - createTagsJson(['PII.Sensitive']) + createTagsJson([PII_SENSITIVE]) ); render(); - expect(screen.getByTestId('tags-viewer')).toBeInTheDocument(); + expect(screen.getByTestId(TAGS_VIEWER)).toBeInTheDocument(); }); it('should only show newly added tags, not existing ones', () => { @@ -87,7 +91,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-NewTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_NEWTAG)).toBeInTheDocument(); expect(screen.queryByTestId('tag-ExistingTag')).not.toBeInTheDocument(); }); }); @@ -95,7 +99,7 @@ describe('ActivityTagsFeed', () => { describe('Tag Removal', () => { it('should display removed tags with delete icon', () => { const activity = createMockActivity( - createTagsJson(['PII.Sensitive', 'Tier.Tier1']), + createTagsJson([PII_SENSITIVE, 'Tier.Tier1']), createTagsJson([]) ); @@ -127,8 +131,8 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-OldTag')).toBeInTheDocument(); - expect(screen.getByTestId('tag-NewTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_OLDTAG)).toBeInTheDocument(); + expect(screen.getByTestId(TAG_NEWTAG)).toBeInTheDocument(); }); it('should render two TagsViewer components when both add and remove', () => { @@ -139,7 +143,7 @@ describe('ActivityTagsFeed', () => { render(); - const tagsViewers = screen.getAllByTestId('tags-viewer'); + const tagsViewers = screen.getAllByTestId(TAGS_VIEWER); expect(tagsViewers).toHaveLength(2); }); @@ -154,7 +158,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.queryByTestId('tags-viewer')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TAGS_VIEWER)).not.toBeInTheDocument(); }); it('should not render when both old and new are empty', () => { @@ -165,7 +169,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.queryByTestId('tags-viewer')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TAGS_VIEWER)).not.toBeInTheDocument(); }); }); @@ -178,7 +182,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-NewTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_NEWTAG)).toBeInTheDocument(); }); it('should handle undefined newValue', () => { @@ -189,7 +193,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-OldTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_OLDTAG)).toBeInTheDocument(); }); it('should handle invalid JSON in oldValue', () => { @@ -200,7 +204,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-NewTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_NEWTAG)).toBeInTheDocument(); }); it('should handle invalid JSON in newValue', () => { @@ -211,7 +215,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-OldTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_OLDTAG)).toBeInTheDocument(); }); it('should handle non-array JSON values', () => { @@ -222,7 +226,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.getByTestId('tag-NewTag')).toBeInTheDocument(); + expect(screen.getByTestId(TAG_NEWTAG)).toBeInTheDocument(); }); it('should handle empty strings', () => { @@ -230,7 +234,7 @@ describe('ActivityTagsFeed', () => { render(); - expect(screen.queryByTestId('tags-viewer')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TAGS_VIEWER)).not.toBeInTheDocument(); }); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardFooter/ActivityEventFooter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardFooter/ActivityEventFooter.test.tsx index 229d4a7ee582..63b50cf05cfe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardFooter/ActivityEventFooter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardV2/FeedCardFooter/ActivityEventFooter.test.tsx @@ -17,6 +17,10 @@ import { ActivityEvent } from '../../../../generated/entity/activity/activityEve import { ReactionType } from '../../../../generated/type/reaction'; import ActivityEventFooter from './ActivityEventFooter'; +const ACTIVITY_123 = 'activity-123'; +const COMMENT_BUTTON = 'comment-button'; +const REACTIONS_COUNT = 'reactions-count'; + const mockUpdateActivityReaction = jest.fn(); jest.mock('../../ActivityFeedProvider/ActivityFeedProvider', () => ({ @@ -28,7 +32,7 @@ jest.mock('../../ActivityFeedProvider/ActivityFeedProvider', () => ({ jest.mock('../../Reactions/Reactions', () => { return jest.fn(({ reactions, onReactionSelect }) => (
- {reactions?.length ?? 0} + {reactions?.length ?? 0}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.test.tsx index 49e608eb46cb..7e44f347d498 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.test.tsx @@ -16,6 +16,8 @@ import { KeyboardEventHandler } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { FeedEditor } from './FeedEditor'; +const REACT_QUILL = 'react-quill'; + const onSave = jest.fn(); const onChangeHandler = jest.fn(); @@ -81,7 +83,11 @@ jest.mock('react-quill-new', () => ({ mockCaptureQuillProps(props); return ( -
+
editor
); @@ -112,7 +118,7 @@ describe('Test FeedEditor Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const reactQuill = await findByTestId(container, 'react-quill'); + const reactQuill = await findByTestId(container, REACT_QUILL); expect(reactQuill).toBeInTheDocument(); @@ -128,7 +134,7 @@ describe('Test FeedEditor Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const reactQuill = await findByTestId(container, 'react-quill'); + const reactQuill = await findByTestId(container, REACT_QUILL); expect(reactQuill).toBeInTheDocument(); @@ -144,7 +150,7 @@ describe('Test FeedEditor Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const reactQuill = await findByTestId(container, 'react-quill'); + const reactQuill = await findByTestId(container, REACT_QUILL); expect(reactQuill).toBeInTheDocument(); @@ -160,7 +166,7 @@ describe('Test FeedEditor Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const reactQuill = await findByTestId(container, 'react-quill'); + const reactQuill = await findByTestId(container, REACT_QUILL); expect(reactQuill).toBeInTheDocument(); @@ -176,7 +182,7 @@ describe('Test FeedEditor Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const reactQuill = await findByTestId(container, 'react-quill'); + const reactQuill = await findByTestId(container, REACT_QUILL); // The mention suggestion list is open (user is picking a mention). act(() => { @@ -195,7 +201,7 @@ describe('Test FeedEditor Component', () => { const { container } = render(, { wrapper: MemoryRouter, }); - const reactQuill = await findByTestId(container, 'react-quill'); + const reactQuill = await findByTestId(container, REACT_QUILL); // Open the list, pick a mention (insert only), then the list closes. act(() => mentionModule().onOpen()); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.tsx index 5351cbca5dfa..72da28a0800f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedEditor/FeedEditor.tsx @@ -197,6 +197,7 @@ export const FeedEditor = forwardRef( return wrapper; }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [userProfilePics] ); /** @@ -225,9 +226,9 @@ export const FeedEditor = forwardRef( setTimeout(() => toggleMentionList(false), 0); }, onSelect: ( - item: Record, + item: Record, - insertItem: (item: Record) => void + insertItem: (item: Record) => void ) => { insertItem(item); }, @@ -241,6 +242,7 @@ export const FeedEditor = forwardRef( matchers: [['del, strike', strikethrough]], }, }), + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Emoji.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Emoji.test.tsx index 9a6f3bd46cf4..00dff4301769 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Emoji.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Emoji.test.tsx @@ -16,6 +16,8 @@ import { User } from '../../../generated/entity/teams/user'; import { ReactionType } from '../../../generated/type/reaction'; import Emoji from './Emoji'; +const EMOJI_BUTTON = 'emoji-button'; + const onReactionSelect = jest.fn(); const mockUserData: User = { name: 'aaron_johnson0', @@ -55,7 +57,7 @@ describe('Test Emoji Component', () => { it('Should render the component', async () => { const { findByTestId } = render(); - const emojiButton = await findByTestId('emoji-button'); + const emojiButton = await findByTestId(EMOJI_BUTTON); expect(emojiButton).toBeInTheDocument(); @@ -73,7 +75,7 @@ describe('Test Emoji Component', () => { it('Should render the tooltip component on hovering the emoji', async () => { const { findByTestId } = render(); - const emojiButton = await findByTestId('emoji-button'); + const emojiButton = await findByTestId(EMOJI_BUTTON); expect(emojiButton).toBeInTheDocument(); @@ -91,7 +93,7 @@ describe('Test Emoji Component', () => { it('Should call onReaction select on click of emoji button', async () => { const { findByTestId } = render(); - const emojiButton = await findByTestId('emoji-button'); + const emojiButton = await findByTestId(EMOJI_BUTTON); expect(emojiButton).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reaction.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reaction.test.tsx index eae2ba79785e..fdb7a6587c9e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reaction.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reaction.test.tsx @@ -15,6 +15,8 @@ import { fireEvent, render } from '@testing-library/react'; import { ReactionType } from '../../../generated/type/reaction'; import Reaction from './Reaction'; +const REACTION_BUTTON = 'reaction-button' as const; + const onReactionSelect = jest.fn(); const onHide = jest.fn(); @@ -33,7 +35,7 @@ describe('Test Reaction Component', () => { it('Should render the component', async () => { const { findByTestId } = render(); - const reactionButton = await findByTestId('reaction-button'); + const reactionButton = await findByTestId(REACTION_BUTTON); expect(reactionButton).toBeInTheDocument(); @@ -45,7 +47,7 @@ describe('Test Reaction Component', () => { it('Should call onReaction select on click of emoji button', async () => { const { findByTestId } = render(); - const reactionButton = await findByTestId('reaction-button'); + const reactionButton = await findByTestId(REACTION_BUTTON); expect(reactionButton).toBeInTheDocument(); @@ -60,7 +62,7 @@ describe('Test Reaction Component', () => { it('Should call onHide method on click of emoji button', async () => { const { findByTestId } = render(); - const reactionButton = await findByTestId('reaction-button'); + const reactionButton = await findByTestId(REACTION_BUTTON); expect(reactionButton).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reactions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reactions.test.tsx index 0e1d5e5b5e5b..c6239a346dd4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reactions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Reactions/Reactions.test.tsx @@ -15,6 +15,10 @@ import { fireEvent, render } from '@testing-library/react'; import { ReactionType } from '../../../generated/type/reaction'; import Reactions from './Reactions'; +const _2E424734_761A_443F_BF2A_A5B361823C80 = + '2e424734-761a-443f-bf2a-a5b361823c80'; +const AARON_JOHNSON = 'Aaron Johnson'; + jest.mock('./Emoji', () => jest.fn().mockReturnValue() ); @@ -29,88 +33,88 @@ const reactions = [ { reactionType: ReactionType.Heart, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.Confused, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.Laugh, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.ThumbsDown, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.ThumbsUp, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.Hooray, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.Rocket, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, { reactionType: ReactionType.Eyes, user: { - id: '2e424734-761a-443f-bf2a-a5b361823c80', + id: _2E424734_761A_443F_BF2A_A5B361823C80, type: 'user', name: 'aaron_johnson0', fullyQualifiedName: 'aaron_johnson0', - displayName: 'Aaron Johnson', + displayName: AARON_JOHNSON, deleted: false, }, }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.test.tsx index ec846429a78f..8c11e54ee79b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.test.tsx @@ -19,6 +19,9 @@ import { } from '../../../generated/entity/feed/thread'; import ActivityFeedActions from './ActivityFeedActions'; +const DELETE_MESSAGE = 'delete-message'; +const EDIT_MESSAGE = 'edit-message'; +const THREAD_123 = 'thread-123'; const mockDeleteFeed = jest.fn().mockResolvedValue(undefined); const mockShowDrawer = jest.fn(); const mockHideDrawer = jest.fn(); @@ -59,7 +62,8 @@ const createMockFeed = ( type: ThreadType, createdBy: string = 'testuser' ): Thread => ({ - id: 'thread-123', + id: THREAD_123, + // eslint-disable-next-line sonarjs/no-clear-text-protocols -- test fixture URL, not a real network call href: 'http://test', threadTs: 1234567890, about: '<#E::table::test>', @@ -97,7 +101,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.getByTestId('edit-message')).toBeInTheDocument(); + expect(screen.getByTestId(EDIT_MESSAGE)).toBeInTheDocument(); }); it('should NOT show edit button when user is not author', () => { @@ -113,7 +117,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.queryByTestId('edit-message')).not.toBeInTheDocument(); + expect(screen.queryByTestId(EDIT_MESSAGE)).not.toBeInTheDocument(); }); it('should NOT show edit button for task thread (non-post)', () => { @@ -129,7 +133,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.queryByTestId('edit-message')).not.toBeInTheDocument(); + expect(screen.queryByTestId(EDIT_MESSAGE)).not.toBeInTheDocument(); }); it('should call onEditPost when edit button is clicked', () => { @@ -145,7 +149,7 @@ describe('ActivityFeedActions', () => { /> ); - fireEvent.click(screen.getByTestId('edit-message')); + fireEvent.click(screen.getByTestId(EDIT_MESSAGE)); expect(mockOnEditPost).toHaveBeenCalled(); }); @@ -165,7 +169,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.getByTestId('delete-message')).toBeInTheDocument(); + expect(screen.getByTestId(DELETE_MESSAGE)).toBeInTheDocument(); }); it('should show delete button when user is admin (not author)', () => { @@ -185,7 +189,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.getByTestId('delete-message')).toBeInTheDocument(); + expect(screen.getByTestId(DELETE_MESSAGE)).toBeInTheDocument(); }); it('should NOT show delete button when user is neither author nor admin', () => { @@ -201,7 +205,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.queryByTestId('delete-message')).not.toBeInTheDocument(); + expect(screen.queryByTestId(DELETE_MESSAGE)).not.toBeInTheDocument(); }); it('should NOT show delete button for task thread (non-post)', () => { @@ -217,7 +221,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.queryByTestId('delete-message')).not.toBeInTheDocument(); + expect(screen.queryByTestId(DELETE_MESSAGE)).not.toBeInTheDocument(); }); it('should show delete button for task post when user is author', () => { @@ -233,7 +237,7 @@ describe('ActivityFeedActions', () => { /> ); - expect(screen.getByTestId('delete-message')).toBeInTheDocument(); + expect(screen.getByTestId(DELETE_MESSAGE)).toBeInTheDocument(); }); }); @@ -251,7 +255,7 @@ describe('ActivityFeedActions', () => { /> ); - fireEvent.click(screen.getByTestId('delete-message')); + fireEvent.click(screen.getByTestId(DELETE_MESSAGE)); expect( screen.getByText('message.confirm-delete-message') @@ -271,11 +275,11 @@ describe('ActivityFeedActions', () => { /> ); - fireEvent.click(screen.getByTestId('delete-message')); + fireEvent.click(screen.getByTestId(DELETE_MESSAGE)); fireEvent.click(screen.getByText('label.delete')); expect(mockDeleteFeed).toHaveBeenCalledWith( - 'thread-123', + THREAD_123, 'post-123', false ); @@ -294,14 +298,10 @@ describe('ActivityFeedActions', () => { /> ); - fireEvent.click(screen.getByTestId('delete-message')); + fireEvent.click(screen.getByTestId(DELETE_MESSAGE)); fireEvent.click(screen.getByText('label.delete')); - expect(mockDeleteFeed).toHaveBeenCalledWith( - 'thread-123', - 'post-123', - true - ); + expect(mockDeleteFeed).toHaveBeenCalledWith(THREAD_123, 'post-123', true); // hideDrawer is called synchronously in handleDelete after deleteFeed is called expect(mockHideDrawer).toHaveBeenCalled(); }); @@ -319,7 +319,7 @@ describe('ActivityFeedActions', () => { /> ); - fireEvent.click(screen.getByTestId('delete-message')); + fireEvent.click(screen.getByTestId(DELETE_MESSAGE)); expect( screen.getByText('message.confirm-delete-message') diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx index ec9318a37cf0..9ae12cc171bf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx @@ -80,6 +80,7 @@ const ActivityFeedActions = ({ } return false; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [post, feed, currentUser]); const deleteCheck = useMemo(() => { @@ -90,6 +91,7 @@ const ActivityFeedActions = ({ } return false; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [post, feed, isAuthor, currentUser]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCard.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCard.component.tsx index dc8d88a840f9..6961bcdd55bc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCard.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCard.component.tsx @@ -59,7 +59,8 @@ const TaskFeedCard = ({ showThread = true, isActive, hidePopover = false, -}: TaskFeedCardProps) => { +}: // eslint-disable-next-line sonarjs/cyclomatic-complexity -- inherent presentational branching +TaskFeedCardProps) => { const navigate = useNavigate(); const { t } = useTranslation(); const { showDrawer, setActiveThread } = useActivityFeedProvider(); @@ -99,6 +100,7 @@ const TaskFeedCard = ({ } return null; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [feed]); const showReplies = () => { @@ -139,6 +141,7 @@ const TaskFeedCard = ({ ) : null, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [isEntityDetailsAvailable, entityFQN, entityType, taskDetails] ); @@ -222,7 +225,17 @@ const TaskFeedCard = ({
+ role="button" + tabIndex={0} + onClick={!hidePopover ? showReplies : noop} + onKeyDown={(e) => { + // eslint-disable-next-line sonarjs/no-collapsible-if -- separate guards + if (e.key === 'Enter' || e.key === ' ') { + if (!hidePopover) { + showReplies(); + } + } + }}> {' '} {postLength} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.test.tsx index 6cbf863778c9..54d9920e5146 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.test.tsx @@ -35,6 +35,8 @@ import { import EntityLink from '../../../utils/EntityLink'; import TaskFeedCardFromTask from './TaskFeedCardFromTask.component'; +const ADMIN_USER = 'Admin User'; + const MOCK_TASK: Task = { id: 'task-id-1', taskId: 'TASK-00002', @@ -53,7 +55,7 @@ const MOCK_TASK: Task = { id: 'user-id-1', type: 'user', name: 'admin', - displayName: 'Admin User', + displayName: ADMIN_USER, }, assignees: [ { @@ -110,7 +112,7 @@ jest.mock('../../../hooks/user-profile/useUserProfile', () => ({ { id: 'user-id-1', name: 'admin', - displayName: 'Admin User', + displayName: ADMIN_USER, }, ]), })); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.tsx index 231fdaa12f96..cb8bd968da05 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardFromTask.component.tsx @@ -80,7 +80,8 @@ const TaskFeedCardFromTask = ({ onUpdateEntityDetails, isOpenInDrawer = false, onTaskClick, -}: TaskFeedCardFromTaskProps) => { +}: // eslint-disable-next-line sonarjs/cyclomatic-complexity -- complex fn; refactor risks behavior change +TaskFeedCardFromTaskProps) => { const navigate = useNavigate(); const { t } = useTranslation(); const { setActiveTask, showTaskDrawer } = useActivityFeedProvider(); @@ -132,6 +133,7 @@ const TaskFeedCardFromTask = ({ return null; }, [entityFQN, entityType, fieldPath, t]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const handleTaskLinkClick = () => { navigate(getTaskDetailPathFromTask(task)); setActiveTask(task); @@ -270,6 +272,7 @@ const TaskFeedCardFromTask = ({ assignee.type === 'team' ? checkIfUserPartOfTeam(assignee.id ?? '') : false ); const hasEditAccess = + // eslint-disable-next-line sonarjs/expression-complexity -- preserve evaluation/short-circuit order (isAdminUser && !isTaskApprovalRequest) || isAssignee || (Boolean(isPartOfAssigneeTeam) && !isCreator); @@ -295,7 +298,8 @@ const TaskFeedCardFromTask = ({ gutter={ isTaskTestCaseResult || isTaskApprovalRequest ? [0, 6] - : isTaskDescription + : // eslint-disable-next-line sonarjs/no-nested-conditional -- preserve exact ternary branches + isTaskDescription ? undefined : [0, 14] }> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.test.tsx index 53891ec0389b..ea65518ab112 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.test.tsx @@ -12,12 +12,19 @@ */ import { act, fireEvent, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; +import { Thread } from '../../../generated/entity/feed/thread'; import { TASK_FEED, TASK_FEED_RECOGNIZER_FEEDBACK, } from '../../../mocks/Task.mock'; import TaskFeedCard from './TaskFeedCardNew.component'; +const ADMIN_USER = 'Admin User'; +const APPROVE_BUTTON = 'approve-button'; +const REJECT_BUTTON = 'reject-button'; +const _31D072F8_7873_4976_88EA_AC0D2F51F632 = + '31d072f8-7873-4976-88ea-ac0d2f51f632'; + jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn(), @@ -55,7 +62,7 @@ jest.mock('../../../hooks/user-profile/useUserProfile', () => ({ { id: 'user-id', name: 'admin', - displayName: 'Admin User', + displayName: ADMIN_USER, }, ]), })); @@ -121,7 +128,7 @@ jest.mock('../../../utils/EntityLink', () => { }); jest.mock('../../../utils/EntityNameUtils', () => ({ - getEntityName: jest.fn().mockReturnValue('Admin User'), + getEntityName: jest.fn().mockReturnValue(ADMIN_USER), })); jest.mock('../../../rest/tasksAPI', () => ({ @@ -157,7 +164,7 @@ describe('TaskFeedCardNew Component', () => { }); expect(screen.getByTestId('task-created-by')).toBeInTheDocument(); - expect(screen.getByText('Admin User')).toBeInTheDocument(); + expect(screen.getByText(ADMIN_USER)).toBeInTheDocument(); }); it('should display timestamp', async () => { @@ -208,8 +215,8 @@ describe('TaskFeedCardNew Component', () => { }); }); - expect(screen.getByTestId('approve-button')).toBeInTheDocument(); - expect(screen.getByTestId('reject-button')).toBeInTheDocument(); + expect(screen.getByTestId(APPROVE_BUTTON)).toBeInTheDocument(); + expect(screen.getByTestId(REJECT_BUTTON)).toBeInTheDocument(); }); it('should handle approve button click', async () => { @@ -223,7 +230,7 @@ describe('TaskFeedCardNew Component', () => { }); }); - const approveButton = screen.getByTestId('approve-button'); + const approveButton = screen.getByTestId(APPROVE_BUTTON); await act(async () => { fireEvent.click(approveButton); }); @@ -242,7 +249,7 @@ describe('TaskFeedCardNew Component', () => { }); }); - const rejectButton = screen.getByTestId('reject-button'); + const rejectButton = screen.getByTestId(REJECT_BUTTON); await act(async () => { fireEvent.click(rejectButton); }); @@ -286,11 +293,11 @@ describe('TaskFeedCardNew Component', () => { } = require('../../../hooks/useApplicationStore'); useApplicationStore.mockReturnValue({ currentUser: { - id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + id: _31D072F8_7873_4976_88EA_AC0D2F51F632, name: 'test-user', teams: [ { - id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + id: _31D072F8_7873_4976_88EA_AC0D2F51F632, name: 'DataGovernance', }, ], @@ -303,8 +310,8 @@ describe('TaskFeedCardNew Component', () => { }); }); - expect(screen.getByTestId('approve-button')).toBeInTheDocument(); - expect(screen.getByTestId('reject-button')).toBeInTheDocument(); + expect(screen.getByTestId(APPROVE_BUTTON)).toBeInTheDocument(); + expect(screen.getByTestId(REJECT_BUTTON)).toBeInTheDocument(); }); it('should handle recognizer feedback approval', async () => { @@ -314,11 +321,11 @@ describe('TaskFeedCardNew Component', () => { } = require('../../../hooks/useApplicationStore'); useApplicationStore.mockReturnValue({ currentUser: { - id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + id: _31D072F8_7873_4976_88EA_AC0D2F51F632, name: 'test-user', teams: [ { - id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + id: _31D072F8_7873_4976_88EA_AC0D2F51F632, name: 'DataGovernance', }, ], @@ -331,7 +338,7 @@ describe('TaskFeedCardNew Component', () => { }); }); - const approveButton = screen.getByTestId('approve-button'); + const approveButton = screen.getByTestId(APPROVE_BUTTON); await act(async () => { fireEvent.click(approveButton); }); @@ -351,11 +358,11 @@ describe('TaskFeedCardNew Component', () => { } = require('../../../hooks/useApplicationStore'); useApplicationStore.mockReturnValue({ currentUser: { - id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + id: _31D072F8_7873_4976_88EA_AC0D2F51F632, name: 'test-user', teams: [ { - id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + id: _31D072F8_7873_4976_88EA_AC0D2F51F632, name: 'DataGovernance', }, ], @@ -368,7 +375,7 @@ describe('TaskFeedCardNew Component', () => { }); }); - const rejectButton = screen.getByTestId('reject-button'); + const rejectButton = screen.getByTestId(REJECT_BUTTON); await act(async () => { fireEvent.click(rejectButton); }); @@ -386,7 +393,7 @@ describe('TaskFeedCardNew Component', () => { const feedWithEmptySuggestion = { ...TASK_FEED, task: { - ...TASK_FEED.task!, + ...(TASK_FEED.task ?? {}), suggestion: '[]', }, }; @@ -394,12 +401,12 @@ describe('TaskFeedCardNew Component', () => { useAuth.mockReturnValue({ isAdminUser: true }); await act(async () => { - render(, { + render(, { wrapper: MemoryRouter, }); }); - expect(screen.queryByTestId('approve-button')).not.toBeInTheDocument(); - expect(screen.queryByTestId('reject-button')).not.toBeInTheDocument(); + expect(screen.queryByTestId(APPROVE_BUTTON)).not.toBeInTheDocument(); + expect(screen.queryByTestId(REJECT_BUTTON)).not.toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.tsx index f561b0aca2a6..7bb969d0d37e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/TaskFeedCard/TaskFeedCardNew.component.tsx @@ -84,7 +84,8 @@ const TaskFeedCard = ({ isForFeedTab = false, isOpenInDrawer = false, hideCardBorder = false, -}: TaskFeedCardProps) => { +}: // eslint-disable-next-line sonarjs/cognitive-complexity, sonarjs/cyclomatic-complexity -- refactor risky +TaskFeedCardProps) => { const navigate = useNavigate(); const { t } = useTranslation(); const { setActiveThread } = useActivityFeedProvider(); @@ -132,6 +133,7 @@ const TaskFeedCard = ({ } return null; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [feed]); const handleTaskLinkClick = () => { @@ -177,6 +179,7 @@ const TaskFeedCard = ({ ) : null; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [isEntityDetailsAvailable, entityFQN, entityType, taskDetails, t]); const isTaskTestCaseResult = @@ -271,12 +274,11 @@ const TaskFeedCard = ({ const isPartOfAssigneeTeam = taskDetails?.assignees?.some((assignee) => assignee.type === 'team' ? checkIfUserPartOfTeam(assignee.id) : false ); + const hasAdminEditAccess = + isAdminUser && !isTaskGlossaryApproval && !isTaskRecognizerFeedbackApproval; + const isAssigneeTeamMember = Boolean(isPartOfAssigneeTeam) && !isCreator; const hasEditAccess = - (isAdminUser && - !isTaskGlossaryApproval && - !isTaskRecognizerFeedbackApproval) || - isAssignee || - (Boolean(isPartOfAssigneeTeam) && !isCreator); + hasAdminEditAccess || isAssignee || isAssigneeTeamMember; const isSuggestionEmpty = (isEqual(taskDetails?.suggestion, '[]') && @@ -288,6 +290,10 @@ const TaskFeedCard = ({ showDrawer?.(feed); }, [showDrawer, feed]); + const descriptionGutter: [number, number] | undefined = isTaskDescription + ? undefined + : [0, 14]; + return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx index d3a285311bb8..7afd4f478586 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx @@ -25,6 +25,10 @@ import { testAlertDestination } from '../../../rest/alertsAPI'; import { showErrorToast } from '../../../utils/ToastUtils'; import DestinationFormItem from './DestinationFormItem.component'; +const ADD_DESTINATION_BUTTON = 'add-destination-button'; +const HTTPS_EXAMPLE_COM_WEBHOOK = 'https://example.com/webhook'; +const TEST_DESTINATION_BUTTON = 'test-destination-button'; + jest.mock('../../../rest/alertsAPI', () => ({ testAlertDestination: jest.fn(), })); @@ -98,7 +102,7 @@ describe('DestinationFormItem', () => { ).toBeInTheDocument(); expect(screen.getByText('label.add-entity')).toBeInTheDocument(); - expect(screen.getByTestId('add-destination-button')).toBeInTheDocument(); + expect(screen.getByTestId(ADD_DESTINATION_BUTTON)).toBeInTheDocument(); }); it('add destination button should be disabled if there is no selected trigger', () => { @@ -117,7 +121,7 @@ describe('DestinationFormItem', () => { render(); - expect(screen.getByTestId('add-destination-button')).toBeDisabled(); + expect(screen.getByTestId(ADD_DESTINATION_BUTTON)).toBeDisabled(); }); it('add destination button should be enabled if there is selected trigger', () => { @@ -136,7 +140,7 @@ describe('DestinationFormItem', () => { render(); - expect(screen.getByTestId('add-destination-button')).toBeEnabled(); + expect(screen.getByTestId(ADD_DESTINATION_BUTTON)).toBeEnabled(); }); it('should display the connection timeout field', () => { @@ -170,7 +174,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: [{ key: 'Content-Type', value: 'application/json' }], queryParams: [{ key: 'param1', value: 'value1' }], }, @@ -190,7 +194,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: { 'Content-Type': 'application/json' }, queryParams: { param1: 'value1' }, }, @@ -233,7 +237,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: { 'Content-Type': 'application/json' }, queryParams: { param1: 'value1' }, }, @@ -243,7 +247,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); expect(testButton).toBeEnabled(); @@ -271,7 +275,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, { @@ -287,7 +291,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, { @@ -325,7 +329,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); await act(async () => { fireEvent.click(testButton); @@ -338,7 +342,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ], @@ -353,7 +357,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, { @@ -369,7 +373,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, { @@ -407,7 +411,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); await act(async () => { fireEvent.click(testButton); @@ -420,7 +424,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ], @@ -436,7 +440,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ]; @@ -469,7 +473,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ]); @@ -477,7 +481,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); await act(async () => { fireEvent.click(testButton); @@ -525,7 +529,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); await act(async () => { fireEvent.click(testButton); @@ -573,7 +577,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); expect(testButton).toBeDisabled(); }); @@ -585,7 +589,7 @@ describe('DestinationFormItem', () => { category: SubscriptionCategory.External, type: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ]; @@ -615,7 +619,7 @@ describe('DestinationFormItem', () => { render(); - const testButton = screen.getByTestId('test-destination-button'); + const testButton = screen.getByTestId(TEST_DESTINATION_BUTTON); expect(testButton).toBeEnabled(); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx index 4d6127c024b9..2fa83f48ca6e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/DestinationSelectItem/DestinationSelectItem.test.tsx @@ -29,6 +29,12 @@ import { import DestinationSelectItem from './DestinationSelectItem'; import { DestinationSelectItemProps } from './DestinationSelectItem.interface'; +const MESSAGE_DESTINATION_SELECTION_WARNING = + 'message.destination-selection-warning'; +const HTTPS_EXAMPLE_COM_WEBHOOK = 'https://example.com/webhook'; +const BEARER_TOKEN123 = 'Bearer token123'; +const TEST_RESOURCE = 'test-resource'; +const CUSTOM_VALUE = 'custom-value'; const MOCK_DESTINATION_SELECT_ITEM_PROPS: DestinationSelectItemProps = { selectorKey: 0, id: 0, @@ -211,6 +217,7 @@ describe('DestinationSelectItem component', () => { setFieldValue: jest.fn(), getFieldValue: jest .fn() + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- test mock .mockImplementation((val: string | string[]) => { if (isString(val)) { return [{ category: 'External' }]; @@ -269,7 +276,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -304,6 +311,7 @@ describe('DestinationSelectItem component', () => { setFieldValue: jest.fn(), getFieldValue: jest .fn() + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- test mock .mockImplementation((val: string | string[]) => { if (isString(val)) { return [{ category: 'External' }]; @@ -362,7 +370,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -386,7 +394,7 @@ describe('DestinationSelectItem component', () => { await waitFor(() => { expect( - screen.getByText('message.destination-selection-warning') + screen.getByText(MESSAGE_DESTINATION_SELECTION_WARNING) ).toBeInTheDocument(); }); useWatchMock.mockRestore(); @@ -397,6 +405,7 @@ describe('DestinationSelectItem component', () => { setFieldValue: jest.fn(), getFieldValue: jest .fn() + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- test mock .mockImplementation((val: string | string[]) => { if (isString(val)) { return [{ category: 'External' }]; @@ -455,7 +464,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -479,7 +488,7 @@ describe('DestinationSelectItem component', () => { await waitFor(() => { expect( - screen.getByText('message.destination-selection-warning') + screen.getByText(MESSAGE_DESTINATION_SELECTION_WARNING) ).toBeInTheDocument(); }); useWatchMock.mockRestore(); @@ -490,6 +499,7 @@ describe('DestinationSelectItem component', () => { setFieldValue: jest.fn(), getFieldValue: jest .fn() + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- test mock .mockImplementation((val: string | string[]) => { if (isString(val)) { return [{ category: 'External' }]; @@ -548,7 +558,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -575,7 +585,7 @@ describe('DestinationSelectItem component', () => { screen.queryByText('message.destination-owner-selection-warning') ).not.toBeInTheDocument(); expect( - screen.queryByText('message.destination-selection-warning') + screen.queryByText(MESSAGE_DESTINATION_SELECTION_WARNING) ).not.toBeInTheDocument(); }); useWatchMock.mockRestore(); @@ -599,7 +609,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(val) && val[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return ''; @@ -633,7 +643,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -650,7 +660,7 @@ describe('DestinationSelectItem component', () => { destinationType: SubscriptionType.Email, }, ], - resources: ['test-resource'], + resources: [TEST_RESOURCE], }}> @@ -861,7 +871,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(val) && val[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return ''; @@ -899,7 +909,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -917,7 +927,7 @@ describe('DestinationSelectItem component', () => { downstreamDepth: 3, }, ], - resources: ['test-resource'], + resources: [TEST_RESOURCE], }}> @@ -961,10 +971,10 @@ describe('DestinationSelectItem component', () => { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: { 'Content-Type': 'application/json', - Authorization: 'Bearer token123', + Authorization: BEARER_TOKEN123, }, queryParams: { param1: 'value1', @@ -1006,10 +1016,10 @@ describe('DestinationSelectItem component', () => { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: [ { key: 'Content-Type', value: 'application/json' }, - { key: 'Authorization', value: 'Bearer token123' }, + { key: 'Authorization', value: BEARER_TOKEN123 }, ], queryParams: [{ key: 'param1', value: 'value1' }], }, @@ -1022,13 +1032,13 @@ describe('DestinationSelectItem component', () => { category: SubscriptionCategory.External, destinationType: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -1044,16 +1054,16 @@ describe('DestinationSelectItem component', () => { category: SubscriptionCategory.External, destinationType: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: [ { key: 'Content-Type', value: 'application/json' }, - { key: 'Authorization', value: 'Bearer token123' }, + { key: 'Authorization', value: BEARER_TOKEN123 }, ], queryParams: [{ key: 'param1', value: 'value1' }], }, }, ], - resources: ['test-resource'], + resources: [TEST_RESOURCE], }}> { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, headers: { - 'X-Custom-Header': 'custom-value', + 'X-Custom-Header': CUSTOM_VALUE, }, queryParams: { apiKey: 'key123', @@ -1123,8 +1133,8 @@ describe('DestinationSelectItem component', () => { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', - headers: [{ key: 'X-Custom-Header', value: 'custom-value' }], + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, + headers: [{ key: 'X-Custom-Header', value: CUSTOM_VALUE }], queryParams: [ { key: 'apiKey', value: 'key123' }, { key: 'version', value: 'v2' }, @@ -1142,7 +1152,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -1158,10 +1168,8 @@ describe('DestinationSelectItem component', () => { category: SubscriptionCategory.External, destinationType: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', - headers: [ - { key: 'X-Custom-Header', value: 'custom-value' }, - ], + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, + headers: [{ key: 'X-Custom-Header', value: CUSTOM_VALUE }], queryParams: [ { key: 'apiKey', value: 'key123' }, { key: 'version', value: 'v2' }, @@ -1169,7 +1177,7 @@ describe('DestinationSelectItem component', () => { }, }, ], - resources: ['test-resource'], + resources: [TEST_RESOURCE], }}> { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }; } @@ -1247,7 +1255,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -1263,11 +1271,11 @@ describe('DestinationSelectItem component', () => { category: SubscriptionCategory.External, destinationType: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ], - resources: ['test-resource'], + resources: [TEST_RESOURCE], }}> { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, statusDetails: { status: Status.Success, @@ -1330,7 +1338,7 @@ describe('DestinationSelectItem component', () => { type: SubscriptionType.Webhook, category: SubscriptionCategory.External, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }; } @@ -1344,7 +1352,7 @@ describe('DestinationSelectItem component', () => { ]; } if (Array.isArray(name) && name[0] === 'resources') { - return ['test-resource']; + return [TEST_RESOURCE]; } return undefined; @@ -1360,11 +1368,11 @@ describe('DestinationSelectItem component', () => { category: SubscriptionCategory.External, destinationType: SubscriptionType.Webhook, config: { - endpoint: 'https://example.com/webhook', + endpoint: HTTPS_EXAMPLE_COM_WEBHOOK, }, }, ], - resources: ['test-resource'], + resources: [TEST_RESOURCE], }}> (['destinations', id], form) ?? []; @@ -141,6 +147,7 @@ function DestinationSelectItem({ ), children, })), + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [] ); @@ -236,6 +243,7 @@ function DestinationSelectItem({ ); } } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, []); return ( @@ -250,8 +258,8 @@ function DestinationSelectItem({ rules={[ { required: true, - message: t('message.field-text-is-required', { - fieldText: t('label.destination'), + message: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { + fieldText: t(LABEL_DESTINATION), }), }, ]}> @@ -262,8 +270,8 @@ function DestinationSelectItem({ customDestinationDropdown(menu, selectorKey) } options={destinationOptions} - placeholder={t('label.select-field', { - field: t('label.destination'), + placeholder={t(LABEL_SELECT_FIELD, { + field: t(LABEL_DESTINATION), })} onSelect={(value) => { form.setFieldValue(['destinations', id], { @@ -284,6 +292,7 @@ function DestinationSelectItem({ selectedDestinations[id]?.destinationType, id )} + {/* eslint-disable-next-line sonarjs/expression-complexity -- preserves short-circuit value */} {destinationType && checkIfDestinationIsInternal(destinationType) && ( <> @@ -294,7 +303,7 @@ function DestinationSelectItem({ rules={[ { required: true, - message: t('message.field-text-is-required', { + message: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { fieldText: t('label.field'), }), }, @@ -303,8 +312,8 @@ function DestinationSelectItem({ className="w-full" data-testid={`destination-type-select-${id}`} options={getSubscriptionTypeOptions(destinationType)} - placeholder={t('label.select-field', { - field: t('label.destination'), + placeholder={t(LABEL_SELECT_FIELD, { + field: t(LABEL_DESTINATION), })} popupClassName="select-options-container" /> @@ -367,7 +376,7 @@ function DestinationSelectItem({ rules={[ { required: true, - message: t('message.field-text-is-required', { + message: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { fieldText: t('label.field'), }), }, @@ -392,8 +401,8 @@ function DestinationSelectItem({ className="w-full" data-testid={`destination-downstream-depth-${id}`} defaultValue={1} - placeholder={t('label.select-field', { - field: t('label.destination'), + placeholder={t(LABEL_SELECT_FIELD, { + field: t(LABEL_DESTINATION), })} type="number" /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.test.tsx index 2fc2517a24ba..ae9f2e059e53 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.test.tsx @@ -19,6 +19,9 @@ import { } from '../../../../constants/TeamAndUserSelectItem.constants'; import TeamAndUserSelectItem from './TeamAndUserSelectItem'; +const DROPDOWN_TRIGGER_BUTTON = 'dropdown-trigger-button'; +const TEAM_USER_SELECT_DROPDOWN_0 = 'team-user-select-dropdown-0'; + jest.mock('../../../../components/common/Loader/Loader', () => jest.fn().mockImplementation(() =>
Loader
) ); @@ -37,16 +40,14 @@ describe('TeamAndUserSelectItem Component', () => { render(); }); - const triggerButton = screen.getByTestId('dropdown-trigger-button'); + const triggerButton = screen.getByTestId(DROPDOWN_TRIGGER_BUTTON); await act(async () => { fireEvent.click(triggerButton); jest.advanceTimersByTime(500); }); - expect( - screen.getByTestId('team-user-select-dropdown-0') - ).toBeInTheDocument(); + expect(screen.getByTestId(TEAM_USER_SELECT_DROPDOWN_0)).toBeInTheDocument(); }); it('should show initial options on click of trigger button', async () => { @@ -54,7 +55,7 @@ describe('TeamAndUserSelectItem Component', () => { render(); }); - const triggerButton = screen.getByTestId('dropdown-trigger-button'); + const triggerButton = screen.getByTestId(DROPDOWN_TRIGGER_BUTTON); await act(async () => { fireEvent.click(triggerButton); @@ -77,7 +78,7 @@ describe('TeamAndUserSelectItem Component', () => { render(); }); - const triggerButton = screen.getByTestId('dropdown-trigger-button'); + const triggerButton = screen.getByTestId(DROPDOWN_TRIGGER_BUTTON); await act(async () => { fireEvent.click(triggerButton); @@ -118,21 +119,19 @@ describe('TeamAndUserSelectItem Component', () => { render(); }); - const triggerButton = screen.getByTestId('dropdown-trigger-button'); + const triggerButton = screen.getByTestId(DROPDOWN_TRIGGER_BUTTON); await act(async () => { fireEvent.click(triggerButton); jest.advanceTimersByTime(500); }); - expect( - screen.getByTestId('team-user-select-dropdown-0') - ).toBeInTheDocument(); + expect(screen.getByTestId(TEAM_USER_SELECT_DROPDOWN_0)).toBeInTheDocument(); await act(async () => { fireEvent.click(document.body); }); - expect(screen.queryByTestId('team-user-select-dropdown-0')).toBeNull(); + expect(screen.queryByTestId(TEAM_USER_SELECT_DROPDOWN_0)).toBeNull(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.tsx index 28a52da6b561..51907e3b99fb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItem/TeamAndUserSelectItem/TeamAndUserSelectItem.tsx @@ -57,6 +57,7 @@ function TeamAndUserSelectItem({ const [searchText, setSearchText] = useState(''); const [options, setOptions] = useState>([]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const selectedOptions = Form.useWatch(['destinations', ...fieldName], form) ?? []; @@ -114,6 +115,7 @@ function TeamAndUserSelectItem({ [onSearch] ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const debouncedOnSearch = useCallback(debounce(handleSearch, 500), [ handleSearch, ]); @@ -137,6 +139,7 @@ function TeamAndUserSelectItem({ ); }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [options, entityType, searchText, loadingOptions] ); @@ -175,6 +179,7 @@ function TeamAndUserSelectItem({ form.setFieldValue(['destinations', ...fieldName], updatedValues); }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [selectedOptions] ); @@ -190,11 +195,13 @@ function TeamAndUserSelectItem({ form.setFieldValue(['destinations', ...fieldName], updatedValues); }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [selectedOptions] ); useEffect(() => { debouncedOnSearch(searchText); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [searchText, entityType]); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx index c1117ec148f5..140170d7fdb8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx @@ -42,6 +42,9 @@ import { showErrorToast } from '../../../utils/ToastUtils'; import { DestinationFormItemV2Props } from './DestinationFormItemV2.interface'; import DestinationSelectItemV2 from './DestinationSelectItemV2/DestinationSelectItemV2'; +const LABEL_DESTINATION = 'label.destination'; +const LABEL_SECOND_PLURAL = 'label.second-plural'; + function DestinationFormItemV2({ isViewMode = false, isRequired = false, @@ -61,6 +64,7 @@ function DestinationFormItemV2({ const selectedResources: string[] = useWatch({ name: 'resources', control }) ?? []; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const destinations: ModifiedDestination[] = (useWatch({ name: 'destinations', control }) as ModifiedDestination[]) ?? []; @@ -72,7 +76,7 @@ function DestinationFormItemV2({ setError('destinations', { type: 'manual', message: t('message.minimum-count-error', { - field: t('label.destination'), + field: t(LABEL_DESTINATION), count: 1, }), }); @@ -132,13 +136,13 @@ function DestinationFormItemV2({ - {`${t('label.connection-timeout')} (${t('label.second-plural')})`} + {`${t('label.connection-timeout')} (${t(LABEL_SECOND_PLURAL)})`} @@ -156,7 +160,7 @@ function DestinationFormItemV2({ defaultValue="10" inputDataTestId="connection-timeout-input-field" placeholder={`${t('label.connection-timeout')} (${t( - 'label.second-plural' + LABEL_SECOND_PLURAL )})`} ref={field.ref} type="number" @@ -171,7 +175,7 @@ function DestinationFormItemV2({ {`${t('label.read-type', { type: t('label.timeout') })} (${t( - 'label.second-plural' + LABEL_SECOND_PLURAL )})`} @@ -191,7 +195,7 @@ function DestinationFormItemV2({ inputDataTestId="read-timeout-input-field" placeholder={`${t('label.read-type', { type: t('label.timeout'), - })} (${t('label.second-plural')})`} + })} (${t(LABEL_SECOND_PLURAL)})`} ref={field.ref} type="number" value={field.value === undefined ? '' : String(field.value)} @@ -241,7 +245,7 @@ function DestinationFormItemV2({ data-testid="add-destination-button" isDisabled={isEmpty(selectedSource) || isNil(selectedSource)} onPress={() => append({})}> - {t('label.add-entity', { entity: t('label.destination') })} + {t('label.add-entity', { entity: t(LABEL_DESTINATION) })} ({ testAlertDestination: jest.fn(), })); @@ -89,6 +94,7 @@ jest.mock('@openmetadata/ui-core-components', () => { 'data-testid': tid, }: { children?: ReactNode; + // eslint-disable-next-line sonarjs/no-duplicate-string 'data-testid'?: string; }) =>
{children}
; @@ -131,6 +137,7 @@ jest.mock('@openmetadata/ui-core-components', () => { defaultValue?: string; }) => ( { expect( screen.getByText('message.alerts-destination-description') ).toBeInTheDocument(); - expect(screen.getByTestId('add-destination-button')).toBeInTheDocument(); + expect(screen.getByTestId(ADD_DESTINATION_BUTTON)).toBeInTheDocument(); }); it('renders connection timeout and read timeout inputs', () => { @@ -188,30 +195,28 @@ describe('DestinationFormItemV2', () => { it('disables add button when no resource is selected', () => { renderWithForm(, { resources: [] }); - expect(screen.getByTestId('add-destination-button')).toBeDisabled(); + expect(screen.getByTestId(ADD_DESTINATION_BUTTON)).toBeDisabled(); }); it('enables add button when a resource is selected', () => { renderWithForm(, { resources: ['container'] }); - expect(screen.getByTestId('add-destination-button')).toBeEnabled(); + expect(screen.getByTestId(ADD_DESTINATION_BUTTON)).toBeEnabled(); }); it('adds a destination row when add button is clicked', async () => { renderWithForm(, { resources: ['container'] }); expect( - screen.queryByTestId('destination-select-item-0') + screen.queryByTestId(DESTINATION_SELECT_ITEM_0) ).not.toBeInTheDocument(); await act(async () => { - fireEvent.click(screen.getByTestId('add-destination-button')); + fireEvent.click(screen.getByTestId(ADD_DESTINATION_BUTTON)); }); await waitFor(() => { - expect( - screen.getByTestId('destination-select-item-0') - ).toBeInTheDocument(); + expect(screen.getByTestId(DESTINATION_SELECT_ITEM_0)).toBeInTheDocument(); }); }); @@ -219,13 +224,11 @@ describe('DestinationFormItemV2', () => { renderWithForm(, { resources: ['container'] }); await act(async () => { - fireEvent.click(screen.getByTestId('add-destination-button')); + fireEvent.click(screen.getByTestId(ADD_DESTINATION_BUTTON)); }); await waitFor(() => { - expect( - screen.getByTestId('destination-select-item-0') - ).toBeInTheDocument(); + expect(screen.getByTestId(DESTINATION_SELECT_ITEM_0)).toBeInTheDocument(); }); await act(async () => { @@ -234,7 +237,7 @@ describe('DestinationFormItemV2', () => { await waitFor(() => { expect( - screen.queryByTestId('destination-select-item-0') + screen.queryByTestId(DESTINATION_SELECT_ITEM_0) ).not.toBeInTheDocument(); }); }); @@ -250,7 +253,7 @@ describe('DestinationFormItemV2', () => { ], }); - expect(screen.getByTestId('test-destination-button')).toBeDisabled(); + expect(screen.getByTestId(TEST_DESTINATION_BUTTON)).toBeDisabled(); }); it('enables test destination button when external destination is selected', () => { @@ -265,7 +268,7 @@ describe('DestinationFormItemV2', () => { ], }); - expect(screen.getByTestId('test-destination-button')).toBeEnabled(); + expect(screen.getByTestId(TEST_DESTINATION_BUTTON)).toBeEnabled(); }); it('calls testAlertDestination with formatted external destinations', async () => { @@ -273,7 +276,7 @@ describe('DestinationFormItemV2', () => { { category: SubscriptionCategory.External, type: SubscriptionType.Slack, - config: { endpoint: 'https://slack.example.com' }, + config: { endpoint: HTTPS_SLACK_EXAMPLE_COM }, }, ]; @@ -287,13 +290,13 @@ describe('DestinationFormItemV2', () => { destinationType: SubscriptionType.Slack, category: SubscriptionCategory.External, type: SubscriptionType.Slack, - config: { endpoint: 'https://slack.example.com' }, + config: { endpoint: HTTPS_SLACK_EXAMPLE_COM }, }, ], }); await act(async () => { - fireEvent.click(screen.getByTestId('test-destination-button')); + fireEvent.click(screen.getByTestId(TEST_DESTINATION_BUTTON)); }); await waitFor(() => { @@ -308,7 +311,7 @@ describe('DestinationFormItemV2', () => { { category: SubscriptionCategory.External, type: SubscriptionType.Slack, - config: { endpoint: 'https://slack.example.com' }, + config: { endpoint: HTTPS_SLACK_EXAMPLE_COM }, }, { category: SubscriptionCategory.External, @@ -335,7 +338,7 @@ describe('DestinationFormItemV2', () => { }); await act(async () => { - fireEvent.click(screen.getByTestId('test-destination-button')); + fireEvent.click(screen.getByTestId(TEST_DESTINATION_BUTTON)); }); await waitFor(() => { @@ -359,7 +362,7 @@ describe('DestinationFormItemV2', () => { }); await act(async () => { - fireEvent.click(screen.getByTestId('test-destination-button')); + fireEvent.click(screen.getByTestId(TEST_DESTINATION_BUTTON)); }); await waitFor(() => { @@ -376,7 +379,7 @@ describe('DestinationFormItemV2', () => { { category: SubscriptionCategory.External, type: SubscriptionType.Slack, - config: { endpoint: 'https://slack.example.com' }, + config: { endpoint: HTTPS_SLACK_EXAMPLE_COM }, }, ]); (testAlertDestination as jest.Mock).mockRejectedValue(mockError); @@ -392,7 +395,7 @@ describe('DestinationFormItemV2', () => { }); await act(async () => { - fireEvent.click(screen.getByTestId('test-destination-button')); + fireEvent.click(screen.getByTestId(TEST_DESTINATION_BUTTON)); }); await waitFor(() => { @@ -406,10 +409,10 @@ describe('DestinationFormItemV2', () => { }); expect( - screen.queryByTestId('add-destination-button') + screen.queryByTestId(ADD_DESTINATION_BUTTON) ).not.toBeInTheDocument(); expect( - screen.queryByTestId('test-destination-button') + screen.queryByTestId(TEST_DESTINATION_BUTTON) ).not.toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationConfigField/DestinationConfigField.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationConfigField/DestinationConfigField.tsx index a46bd4d27a8d..bf0c162fdc0e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationConfigField/DestinationConfigField.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationConfigField/DestinationConfigField.tsx @@ -39,6 +39,11 @@ import { searchEntity } from '../../../../../utils/Alerts/AlertsUtil'; import { getTermQuery } from '../../../../../utils/SearchPureUtils'; import TeamAndUserSelectItemV2 from '../../TeamAndUserSelectItemV2/TeamAndUserSelectItemV2'; +const MESSAGE_FIELD_TEXT_IS_REQUIRED = 'message.field-text-is-required'; +const LABEL_SECRET_KEY = 'label.secret-key'; +const LABEL_CLIENT_ID = 'label.client-id'; +const LABEL_CLIENT_SECRET = 'label.client-secret'; + interface DestinationConfigFieldProps { type: SubscriptionType | SubscriptionCategory; fieldName: number; @@ -69,6 +74,7 @@ function EmailTagInput({ fieldName }: { fieldName: number }) { const { setValue, control } = useFormContext(); const [inputValue, setInputValue] = useState(''); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const receivers: string[] = useWatch({ name: `destinations.${fieldName}.config.receivers`, @@ -177,7 +183,7 @@ function DestinationConfigField({
)} rules={{ - required: t('message.field-text-is-required', { + required: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { fieldText: t('label.endpoint-url'), }), }} @@ -229,8 +235,8 @@ function DestinationConfigField({
field.onBlur()} @@ -240,8 +246,8 @@ function DestinationConfigField({
)} rules={{ - required: t('message.field-text-is-required', { - fieldText: t('label.secret-key'), + required: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { + fieldText: t(LABEL_SECRET_KEY), }), }} /> @@ -270,7 +276,7 @@ function DestinationConfigField({
)} rules={{ - required: t('message.field-text-is-required', { + required: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { fieldText: t('label.token-url'), }), }} @@ -284,8 +290,8 @@ function DestinationConfigField({
field.onBlur()} @@ -295,8 +301,8 @@ function DestinationConfigField({
)} rules={{ - required: t('message.field-text-is-required', { - fieldText: t('label.client-id'), + required: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { + fieldText: t(LABEL_CLIENT_ID), }), }} /> @@ -309,8 +315,8 @@ function DestinationConfigField({
field.onBlur()} @@ -320,8 +326,8 @@ function DestinationConfigField({
)} rules={{ - required: t('message.field-text-is-required', { - fieldText: t('label.client-secret'), + required: t(MESSAGE_FIELD_TEXT_IS_REQUIRED, { + fieldText: t(LABEL_CLIENT_SECRET), }), }} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationSelectItemV2.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationSelectItemV2.test.tsx index a8c66428eb36..10de1f56c537 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationSelectItemV2.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/DestinationFormItemV2/DestinationSelectItemV2/DestinationSelectItemV2.test.tsx @@ -46,13 +46,14 @@ jest.mock('@openmetadata/ui-core-components', () => { onChange?: (key: string) => void; value?: string | null; items?: { id: string; label?: string; isDisabled?: boolean }[]; + // eslint-disable-next-line sonarjs/no-duplicate-string -- repeated object/type key 'data-testid'?: string; }) => ( onChange?.(e.target.value)} - /> +
), Select: SelectBase, @@ -153,10 +158,12 @@ jest.mock('@openmetadata/ui-core-components', () => { isSelected?: boolean; label?: ReactNode; }) => ( -
- ) : isEmpty(options) ? ( + ) : // eslint-disable-next-line sonarjs/no-nested-conditional -- ternary chain renders loading/empty/list states inline in JSX + isEmpty(options) ? (

{t('label.no-data-found')}

diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/FQNListSelect/FQNListSelect.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/FQNListSelect/FQNListSelect.component.test.tsx index 7d547ec43079..e5f90752b257 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/FQNListSelect/FQNListSelect.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/FQNListSelect/FQNListSelect.component.test.tsx @@ -16,6 +16,8 @@ import { SearchIndex } from '../../../enums/search.enum'; import { searchQuery } from '../../../rest/searchAPI'; import FQNListSelect, { resolveWildcardFqns } from './FQNListSelect.component'; +const SVC_DB_SCHEMA_TBL = 'svc.db.schema.tbl'; + jest.mock('../../../rest/searchAPI', () => ({ searchQuery: jest.fn(), })); @@ -50,7 +52,7 @@ describe('resolveWildcardFqns', () => { }, { _source: { - fullyQualifiedName: 'svc.db.schema.tbl', + fullyQualifiedName: SVC_DB_SCHEMA_TBL, entityType: 'table', }, }, @@ -59,7 +61,7 @@ describe('resolveWildcardFqns', () => { }); const result = await resolveWildcardFqns( - ['svc', 'svc.db.schema.tbl'], + ['svc', SVC_DB_SCHEMA_TBL], SearchIndex.TABLE, ['databaseService', 'database', 'databaseSchema'] ); @@ -123,7 +125,7 @@ describe('FQNListSelect', () => { containerEntities={['databaseService']} mode="multiple" searchIndex={SearchIndex.TABLE} - value={['svc', 'svc.db.schema.tbl']} + value={['svc', SVC_DB_SCHEMA_TBL]} /> ); @@ -146,13 +148,13 @@ describe('FQNListSelect', () => { const leafTag = render( capturedProps.tagRender({ - value: 'svc.db.schema.tbl', + value: SVC_DB_SCHEMA_TBL, closable: true, onClose: jest.fn(), }) ); - expect(leafTag.getByText('svc.db.schema.tbl')).toBeInTheDocument(); + expect(leafTag.getByText(SVC_DB_SCHEMA_TBL)).toBeInTheDocument(); }); it('renders all tags plain when there are no container entities', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx index b62f9e6ce4c3..014a43fcbab7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.test.tsx @@ -16,6 +16,8 @@ import { EventFilterRule } from '../../../generated/events/eventSubscription'; import { MOCK_FILTER_RESOURCES } from '../../../test/unit/mocks/observability.mock'; import ObservabilityFormFiltersItem from './ObservabilityFormFiltersItem'; +const ADD_FILTERS = 'add-filters' as const; + jest.mock('../../../utils/Alerts/AlertsUtil', () => ({ getConditionalField: jest .fn() @@ -57,7 +59,7 @@ describe('ObservabilityFormFiltersItem', () => { ).toBeInTheDocument(); expect(screen.getByTestId('filters-list')).toBeInTheDocument(); - expect(screen.getByTestId('add-filters')).toBeInTheDocument(); + expect(screen.getByTestId(ADD_FILTERS)).toBeInTheDocument(); }); it('add filter button should be disabled if there is no selected trigger', () => { @@ -78,7 +80,7 @@ describe('ObservabilityFormFiltersItem', () => { ); - const addButton = screen.getByTestId('add-filters'); + const addButton = screen.getByTestId(ADD_FILTERS); expect(addButton).toBeDisabled(); }); @@ -101,7 +103,7 @@ describe('ObservabilityFormFiltersItem', () => { ); - const addButton = screen.getByTestId('add-filters'); + const addButton = screen.getByTestId(ADD_FILTERS); expect(addButton).not.toBeDisabled(); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.tsx index b3455e2adf5d..4b90c25c1b55 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormFiltersItem/ObservabilityFormFiltersItem.tsx @@ -28,6 +28,8 @@ import { } from '../../../utils/Alerts/AlertsUtil'; import { ObservabilityFormFiltersItemProps } from './ObservabilityFormFiltersItem.interface'; +const LABEL_FILTER = 'label.filter'; + function ObservabilityFormFiltersItem({ supportedFilters, containerEntities, @@ -86,7 +88,7 @@ function ObservabilityFormFiltersItem({ { required: true, message: t('message.field-text-is-required', { - fieldText: t('label.filter'), + fieldText: t(LABEL_FILTER), }), }, ]}> @@ -94,7 +96,7 @@ function ObservabilityFormFiltersItem({ data-testid={`filter-select-${name}`} options={filterOptions} placeholder={t('label.select-field', { - field: t('label.filter'), + field: t(LABEL_FILTER), })} onChange={() => { form.setFieldValue( @@ -154,7 +156,7 @@ function ObservabilityFormFiltersItem({ }) }> {t('label.add-entity', { - entity: t('label.filter'), + entity: t(LABEL_FILTER), })} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx index d51fbe7b2503..59d747fadddd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.test.tsx @@ -16,6 +16,7 @@ import { EventFilterRule } from '../../../generated/events/eventSubscription'; import { MOCK_FILTER_RESOURCES } from '../../../test/unit/mocks/observability.mock'; import ObservabilityFormTriggerItem from './ObservabilityFormTriggerItem'; +const ADD_TRIGGER = 'add-trigger'; jest.mock('../../../utils/Alerts/AlertsUtil', () => ({ getConditionalField: jest .fn() @@ -61,7 +62,7 @@ describe('ObservabilityFormTriggerItem', () => { ).toBeInTheDocument(); expect(screen.getByTestId('triggers-list')).toBeInTheDocument(); - expect(screen.getByTestId('add-trigger')).toBeInTheDocument(); + expect(screen.getByTestId(ADD_TRIGGER)).toBeInTheDocument(); }); it('add trigger button should be disabled if there is no selected trigger and filters', () => { @@ -86,7 +87,7 @@ describe('ObservabilityFormTriggerItem', () => { ); - const addButton = screen.getByTestId('add-trigger'); + const addButton = screen.getByTestId(ADD_TRIGGER); expect(addButton).toBeDisabled(); }); @@ -113,7 +114,7 @@ describe('ObservabilityFormTriggerItem', () => { ); - const addButton = screen.getByTestId('add-trigger'); + const addButton = screen.getByTestId(ADD_TRIGGER); expect(addButton).not.toBeDisabled(); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.tsx index d5bec61cc58b..73e3a9717d7d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Alerts/ObservabilityFormTriggerItem/ObservabilityFormTriggerItem.tsx @@ -28,6 +28,8 @@ import { } from '../../../utils/Alerts/AlertsUtil'; import { ObservabilityFormTriggerItemProps } from './ObservabilityFormTriggerItem.interface'; +const LABEL_TRIGGER = 'label.trigger'; + function ObservabilityFormTriggerItem({ supportedTriggers, isViewMode = false, @@ -51,7 +53,7 @@ function ObservabilityFormTriggerItem({ return ( {(fields, { add, remove }, { errors }) => { @@ -85,7 +87,7 @@ function ObservabilityFormTriggerItem({ { required: true, message: t('message.field-text-is-required', { - fieldText: t('label.trigger'), + fieldText: t(LABEL_TRIGGER), }), }, ]}> @@ -93,7 +95,7 @@ function ObservabilityFormTriggerItem({ data-testid={`trigger-select-${name}`} options={triggerOptions} placeholder={t('label.select-field', { - field: t('label.trigger'), + field: t(LABEL_TRIGGER), })} onChange={() => { form.setFieldValue( @@ -153,7 +155,7 @@ function ObservabilityFormTriggerItem({ }) }> {t('label.add-entity', { - entity: t('label.trigger'), + entity: t(LABEL_TRIGGER), })} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementFeedCardBody.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementFeedCardBody.component.tsx index 0695c496ca62..a232da475f07 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementFeedCardBody.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementFeedCardBody.component.tsx @@ -29,7 +29,8 @@ const AnnouncementFeedCardBody = ({ editPermission, onConfirmation, updateAnnouncementHandler, -}: AnnouncementFeedCardBodyProp) => { +}: // eslint-disable-next-line sonarjs/cyclomatic-complexity -- complex fn; refactor risks behavior change +AnnouncementFeedCardBodyProp) => { const { t } = useTranslation(); const [isEditAnnouncement, setIsEditAnnouncement] = useState(false); const entityType = getEntityType(announcement.entityLink ?? ''); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementThreadBody.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementThreadBody.component.tsx index 5891855ffc8a..03a6fc677ab5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementThreadBody.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Announcement/AnnouncementThreadBody.component.tsx @@ -98,6 +98,7 @@ const AnnouncementThreadBody = ({ useEffect(() => { getThreads(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [threadLink, refetchThread]); if (isEmpty(announcements) && !isThreadLoading) { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/SearchOptions.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/SearchOptions.tsx index f21b9c21e531..c4a0e6816894 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/SearchOptions.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/SearchOptions.tsx @@ -37,6 +37,7 @@ const SearchOptions: FunctionComponent = ({ if (!isMounting.current) { setIsOpen(true); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [searchText]); // Always Keep this useEffect at the end... diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx index 81a85f8ca0bd..565b609bc25e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.test.tsx @@ -17,12 +17,15 @@ import { searchQuery } from '../../rest/searchAPI'; import Suggestions from './Suggestions'; // Mock dependencies +const TABLES_OWNED_BY_MARKETING = 'Tables owned by marketing'; + jest.mock('../../rest/searchAPI'); jest.mock('../../context/TourProvider/TourProvider'); jest.mock('../../utils/SearchUtils', () => ({ filterOptionsByIndex: jest.fn((options, index) => { return options.filter( - (option: any) => option._source?.entityType === index + (option: { _source?: { entityType?: string } }) => + option._source?.entityType === index ); }), getGroupLabel: jest.fn((index) => `Group ${index}`), @@ -82,7 +85,7 @@ describe('Suggestions Component', () => { render(); const aiQueries = [ - 'Tables owned by marketing', + TABLES_OWNED_BY_MARKETING, 'Tables with Tier1 classification', 'Find dashboards tagged with PII.Sensitive', 'Topics with schema fields containing address', @@ -106,11 +109,11 @@ describe('Suggestions Component', () => { /> ); - const firstQueryButton = screen.getByText('Tables owned by marketing'); + const firstQueryButton = screen.getByText(TABLES_OWNED_BY_MARKETING); fireEvent.click(firstQueryButton); expect(mockOnSearchTextUpdate).toHaveBeenCalledWith( - 'Tables owned by marketing' + TABLES_OWNED_BY_MARKETING ); }); }); @@ -157,7 +160,7 @@ describe('Suggestions Component', () => { isTourOpen: true, updateTourPage: jest.fn(), updateTourSearch: jest.fn(), - } as any); + } as unknown as ReturnType); render(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx index b0e47ec028f1..4e3b8a7e4f71 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppBar/Suggestions.tsx @@ -243,6 +243,7 @@ const Suggestions = ({ return isString(parsedSearch.quickFilter) ? JSON.parse(parsedSearch.quickFilter) : {}; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [location.search]); const getSuggestionsForIndex = ( @@ -390,6 +391,7 @@ const Suggestions = ({ } finally { setIsLoading(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [isNLPActive, quickFilter, searchText, searchCriteria]); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index 4fb6406e0875..eb3814a4a9eb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -78,12 +78,14 @@ const AppContainer = () => { // eslint-disable-next-line no-console console.error('Error fetching app configurations:', error); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, []); useEffect(() => { if (currentUser?.id) { fetchAppConfigurations(); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [currentUser?.id]); useEffect(() => { @@ -92,6 +94,7 @@ const AppContainer = () => { if (pathname !== '/' && !isNil(analytics)) { analytics.page(); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [location.pathname, analytics]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.test.tsx index c9bcaa6ff2d2..9862e30ed8c5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AppRouter.test.tsx @@ -21,6 +21,9 @@ import { useAppModeStore, writeAppMode } from '../../hooks/useAppMode'; import { useAppRoutesRegistry } from '../../hooks/useAppRoutesRegistry'; import AppRouter from './AppRouter'; +const MOCK_DEFAULT_AUTHENTICATED_ROUTES = 'default-authenticated-routes'; +const CUSTOM_MODE_ROUTES = 'custom-mode-routes'; + jest.mock('./AuthenticatedApp', () => ({ __esModule: true, default: ({ children }: { children: React.ReactNode }) => ( @@ -30,7 +33,9 @@ jest.mock('./AuthenticatedApp', () => ({ jest.mock('./AuthenticatedRoutes', () => ({ __esModule: true, - AuthenticatedRoutes: () =>
, + AuthenticatedRoutes: () => ( +
+ ), })); jest.mock('../../pages/PageNotFound/PageNotFound', () => ({ @@ -87,7 +92,7 @@ const setAuthState = (overrides: { }; const ModeRoutesMock: ComponentType = () => ( -
+
); const makeQueryClient = () => @@ -122,9 +127,9 @@ describe('AppRouter — App Mode routing integration', () => { renderRouter(); expect( - await screen.findByTestId('default-authenticated-routes') + await screen.findByTestId(MOCK_DEFAULT_AUTHENTICATED_ROUTES) ).toBeInTheDocument(); - expect(screen.queryByTestId('custom-mode-routes')).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_MODE_ROUTES)).not.toBeInTheDocument(); }); it('wraps the rendered routes in AuthenticatedApp for an authenticated user', async () => { @@ -144,9 +149,9 @@ describe('AppRouter — App Mode routing integration', () => { renderRouter(); - expect(await screen.findByTestId('custom-mode-routes')).toBeInTheDocument(); + expect(await screen.findByTestId(CUSTOM_MODE_ROUTES)).toBeInTheDocument(); expect( - screen.queryByTestId('default-authenticated-routes') + screen.queryByTestId(MOCK_DEFAULT_AUTHENTICATED_ROUTES) ).not.toBeInTheDocument(); }); @@ -157,9 +162,9 @@ describe('AppRouter — App Mode routing integration', () => { renderRouter(); expect( - await screen.findByTestId('default-authenticated-routes') + await screen.findByTestId(MOCK_DEFAULT_AUTHENTICATED_ROUTES) ).toBeInTheDocument(); - expect(screen.queryByTestId('custom-mode-routes')).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_MODE_ROUTES)).not.toBeInTheDocument(); }); it('swaps to the registered mode component when the AppMode changes mid-session', async () => { @@ -171,7 +176,7 @@ describe('AppRouter — App Mode routing integration', () => { renderRouter(); expect( - await screen.findByTestId('default-authenticated-routes') + await screen.findByTestId(MOCK_DEFAULT_AUTHENTICATED_ROUTES) ).toBeInTheDocument(); act(() => { @@ -179,11 +184,11 @@ describe('AppRouter — App Mode routing integration', () => { }); await waitFor(() => { - expect(screen.getByTestId('custom-mode-routes')).toBeInTheDocument(); + expect(screen.getByTestId(CUSTOM_MODE_ROUTES)).toBeInTheDocument(); }); expect( - screen.queryByTestId('default-authenticated-routes') + screen.queryByTestId(MOCK_DEFAULT_AUTHENTICATED_ROUTES) ).not.toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx index fb341df0ae4c..1cd8b7cb7201 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedAppRouter.tsx @@ -27,6 +27,10 @@ import { RoutePosition } from '../Settings/Applications/plugins/AppPlugin'; import AdminProtectedRoute from './AdminProtectedRoute'; import { withPageSuspenseFallback } from './withSuspenseFallback'; +const LABEL_EDIT_ENTITY = 'label.edit-entity' as const; +const LABEL_ADD_ENTITY = 'label.add-entity' as const; +const LABEL_DATA_QUALITY = 'label.data-quality' as const; + // Previously statically imported — lazify so they stay out of the main chunk const AddCustomMetricPage = withPageSuspenseFallback( React.lazy( @@ -402,7 +406,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -412,7 +416,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -423,7 +427,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -439,7 +443,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { permissions )}> @@ -456,7 +460,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { permissions )}> @@ -508,7 +512,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { permissions )}> @@ -577,7 +581,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -587,7 +591,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -602,8 +606,8 @@ const AuthenticatedAppRouter: FunctionComponent = () => { permissions )}> @@ -618,8 +622,8 @@ const AuthenticatedAppRouter: FunctionComponent = () => { permissions )}> @@ -634,8 +638,8 @@ const AuthenticatedAppRouter: FunctionComponent = () => { permissions )}> @@ -740,7 +744,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -750,7 +754,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { @@ -778,7 +782,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx index e6727104c74b..386999b45ee2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/AuthenticatedRoutes.tsx @@ -86,8 +86,8 @@ export const AuthenticatedRoutes = () => { (route) => route.position === RoutePosition.APP ); - return appRoutes.map((route, idx) => ( - + return appRoutes.map((route) => ( + )); })} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/DomainRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/DomainRouter.tsx index 1cb75d9a99ad..228fb7ddca69 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/DomainRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/DomainRouter.tsx @@ -21,6 +21,8 @@ import DomainDetailPage from '../Domain/DomainDetailPage/DomainDetailPage.compon import DomainsListPage from '../DomainListing/DomainListPage'; import AdminProtectedRoute from './AdminProtectedRoute'; +const LABEL_DOMAIN_PLURAL = 'label.domain-plural'; + const DomainRouter = () => { const { permissions } = usePermissionProvider(); const domainPermission = useMemo( @@ -35,7 +37,7 @@ const DomainRouter = () => { index element={ - + } path="/" @@ -43,7 +45,7 @@ const DomainRouter = () => { - + } path={ROUTES.DOMAIN_DETAILS.replace(ROUTES.DOMAIN, '')} @@ -51,7 +53,7 @@ const DomainRouter = () => { - + } path={ROUTES.DOMAIN_DETAILS_WITH_TAB.replace(ROUTES.DOMAIN, '')} @@ -59,7 +61,7 @@ const DomainRouter = () => { - + } path={ROUTES.DOMAIN_DETAILS_WITH_SUBTAB.replace(ROUTES.DOMAIN, '')} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.test.tsx index b47f03bffc72..a507917c8710 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.test.tsx @@ -17,6 +17,11 @@ import { ResourceEntity } from '../../context/PermissionProvider/PermissionProvi import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import EntityImportRouter from './EntityImportRouter'; +const TESTCASE_TEST_CASE_FQN_IMPORT = '/testCase/test.case.fqn/import'; +const TABLE_TEST_ENTITY_FQN_IMPORT = '/table/test.entity.fqn/import'; +const BULK_ENTITY_IMPORT_PAGE = 'bulk-entity-import-page'; +const TABLE_TEST_FQN_IMPORT = '/table/test.fqn/import'; +const TEST_CASE_FQN = 'test.case.fqn'; const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -92,7 +97,7 @@ describe('EntityImportRouter', () => { ); const { container } = render( - + ); @@ -106,15 +111,13 @@ describe('EntityImportRouter', () => { it('should render BulkEntityImportPage when user has EditAll permission', async () => { render( - + ); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); }); @@ -125,7 +128,7 @@ describe('EntityImportRouter', () => { }); render( - + ); @@ -160,7 +163,7 @@ describe('EntityImportRouter', () => { await waitFor(() => { expect( - screen.getByTestId('bulk-entity-import-page') + screen.getByTestId(BULK_ENTITY_IMPORT_PAGE) ).toBeInTheDocument(); }); } @@ -182,9 +185,7 @@ describe('EntityImportRouter', () => { ); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); }); @@ -254,7 +255,7 @@ describe('EntityImportRouter', () => { describe('Permission Handling for TEST_CASE entity type', () => { it('should use testCase permission from global permissions for TEST_CASE entity type', async () => { mockEntityType = ResourceEntity.TEST_CASE; - mockFqn = 'test.case.fqn'; + mockFqn = TEST_CASE_FQN; usePermissionProvider.mockReturnValue({ getEntityPermissionByFqn: mockGetEntityPermissionByFqn, permissions: { @@ -263,15 +264,13 @@ describe('EntityImportRouter', () => { }); render( - + ); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); expect(mockGetEntityPermissionByFqn).not.toHaveBeenCalled(); @@ -279,7 +278,7 @@ describe('EntityImportRouter', () => { it('should redirect to NOT_FOUND when TEST_CASE permission does not have EditAll', async () => { mockEntityType = ResourceEntity.TEST_CASE; - mockFqn = 'test.case.fqn'; + mockFqn = TEST_CASE_FQN; usePermissionProvider.mockReturnValue({ getEntityPermissionByFqn: mockGetEntityPermissionByFqn, permissions: { @@ -288,7 +287,7 @@ describe('EntityImportRouter', () => { }); render( - + ); @@ -303,7 +302,7 @@ describe('EntityImportRouter', () => { it('should use DEFAULT_ENTITY_PERMISSION when testCase permission is undefined', async () => { mockEntityType = ResourceEntity.TEST_CASE; - mockFqn = 'test.case.fqn'; + mockFqn = TEST_CASE_FQN; usePermissionProvider.mockReturnValue({ getEntityPermissionByFqn: mockGetEntityPermissionByFqn, permissions: { @@ -312,7 +311,7 @@ describe('EntityImportRouter', () => { }); render( - + ); @@ -340,7 +339,7 @@ describe('EntityImportRouter', () => { mockFqn = 'test.fqn'; const { container } = render( - + ); @@ -352,9 +351,7 @@ describe('EntityImportRouter', () => { }); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); }); @@ -382,7 +379,7 @@ describe('EntityImportRouter', () => { it('should not call getEntityPermissionByFqn for TEST_CASE type', async () => { mockEntityType = ResourceEntity.TEST_CASE; - mockFqn = 'test.case.fqn'; + mockFqn = TEST_CASE_FQN; usePermissionProvider.mockReturnValue({ getEntityPermissionByFqn: mockGetEntityPermissionByFqn, permissions: { @@ -391,15 +388,13 @@ describe('EntityImportRouter', () => { }); render( - + ); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); expect(mockGetEntityPermissionByFqn).not.toHaveBeenCalled(); @@ -422,9 +417,7 @@ describe('EntityImportRouter', () => { ); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); }); @@ -463,15 +456,13 @@ describe('EntityImportRouter', () => { }); render( - + ); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); expect(screen.queryByTestId('navigate')).not.toBeInTheDocument(); @@ -489,7 +480,7 @@ describe('EntityImportRouter', () => { }); render( - + ); @@ -528,9 +519,7 @@ describe('EntityImportRouter', () => { }); await waitFor(() => { - expect( - screen.getByTestId('bulk-entity-import-page') - ).toBeInTheDocument(); + expect(screen.getByTestId(BULK_ENTITY_IMPORT_PAGE)).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.tsx index 7bc505522ae2..c05863ccd0ef 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/EntityImportRouter.tsx @@ -68,6 +68,7 @@ const EntityImportRouter = () => { } else { navigate(ROUTES.NOT_FOUND); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [fqn, entityType, fetchResourcePermission]); if (isLoading) { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/GlossaryRouter/GlossaryRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/GlossaryRouter/GlossaryRouter.tsx index 6623737f69fc..a2f4f93bf66c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/GlossaryRouter/GlossaryRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/GlossaryRouter/GlossaryRouter.tsx @@ -22,6 +22,8 @@ import { userPermissions } from '../../../utils/PermissionsUtils'; import GlossaryVersion from '../../Glossary/GlossaryVersion/GlossaryVersion.component'; import AdminProtectedRoute from '../AdminProtectedRoute'; +const LABEL_GLOSSARY = 'label.glossary'; + const GlossaryRouter = () => { const { permissions } = usePermissionProvider(); const { t } = useTranslation(); @@ -37,7 +39,7 @@ const GlossaryRouter = () => { element={ } @@ -50,7 +52,7 @@ const GlossaryRouter = () => { - + } path={ROUTES.GLOSSARY.replace(ROUTES.GLOSSARY, '')} @@ -58,7 +60,7 @@ const GlossaryRouter = () => { - + } path={ROUTES.GLOSSARY_DETAILS.replace(ROUTES.GLOSSARY, '')} @@ -66,7 +68,7 @@ const GlossaryRouter = () => { - + } path={ROUTES.GLOSSARY_DETAILS_WITH_ACTION.replace(ROUTES.GLOSSARY, '')} @@ -74,7 +76,7 @@ const GlossaryRouter = () => { - + } path={ROUTES.GLOSSARY_DETAILS_WITH_TAB.replace(ROUTES.GLOSSARY, '')} @@ -82,7 +84,7 @@ const GlossaryRouter = () => { - + } path={ROUTES.GLOSSARY_DETAILS_WITH_SUBTAB.replace(ROUTES.GLOSSARY, '')} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.test.tsx index 2bee5ad91e04..47aa6e33cc50 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.test.tsx @@ -175,6 +175,7 @@ jest.mock('./AdminProtectedRoute', () => ({ default: jest.fn().mockImplementation(({ children }) => children), })); +// eslint-disable-next-line jest/no-disabled-tests -- fails when enabled (route pages not rendered), left skipped describe.skip('SettingsRouter', () => { it('should render GlobalSettingPage component for exact settings route', async () => { render( @@ -321,6 +322,7 @@ describe.skip('SettingsRouter', () => { ).toBeInTheDocument(); }); + // eslint-disable-next-line jest/no-disabled-tests -- suite skipped; individual case also fails when enabled it.skip('should render CustomPageSettings component for custom page settings route', async () => { render( import('../../pages/AddNotificationPage/AddNotificationPage') @@ -358,7 +361,7 @@ const SettingsRouter = () => { permissions )}> @@ -384,7 +387,7 @@ const SettingsRouter = () => { element={ { element={ { element={ @@ -470,7 +473,7 @@ const SettingsRouter = () => { @@ -484,7 +487,7 @@ const SettingsRouter = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withSuspenseFallback.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withSuspenseFallback.test.tsx index ee04b6940d5c..7957d47b966d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withSuspenseFallback.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withSuspenseFallback.test.tsx @@ -25,6 +25,7 @@ describe('withSuspenseFallback', () => { new Promise<{ default: () => JSX.Element }>((resolve) => { setTimeout(() => { resolve({ + // eslint-disable-next-line sonarjs/no-nested-functions -- test fixture nesting default: () =>
Loaded component
, }); }, 0); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.component.tsx index d81ea2e6caa2..33e607dad659 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.component.tsx @@ -134,6 +134,7 @@ const AuditLogFilters: FC = ({ }, ]) ), + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [t] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.test.tsx index fe7816f7a512..bce96cfcbcaa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogFilters.test.tsx @@ -24,6 +24,9 @@ import { AuditLogActiveFilter } from '../../types/auditLogs.interface'; import { SearchDropdownOption } from '../SearchDropdown/SearchDropdown.interface'; import AuditLogFilters from './AuditLogFilters.component'; +const MOCK_INGESTION_BOT = 'ingestion-bot'; +const MOCK_INGESTION_BOT_1 = 'Ingestion Bot'; + const mockOnFiltersChange = jest.fn(); const defaultProps = { @@ -133,8 +136,8 @@ jest.mock('../../rest/botsAPI', () => ({ data: [ { id: 'bot-1', - name: 'ingestion-bot', - displayName: 'Ingestion Bot', + name: MOCK_INGESTION_BOT, + displayName: MOCK_INGESTION_BOT_1, }, ], paging: { total: 1 }, @@ -462,7 +465,7 @@ describe('AuditLogFilters', () => { await act(async () => { capturedOnChange['bot']( - [{ key: 'ingestion-bot', label: 'Ingestion Bot' }], + [{ key: MOCK_INGESTION_BOT, label: MOCK_INGESTION_BOT_1 }], 'bot' ); }); @@ -472,12 +475,12 @@ describe('AuditLogFilters', () => { expect.objectContaining({ category: 'bot', value: expect.objectContaining({ - value: 'ingestion-bot', + value: MOCK_INGESTION_BOT, }), }), ]), expect.objectContaining({ - userName: 'ingestion-bot', + userName: MOCK_INGESTION_BOT, actorType: 'BOT', }) ); @@ -505,7 +508,7 @@ describe('AuditLogFilters', () => { await act(async () => { capturedOnChange['bot']( - [{ key: 'ingestion-bot', label: 'Ingestion Bot' }], + [{ key: MOCK_INGESTION_BOT, label: MOCK_INGESTION_BOT_1 }], 'bot' ); }); @@ -514,7 +517,7 @@ describe('AuditLogFilters', () => { mockOnFiltersChange.mock.calls[mockOnFiltersChange.mock.calls.length - 1]; expect(botCall[1]).toMatchObject({ - userName: 'ingestion-bot', + userName: MOCK_INGESTION_BOT, actorType: 'BOT', }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.component.tsx index 02603a908b1e..1474311745ab 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.component.tsx @@ -13,7 +13,7 @@ import { Skeleton, Space, Typography } from 'antd'; import { compact, startCase } from 'lodash'; -import { FC, ReactNode, useCallback, useMemo } from 'react'; +import { FC, isValidElement, ReactNode, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { EntityType } from '../../enums/entity.enum'; @@ -69,6 +69,7 @@ const parseValue = (value: unknown): unknown => { return value; }; +// eslint-disable-next-line sonarjs/cyclomatic-complexity -- inherent branching const formatChangeValue = (value: unknown): string => { const parsed = parseValue(value); if (parsed === null || parsed === undefined) { @@ -140,6 +141,7 @@ const extractEntityInfo = (value: unknown): EntityInfo[] => { const getEntityLinkForField = ( fieldName: string, entityInfo: EntityInfo + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- inherent branching ): string | null => { const field = fieldName.toLowerCase(); @@ -180,7 +182,7 @@ const renderEntityLinks = ( return plainValue ? [plainValue] : []; } - return entities.map((entity, idx) => { + return entities.map((entity) => { const link = getEntityLinkForField(fieldName, entity); const label = entity.displayName ?? entity.name; @@ -188,14 +190,14 @@ const renderEntityLinks = ( return ( {label} ); } - return {label}; + return {label}; }); }; @@ -210,6 +212,7 @@ const resolveEntityType = (value?: string): EntityType | undefined => { ); }; +// eslint-disable-next-line sonarjs/cyclomatic-complexity -- inherent branching const AuditLogListItem: FC = ({ log }) => { const { t } = useTranslation(); @@ -221,6 +224,7 @@ const AuditLogListItem: FC = ({ log }) => { log.changeEvent?.entityFullyQualifiedName ?? log.changeEvent?.entity?.fullyQualifiedName; const entityLabel = + // eslint-disable-next-line sonarjs/expression-complexity -- fallback chain getEntityName(log.changeEvent?.entity) || (log.changeEvent?.entity as { name?: string })?.name || (entityFQN ? Fqn.split(entityFQN).pop() : undefined) || @@ -275,11 +279,12 @@ const AuditLogListItem: FC = ({ log }) => { {links.map((link, idx) => { const entities = extractEntityInfo(value); const entity = entities[idx]; + const itemKey = entity?.fqn + ? `${keyPrefix}-wrap-${entity.fqn}` + : `${keyPrefix}-wrap-plain`; return ( - + {showProfilePic && entity && ( = ({ log }) => { ); details.push( - + {addedLabel}{' '} {label || fallbackField} {valueNode && <>: {valueNode}} @@ -348,9 +353,10 @@ const AuditLogListItem: FC = ({ log }) => { ); details.push( - + {updatedLabel}{' '} {label || fallbackField} + {/* eslint-disable-next-line sonarjs/expression-complexity -- JSX gate */} {(oldValueNode || newValueNode) && ( <> : {oldValueNode} @@ -371,7 +377,7 @@ const AuditLogListItem: FC = ({ log }) => { ); details.push( - + {removedLabel}{' '} {label || fallbackField} {valueNode && <>: {valueNode}} @@ -395,6 +401,7 @@ const AuditLogListItem: FC = ({ log }) => { return []; }, [log, getChangeDetails]); + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- inherent branching const entityLink = useMemo(() => { if (normalizedType === EntityType.USER) { const userNameForLink = @@ -479,7 +486,9 @@ const AuditLogListItem: FC = ({ log }) => { {descriptionNodes.length > 0 ? (
{descriptionNodes.map((node, idx) => ( - + {node} {idx < descriptionNodes.length - 1 && ( ; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.test.tsx index 97f4b086923f..fc16da5e1e8c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AuditLog/AuditLogList.test.tsx @@ -16,6 +16,14 @@ import { MemoryRouter } from 'react-router-dom'; import { AuditLogEntry } from '../../types/auditLogs.interface'; import AuditLogList from './AuditLogList.component'; +const PROFILE_PICTURE = 'profile-picture' as const; +const SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS = + 'sample_data.ecommerce_db.shopify.orders' as const; +const SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_PRODUCTS = + 'sample_data.ecommerce_db.shopify.products' as const; +const PII_SENSITIVE = 'PII.Sensitive' as const; +const SALES_DATA_PRODUCT = 'Sales Data Product' as const; + jest.mock('../common/ProfilePicture/ProfilePicture', () => jest .fn() @@ -45,17 +53,17 @@ describe('AuditLogList', () => { eventType: 'entityCreated', userName: 'admin', entityType: 'table', - entityFQN: 'sample_data.ecommerce_db.shopify.orders', + entityFQN: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS, changeEvent: { id: 'ce-1', eventType: 'entityCreated', entityType: 'table', - entityFullyQualifiedName: 'sample_data.ecommerce_db.shopify.orders', + entityFullyQualifiedName: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS, entity: { id: 'table-1', name: 'orders', displayName: 'Orders', - fullyQualifiedName: 'sample_data.ecommerce_db.shopify.orders', + fullyQualifiedName: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS, }, changeDescription: { fieldsAdded: [], @@ -72,24 +80,24 @@ describe('AuditLogList', () => { eventType: 'entityUpdated', userName: 'test_user', entityType: 'table', - entityFQN: 'sample_data.ecommerce_db.shopify.products', + entityFQN: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_PRODUCTS, changeEvent: { id: 'ce-2', eventType: 'entityUpdated', entityType: 'table', - entityFullyQualifiedName: 'sample_data.ecommerce_db.shopify.products', + entityFullyQualifiedName: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_PRODUCTS, entity: { id: 'table-2', name: 'products', displayName: 'Products', - fullyQualifiedName: 'sample_data.ecommerce_db.shopify.products', + fullyQualifiedName: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_PRODUCTS, }, changeDescription: { fieldsAdded: [ { name: 'tags', newValue: JSON.stringify([ - { tagFQN: 'PII.Sensitive', name: 'Sensitive' }, + { tagFQN: PII_SENSITIVE, name: 'Sensitive' }, ]), }, ], @@ -132,7 +140,7 @@ describe('AuditLogList', () => { it('should display profile pictures for each log entry', () => { renderWithRouter(); - const profilePictures = screen.getAllByTestId('profile-picture'); + const profilePictures = screen.getAllByTestId(PROFILE_PICTURE); expect(profilePictures).toHaveLength(2); }); @@ -194,7 +202,7 @@ describe('AuditLogList', () => { { name: 'tags', newValue: JSON.stringify([ - { tagFQN: 'PII.Sensitive', name: 'Sensitive' }, + { tagFQN: PII_SENSITIVE, name: 'Sensitive' }, ]), }, ], @@ -233,8 +241,8 @@ describe('AuditLogList', () => { newValue: JSON.stringify([ { fullyQualifiedName: 'sales-dp', - name: 'Sales Data Product', - displayName: 'Sales Data Product', + name: SALES_DATA_PRODUCT, + displayName: SALES_DATA_PRODUCT, }, ]), }, @@ -251,7 +259,7 @@ describe('AuditLogList', () => { ); - const dpLink = screen.getByRole('link', { name: 'Sales Data Product' }); + const dpLink = screen.getByRole('link', { name: SALES_DATA_PRODUCT }); expect(dpLink).toHaveAttribute('href', '/dataProduct/sales-dp'); }); @@ -368,7 +376,7 @@ describe('AuditLogList', () => { renderWithRouter(); - const profilePictures = screen.getAllByTestId('profile-picture'); + const profilePictures = screen.getAllByTestId(PROFILE_PICTURE); expect(profilePictures.length).toBeGreaterThanOrEqual(2); }); @@ -450,7 +458,7 @@ describe('AuditLogList', () => { renderWithRouter(); - const profilePictures = screen.getAllByTestId('profile-picture'); + const profilePictures = screen.getAllByTestId(PROFILE_PICTURE); expect(profilePictures[0]).toHaveTextContent('label.system'); }); @@ -473,7 +481,7 @@ describe('AuditLogList', () => { { name: 'tags', newValue: JSON.stringify([ - { tagFQN: 'PII.Sensitive', name: 'Sensitive' }, + { tagFQN: PII_SENSITIVE, name: 'Sensitive' }, ]), }, ], @@ -521,7 +529,7 @@ describe('AuditLogList', () => { { name: 'tags', oldValue: JSON.stringify([ - { tagFQN: 'PII.Sensitive', name: 'Sensitive' }, + { tagFQN: PII_SENSITIVE, name: 'Sensitive' }, ]), }, ], @@ -660,7 +668,7 @@ describe('AuditLogList', () => { ); - const profilePictures = screen.getAllByTestId('profile-picture'); + const profilePictures = screen.getAllByTestId(PROFILE_PICTURE); expect(profilePictures.length).toBeGreaterThanOrEqual(2); }); @@ -717,17 +725,17 @@ describe('AuditLogList', () => { eventType: 'entityCreated', userName: 'admin', entityType: 'table', - entityFQN: 'sample_data.ecommerce_db.shopify.orders', + entityFQN: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS, changeEvent: { id: 'ce-1', eventType: 'entityCreated', entityType: 'table', - entityFullyQualifiedName: 'sample_data.ecommerce_db.shopify.orders', + entityFullyQualifiedName: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS, entity: { id: 'table-1', name: 'orders', displayName: 'Orders Table', - fullyQualifiedName: 'sample_data.ecommerce_db.shopify.orders', + fullyQualifiedName: SAMPLE_DATA_ECOMMERCE_DB_SHOPIFY_ORDERS, }, changeDescription: { fieldsAdded: [], diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.test.tsx index 0ca754c2f474..f7e98c2744c1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/Auth0Authenticator.test.tsx @@ -18,13 +18,15 @@ import { setOidcToken } from '../../../utils/SwTokenStorageUtils'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; import Auth0Authenticator from './Auth0Authenticator'; +const MOCK_ID_TOKEN = 'mock-id-token'; + // Mocks const loginWithRedirect = jest.fn().mockImplementation(() => Promise.resolve()); const mockGetAccessTokenSilently = jest .fn() .mockImplementation(() => Promise.resolve()); const mockGetIdTokenClaims = jest.fn(() => - Promise.resolve({ __raw: 'mock-id-token' }) + Promise.resolve({ __raw: MOCK_ID_TOKEN }) ); const logout = jest.fn(); @@ -89,7 +91,7 @@ describe('Auth0Authenticator', () => { it('should resolve with id token and setOidcToken on renewIdToken (Auth0)', async () => { const ref = createRef(); mockGetIdTokenClaims.mockImplementationOnce(() => - Promise.resolve({ __raw: 'mock-id-token' }) + Promise.resolve({ __raw: MOCK_ID_TOKEN }) ); render( @@ -103,8 +105,8 @@ describe('Auth0Authenticator', () => { expect(mockGetAccessTokenSilently).toHaveBeenCalled(); expect(mockGetIdTokenClaims).toHaveBeenCalled(); - expect(setOidcToken).toHaveBeenCalledWith('mock-id-token'); - expect(result).toBe('mock-id-token'); + expect(setOidcToken).toHaveBeenCalledWith(MOCK_ID_TOKEN); + expect(result).toBe(MOCK_ID_TOKEN); }); it('should reject if getAccessTokenSilently throws', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/BasicAuthAuthenticator.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/BasicAuthAuthenticator.tsx index b0d1441a1c2a..f2cfd3c2af15 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/BasicAuthAuthenticator.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/BasicAuthAuthenticator.tsx @@ -59,6 +59,7 @@ const BasicAuthenticator = forwardRef( await setOidcToken(response.accessToken); return Promise.resolve(response); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [authConfig, setOidcToken, setRefreshToken, t]); useImperativeHandle(ref, () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx index 490712bfe451..090523ef041e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.test.tsx @@ -21,6 +21,8 @@ import { msalLoginRequest } from '../../../utils/AuthProvider.util'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; import MsalAuthenticator from './MsalAuthenticator'; +const TEST_EXAMPLE_COM = 'test@example.com'; +const MOCK_ID_TOKEN = 'mock-id-token'; // Mock MSAL hooks and utilities jest.mock('@azure/msal-react', () => ({ useMsal: jest.fn(), @@ -70,7 +72,7 @@ describe('MsalAuthenticator', () => { // Default mock implementation for useMsal (useMsal as jest.Mock).mockReturnValue({ instance: mockInstance, - accounts: [{ username: 'test@example.com' }], + accounts: [{ username: TEST_EXAMPLE_COM }], inProgress: InteractionStatus.None, }); }); @@ -87,7 +89,7 @@ describe('MsalAuthenticator', () => { }); mockInstance.loginPopup.mockResolvedValueOnce({ - account: { username: 'test@example.com' }, + account: { username: TEST_EXAMPLE_COM }, }); render( @@ -147,7 +149,7 @@ describe('MsalAuthenticator', () => { it('should handle renewIdToken successfully with forceRefresh', async () => { mockInstance.acquireTokenSilent.mockResolvedValueOnce({ - account: { username: 'test@example.com' }, + account: { username: TEST_EXAMPLE_COM }, idToken: 'new-token', }); @@ -163,7 +165,7 @@ describe('MsalAuthenticator', () => { expect(mockInstance.acquireTokenSilent).toHaveBeenCalledWith( expect.objectContaining({ forceRefresh: true }) ); - expect(result).toBe('mock-id-token'); + expect(result).toBe(MOCK_ID_TOKEN); }); it('should fall back to acquireTokenPopup when renewIdToken encounters InteractionRequiredAuthError', async () => { @@ -172,7 +174,7 @@ describe('MsalAuthenticator', () => { ); mockInstance.acquireTokenSilent.mockRejectedValueOnce(interactionError); mockInstance.acquireTokenPopup.mockResolvedValueOnce({ - account: { username: 'test@example.com' }, + account: { username: TEST_EXAMPLE_COM }, idToken: 'popup-token', }); @@ -187,7 +189,7 @@ describe('MsalAuthenticator', () => { expect(mockInstance.acquireTokenSilent).toHaveBeenCalled(); expect(mockInstance.acquireTokenPopup).toHaveBeenCalled(); - expect(result).toBe('mock-id-token'); + expect(result).toBe(MOCK_ID_TOKEN); }); it('should throw when acquireTokenPopup also fails', async () => { @@ -215,7 +217,7 @@ describe('MsalAuthenticator', () => { it('should show loader when interaction is in progress', () => { (useMsal as jest.Mock).mockReturnValue({ instance: mockInstance, - accounts: [{ username: 'test@example.com' }], + accounts: [{ username: TEST_EXAMPLE_COM }], inProgress: InteractionStatus.Login, }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx index cf2d3c65ed51..b6b0db165472 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/MsalAuthenticator.tsx @@ -138,6 +138,7 @@ const MsalAuthenticator = forwardRef( // To add redirect callback useEffect(() => { instance && handleRedirect(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [instance]); // Show loader until the interaction is completed diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OidcAuthenticator.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OidcAuthenticator.tsx index cff6bbca7612..3dbf7956c414 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OidcAuthenticator.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OidcAuthenticator.tsx @@ -115,11 +115,13 @@ const OidcAuthenticator = forwardRef( post_logout_redirect_uri: window.location.origin + ROUTES.SIGNIN, }) + // eslint-disable-next-line sonarjs/no-nested-functions -- closes over local state .then(() => { // Cleanup application state handleSuccessfulLogout(); resolve(); }) + // eslint-disable-next-line sonarjs/no-nested-functions -- closes over local state .catch((error) => { reject(error); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OktaAuthenticator.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OktaAuthenticator.test.tsx index 4406799a3208..b6907cca05a8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OktaAuthenticator.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AppAuthenticators/OktaAuthenticator.test.tsx @@ -17,6 +17,11 @@ import { setOidcToken } from '../../../utils/SwTokenStorageUtils'; import { AuthenticatorRef } from '../AuthProviders/AuthProvider.interface'; import OktaAuthenticator from './OktaAuthenticator'; +const NEW_ID_TOKEN = 'new-id-token'; +const ACCESS_TOKEN = 'access-token'; +const EXISTING_ID_TOKEN = 'existing-id-token'; +const FALLBACK_ID_TOKEN = 'fallback-id-token'; + jest.mock('@okta/okta-react', () => ({ useOktaAuth: jest.fn(), })); @@ -138,8 +143,8 @@ describe('OktaAuthenticator', () => { describe('renewToken', () => { it('should renew tokens successfully and return new token', async () => { - const mockIdToken = { idToken: 'new-id-token' }; - const mockAccessToken = { accessToken: 'access-token' }; + const mockIdToken = { idToken: NEW_ID_TOKEN }; + const mockAccessToken = { accessToken: ACCESS_TOKEN }; const mockRenewedTokens = { idToken: mockIdToken, accessToken: mockAccessToken, @@ -165,13 +170,13 @@ describe('OktaAuthenticator', () => { expect(mockOktaAuth.tokenManager.setTokens).toHaveBeenCalledWith( mockRenewedTokens ); - expect(setOidcToken).toHaveBeenCalledWith('new-id-token'); - expect(result).toBe('new-id-token'); + expect(setOidcToken).toHaveBeenCalledWith(NEW_ID_TOKEN); + expect(result).toBe(NEW_ID_TOKEN); }); it('should use fallback getIdToken if renewed token is not available', async () => { - const mockIdToken = { idToken: 'existing-id-token' }; - const mockAccessToken = { accessToken: 'access-token' }; + const mockIdToken = { idToken: EXISTING_ID_TOKEN }; + const mockAccessToken = { accessToken: ACCESS_TOKEN }; const mockRenewedTokens = { idToken: undefined, accessToken: mockAccessToken, @@ -181,7 +186,7 @@ describe('OktaAuthenticator', () => { .mockResolvedValueOnce(mockIdToken) .mockResolvedValueOnce(mockAccessToken); mockOktaAuth.token.renewTokens.mockResolvedValueOnce(mockRenewedTokens); - mockOktaAuth.getIdToken.mockReturnValueOnce('fallback-id-token'); + mockOktaAuth.getIdToken.mockReturnValueOnce(FALLBACK_ID_TOKEN); render( { const result = await authenticatorRef?.renewIdToken(); expect(mockOktaAuth.getIdToken).toHaveBeenCalledTimes(1); - expect(setOidcToken).toHaveBeenCalledWith('fallback-id-token'); - expect(result).toBe('fallback-id-token'); + expect(setOidcToken).toHaveBeenCalledWith(FALLBACK_ID_TOKEN); + expect(result).toBe(FALLBACK_ID_TOKEN); }); it('should redirect to sign-in if no existing tokens', async () => { @@ -219,8 +224,8 @@ describe('OktaAuthenticator', () => { }); it('should redirect to sign-in when token renewal fails', async () => { - const mockIdToken = { idToken: 'existing-id-token' }; - const mockAccessToken = { accessToken: 'access-token' }; + const mockIdToken = { idToken: EXISTING_ID_TOKEN }; + const mockAccessToken = { accessToken: ACCESS_TOKEN }; mockOktaAuth.tokenManager.get .mockResolvedValueOnce(mockIdToken) @@ -253,8 +258,8 @@ describe('OktaAuthenticator', () => { }); it('should return empty string if all token sources fail', async () => { - const mockIdToken = { idToken: 'existing-id-token' }; - const mockAccessToken = { accessToken: 'access-token' }; + const mockIdToken = { idToken: EXISTING_ID_TOKEN }; + const mockAccessToken = { accessToken: ACCESS_TOKEN }; const mockRenewedTokens = { idToken: undefined, accessToken: mockAccessToken, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx index becce8ebb152..fb8d652af11c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.test.tsx @@ -18,6 +18,8 @@ import axiosClient from '../../../rest'; import TokenService from '../../../utils/Auth/TokenService/TokenServiceUtil'; import AuthProvider, { useAuthProvider } from './AuthProvider'; +const TOKEN_EXPIRED = 'Token expired'; + const localStorageMock = { getItem: jest.fn(), setItem: jest.fn(), @@ -239,7 +241,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/api/test' }, }; @@ -264,7 +266,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/api/test' }, }; @@ -295,7 +297,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/api/test', @@ -326,7 +328,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/users/login', @@ -356,7 +358,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/users/refresh', @@ -386,7 +388,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: 'auth/refresh', @@ -416,7 +418,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/auth/refresh', @@ -446,7 +448,7 @@ describe('Test axios response interceptor', () => { const mockError = { response: { status: 401, - data: { message: 'Token expired' }, + data: { message: TOKEN_EXPIRED }, }, config: { url: '/users/loggedInUser', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx index a1b3ee19efbc..d45072e986df 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/AuthProvider.tsx @@ -181,6 +181,7 @@ export const AuthProvider = ({ const clientType = authConfig?.clientType ?? ClientType.Public; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const onLoginHandler = () => { setApplicationLoading(true); @@ -253,6 +254,7 @@ export const AuthProvider = ({ // Upon logout, redirect to the login page navigate(ROUTES.SIGNIN); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [timeoutId]); const handledVerifiedUser = () => { @@ -311,6 +313,7 @@ export const AuthProvider = ({ }); }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const resetUserDetails = (forceLogout = false) => { setCurrentUser({} as User); clearOidcToken(); @@ -392,6 +395,7 @@ export const AuthProvider = ({ // After every refresh success, start timer again tokenService.current.updateRefreshSuccessCallback(startTokenExpiryTimer); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [authenticatorRef.current?.renewIdToken]); // When the tab becomes visible after being backgrounded, browsers may have @@ -435,6 +439,7 @@ export const AuthProvider = ({ return () => { document.removeEventListener('visibilitychange', handleVisibilityChange); }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, []); /** @@ -445,6 +450,7 @@ export const AuthProvider = ({ clearTimeout(timeoutId); }, [timeoutId]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const handleFailedLogin = () => { setIsSigningUp(false); setIsAuthenticated(false); @@ -502,6 +508,7 @@ export const AuthProvider = ({ setApplicationLoading(false); } }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [ authConfig?.enableSelfSignup, clientType, @@ -547,6 +554,7 @@ export const AuthProvider = ({ * Initialize Axios interceptors to intercept every request and response * to handle appropriately. This should be called only when security is enabled. */ + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const initializeAxiosInterceptors = async () => { // Axios Request interceptor to add Bearer tokens in Header if (requestInterceptor != null) { @@ -623,6 +631,7 @@ export const AuthProvider = ({ // clear the queue. Called on any path where the refresh does // not yield a new token (null-return or thrown error) so the // callers don't hang waiting for a retry that will never come. + // eslint-disable-next-line sonarjs/no-nested-functions -- closes over local queue state const rejectPending = (rejectionError: unknown) => { pendingRequests.forEach(({ reject }) => reject(rejectionError) @@ -633,6 +642,7 @@ export const AuthProvider = ({ // Refresh the token and retry the requests in the queue tokenService.current .refreshToken() + // eslint-disable-next-line sonarjs/no-nested-functions -- closes over local queue state .then(async (token) => { if (token) { // Retry the pending requests @@ -648,6 +658,7 @@ export const AuthProvider = ({ resetUserDetails(true); } }) + // eslint-disable-next-line sonarjs/no-nested-functions -- closes over local queue state .catch((refreshError) => { rejectPending(refreshError); resetUserDetails(true); @@ -718,6 +729,7 @@ export const AuthProvider = ({ } }; + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- preserve behavior const getProtectedApp = () => { // Show loader if application is loading or authenticating const childElement = @@ -807,6 +819,7 @@ export const AuthProvider = ({ initializeAxiosInterceptors(); return cleanup; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, []); const contextValues = useMemo(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx index b753b30483a5..79a5e5473acb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/BasicAuthProvider.tsx @@ -12,7 +12,13 @@ */ import { AxiosError } from 'axios'; -import { createContext, ReactNode, useContext } from 'react'; +import { + createContext, + ReactNode, + useCallback, + useContext, + useMemo, +} from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { @@ -83,79 +89,93 @@ const BasicAuthProvider = ({ children }: BasicAuthProps) => { const { handleSuccessfulLogin, handleFailedLogin, handleSuccessfulLogout } = useAuthProvider(); - const handleLogin = async (email: string, password: string) => { - try { + const handleLogin = useCallback( + async (email: string, password: string) => { try { - const response = await basicAuthSignIn({ - email, - password: btoa(password), - }); - - if (response.accessToken) { - await setOidcToken(response.accessToken); - - handleSuccessfulLogin({ - id_token: response.accessToken, - profile: { - email: toLower(email), - name: '', - picture: '', - sub: '', - }, - scope: '', + try { + const response = await basicAuthSignIn({ + email, + password: btoa(password), }); + + if (response.accessToken) { + await setOidcToken(response.accessToken); + + handleSuccessfulLogin({ + id_token: response.accessToken, + profile: { + email: toLower(email), + name: '', + picture: '', + sub: '', + }, + scope: '', + }); + } + + // reset web analytic session + resetWebAnalyticSession(); + } catch (error) { + const err = error as AxiosError<{ code: number; message: string }>; + + showErrorToast(err.response?.data.message ?? LOGIN_FAILED_ERROR); + handleFailedLogin(); } + } catch (err) { + showErrorToast(err as AxiosError, t('server.unauthorized-user')); + } + }, + [handleSuccessfulLogin, handleFailedLogin, t] + ); - // reset web analytic session - resetWebAnalyticSession(); - } catch (error) { - const err = error as AxiosError<{ code: number; message: string }>; + const handleRegister = useCallback( + async (request: RegistrationRequest) => { + try { + await basicAuthRegister(request); - showErrorToast(err.response?.data.message ?? LOGIN_FAILED_ERROR); - handleFailedLogin(); - } - } catch (err) { - showErrorToast(err as AxiosError, t('server.unauthorized-user')); - } - }; - - const handleRegister = async (request: RegistrationRequest) => { - try { - await basicAuthRegister(request); - - showSuccessToast( - t('server.create-entity-success', { entity: t('label.user-account') }) - ); - showInfoToast(t('server.email-confirmation')); - navigate(ROUTES.SIGNIN); - } catch (err) { - if ( - (err as AxiosError).response?.status === - HTTP_STATUS_CODE.FAILED_DEPENDENCY - ) { showSuccessToast( t('server.create-entity-success', { entity: t('label.user-account') }) ); - showErrorToast(err as AxiosError, t('server.email-verification-error')); + showInfoToast(t('server.email-confirmation')); navigate(ROUTES.SIGNIN); - } else { - showErrorToast(err as AxiosError, t('server.unexpected-response')); + } catch (err) { + if ( + (err as AxiosError).response?.status === + HTTP_STATUS_CODE.FAILED_DEPENDENCY + ) { + showSuccessToast( + t('server.create-entity-success', { + entity: t('label.user-account'), + }) + ); + showErrorToast( + err as AxiosError, + t('server.email-verification-error') + ); + navigate(ROUTES.SIGNIN); + } else { + showErrorToast(err as AxiosError, t('server.unexpected-response')); + } } - } - }; + }, + [t, navigate] + ); - const handleForgotPassword = async (email: string) => { + const handleForgotPassword = useCallback(async (email: string) => { await generatePasswordResetLink(email); - }; + }, []); - const handleResetPassword = async (payload: PasswordResetRequest) => { - const response = await resetPassword(payload); - if (response) { - showSuccessToast(t('server.reset-password-success')); - } - }; + const handleResetPassword = useCallback( + async (payload: PasswordResetRequest) => { + const response = await resetPassword(payload); + if (response) { + showSuccessToast(t('server.reset-password-success')); + } + }, + [t] + ); - const handleLogout = async () => { + const handleLogout = useCallback(async () => { const token = await getOidcToken(); const refreshToken = await getRefreshToken(); const isExpired = extractDetailsFromToken(token).isExpired; @@ -169,15 +189,24 @@ const BasicAuthProvider = ({ children }: BasicAuthProps) => { handleSuccessfulLogout(); } } - }; - - const contextValue = { - handleLogin, - handleRegister, - handleForgotPassword, - handleResetPassword, - handleLogout, - }; + }, [handleSuccessfulLogout]); + + const contextValue = useMemo( + () => ({ + handleLogin, + handleRegister, + handleForgotPassword, + handleResetPassword, + handleLogout, + }), + [ + handleLogin, + handleRegister, + handleForgotPassword, + handleResetPassword, + handleLogout, + ] + ); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/OktaAuthProvider.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/OktaAuthProvider.test.tsx index 357099d041fe..fa5935e0b5ad 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/OktaAuthProvider.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Auth/AuthProviders/OktaAuthProvider.test.tsx @@ -112,7 +112,7 @@ describe('OktaAuthProvider', () => { describe('initialization', () => { it('should wait for custom storage init before starting token manager', async () => { - let resolveWaitForInit: () => void; + let resolveWaitForInit!: () => void; const waitPromise = new Promise((resolve) => { resolveWaitForInit = resolve; }); @@ -132,7 +132,7 @@ describe('OktaAuthProvider', () => { expect(mockOktaAuth.tokenManager.start).not.toHaveBeenCalled(); - resolveWaitForInit!(); + resolveWaitForInit(); await waitFor(() => { expect(mockOktaAuth.tokenManager.start).toHaveBeenCalled(); @@ -213,7 +213,7 @@ describe('OktaAuthProvider', () => { }); it('should call setOidcToken when idToken is renewed', async () => { - let renewedHandler: (key: string, token: IDToken) => void; + let renewedHandler!: (key: string, token: IDToken) => void; mockOktaAuth.tokenManager.on.mockImplementation((event, handler) => { if (event === 'renewed') { @@ -242,7 +242,7 @@ describe('OktaAuthProvider', () => { expiresAt: Math.floor(Date.now() / 1000) + 3600, } as IDToken; - await renewedHandler!('idToken', renewedToken); + await renewedHandler('idToken', renewedToken); await waitFor(() => { expect(mockSetOidcToken).toHaveBeenCalledWith('new-renewed-token'); @@ -250,7 +250,7 @@ describe('OktaAuthProvider', () => { }); it('should not call setOidcToken when accessToken is renewed', async () => { - let renewedHandler: (key: string, token: IDToken) => void; + let renewedHandler!: (key: string, token: IDToken) => void; mockOktaAuth.tokenManager.on.mockImplementation((event, handler) => { if (event === 'renewed') { @@ -275,7 +275,7 @@ describe('OktaAuthProvider', () => { expiresAt: Math.floor(Date.now() / 1000) + 3600, } as unknown as IDToken; - await renewedHandler!('accessToken', renewedToken); + await renewedHandler('accessToken', renewedToken); await waitFor(() => { expect(mockSetOidcToken).not.toHaveBeenCalled(); @@ -283,7 +283,7 @@ describe('OktaAuthProvider', () => { }); it('should handle missing idToken in renewed event', async () => { - let renewedHandler: (key: string, token: IDToken) => void; + let renewedHandler!: (key: string, token: IDToken) => void; mockOktaAuth.tokenManager.on.mockImplementation((event, handler) => { if (event === 'renewed') { @@ -307,7 +307,7 @@ describe('OktaAuthProvider', () => { claims: { sub: 'user123' }, } as IDToken; - await renewedHandler!('idToken', renewedToken); + await renewedHandler('idToken', renewedToken); await waitFor(() => { expect(mockSetOidcToken).not.toHaveBeenCalled(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockEditor.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockEditor.tsx index 57a287d8cf03..ceb0ae399a32 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockEditor.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockEditor.tsx @@ -42,6 +42,9 @@ import './Extensions/File/file-node.less'; import { slashMenuPluginKey } from './Extensions/slash-command'; import { useCustomEditor } from './hooks/useCustomEditor'; +const PROSE_MIRROR_CONTENTEDITABLE_TRUE = + '.ProseMirror[contenteditable="true"]'; + const BlockEditor = forwardRef( ( { @@ -142,7 +145,7 @@ const BlockEditor = forwardRef( if (hasFiles) { const editorElement = document.querySelector( - '.ProseMirror[contenteditable="true"]' + PROSE_MIRROR_CONTENTEDITABLE_TRUE ); if (editorElement) { (editorElement as HTMLElement).classList.add('drag-over'); @@ -152,7 +155,7 @@ const BlockEditor = forwardRef( const handleDragLeave = (e: React.DragEvent) => { const editorElement = document.querySelector( - '.ProseMirror[contenteditable="true"]' + PROSE_MIRROR_CONTENTEDITABLE_TRUE ); // Only remove class if we're leaving the editor area if (editorElement && !editorElement.contains(e.relatedTarget as Node)) { @@ -168,7 +171,7 @@ const BlockEditor = forwardRef( e.preventDefault(); const editorElement = document.querySelector( - '.ProseMirror[contenteditable="true"]' + PROSE_MIRROR_CONTENTEDITABLE_TRUE ); if (editorElement) { (editorElement as HTMLElement).classList.remove('drag-over'); @@ -234,6 +237,7 @@ const BlockEditor = forwardRef( setTimeout(() => { setEditorContent(editor, htmlContent); }); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [content, editor]); // this effect to handle the editable state @@ -269,6 +273,7 @@ const BlockEditor = forwardRef( })} id="block-editor-wrapper" ref={editorWrapperRef} + role="presentation" onDragEnter={handleDragEnter} onDragLeave={handleDragLeave} onDragOver={(e) => e.preventDefault()} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockMenu/BlockMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockMenu/BlockMenu.tsx index c8cae9b7cde9..98271231ae9a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockMenu/BlockMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BlockMenu/BlockMenu.tsx @@ -94,9 +94,11 @@ export const BlockMenu = (props: BlockMenuProps) => { popup.current?.show(); }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [view] ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const handleKeyDown = () => { popup.current?.hide(); }; @@ -153,6 +155,7 @@ export const BlockMenu = (props: BlockMenuProps) => { popup.current?.destroy(); popup.current = null; }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [isEditable]); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx index 0c7584b7dce4..55d876fbfbbb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx @@ -97,6 +97,7 @@ const BubbleMenu: FC = ({ editor, toggleLink }) => { ]; return { menuList }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [editor]); const handleShouldShow: CoreBubbleMenuProps['shouldShow'] = ({ @@ -112,14 +113,11 @@ const BubbleMenu: FC = ({ editor, toggleLink }) => { // - the selection is a node selection (for drag handles) // - link is active // - editor is not editable - if ( - editor.isActive('image') || - empty || - isNodeSelection(selection) || - editor.isActive('link') || - editor.isActive('table') || - !editor.isEditable - ) { + const isNonTextSelection = + editor.isActive('image') || empty || isNodeSelection(selection); + const isBlockedContext = + editor.isActive('link') || editor.isActive('table') || !editor.isEditable; + if (isNonTextSelection || isBlockedContext) { return false; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/BlockAndDragDrop/BlockAndDragHandle.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/BlockAndDragDrop/BlockAndDragHandle.ts index eb0c4ad929fd..8485a8e11bfb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/BlockAndDragDrop/BlockAndDragHandle.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/BlockAndDragDrop/BlockAndDragHandle.ts @@ -18,6 +18,8 @@ import i18n from '../../../../utils/i18next/LocalUtil'; import { BlockAndDragHandleOptions } from './BlockAndDragDrop'; import { absoluteRect, nodeDOMAtCoords, nodePosAtDOM } from './helpers'; +const OM_NODE_DRAGGING = 'om-node-dragging' as const; + export const BlockAndDragHandle = (options: BlockAndDragHandleOptions) => { let dragHandleElement: HTMLElement | null = null; let blockHandleElement: HTMLElement | null = null; @@ -71,7 +73,7 @@ export const BlockAndDragHandle = (options: BlockAndDragHandleOptions) => { const handleDragClick = (event: MouseEvent, view: EditorView) => { view.focus(); - view.dom.classList.remove('om-node-dragging'); + view.dom.classList.remove(OM_NODE_DRAGGING); const node = nodeDOMAtCoords({ x: event.clientX + 50 + options.dragHandleWidth, @@ -275,13 +277,13 @@ export const BlockAndDragHandle = (options: BlockAndDragHandleOptions) => { }, // dragging class is used for CSS dragstart: (view) => { - view.dom.classList.add('om-node-dragging'); + view.dom.classList.add(OM_NODE_DRAGGING); }, drop: (view) => { - view.dom.classList.remove('om-node-dragging'); + view.dom.classList.remove(OM_NODE_DRAGGING); }, dragend: (view) => { - view.dom.classList.remove('om-node-dragging'); + view.dom.classList.remove(OM_NODE_DRAGGING); }, }, }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/Callout/CalloutComponent.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/Callout/CalloutComponent.test.tsx index ec79e85d1185..d09d2d9610a3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/Callout/CalloutComponent.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/Callout/CalloutComponent.test.tsx @@ -15,6 +15,8 @@ import userEvent from '@testing-library/user-event'; import { NodeViewProps } from '@tiptap/core'; import CalloutComponent from './CalloutComponent'; +const CALLOUT_INFO_BTN = 'callout-info-btn'; + const mockNode = { attrs: { calloutType: 'info', @@ -47,7 +49,7 @@ describe('CalloutComponent', () => { expect(calloutNode).toBeInTheDocument(); expect(calloutNode).toHaveAttribute('data-type', 'callout'); - expect(screen.getByTestId('callout-info-btn')).toBeInTheDocument(); + expect(screen.getByTestId(CALLOUT_INFO_BTN)).toBeInTheDocument(); expect(screen.getByTestId('callout-content')).toBeInTheDocument(); }); @@ -57,7 +59,7 @@ describe('CalloutComponent', () => { render(); }); - const calloutButton = screen.getByTestId('callout-info-btn'); + const calloutButton = screen.getByTestId(CALLOUT_INFO_BTN); fireEvent.click(calloutButton); @@ -85,7 +87,7 @@ describe('CalloutComponent', () => { render(); }); - const calloutButton = screen.getByTestId('callout-info-btn'); + const calloutButton = screen.getByTestId(CALLOUT_INFO_BTN); await act(async () => { userEvent.click(calloutButton); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/AttachmentPlaceholder.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/AttachmentPlaceholder.test.tsx index f30c75748445..1c7f0f927a4e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/AttachmentPlaceholder.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/AttachmentPlaceholder.test.tsx @@ -17,6 +17,8 @@ import { getFileIcon } from '../../../../../utils/BlockEditorUtils'; import { FileType } from '../../../BlockEditor.interface'; import AttachmentPlaceholder from './AttachmentPlaceholder'; +const LABEL_ADD_AN_FILE_TYPE = 'label.add-an-file-type'; +const IMAGE_PLACEHOLDER = 'image-placeholder'; // Mock the translation hook jest.mock('react-i18next', () => ({ useTranslation: jest.fn(), @@ -54,7 +56,7 @@ describe('AttachmentPlaceholder', () => { it('should render the placeholder with correct file type', () => { const fileType = FileType.FILE; mockTranslate.mockImplementation((key, options) => { - if (key === 'label.add-an-file-type') { + if (key === LABEL_ADD_AN_FILE_TYPE) { return `Add a ${options.fileType} file`; } if (key === `label.${fileType}`) { @@ -67,13 +69,13 @@ describe('AttachmentPlaceholder', () => { render(); // Verify the placeholder is rendered - expect(screen.getByTestId('image-placeholder')).toBeInTheDocument(); + expect(screen.getByTestId(IMAGE_PLACEHOLDER)).toBeInTheDocument(); // Verify the icon is rendered expect(screen.getByTestId('mock-file-icon')).toBeInTheDocument(); // Verify the translation was called with correct parameters - expect(mockTranslate).toHaveBeenCalledWith('label.add-an-file-type', { + expect(mockTranslate).toHaveBeenCalledWith(LABEL_ADD_AN_FILE_TYPE, { fileType: expect.any(String), }); }); @@ -84,7 +86,7 @@ describe('AttachmentPlaceholder', () => { [FileType.VIDEO, 'video'], ])('should render with %s file type', (fileType, expectedText) => { mockTranslate.mockImplementation((key, options) => { - if (key === 'label.add-an-file-type') { + if (key === LABEL_ADD_AN_FILE_TYPE) { return `Add a ${options.fileType} file`; } if (key === `label.${fileType}`) { @@ -96,7 +98,7 @@ describe('AttachmentPlaceholder', () => { render(); - expect(screen.getByTestId('image-placeholder')).toBeInTheDocument(); + expect(screen.getByTestId(IMAGE_PLACEHOLDER)).toBeInTheDocument(); expect(screen.getByTestId('mock-file-icon')).toBeInTheDocument(); expect(screen.getByText(`Add a ${expectedText} file`)).toBeInTheDocument(); }); @@ -104,7 +106,7 @@ describe('AttachmentPlaceholder', () => { it('should have contentEditable set to false', () => { const fileType = FileType.FILE; mockTranslate.mockImplementation((key, options) => { - if (key === 'label.add-an-file-type') { + if (key === LABEL_ADD_AN_FILE_TYPE) { return `Add a ${options.fileType} file`; } if (key === `label.${fileType}`) { @@ -116,7 +118,7 @@ describe('AttachmentPlaceholder', () => { render(); - const placeholder = screen.getByTestId('image-placeholder'); + const placeholder = screen.getByTestId(IMAGE_PLACEHOLDER); expect(placeholder).toHaveAttribute('contentEditable', 'false'); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/FileAttachment.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/FileAttachment.tsx index 4de18af31e91..1c02e3127bdf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/FileAttachment.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/File/AttachmentComponents/FileAttachment.tsx @@ -39,7 +39,10 @@ const FileAttachment = ({ } = node.attrs; return ( -
e.preventDefault()}> +
e.preventDefault()}>
@@ -50,7 +53,7 @@ const FileAttachment = ({ data-mimetype={mimeType || tempFile?.type} data-type="file-attachment" data-url={url} - href="#" + href={url || '#'} onClick={onFileClick}> {fileName || tempFile?.name} 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 c841feb680f7..84b68dc33816 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 @@ -15,6 +15,10 @@ import { NodeViewProps } from '@tiptap/react'; import { UPLOADED_ASSETS_URL } from '../../../../../constants/BlockEditor.constants'; import ImageAttachment from './ImageAttachment'; +const IMAGE_CONTAINER = 'image-container'; +const LOADING_STATE = 'loading-state'; +const UPLOADED_IMAGE_NODE = 'uploaded-image-node'; + describe('ImageAttachment', () => { const mockNode = { attrs: { @@ -45,10 +49,10 @@ describe('ImageAttachment', () => { /> ); - const imageContainer = screen.getByTestId('image-container'); + const imageContainer = screen.getByTestId(IMAGE_CONTAINER); - expect(imageContainer).toHaveClass('loading-state'); - expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument(); + 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', () => { @@ -64,10 +68,10 @@ describe('ImageAttachment', () => { ); - const imageContainer = screen.getByTestId('image-container'); + const imageContainer = screen.getByTestId(IMAGE_CONTAINER); - expect(imageContainer).toHaveClass('loading-state'); - expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument(); + expect(imageContainer).toHaveClass(LOADING_STATE); + expect(screen.queryByTestId(UPLOADED_IMAGE_NODE)).not.toBeInTheDocument(); }); it('should render image when mediaSrc is provided', async () => { @@ -80,7 +84,7 @@ describe('ImageAttachment', () => { /> ); - const image = screen.getByTestId('uploaded-image-node'); + const image = screen.getByTestId(UPLOADED_IMAGE_NODE); expect(image).toBeInTheDocument(); expect(image).toHaveAttribute('src', mediaSrc); @@ -93,9 +97,7 @@ describe('ImageAttachment', () => { ); await waitFor(() => { - expect( - screen.queryByTestId('uploaded-image-node') - ).not.toBeInTheDocument(); + expect(screen.queryByTestId(UPLOADED_IMAGE_NODE)).not.toBeInTheDocument(); }); }); @@ -112,10 +114,10 @@ describe('ImageAttachment', () => { ); - const imageContainer = screen.getByTestId('image-container'); + const imageContainer = screen.getByTestId(IMAGE_CONTAINER); - expect(imageContainer).toHaveClass('loading-state'); - expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument(); + expect(imageContainer).toHaveClass(LOADING_STATE); + expect(screen.queryByTestId(UPLOADED_IMAGE_NODE)).not.toBeInTheDocument(); }); it('should display authenticated image when mediaSrc is provided', async () => { @@ -136,7 +138,7 @@ describe('ImageAttachment', () => { /> ); - const image = screen.getByTestId('uploaded-image-node'); + const image = screen.getByTestId(UPLOADED_IMAGE_NODE); expect(image).toBeInTheDocument(); expect(image).toHaveAttribute('src', mediaSrc); @@ -152,7 +154,7 @@ describe('ImageAttachment', () => { ); // Simulate image load - const image = screen.getByTestId('uploaded-image-node'); + const image = screen.getByTestId(UPLOADED_IMAGE_NODE); fireEvent.load(image); // Rerender with new url @@ -173,7 +175,7 @@ describe('ImageAttachment', () => { ); // Image should be hidden again until it loads - expect(screen.getByTestId('uploaded-image-node')).toHaveAttribute( + expect(screen.getByTestId(UPLOADED_IMAGE_NODE)).toHaveAttribute( 'src', 'https://example.com/image1.jpg' ); @@ -189,7 +191,7 @@ describe('ImageAttachment', () => { ); // Simulate image load - const image = screen.getByTestId('uploaded-image-node'); + const image = screen.getByTestId(UPLOADED_IMAGE_NODE); fireEvent.load(image); // Rerender with new mediaSrc @@ -202,7 +204,7 @@ describe('ImageAttachment', () => { ); // Image should be hidden again until it loads - expect(screen.getByTestId('uploaded-image-node')).toHaveAttribute( + expect(screen.getByTestId(UPLOADED_IMAGE_NODE)).toHaveAttribute( 'src', 'https://example.com/image2.jpg' ); @@ -225,7 +227,7 @@ describe('ImageAttachment', () => { /> ); - const image = screen.getByTestId('uploaded-image-node'); + 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 e52ba0e6d1e9..45752dcc9350 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 @@ -58,6 +58,7 @@ const ImageAttachment = ({ })} data-testid="image-container"> {displaySrc ? ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- lifecycle, not interaction {alt = ({ updateAttributes, deleteNode, editor, + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- complex fn; refactor risks behavior change }) => { const { t } = useTranslation(); const { setPopoverOpen } = useEntityAttachment(); @@ -131,9 +132,21 @@ const FileNodeView: FC = ({ return (
{isVideo ? ( -
); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx index 378fa5f16af1..35dfe2633f90 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/MathEquation/MathEquationComponent.tsx @@ -51,6 +51,7 @@ export const MathEquationComponent: FC = ({ {isEditing ? (
{ it('should be a Tiptap Node', () => { expect(DiffView).toBeInstanceOf(Node); @@ -56,7 +59,7 @@ describe('DiffView renderHTML — textContent vs innerHTML behaviour', () => { const output = editor.getHTML(); - expect(output).toContain('data-diff="true"'); + expect(output).toContain(DATA_DIFF_TRUE); expect(output).toContain('plain text'); }); @@ -97,7 +100,7 @@ describe('DiffView renderHTML — textContent vs innerHTML behaviour', () => { const output = editor.getHTML(); - expect(output).toContain('data-diff="true"'); + expect(output).toContain(DATA_DIFF_TRUE); }); it('preserves data-testid attribute when present', () => { @@ -126,7 +129,7 @@ describe('DiffView renderHTML — textContent vs innerHTML behaviour', () => { const output = editor.getHTML(); expect(output).toContain('diff-removed'); - expect(output).toContain('diff-added'); + expect(output).toContain(DIFF_ADDED); expect(output).toContain('old'); expect(output).toContain('new'); }); @@ -138,7 +141,7 @@ describe('DiffView renderHTML — textContent vs innerHTML behaviour', () => { const output = editor.getHTML(); - expect(output).toContain('data-diff="true"'); + expect(output).toContain(DATA_DIFF_TRUE); expect(output).not.toContain('data-unknown'); }); @@ -161,7 +164,7 @@ describe('DiffView renderHTML — textContent vs innerHTML behaviour', () => { { type: 'diffView', attrs: { - class: 'diff-added', + class: DIFF_ADDED, 'data-diff': 'true', 'data-testid': '', }, @@ -245,7 +248,7 @@ describe('DiffView — Tiptap marks vs hardcoded HTML string as content', () => { type: 'diffView', attrs: { - class: 'diff-added', + class: DIFF_ADDED, 'data-diff': 'true', 'data-testid': '', }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/focus.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/focus.ts index 13911f19046f..57a3a45d07b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/focus.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/focus.ts @@ -60,8 +60,6 @@ export const Focus = Extension.create({ } maxLevels += 1; - - return; }); } @@ -96,8 +94,6 @@ export const Focus = Extension.create({ class: this.options.className, }) ); - - return; }); return DecorationSet.create(doc, decorations); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.test.tsx index 268b3266c19e..0cc2133587f6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.test.tsx @@ -15,6 +15,9 @@ import { useTranslation } from 'react-i18next'; import { FileType } from '../../../BlockEditor.interface'; import EmbedLinkElement from './EmbedLinkElement'; +const EMBED_INPUT = 'embed-input'; +const LABEL_EMBED_FILE_TYPE = 'label.embed-file-type'; + describe('EmbedLinkElement', () => { const mockUpdateAttributes = jest.fn(); const mockOnPopupVisibleChange = jest.fn(); @@ -50,7 +53,7 @@ describe('EmbedLinkElement', () => { expect(screen.getByTestId('embed-link-form')).toBeInTheDocument(); // Check if input field has initial value - const input = screen.getByTestId('embed-input'); + const input = screen.getByTestId(EMBED_INPUT); expect(input).toHaveValue(mockSrc); }); @@ -69,8 +72,8 @@ describe('EmbedLinkElement', () => { /> ); - const input = screen.getByTestId('embed-input'); - const submitButton = screen.getByText('label.embed-file-type'); + const input = screen.getByTestId(EMBED_INPUT); + const submitButton = screen.getByText(LABEL_EMBED_FILE_TYPE); // Update input value fireEvent.change(input, { @@ -105,8 +108,8 @@ describe('EmbedLinkElement', () => { /> ); - const input = screen.getByTestId('embed-input'); - const submitButton = screen.getByText('label.embed-file-type'); + const input = screen.getByTestId(EMBED_INPUT); + const submitButton = screen.getByText(LABEL_EMBED_FILE_TYPE); // Set invalid URL fireEvent.change(input, { target: { value: 'invalid-url' } }); @@ -139,8 +142,8 @@ describe('EmbedLinkElement', () => { /> ); - const input = screen.getByTestId('embed-input'); - const submitButton = screen.getByText('label.embed-file-type'); + const input = screen.getByTestId(EMBED_INPUT); + const submitButton = screen.getByText(LABEL_EMBED_FILE_TYPE); fireEvent.change(input, { target: { value: '' } }); fireEvent.click(submitButton); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.tsx index 629f14f5dd48..d1f74f6ffd14 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/image/EmbedLinkElement/EmbedLinkElement.tsx @@ -42,6 +42,7 @@ const EmbedLinkElement: FC = ({ useEffect(() => { form.reset({ Url: src }); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [src]); const isAssetsUrl = useMemo(() => { @@ -77,6 +78,7 @@ const EmbedLinkElement: FC = ({ {({ field, fieldState }) => ( <> { return true; } - if (props.event.key === 'Enter') { - if ( - suggestionProps.items.filter((item) => - item.title - .toLowerCase() - .startsWith(suggestionProps.query.toLowerCase()) - ).length === 0 - ) { - this.onExit(); - } + if ( + props.event.key === 'Enter' && + suggestionProps.items.filter((item) => + item.title + .toLowerCase() + .startsWith(suggestionProps.query.toLowerCase()) + ).length === 0 + ) { + this.onExit(); } return (component?.ref as SlashCommandRef)?.onKeyDown(props) || false; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkModal/LinkModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkModal/LinkModal.tsx index b5f283bc0c0b..31a9786e0130 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkModal/LinkModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/LinkModal/LinkModal.tsx @@ -61,7 +61,10 @@ const LinkModal: FC = ({ layout="vertical" onFinish={handleSubmit}> - + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index 388155af1dcb..240d8f1e04e8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -142,6 +142,7 @@ const TableMenu = (props: TableMenuProps) => { tableMenuPopup.current?.destroy(); tableMenuPopup.current = null; }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [isEditable]); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/hooks/useCustomEditor.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/hooks/useCustomEditor.ts index d985a63ff012..a10aae512fd7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/hooks/useCustomEditor.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/hooks/useCustomEditor.ts @@ -125,6 +125,7 @@ export const useCustomEditor = ( instance.on('transaction', () => { requestAnimationFrame(() => { + // eslint-disable-next-line sonarjs/no-nested-functions -- rAF callback closes over isMounted requestAnimationFrame(() => { if (isMounted) { forceUpdate(); @@ -136,6 +137,7 @@ export const useCustomEditor = ( return () => { isMounted = false; }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, deps); return editor; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.component.tsx index dba188f68525..0586721ef56d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.component.tsx @@ -143,7 +143,8 @@ const BulkEditEntity = ({ sourceEntityType, workflowHeaderConfig, workflowMode = 'bulkEdit', -}: BulkEditEntityProps) => { +}: // eslint-disable-next-line sonarjs/cognitive-complexity, sonarjs/cyclomatic-complexity -- inherent branching +BulkEditEntityProps) => { const { t } = useTranslation(); const navigate = useNavigate(); const { fqn } = useFqn(); @@ -224,6 +225,7 @@ const BulkEditEntity = ({ () => setHighlightedRowId((id) => (id === newRowId ? undefined : id)), 2000 ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [columns, dataSource, handleOnRowsChange]); const handleRemoveRow = useCallback( @@ -466,6 +468,7 @@ const BulkEditEntity = ({ return row.id === highlightedRowId ? `${operationClass}${ + // eslint-disable-next-line sonarjs/no-nested-conditional -- template ternary isNewMetricRowMissingName(row) ? '' : ' bulk-edit-row-highlight' @@ -569,6 +572,7 @@ const BulkEditEntity = ({ )}
+ {/* eslint-disable sonarjs/no-nested-conditional, sonarjs/expression-complexity -- chained render ternary */} {isExportHydrationRequired && csvExportError ? (
)} + {/* eslint-enable sonarjs/no-nested-conditional, sonarjs/expression-complexity */} ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.test.tsx index 9ed16d74087b..6d7633b2b7e7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BulkEditEntity/BulkEditEntity.test.tsx @@ -19,6 +19,17 @@ import { CSVImportResult, Status } from '../../generated/type/csvImportResult'; import BulkEditEntity from './BulkEditEntity.component'; import { BulkEditEntityProps } from './BulkEditEntity.interface'; +const TEST_ENTITY_FQN = 'test.entity.fqn' as const; +const COL1_COL2_VAL1_VAL2 = 'col1,col2\nval1,val2' as const; +const COLUMN_COUNT = 'column-count' as const; +const BANNER_MESSAGE = 'banner-message' as const; +const IMPORT_STATUS = 'import-status' as const; +const LABEL_CANCEL = 'label.cancel' as const; +const LABEL_NEXT = 'label.next' as const; +const TEST_JOB_123 = 'test-job-123' as const; +const PROCESSING = 'Processing...' as const; +const DATA_FROZEN = 'data-frozen' as const; + const mockNavigate = jest.fn(); const mockTriggerExportForBulkEdit = jest.fn(); const mockClearCSVExportData = jest.fn(); @@ -47,7 +58,7 @@ jest.mock('../../utils/useRequiredParams', () => ({ })), })); -let mockCsvExportData: string | undefined = 'col1,col2\nval1,val2'; +let mockCsvExportData: string | undefined = COL1_COL2_VAL1_VAL2; jest.mock( '../Entity/EntityExportModalProvider/EntityExportModalProvider.component', () => ({ @@ -198,7 +209,7 @@ describe('BulkEditEntity', () => { beforeEach(() => { jest.clearAllMocks(); mockEntityType = EntityType.TABLE; - mockCsvExportData = 'col1,col2\nval1,val2'; + mockCsvExportData = COL1_COL2_VAL1_VAL2; useEntityExportModalProvider.mockReturnValue({ triggerExportForBulkEdit: mockTriggerExportForBulkEdit, csvExportData: mockCsvExportData, @@ -257,7 +268,7 @@ describe('BulkEditEntity', () => { renderComponent({ activeStep: VALIDATION_STEP.EDIT_VALIDATE }); expect( - screen.getByRole('button', { name: 'label.cancel' }) + screen.getByRole('button', { name: LABEL_CANCEL }) ).toBeInTheDocument(); }); @@ -276,14 +287,14 @@ describe('BulkEditEntity', () => { renderComponent({ activeStep: VALIDATION_STEP.EDIT_VALIDATE }); expect( - screen.getByRole('button', { name: 'label.next' }) + screen.getByRole('button', { name: LABEL_NEXT }) ).toBeInTheDocument(); }); it('should disable next button when there are no bulk edit changes', () => { renderComponent({ activeStep: VALIDATION_STEP.EDIT_VALIDATE }); - expect(screen.getByRole('button', { name: 'label.next' })).toBeDisabled(); + expect(screen.getByRole('button', { name: LABEL_NEXT })).toBeDisabled(); }); it('should allow metric import preview validation without additional edits', () => { @@ -295,7 +306,7 @@ describe('BulkEditEntity', () => { }); expect( - screen.getByRole('button', { name: 'label.next' }) + screen.getByRole('button', { name: LABEL_NEXT }) ).not.toBeDisabled(); }); @@ -314,10 +325,10 @@ describe('BulkEditEntity', () => { renderComponent({ activeStep: VALIDATION_STEP.UPLOAD }); expect( - screen.queryByRole('button', { name: 'label.cancel' }) + screen.queryByRole('button', { name: LABEL_CANCEL }) ).not.toBeInTheDocument(); expect( - screen.queryByRole('button', { name: 'label.next' }) + screen.queryByRole('button', { name: LABEL_NEXT }) ).not.toBeInTheDocument(); }); }); @@ -333,7 +344,7 @@ describe('BulkEditEntity', () => { handleValidate, }); - const nextButton = screen.getByRole('button', { name: 'label.next' }); + const nextButton = screen.getByRole('button', { name: LABEL_NEXT }); await act(async () => { fireEvent.click(nextButton); @@ -361,7 +372,7 @@ describe('BulkEditEntity', () => { it('should navigate away and clear data when cancel button is clicked', () => { renderComponent({ activeStep: VALIDATION_STEP.EDIT_VALIDATE }); - const cancelButton = screen.getByRole('button', { name: 'label.cancel' }); + const cancelButton = screen.getByRole('button', { name: LABEL_CANCEL }); fireEvent.click(cancelButton); expect(mockClearCSVExportData).toHaveBeenCalledTimes(1); @@ -374,8 +385,8 @@ describe('BulkEditEntity', () => { isValidating: true, }); - const cancelButton = screen.getByRole('button', { name: 'label.cancel' }); - const nextButton = screen.getByRole('button', { name: 'label.next' }); + const cancelButton = screen.getByRole('button', { name: LABEL_CANCEL }); + const nextButton = screen.getByRole('button', { name: LABEL_NEXT }); expect(cancelButton).toBeDisabled(); expect(nextButton).toBeDisabled(); @@ -386,28 +397,26 @@ describe('BulkEditEntity', () => { it('should render banner when activeAsyncImportJob has jobId', () => { renderComponent({ activeAsyncImportJob: { - jobId: 'test-job-123', - message: 'Processing...', + jobId: TEST_JOB_123, + message: PROCESSING, type: 'onValidate', }, }); expect(screen.getByTestId('banner')).toBeInTheDocument(); - expect(screen.getByTestId('banner-message')).toHaveTextContent( - 'Processing...' - ); + expect(screen.getByTestId(BANNER_MESSAGE)).toHaveTextContent(PROCESSING); }); it('should show error type banner when activeAsyncImportJob has error', () => { renderComponent({ activeAsyncImportJob: { - jobId: 'test-job-123', + jobId: TEST_JOB_123, error: 'Something went wrong', type: 'onValidate', }, }); - expect(screen.getByTestId('banner-message')).toHaveTextContent( + expect(screen.getByTestId(BANNER_MESSAGE)).toHaveTextContent( 'Something went wrong' ); expect(screen.getByTestId('banner-type')).toHaveTextContent('error'); @@ -416,8 +425,8 @@ describe('BulkEditEntity', () => { it('should show success type banner when no error', () => { renderComponent({ activeAsyncImportJob: { - jobId: 'test-job-123', - message: 'Processing...', + jobId: TEST_JOB_123, + message: PROCESSING, type: 'onValidate', }, }); @@ -443,7 +452,7 @@ describe('BulkEditEntity', () => { validationData: mockValidationData, }); - expect(screen.getByTestId('import-status')).toBeInTheDocument(); + expect(screen.getByTestId(IMPORT_STATUS)).toBeInTheDocument(); expect(screen.getByTestId('import-status-value')).toHaveTextContent( 'success' ); @@ -467,7 +476,7 @@ describe('BulkEditEntity', () => { validationData: mockValidationData, }); - expect(screen.queryByTestId('import-status')).not.toBeInTheDocument(); + expect(screen.queryByTestId(IMPORT_STATUS)).not.toBeInTheDocument(); }); }); @@ -478,7 +487,7 @@ describe('BulkEditEntity', () => { expect(mockTriggerExportForBulkEdit).toHaveBeenCalledTimes(1); expect(mockTriggerExportForBulkEdit).toHaveBeenCalledWith( expect.objectContaining({ - name: 'test.entity.fqn', + name: TEST_ENTITY_FQN, exportTypes: ['CSV'], }) ); @@ -495,7 +504,7 @@ describe('BulkEditEntity', () => { const secondTrigger = jest.fn(); useEntityExportModalProvider.mockReturnValue({ triggerExportForBulkEdit: firstTrigger, - csvExportData: 'col1,col2\nval1,val2', + csvExportData: COL1_COL2_VAL1_VAL2, clearCSVExportData: mockClearCSVExportData, }); @@ -550,7 +559,7 @@ describe('BulkEditEntity', () => { renderComponent({ columns: [] }); expect(screen.getByTestId('data-grid')).toBeInTheDocument(); - expect(screen.getByTestId('column-count')).toHaveTextContent('1'); + expect(screen.getByTestId(COLUMN_COUNT)).toHaveTextContent('1'); }); it('should handle empty breadcrumbList', () => { @@ -566,7 +575,7 @@ describe('BulkEditEntity', () => { validationData: undefined, }); - expect(screen.queryByTestId('import-status')).not.toBeInTheDocument(); + expect(screen.queryByTestId(IMPORT_STATUS)).not.toBeInTheDocument(); }); it('should handle undefined validateCSVData at UPDATE step', () => { @@ -576,7 +585,7 @@ describe('BulkEditEntity', () => { validateCSVData: undefined, }); - expect(screen.getByTestId('import-status')).toBeInTheDocument(); + expect(screen.getByTestId(IMPORT_STATUS)).toBeInTheDocument(); }); }); @@ -591,12 +600,12 @@ describe('BulkEditEntity', () => { sourceEntityType: EntityType.TEST_SUITE, }); - const cancelButton = screen.getByRole('button', { name: 'label.cancel' }); + const cancelButton = screen.getByRole('button', { name: LABEL_CANCEL }); fireEvent.click(cancelButton); expect(getBulkEntityNavigationPath).toHaveBeenCalledWith( EntityType.TABLE, - 'test.entity.fqn', + TEST_ENTITY_FQN, EntityType.TEST_SUITE ); }); @@ -648,7 +657,7 @@ describe('BulkEditEntity', () => { }, }); - expect(screen.getByTestId('banner-message')).toHaveTextContent( + expect(screen.getByTestId(BANNER_MESSAGE)).toHaveTextContent( 'Error occurred' ); }); @@ -661,7 +670,7 @@ describe('BulkEditEntity', () => { }, }); - expect(screen.getByTestId('banner-message')).toHaveTextContent(''); + expect(screen.getByTestId(BANNER_MESSAGE)).toHaveTextContent(''); }); it('should handle step 3 (no next/update button shown)', () => { @@ -671,7 +680,7 @@ describe('BulkEditEntity', () => { }); expect( - screen.queryByRole('button', { name: 'label.next' }) + screen.queryByRole('button', { name: LABEL_NEXT }) ).not.toBeInTheDocument(); expect( screen.queryByRole('button', { name: 'label.update' }) @@ -705,7 +714,7 @@ describe('BulkEditEntity', () => { }); expect(screen.getByTestId('row-count')).toHaveTextContent('2'); - expect(screen.getByTestId('column-count')).toHaveTextContent('3'); + expect(screen.getByTestId(COLUMN_COUNT)).toHaveTextContent('3'); }); it('should add an operation column to the edit grid', () => { @@ -719,7 +728,7 @@ describe('BulkEditEntity', () => { expect( screen.getByTestId('bulk-edit-operation-summary') ).toBeInTheDocument(); - expect(screen.getByTestId('column-count')).toHaveTextContent('3'); + expect(screen.getByTestId(COLUMN_COUNT)).toHaveTextContent('3'); }); it('should freeze operation and metric name columns in the edit grid', () => { @@ -733,11 +742,11 @@ describe('BulkEditEntity', () => { }); expect(screen.getByTestId('column-__bulkEditOperation')).toHaveAttribute( - 'data-frozen', + DATA_FROZEN, 'true' ); expect(screen.getByTestId('column-name*')).toHaveAttribute( - 'data-frozen', + DATA_FROZEN, 'true' ); expect(screen.getByTestId('column-name*')).toHaveAttribute( @@ -745,7 +754,7 @@ describe('BulkEditEntity', () => { '200' ); expect(screen.getByTestId('column-displayName')).toHaveAttribute( - 'data-frozen', + DATA_FROZEN, 'false' ); }); @@ -827,7 +836,7 @@ describe('BulkEditEntity', () => { renderComponent({ activeAsyncImportJob: { jobId: 'test-job', - message: 'Processing...', + message: PROCESSING, type: 'onValidate', }, }); @@ -862,7 +871,7 @@ describe('BulkEditEntity', () => { renderComponent(); expect(screen.queryByTestId('loader')).not.toBeInTheDocument(); - expect(screen.getByTestId('banner-message')).toHaveTextContent( + expect(screen.getByTestId(BANNER_MESSAGE)).toHaveTextContent( 'Entity not found: databaseService BigQuery' ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.component.tsx index ac2e69f14fb3..d69fcd8a1145 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.component.tsx @@ -114,6 +114,7 @@ const Certification = ({ } }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped const handleScroll = async (e: React.UIEvent) => { const { currentTarget } = e; const isAtBottom = @@ -164,11 +165,17 @@ const Certification = ({ alt: title, }) : null; + const renderedIconContent = isIcon ? ( +
{renderedIcon}
+ ) : ( + renderedIcon + ); return (
{ setSelectedCertification(fullyQualifiedName ?? ''); @@ -180,11 +187,7 @@ const Certification = ({ />
{renderedIcon ? ( - isIcon ? ( -
{renderedIcon}
- ) : ( - renderedIcon - ) + renderedIconContent ) : (
@@ -246,6 +249,7 @@ const Certification = ({ setCertifications([]); setSelectedCertification(''); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [popoverProps?.open]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.test.tsx index 50c40d0f5490..32d5ac2b0ed8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Certification/Certification.test.tsx @@ -22,6 +22,10 @@ import { getTags } from '../../rest/tagAPI'; import { showErrorToast } from '../../utils/ToastUtils'; import Certification from './Certification.component'; +const CERTIFICATION_GOLD = 'Certification.Gold'; +const DATA_TESTID = 'data-testid'; +const RADIO_BTN_CERTIFICATION_SILVER = 'radio-btn-Certification.Silver'; + jest.mock('../../assets/svg/ic-certification.svg', () => ({ ReactComponent: () =>
, })); @@ -50,7 +54,7 @@ const mockCertifications: Tag[] = [ id: 'gold-id', name: 'Gold', displayName: 'Gold', - fullyQualifiedName: 'Certification.Gold', + fullyQualifiedName: CERTIFICATION_GOLD, description: 'Gold certification', }, { @@ -67,7 +71,7 @@ const mockOnClose = jest.fn(); const defaultProps = { permission: true, - currentCertificate: 'Certification.Gold', + currentCertificate: CERTIFICATION_GOLD, onCertificationUpdate: mockOnCertificationUpdate, onClose: mockOnClose, children: , @@ -112,15 +116,15 @@ describe('Certification', () => { const radioButtons = screen.getAllByTestId(/radio-btn-/); expect(radioButtons[0]).toHaveAttribute( - 'data-testid', + DATA_TESTID, 'radio-btn-Certification.Gold' ); expect(radioButtons[1]).toHaveAttribute( - 'data-testid', - 'radio-btn-Certification.Silver' + DATA_TESTID, + RADIO_BTN_CERTIFICATION_SILVER ); expect(radioButtons[2]).toHaveAttribute( - 'data-testid', + DATA_TESTID, 'radio-btn-Certification.Bronze' ); }); @@ -143,13 +147,13 @@ describe('Certification', () => { await waitFor(() => { expect( - screen.getByTestId('radio-btn-Certification.Silver') + screen.getByTestId(RADIO_BTN_CERTIFICATION_SILVER) ).toBeInTheDocument(); }); - fireEvent.click(screen.getByTestId('radio-btn-Certification.Silver')); + fireEvent.click(screen.getByTestId(RADIO_BTN_CERTIFICATION_SILVER)); - expect(screen.getByTestId('radio-btn-Certification.Silver')).toBeChecked(); + expect(screen.getByTestId(RADIO_BTN_CERTIFICATION_SILVER)).toBeChecked(); }); it('should call onCertificationUpdate with the selected certification', async () => { @@ -168,7 +172,7 @@ describe('Certification', () => { await waitFor(() => { expect(mockOnCertificationUpdate).toHaveBeenCalledWith( expect.objectContaining({ - fullyQualifiedName: 'Certification.Gold', + fullyQualifiedName: CERTIFICATION_GOLD, }) ); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.test.tsx index 10de24da65be..fcd54122a551 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.test.tsx @@ -23,11 +23,13 @@ const mockChartDetails: Chart = { id: 'test-chart-id', name: 'test-chart', displayName: 'Test Chart', + // eslint-disable-next-line sonarjs/no-duplicate-string -- duplicated inside jest.mock factory fullyQualifiedName: 'test.chart', description: 'Test chart description', version: 0.1, updatedAt: 1234567890, updatedBy: 'test-user', + // eslint-disable-next-line sonarjs/no-clear-text-protocols -- test fixture URL, not a network call href: 'http://test.com', chartType: ChartType.Line, service: { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.tsx index 17417df9435d..0c87fe17fd02 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Chart/ChartDetails/ChartDetails.component.tsx @@ -61,6 +61,8 @@ import { DataAssetsHeader } from '../../DataAssets/DataAssetsHeader/DataAssetsHe import { EntityName } from '../../Modals/EntityNameModal/EntityNameModal.interface'; import PageLayoutV1 from '../../PageLayoutV1/PageLayoutV1'; import { ChartDetailsProps } from './ChartDetails.interface'; + +const LABEL_CHART = 'label.chart'; const ChartDetails = ({ updateChartDetailsState, chartDetails, @@ -91,6 +93,7 @@ const ChartDetails = ({ const { followers = [], deleted } = useMemo(() => { return chartDetails; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [chartDetails.owners, chartDetails.followers, chartDetails.deleted]); const { isFollowing } = useMemo(() => { @@ -111,16 +114,18 @@ const ChartDetails = ({ } catch { showErrorToast( t('server.fetch-entity-permissions-error', { - entity: t('label.chart'), + entity: t(LABEL_CHART), }) ); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [chartDetails.id, getEntityPermission, setChartPermissions]); useEffect(() => { if (chartDetails.id) { fetchResourcePermission(); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [chartDetails.id]); const handleFeedCount = useCallback((data: FeedCounts) => { @@ -149,6 +154,7 @@ const ChartDetails = ({ useEffect(() => { fetchTaskCounts(); fetchActivityCount(); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [decodedChartFQN]); const handleTabChange = (activeKey: string) => { @@ -195,7 +201,7 @@ const ChartDetails = ({ const { version: newVersion } = await restoreChart(chartDetails.id); showSuccessToast( t('message.restore-entities-success', { - entity: t('label.chart'), + entity: t(LABEL_CHART), }) ); handleToggleDelete(newVersion); @@ -203,7 +209,7 @@ const ChartDetails = ({ showErrorToast( error as AxiosError, t('message.restore-entities-error', { - entity: t('label.chart'), + entity: t(LABEL_CHART), }) ); } @@ -264,6 +270,7 @@ const ChartDetails = ({ customizedPage?.tabs, EntityTabs.DETAILS ); + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [ customizedPage, feedCount.totalCount, @@ -298,6 +305,7 @@ const ChartDetails = ({ const isExpandViewSupported = useMemo( () => checkIfExpandViewSupported(tabs[0], activeTab, 'Chart' as PageType), + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [tabs[0], activeTab] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.test.tsx index ebbe6743dbdd..ef1659910f4d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.test.tsx @@ -21,6 +21,14 @@ import { ENTITY_PERMISSIONS } from '../../../mocks/Permissions.mock'; import { getTags } from '../../../rest/tagAPI'; import ClassificationDetails from './ClassificationDetails'; +const TAGS_TABLE = 'tags-table'; +const DOMAIN_LABEL = 'domain-label'; +const OWNER_LABEL = 'owner-label'; +const ADD_NEW_TAG_BUTTON = 'add-new-tag-button'; +const DISABLE_BUTTON = 'disable-button'; +const TAG_DISABLE_TOGGLE_TAG1 = 'tag-disable-toggle-Tag1'; +const ARIA_DISABLED = 'aria-disabled'; + const mockNavigate = jest.fn(); jest.mock('@openmetadata/ui-core-components', () => ({ @@ -160,7 +168,7 @@ jest.mock('../../Entity/EntityHeaderTitle/EntityHeaderTitle.component', () => jest.mock('../../common/Table/Table', () => jest.fn().mockImplementation(({ columns, dataSource, loading, locale }) => ( -
+
{loading && Loading...} {dataSource?.length === 0 && !loading && locale?.emptyText} {dataSource?.map((tag: Tag) => ( @@ -199,13 +207,13 @@ jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({ jest.mock('../../DataAssets/DomainLabelV2/DomainLabelV2', () => ({ DomainLabelV2: jest .fn() - .mockImplementation(() =>
Domain
), + .mockImplementation(() =>
Domain
), })); jest.mock('../../DataAssets/OwnerLabelV2/OwnerLabelV2', () => ({ OwnerLabelV2: jest .fn() - .mockImplementation(() =>
Owner
), + .mockImplementation(() =>
Owner
), })); jest.mock('../../common/Badge/Badge.component', () => @@ -295,8 +303,8 @@ describe('ClassificationDetails', () => { 'TestClassification' ); expect(screen.getByTestId('tag-row-Tag2')).toBeInTheDocument(); - expect(screen.getByTestId('domain-label')).toBeInTheDocument(); - expect(screen.getByTestId('owner-label')).toBeInTheDocument(); + expect(screen.getByTestId(DOMAIN_LABEL)).toBeInTheDocument(); + expect(screen.getByTestId(OWNER_LABEL)).toBeInTheDocument(); }); it('should show empty state when classification has no tags', async () => { @@ -321,10 +329,10 @@ describe('ClassificationDetails', () => { ); await waitFor(() => - expect(screen.getByTestId('add-new-tag-button')).toBeInTheDocument() + expect(screen.getByTestId(ADD_NEW_TAG_BUTTON)).toBeInTheDocument() ); - fireEvent.click(screen.getByTestId('add-new-tag-button')); + fireEvent.click(screen.getByTestId(ADD_NEW_TAG_BUTTON)); expect(defaultProps.handleAddNewTagClick).toHaveBeenCalled(); }); @@ -420,7 +428,7 @@ describe('ClassificationDetails', () => { expect(screen.getByTestId('system-badge')).toBeInTheDocument() ); - expect(screen.getByTestId('disable-button')).toBeInTheDocument(); + expect(screen.getByTestId(DISABLE_BUTTON)).toBeInTheDocument(); }); it('should toggle classification enabled state when disable button is clicked', async () => { @@ -439,10 +447,10 @@ describe('ClassificationDetails', () => { ); await waitFor(() => - expect(screen.getByTestId('disable-button')).toBeInTheDocument() + expect(screen.getByTestId(DISABLE_BUTTON)).toBeInTheDocument() ); - fireEvent.click(screen.getByTestId('disable-button')); + fireEvent.click(screen.getByTestId(DISABLE_BUTTON)); expect(defaultProps.handleUpdateClassification).toHaveBeenCalledWith( expect.objectContaining({ disabled: true }) @@ -465,7 +473,7 @@ describe('ClassificationDetails', () => { expect(screen.getByTestId('disabled-indicator')).toBeInTheDocument() ); - expect(screen.getByTestId('add-new-tag-button')).toBeDisabled(); + expect(screen.getByTestId(ADD_NEW_TAG_BUTTON)).toBeDisabled(); }); it('should hide edit controls when user is in version view or lacks permissions', async () => { @@ -479,7 +487,7 @@ describe('ClassificationDetails', () => { expect(screen.queryByTestId('manage-button')).not.toBeInTheDocument() ); - expect(screen.queryByTestId('add-new-tag-button')).not.toBeInTheDocument(); + expect(screen.queryByTestId(ADD_NEW_TAG_BUTTON)).not.toBeInTheDocument(); unmount(); @@ -512,11 +520,11 @@ describe('ClassificationDetails', () => { ); await waitFor(() => - expect(screen.getByTestId('tag-disable-toggle-Tag1')).toBeInTheDocument() + expect(screen.getByTestId(TAG_DISABLE_TOGGLE_TAG1)).toBeInTheDocument() ); - expect(screen.getByTestId('tag-disable-toggle-Tag1')).not.toHaveAttribute( - 'aria-disabled', + expect(screen.getByTestId(TAG_DISABLE_TOGGLE_TAG1)).not.toHaveAttribute( + ARIA_DISABLED, 'true' ); @@ -537,8 +545,8 @@ describe('ClassificationDetails', () => { ); await waitFor(() => - expect(screen.getByTestId('tag-disable-toggle-Tag1')).toHaveAttribute( - 'aria-disabled', + expect(screen.getByTestId(TAG_DISABLE_TOGGLE_TAG1)).toHaveAttribute( + ARIA_DISABLED, 'true' ) ); @@ -557,8 +565,8 @@ describe('ClassificationDetails', () => { ); await waitFor(() => - expect(screen.getByTestId('tag-disable-toggle-Tag1')).toHaveAttribute( - 'aria-disabled', + expect(screen.getByTestId(TAG_DISABLE_TOGGLE_TAG1)).toHaveAttribute( + ARIA_DISABLED, 'true' ) ); @@ -579,9 +587,9 @@ describe('ClassificationDetails', () => { expect(screen.getByTestId('loader')).toBeInTheDocument(); expect(screen.queryByTestId('header')).not.toBeInTheDocument(); - expect(screen.queryByTestId('tags-table')).not.toBeInTheDocument(); - expect(screen.queryByTestId('domain-label')).not.toBeInTheDocument(); - expect(screen.queryByTestId('owner-label')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TAGS_TABLE)).not.toBeInTheDocument(); + expect(screen.queryByTestId(DOMAIN_LABEL)).not.toBeInTheDocument(); + expect(screen.queryByTestId(OWNER_LABEL)).not.toBeInTheDocument(); }); it('should not show loader or content when classification is undefined and not loading', async () => { @@ -599,7 +607,7 @@ describe('ClassificationDetails', () => { expect(screen.queryByTestId('loader')).not.toBeInTheDocument(); expect(screen.queryByTestId('header')).not.toBeInTheDocument(); - expect(screen.queryByTestId('tags-table')).not.toBeInTheDocument(); + expect(screen.queryByTestId(TAGS_TABLE)).not.toBeInTheDocument(); }); it('should render content and not loader when classification is available', async () => { @@ -613,9 +621,9 @@ describe('ClassificationDetails', () => { expect(screen.queryByTestId('loader')).not.toBeInTheDocument(); expect(screen.getByTestId('header')).toBeInTheDocument(); - expect(screen.getByTestId('tags-table')).toBeInTheDocument(); - expect(screen.getByTestId('domain-label')).toBeInTheDocument(); - expect(screen.getByTestId('owner-label')).toBeInTheDocument(); + expect(screen.getByTestId(TAGS_TABLE)).toBeInTheDocument(); + expect(screen.getByTestId(DOMAIN_LABEL)).toBeInTheDocument(); + expect(screen.getByTestId(OWNER_LABEL)).toBeInTheDocument(); }); it('should pass currentClassification directly to GenericProvider', async () => { @@ -650,6 +658,6 @@ describe('ClassificationDetails', () => { expect(screen.queryByTestId('loader')).not.toBeInTheDocument(); expect(screen.getByTestId('header')).toBeInTheDocument(); - expect(screen.getByTestId('tags-table')).toBeInTheDocument(); + expect(screen.getByTestId(TAGS_TABLE)).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.tsx index 832ae209ce64..94357ac3f935 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Classifications/ClassificationDetails/ClassificationDetails.tsx @@ -90,6 +90,7 @@ const ClassificationDetails = forwardRef( handleToggleDisable, }: Readonly, ref + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- preserve behavior ) => { const { theme } = useApplicationStore(); const { permissions } = usePermissionProvider(); @@ -184,6 +185,7 @@ const ClassificationDetails = forwardRef( ) ); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [currentVersion, tagCategoryName]); const { @@ -194,6 +196,7 @@ const ClassificationDetails = forwardRef( editDisplayNamePermission, editOwnerPermission, editDomainPermission, + // eslint-disable-next-line sonarjs/cyclomatic-complexity -- preserve behavior } = useMemo(() => { const isEditable = !isClassificationDisabled && !isClassificationDeleted; @@ -300,6 +303,7 @@ const ClassificationDetails = forwardRef( } return null; + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [createPermission, isClassificationDisabled]); const tableColumn: ColumnsType = useMemo( @@ -377,6 +381,7 @@ const ClassificationDetails = forwardRef( fetchClassificationChildren(currentClassification.fullyQualifiedName); } } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [currentClassification?.fullyQualifiedName, pageSize, pagingCursor]); useImperativeHandle(ref, () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx index 2a5625cc918b..e0594b6b9644 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx @@ -321,7 +321,9 @@ describe('ContainerDataModel', () => { }); expect(mockWriteText).toHaveBeenCalledWith( - expect.stringContaining(props.dataModel.columns[0].fullyQualifiedName!) + expect.stringContaining( + props.dataModel.columns[0].fullyQualifiedName as string + ) ); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.tsx index d5cb15dbb591..dab2ef312718 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.tsx @@ -305,6 +305,7 @@ const ContainerDataModel: FC = ({ ), }, ], + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [ isReadOnly, entityFqn, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.component.tsx index 4148e409eeba..145eac35f66d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.component.tsx @@ -74,6 +74,7 @@ const ContainerVersion: React.FC = ({ const entityFqn = useMemo( () => currentVersionData.fullyQualifiedName ?? '', + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [currentVersionData.fullyQualifiedName ?? ''] ); @@ -229,6 +230,7 @@ const ContainerVersion: React.FC = ({ ), }, ], + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [ description, entityFqn, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.test.tsx index 01ef0963366e..88c25c5181e1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerVersion/ContainerVersion.test.tsx @@ -16,6 +16,8 @@ import { MemoryRouter } from 'react-router-dom'; import { containerVersionMockProps } from '../../../mocks/ContainerVersion.mock'; import ContainerVersion from './ContainerVersion.component'; +const LABEL_CUSTOM_PROPERTY_PLURAL = 'label.custom-property-plural'; + const mockNavigate = jest.fn(); jest.mock( @@ -84,7 +86,7 @@ describe('ContainerVersion tests', () => { const description = screen.getByText('Description'); const schemaTabLabel = screen.getByText('label.schema'); const customPropertyTabLabel = screen.getByText( - 'label.custom-property-plural' + LABEL_CUSTOM_PROPERTY_PLURAL ); const entityVersionTimeLine = screen.getByText('EntityVersionTimeLine'); const versionTable = screen.getByText('VersionTable'); @@ -114,7 +116,7 @@ describe('ContainerVersion tests', () => { ); const schemaTabLabel = screen.queryByText('label.schema'); const customPropertyTabLabel = screen.queryByText( - 'label.custom-property-plural' + LABEL_CUSTOM_PROPERTY_PLURAL ); const versionTable = screen.queryByText('VersionTable'); @@ -134,7 +136,7 @@ describe('ContainerVersion tests', () => { }); const customPropertyTabLabel = screen.getByText( - 'label.custom-property-plural' + LABEL_CUSTOM_PROPERTY_PLURAL ); const versionTable = screen.getByText('VersionTable'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArchiveView/ArchiveView.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArchiveView/ArchiveView.component.tsx index bfa254fe02d9..652e59d116b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArchiveView/ArchiveView.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArchiveView/ArchiveView.component.tsx @@ -152,6 +152,7 @@ const ArchiveView: FC = ({ return ( {Array.from({ length: 8 }).map((_, idx) => ( + // eslint-disable-next-line react/no-array-index-key -- fixed-length skeleton placeholders ))} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.component.tsx index f70d1e979bdf..f164f913d292 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.component.tsx @@ -79,6 +79,8 @@ import { UserTeamSelectableList } from '../../common/UserTeamSelectableList/User import CopyLinkButton from '../../CopyLinkButton/CopyLinkButton.component'; import { ArticleDetailHeaderProps } from './ArticleDetailHeader.interface'; +const LABEL_DOMAIN = 'label.domain' as const; + const ArticleDetailHeader: FC = ({ knowledgePage, contentChangeState, @@ -94,6 +96,7 @@ const ArticleDetailHeader: FC = ({ onSetThreadLink, fetchKnowledgePageHierarchy, onUpdate, + // eslint-disable-next-line sonarjs/cognitive-complexity, sonarjs/cyclomatic-complexity -- inherent branching }) => { const { t } = useTranslation(); const navigate = useNavigate(); @@ -123,6 +126,7 @@ const ArticleDetailHeader: FC = ({ label: getKnowledgePageName(knowledgePage, t), }, ], + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped [knowledgePage?.id, knowledgePage?.name, knowledgePage?.displayName, t] ); @@ -186,6 +190,7 @@ const ArticleDetailHeader: FC = ({ } finally { setIsDeleting(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [knowledgePage, recentlyViewed, fetchKnowledgePageHierarchy, removeDraft]); const handleVersionClick = () => { @@ -290,6 +295,7 @@ const ArticleDetailHeader: FC = ({ } else { return null; } + // eslint-disable-next-line react-hooks/exhaustive-deps -- deps intentionally scoped }, [contentChangeState]); const breadcrumbInsideCard = contextCenterClassBase.isBreadcrumbInsideCard(); @@ -322,7 +328,7 @@ const ArticleDetailHeader: FC = ({ const metaEl = ( - + = ({ weight="regular"> {firstDomain ? firstDomain.displayName ?? firstDomain.name - : t('label.no-entity', { entity: t('label.domain') })} + : t('label.no-entity', { entity: t(LABEL_DOMAIN) })} {extraDomains.length > 0 && ( @@ -358,7 +364,7 @@ const ArticleDetailHeader: FC = ({ data-testid="edit-domain-btn" icon={} tooltip={t('label.edit-entity', { - entity: t('label.domain'), + entity: t(LABEL_DOMAIN), })} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.test.tsx index 74bad21c6e4c..5234f3a8d512 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContextCenter/ArticleDetailHeader/ArticleDetailHeader.test.tsx @@ -25,6 +25,7 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('../../../hooks/useFqn', () => ({ + // eslint-disable-next-line sonarjs/no-duplicate-string useFqn: jest.fn(() => ({ fqn: 'test-article' })), })); @@ -144,6 +145,7 @@ jest.mock('@openmetadata/ui-core-components', () => ({ }: { children: React.ReactNode; onClick?: () => void; + // eslint-disable-next-line sonarjs/no-duplicate-string 'data-testid'?: string; }) => (