diff --git a/.github/playwright/impact-map.generated.json b/.github/playwright/impact-map.generated.json index 73744ba0865a..25a5eb53facf 100644 --- a/.github/playwright/impact-map.generated.json +++ b/.github/playwright/impact-map.generated.json @@ -8247,6 +8247,8 @@ ], "specs": [ "playwright/e2e/Features/AccessControlSettings.spec.ts", + "playwright/e2e/Features/IncidentManager.spec.ts", + "playwright/e2e/Features/Tasks/TaskAssigneeManagement.spec.ts", "playwright/e2e/Pages/CustomProperties.spec.ts" ] }, diff --git a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json index 9dbc67a09056..667609d0cd07 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json +++ b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json @@ -234,7 +234,7 @@ }, "playwright/e2e/Features/IncidentManager.spec.ts": { "om-playwright/no-positional-locator": { - "count": 10 + "count": 9 } }, "playwright/e2e/Features/IngestionListNameSorting.spec.ts": { @@ -352,7 +352,7 @@ }, "playwright/e2e/Features/Tasks/TaskCustomFormWorkflow.spec.ts": { "om-playwright/no-positional-locator": { - "count": 6 + "count": 4 } }, "playwright/e2e/Features/Tasks/TaskDashboardEntity.spec.ts": { @@ -598,9 +598,6 @@ "playwright/e2e/Pages/TasksUIFlow.spec.ts": { "om-playwright/no-positional-locator": { "count": 5 - }, - "playwright/no-wait-for-selector": { - "count": 4 } }, "playwright/e2e/Pages/TestSuite.spec.ts": { @@ -770,7 +767,7 @@ }, "playwright/utils/incidentManager.ts": { "om-playwright/no-positional-locator": { - "count": 8 + "count": 7 } }, "playwright/utils/lineage.ts": { 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 18cbfca3a187..b224288c11d6 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 @@ -262,12 +262,19 @@ const reassignIncidentTask = async ( ) => { const reassignModal = await openIncidentReassignModal(page, testCaseName); const assigneeSelect = reassignModal.getByTestId('select-assignee'); - const assigneeSelector = assigneeSelect.locator('.ant-select-selector'); const assigneeInput = assigneeSelect.getByRole('combobox'); const assigneeOption = page.getByTestId(assignee.name.toLowerCase()); - await expect(assigneeSelector).toBeVisible(); - await assigneeSelector.click(); + // Single-select core Autocomplete hides its input while an item is selected. + const selectedAssignee = assigneeSelect.getByTestId( + 'autocomplete-selected-item' + ); + if (await selectedAssignee.isVisible()) { + await selectedAssignee.getByRole('button').click(); + } + + await expect(assigneeInput).toBeVisible(); + await assigneeInput.click(); await assigneeInput.fill(assignee.displayName); await expect(assigneeOption).toBeVisible({ timeout: ACTION_TIMEOUT }); @@ -745,15 +752,14 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => { await openIncidentTaskTab(actorPage, true); const resolveModal = await openIncidentResolveDialog(actorPage); const resolveTextareas = resolveModal.locator('textarea'); - const resolveReasonSelect = resolveModal - .locator('.ant-select-selector') - .first(); + const resolveReasonSelect = resolveModal.getByRole('button', { + name: /Root Cause/i, + }); const textareaCount = await resolveTextareas.count(); if (await resolveReasonSelect.isVisible().catch(() => false)) { await resolveReasonSelect.click(); - await actorPage.keyboard.press('ArrowDown'); - await actorPage.keyboard.press('Enter'); + await actorPage.getByRole('option', { name: 'MissingData' }).click(); await resolveTextareas.first().fill('test'); } else if (textareaCount >= 2) { await resolveTextareas.nth(0).fill('Missing Data'); @@ -1126,7 +1132,7 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => { await afterAction(); } - await page.click('[data-testid="select-assignee"]'); + await page.click('[data-testid="select-assignee"] input'); const assigneeOption = page.locator( `[data-testid="${assigneeTestCase.username}"]` ); @@ -1152,7 +1158,8 @@ test.describe('Incident Manager', PLAYWRIGHT_INGESTION_TAG_OBJ, () => { ); await page .getByTestId('select-assignee') - .getByLabel('close-circle') + .getByTestId('autocomplete-selected-item') + .getByRole('button') .click(); await nonAssigneeFilterRes; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskAssigneeManagement.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskAssigneeManagement.spec.ts index 7faea5f3ea15..591b087b3982 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskAssigneeManagement.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskAssigneeManagement.spec.ts @@ -100,7 +100,7 @@ test.describe('Task Assignee Management', () => { const existingAssigneeRemoveButton = page .locator( - '[data-testid="select-assignee"] .ant-select-selection-item-remove' + '[data-testid="select-assignee"] [data-testid="autocomplete-selected-item"] button' ) .first(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCreation.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCreation.spec.ts index b375f0f89b6e..bfd510f5bb36 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCreation.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCreation.spec.ts @@ -84,7 +84,7 @@ test.describe('Task Creation - Request Description', () => { await expect(page.getByTestId('form-container')).toBeVisible(); // Verify title contains description request info - const titleField = page.locator('#title'); + const titleField = page.getByTestId('title').locator('input'); await expect(titleField).toBeVisible(); const titleValue = await titleField.inputValue(); expect(titleValue.toLowerCase()).toContain('description'); @@ -144,7 +144,7 @@ test.describe('Task Creation - Request Description', () => { await expect(page.getByTestId('form-container')).toBeVisible(); // Verify this is a column-level task by checking the about field references a column - const titleField = page.locator('#title'); + const titleField = page.getByTestId('title').locator('input'); const titleValue = await titleField.inputValue(); expect(titleValue.toLowerCase()).toContain('description'); @@ -175,9 +175,7 @@ test.describe('Task Creation - Request Description', () => { await expect(page.getByTestId('form-container')).toBeVisible(); // Assignee field - search and select user - const assigneeInput = page.locator( - '[data-testid="select-assignee"] .ant-select-selector input' - ); + const assigneeInput = page.locator('[data-testid="select-assignee"] input'); await assigneeInput.click(); // Search for user @@ -217,7 +215,7 @@ test.describe('Task Creation - Request Description', () => { await submitBtn.click(); // Should show validation error for assignee field - const assigneeError = page.locator('.ant-form-item-explain-error'); + const assigneeError = page.getByTestId('assignees').getByText(/required/i); await expect(assigneeError).toBeVisible(); }); }); @@ -274,7 +272,7 @@ test.describe('Task Creation - Request Tags', () => { await expect(page.getByTestId('form-container')).toBeVisible(); // Verify title contains tag info - const titleField = page.locator('#title'); + const titleField = page.getByTestId('title').locator('input'); const titleValue = await titleField.inputValue(); expect(titleValue.toLowerCase()).toContain('tag'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCustomFormWorkflow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCustomFormWorkflow.spec.ts index 4d33a8108095..f2c8566842df 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCustomFormWorkflow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Tasks/TaskCustomFormWorkflow.spec.ts @@ -474,16 +474,12 @@ test.describe.serial( const visibleModal = page.getByRole('dialog').first(); await expect(visibleModal).toBeVisible(); - const proposedTextField = visibleModal - .locator('.ant-form-item') - .filter({ hasText: 'Proposed Text' }) - .getByRole('textbox') - .first(); - const reviewNotesField = visibleModal - .locator('.ant-form-item') - .filter({ hasText: 'Review Notes' }) - .getByRole('textbox') - .first(); + const proposedTextField = visibleModal.getByRole('textbox', { + name: /Proposed Text/, + }); + const reviewNotesField = visibleModal.getByRole('textbox', { + name: /Review Notes/, + }); await proposedTextField.fill(updatedDescription); await reviewNotesField.fill(updatedReviewNotes); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts index 27faef5f0c5f..e23d65cffea8 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Tags.spec.ts @@ -390,7 +390,7 @@ test('Classification Page', async ({ page }) => { await page.click('[data-testid="request-entity-tags"]'); - await page.click('[data-testid="select-assignee"]'); + await page.click('[data-testid="select-assignee"] input'); const assigneeResponse = page.waitForResponse( '/api/v1/search/query?q=*&index=user*team*' ); 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 e9d316486e57..b1642f78536b 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 @@ -57,11 +57,11 @@ const createDescriptionTaskViaUI = async ( ) => { await page.getByTestId('request-description').click(); - await page.waitForSelector('#title', { state: 'visible' }); + await expect(page.getByTestId('title').locator('input')).toBeVisible(); - expect(await page.locator('#title').inputValue()).toContain( - `description for ${entityType}` - ); + expect( + await page.getByTestId('title').locator('input').inputValue() + ).toContain(`description for ${entityType}`); await selectAssignee(page, assigneeName); @@ -84,11 +84,11 @@ const createTagTaskViaUI = async ( ) => { await page.getByTestId('request-entity-tags').click(); - await page.waitForSelector('#title', { state: 'visible' }); + await expect(page.getByTestId('title').locator('input')).toBeVisible(); - expect(await page.locator('#title').inputValue()).toContain( - `tags for ${entityType}` - ); + expect( + await page.getByTestId('title').locator('input').inputValue() + ).toContain(`tags for ${entityType}`); await selectAssignee(page, assigneeName); await addTagSuggestion({ @@ -326,9 +326,11 @@ test.describe('Task Workflow - Table Column Tasks', () => { }); await test.step('Fill task form and submit', async () => { - await page.waitForSelector('#title', { state: 'visible' }); + await expect(page.getByTestId('title').locator('input')).toBeVisible(); - expect(await page.locator('#title').inputValue()).toContain('columns'); + expect( + await page.getByTestId('title').locator('input').inputValue() + ).toContain('columns'); await selectAssignee(page, userName); @@ -375,9 +377,11 @@ test.describe('Task Workflow - Table Column Tasks', () => { }); await test.step('Fill tag task form and submit', async () => { - await page.waitForSelector('#title', { state: 'visible' }); + await expect(page.getByTestId('title').locator('input')).toBeVisible(); - expect(await page.locator('#title').inputValue()).toContain('columns'); + expect( + await page.getByTestId('title').locator('input').inputValue() + ).toContain('columns'); await selectAssignee(page, userName); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs b/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs index 7c996269e634..d732448a6890 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs +++ b/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs @@ -46,11 +46,13 @@ test('the suppressions baseline matches its recorded state exactly', () => { // DomainFilterQueryFilter, ActivityFeed, and SearchExport; the core // Popover/Dropdown migration also removed a positional locator. // Removing the S3 sample-data storage tests and scoping - // ChangeSummaryBadge's button lookup remove three more positional locators. - 'om-playwright/no-positional-locator': 602, + // ChangeSummaryBadge's button lookup remove three more positional locators; + // the core assignee/schema-field selectors in the task and incident specs + // remove three more. + 'om-playwright/no-positional-locator': 598, 'om-playwright/require-assertion-per-test': 1, 'playwright/no-skipped-test': 2, - 'playwright/no-wait-for-selector': 18, + 'playwright/no-wait-for-selector': 14, }; assert.deepStrictEqual( 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 346ad592747f..643a5266a182 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts @@ -1280,26 +1280,22 @@ export const createDescriptionTaskForGlossary = async ( const entityType = isGlossary ? 'glossary' : 'glossaryTerm'; const entityName = get(entity, 'responseData.displayName'); - await expect(page.locator('#title')).toHaveValue( + await expect(page.getByTestId('title').locator('input')).toHaveValue( `${ addDescription ? 'Update' : 'Request' } description for ${entityType} ${entityName}` ); if (isUndefined(value.assignee)) { - await expect( - page.locator('[data-testid="select-assignee"] > .ant-select-selector') - ).toHaveText(value.assignee ?? ''); + await expect(page.getByTestId('select-assignee')).toContainText( + value.assignee ?? '' + ); await expect( - page.locator( - '[data-testid="select-assignee"] > .ant-select-selector input' - ) + page.locator('[data-testid="select-assignee"] input') ).toBeDisabled(); } else { - const assigneeField = page.locator( - '[data-testid="select-assignee"] > .ant-select-selector #assignees' - ); + const assigneeField = page.locator('[data-testid="select-assignee"] input'); await assigneeField.click(); const userSearchResponse = page.waitForResponse( @@ -1336,25 +1332,21 @@ export const createTagTaskForGlossary = async ( const entityType = isGlossary ? 'glossary' : 'glossaryTerm'; const entityName = get(entity, 'responseData.displayName'); - await expect(page.locator('#title')).toHaveValue( + await expect(page.getByTestId('title').locator('input')).toHaveValue( `Request tags for ${entityType} ${entityName}` ); if (isUndefined(value.assignee)) { - await expect( - page.locator('[data-testid="select-assignee"] > .ant-select-selector') - ).toHaveText(value.assignee ?? ''); + await expect(page.getByTestId('select-assignee')).toContainText( + value.assignee ?? '' + ); await expect( - page.locator( - '[data-testid="select-assignee"] > .ant-select-selector input' - ) + page.locator('[data-testid="select-assignee"] input') ).toBeDisabled(); } else { // select assignee - const assigneeField = page.locator( - '[data-testid="select-assignee"] > .ant-select-selector #assignees' - ); + const assigneeField = page.locator('[data-testid="select-assignee"] input'); await assigneeField.click(); const userSearchResponse = page.waitForResponse( `/api/v1/search/query?q=*${value.assignee}**&index=user%2Cteam*` diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/incidentManager.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/incidentManager.ts index be15c477bfd3..487e43df59de 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/incidentManager.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/incidentManager.ts @@ -189,17 +189,25 @@ export const addAssigneeFromPopoverWidget = async (data: { const assigneeModal = page.locator('.ant-modal-content').last(); const assigneeSelect = assigneeModal.getByTestId('select-assignee'); - const assigneeSelector = assigneeSelect.locator('.ant-select-selector'); - const assigneeInput = assigneeSelect.locator('input').last(); + const assigneeInput = assigneeSelect.locator('input'); const assigneeOption = page.getByTestId(user.name).first(); const normalizedAssigneeOption = page .getByTestId(user.name.toLowerCase()) .first(); await expect(assigneeModal).toBeVisible(); - await expect(assigneeSelector).toBeVisible(); - await assigneeSelector.click(); + // Single-select core Autocomplete hides its input while an item is selected. + const selectedAssignee = assigneeSelect.getByTestId( + 'autocomplete-selected-item' + ); + if (await selectedAssignee.isVisible()) { + await selectedAssignee.getByRole('button').click(); + } + + await expect(assigneeInput).toBeVisible(); + + await assigneeInput.click(); await assigneeInput.fill(user.displayName); if (await assigneeOption.isVisible().catch(() => false)) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts index 2a7e1b68e83b..41a1dcc93526 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts @@ -108,7 +108,7 @@ export const createDescriptionTask = async ( addDescription = true, assigneeDisabled?: boolean ) => { - await expect(page.locator('#title')).toHaveValue( + await expect(page.getByTestId('title').locator('input')).toHaveValue( `${addDescription ? 'Update' : 'Request'} description for table ${ value.columnName ? `${value.term} columns/${value.columnName}` @@ -117,19 +117,15 @@ export const createDescriptionTask = async ( ); if (isUndefined(value.assignee) || assigneeDisabled) { - await expect( - page.locator('[data-testid="select-assignee"] > .ant-select-selector') - ).toHaveText(value.assignee); + await expect(page.getByTestId('select-assignee')).toContainText( + value.assignee ?? '' + ); await expect( - page.locator( - '[data-testid="select-assignee"] > .ant-select-selector input' - ) + page.locator('[data-testid="select-assignee"] input') ).toBeDisabled(); } else { - const assigneeField = page.locator( - '[data-testid="select-assignee"] > .ant-select-selector #assignees' - ); + const assigneeField = page.locator('[data-testid="select-assignee"] input'); await assigneeField.click(); await assigneeField.fill(value.assignee); @@ -157,25 +153,21 @@ export const createTagTask = async ( addTag = true, assigneeDisabled?: boolean ) => { - await expect(page.locator('#title')).toHaveValue( + await expect(page.getByTestId('title').locator('input')).toHaveValue( `Request tags for table ${value.term}` ); if (isUndefined(value.assignee) || assigneeDisabled) { - await expect( - page.locator('[data-testid="select-assignee"] > .ant-select-selector') - ).toHaveText(value.assignee); + await expect(page.getByTestId('select-assignee')).toContainText( + value.assignee ?? '' + ); await expect( - page.locator( - '[data-testid="select-assignee"] > .ant-select-selector input' - ) + page.locator('[data-testid="select-assignee"] input') ).toBeDisabled(); } else { // select assignee - const assigneeField = page.locator( - '[data-testid="select-assignee"] > .ant-select-selector #assignees' - ); + const assigneeField = page.locator('[data-testid="select-assignee"] input'); await assigneeField.click(); await assigneeField.fill(value.assignee); 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 1c75883a602b..d83bbf392859 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/taskWorkflow.ts @@ -214,9 +214,7 @@ export const openTaskForm = async (page: Page, route: string) => { }; export const selectAssignee = async (page: Page, assigneeName: string) => { - const assigneeInput = page.locator( - '[data-testid="select-assignee"] .ant-select-selection-search input' - ); + const assigneeInput = page.locator('[data-testid="select-assignee"] input'); const assigneeOption = page.getByTestId(assigneeName); const assigneeSearchResponse = page .waitForResponse( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCaseStatusModal/TestCaseStatusModal.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCaseStatusModal/TestCaseStatusModal.component.tsx index 0691340a8a5a..28dd4166b5d8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCaseStatusModal/TestCaseStatusModal.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCaseStatusModal/TestCaseStatusModal.component.tsx @@ -371,7 +371,6 @@ export const TestCaseStatusModal = ({ }, ]}> = ({ {showLoadedCount && ( {t('label.loaded-x-of-y-entity', { loaded: loadedTermCount, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyAuthoringInspector.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyAuthoringInspector.tsx index ca3328dfc8df..a5e9afc0708b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyAuthoringInspector.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyAuthoringInspector.tsx @@ -270,7 +270,7 @@ const OntologyAuthoringInspector = ({ ); const colorMeta = effectiveColor ? COLOR_META_BY_HEX[effectiveColor.toLowerCase()] ?? { - background: 'var(--color-bg-secondary)', + background: 'var(--tw-color-bg-secondary)', color: effectiveColor, } : undefined; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyDataGraph.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyDataGraph.tsx index ae40a9a4e54c..dacec75e25fa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyDataGraph.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyDataGraph.tsx @@ -26,14 +26,10 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { RelationshipType } from '../../generated/entity/data/relationshipType'; -import { resolveCssColor } from '../../utils/common/cssColor.utils'; import entityUtilClassBase from '../../utils/EntityUtilClassBase'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; import OntologyControlButtons from './OntologyControlButtons'; -import { - DATA_MODE_MAX_RENDER_COUNT, - EDGE_STROKE_COLOR_FALLBACK, -} from './OntologyExplorer.constants'; +import { DATA_MODE_MAX_RENDER_COUNT } from './OntologyExplorer.constants'; import { OntologyEdge, OntologyGraphData, @@ -58,7 +54,6 @@ import { formatRelationLabel, getEffectiveRelationColor, } from './utils/graphStyles'; -import { getRelationshipHexColor } from './utils/relationshipTypeUtils'; interface OntologyDataGraphProps { data: OntologyGraphData; @@ -401,15 +396,13 @@ const OntologyDataGraph = ({ const relationshipType = relationshipTypeByName.get( layout.edge.relationType ); - const effectiveColor = + // DOM/SVG resolves theme variables after a theme switch; a stored palette + // hex would discard the built-in relation's active foreground. + const color = getEffectiveRelationColor( layout.edge.relationType, relationshipType - ) ?? 'var(--color-border-brand)'; - const color = - effectiveColor.startsWith('var(') && relationshipType - ? getRelationshipHexColor(relationshipType) - : resolveCssColor(effectiveColor, EDGE_STROKE_COLOR_FALLBACK); + ) ?? 'var(--tw-color-border-brand)'; return { ...layout, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.constants.ts b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.constants.ts index fb2a96e860c8..d2b11394f933 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.constants.ts @@ -46,203 +46,235 @@ export const DEFAULT_GLOSSARY_TERM_RELATION_TYPES_FALLBACK = [ DEFAULT_RELATIONSHIP_TYPE, ]; +const RELATION_PALETTES = { + brand: { + color: 'var(--tw-color-utility-brand-700)', + background: 'var(--tw-color-utility-brand-50)', + }, + error: { + color: 'var(--tw-color-utility-error-700)', + background: 'var(--tw-color-utility-error-50)', + }, + warning: { + color: 'var(--tw-color-utility-warning-700)', + background: 'var(--tw-color-utility-warning-50)', + }, + success: { + color: 'var(--tw-color-utility-success-700)', + background: 'var(--tw-color-utility-success-50)', + }, + 'blue-light': { + color: 'var(--tw-color-utility-blue-light-700)', + background: 'var(--tw-color-utility-blue-light-50)', + }, + blue: { + color: 'var(--tw-color-utility-blue-700)', + background: 'var(--tw-color-utility-blue-50)', + }, + purple: { + color: 'var(--tw-color-utility-purple-700)', + background: 'var(--tw-color-utility-purple-50)', + }, + fuchsia: { + color: 'var(--tw-color-utility-fuchsia-700)', + background: 'var(--tw-color-utility-fuchsia-50)', + }, + orange: { + color: 'var(--tw-color-utility-orange-700)', + background: 'var(--tw-color-utility-orange-50)', + }, + pink: { + color: 'var(--tw-color-utility-pink-700)', + background: 'var(--tw-color-utility-pink-50)', + }, + 'gray-blue': { + color: 'var(--tw-color-utility-gray-blue-700)', + background: 'var(--tw-color-utility-gray-blue-50)', + }, + teal: { + color: 'var(--tw-color-utility-teal-700)', + background: 'var(--tw-color-utility-teal-50)', + }, + gray: { + color: 'var(--tw-color-utility-gray-700)', + background: 'var(--tw-color-utility-gray-50)', + }, + violet: { + color: 'var(--tw-color-utility-violet-700)', + background: 'var(--tw-color-utility-violet-50)', + }, + moss: { + color: 'var(--tw-color-utility-moss-700)', + background: 'var(--tw-color-utility-moss-50)', + }, + cyan: { + color: 'var(--tw-color-utility-cyan-700)', + background: 'var(--tw-color-utility-cyan-50)', + }, + rose: { + color: 'var(--tw-color-utility-rose-700)', + background: 'var(--tw-color-utility-rose-50)', + }, +}; + export const RELATION_META: Record< string, { color: string; background: string; labelKey: string } > = { relatedTo: { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.related-to', }, related: { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.related', }, synonym: { - color: '#b42318', - background: '#fef3f2', + ...RELATION_PALETTES['error'], labelKey: 'label.synonym', }, antonym: { - color: '#b54708', - background: '#fffaeb', + ...RELATION_PALETTES['warning'], labelKey: 'label.antonym', }, typeOf: { - color: '#067647', - background: '#ecfdf3', + ...RELATION_PALETTES['success'], labelKey: 'label.type-of', }, hasTypes: { - color: '#067647', - background: '#ecfdf3', + ...RELATION_PALETTES['success'], labelKey: 'label.has-types', }, hasA: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.has-a', }, partOf: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.part-of', }, hasPart: { - color: '#155eef', - background: '#eff4ff', + ...RELATION_PALETTES['blue'], labelKey: 'label.has-part', }, componentOf: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.component-of', }, composedOf: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.composed-of', }, calculatedFrom: { - color: '#6938ef', - background: '#f4f3ff', + ...RELATION_PALETTES['purple'], labelKey: 'label.calculated-from', }, usedToCalculate: { - color: '#ba24d5', - background: '#fdf4ff', + ...RELATION_PALETTES['fuchsia'], labelKey: 'label.used-to-calculate', }, derivedFrom: { - color: '#bc1b06', - background: '#fff4ed', + ...RELATION_PALETTES['orange'], labelKey: 'label.derived-from', }, seeAlso: { - color: '#c11574', - background: '#fdf2fa', + ...RELATION_PALETTES['pink'], labelKey: 'label.see-also', }, parentOf: { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.parent-of', }, childOf: { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.child-of', }, broader: { - color: '#067647', - background: '#ecfdf3', + ...RELATION_PALETTES['success'], labelKey: 'label.broader', }, narrower: { - color: '#4e5ba6', - background: '#f8f9fc', + ...RELATION_PALETTES['gray-blue'], labelKey: 'label.narrower', }, isA: { - color: '#067647', - background: '#ecfdf3', + ...RELATION_PALETTES['success'], labelKey: 'label.is-a', }, instanceOf: { - color: '#067647', - background: '#ecfdf3', + ...RELATION_PALETTES['success'], labelKey: 'label.instance-of', }, owns: { - color: '#6938ef', - background: '#f4f3ff', + ...RELATION_PALETTES['purple'], labelKey: 'label.owns', }, ownedBy: { - color: '#6938ef', - background: '#f4f3ff', + ...RELATION_PALETTES['purple'], labelKey: 'label.owned-by', }, manages: { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.manages', }, managedBy: { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.managed-by', }, contains: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.contains', }, containedIn: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.contained-in', }, dependsOn: { - color: '#b42318', - background: '#fef3f2', + ...RELATION_PALETTES['error'], labelKey: 'label.depends-on', }, usedBy: { - color: '#b54708', - background: '#fffaeb', + ...RELATION_PALETTES['warning'], labelKey: 'label.used-by', }, metricFor: { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.metric-for', }, hasGlossaryTerm: { - color: '#107569', - background: '#f0fdf9', + ...RELATION_PALETTES['teal'], labelKey: 'label.tagged-with', }, custom1: { - color: '#bc1b06', - background: '#fff4ed', + ...RELATION_PALETTES['orange'], labelKey: 'label.color-orange', }, custom2: { - color: '#535862', - background: '#fafafa', + ...RELATION_PALETTES['gray'], labelKey: 'label.color-gray', }, custom6: { - color: '#107569', - background: '#f0fdf9', + ...RELATION_PALETTES['teal'], labelKey: 'label.color-rose', }, custom4: { - color: '#7839ee', - background: '#f5f3ff', + ...RELATION_PALETTES['violet'], labelKey: 'label.color-teal', }, custom5: { - color: '#4f7a21', - background: '#f5fbee', + ...RELATION_PALETTES['moss'], labelKey: 'label.color-moss', }, custom7: { - color: '#0e7090', - background: '#ecfdff', + ...RELATION_PALETTES['cyan'], labelKey: 'label.color-cyan', }, custom3: { - color: '#e31b54', - background: '#fff1f3', + ...RELATION_PALETTES['rose'], labelKey: 'label.color-violet', }, default: { - color: '#535862', - background: '#fafafa', + ...RELATION_PALETTES['gray'], labelKey: 'label.relation-type', }, }; @@ -256,111 +288,97 @@ export const COLOR_META_BY_HEX: Record< { color: string; background: string; labelKey: string } > = { '#1570ef': { - color: '#1570ef', - background: '#eff8ff', + ...RELATION_PALETTES['brand'], labelKey: 'label.color-blue', }, '#b42318': { - color: '#b42318', - background: '#fef3f2', + ...RELATION_PALETTES['error'], labelKey: 'label.color-red', }, '#b54708': { - color: '#b54708', - background: '#fffaeb', + ...RELATION_PALETTES['warning'], labelKey: 'label.color-yellow', }, '#067647': { - color: '#067647', - background: '#ecfdf3', + ...RELATION_PALETTES['success'], labelKey: 'label.color-green', }, '#4e5ba6': { - color: '#4e5ba6', - background: '#f8f9fc', + ...RELATION_PALETTES['gray-blue'], labelKey: 'label.color-blue-gray', }, '#026aa2': { - color: '#026aa2', - background: '#f0f9ff', + ...RELATION_PALETTES['blue-light'], labelKey: 'label.color-blue-light', }, '#155eef': { - color: '#155eef', - background: '#eff4ff', + ...RELATION_PALETTES['blue'], labelKey: 'label.color-dark-blue', }, '#6938ef': { - color: '#6938ef', - background: '#f4f3ff', + ...RELATION_PALETTES['purple'], labelKey: 'label.color-purple', }, '#ba24d5': { - color: '#ba24d5', - background: '#fdf4ff', + ...RELATION_PALETTES['fuchsia'], labelKey: 'label.color-fuchsia', }, '#c11574': { - color: '#c11574', - background: '#fdf2fa', + ...RELATION_PALETTES['pink'], labelKey: 'label.color-pink', }, '#bc1b06': { - color: '#bc1b06', - background: '#fff4ed', + ...RELATION_PALETTES['orange'], labelKey: 'label.color-orange', }, '#107569': { - color: '#107569', - background: '#f0fdf9', + ...RELATION_PALETTES['teal'], labelKey: 'label.color-rose', }, '#535862': { - color: '#535862', - background: '#fafafa', + ...RELATION_PALETTES['gray'], labelKey: 'label.color-gray', }, '#e31b54': { - color: '#e31b54', - background: '#fff1f3', + ...RELATION_PALETTES['rose'], labelKey: 'label.color-violet', }, '#7839ee': { - color: '#7839ee', - background: '#f5f3ff', + ...RELATION_PALETTES['violet'], labelKey: 'label.color-teal', }, '#4f7a21': { - color: '#4f7a21', - background: '#f5fbee', + ...RELATION_PALETTES['moss'], labelKey: 'label.color-moss', }, '#0e7090': { - color: '#0e7090', - background: '#ecfdff', + ...RELATION_PALETTES['cyan'], labelKey: 'label.color-cyan', }, }; -const BORDER_PRIMARY_TOKEN = 'var(--color-border-primary)'; +// Persisted palettes use hex keys, while built-in relations now supply theme tokens. +// Both forms must find the same badge background and palette metadata. +for (const meta of Object.values(COLOR_META_BY_HEX)) { + COLOR_META_BY_HEX[meta.color] = meta; +} + +const BORDER_PRIMARY_TOKEN = 'var(--tw-color-border-primary)'; const BORDER_PRIMARY_FALLBACK = '#D5D7DA'; -const BORDER_SECONDARY_TOKEN = 'var(--color-border-secondary)'; +const BORDER_SECONDARY_TOKEN = 'var(--tw-color-border-secondary)'; export const EDGE_STROKE_COLOR = BORDER_PRIMARY_TOKEN; -// SVG presentation attributes do not resolve CSS variables, so DOM-drawn -// edges resolve the token first and fall back to this. -export const EDGE_STROKE_COLOR_FALLBACK = BORDER_PRIMARY_FALLBACK; export const DATA_MODE_ASSET_EDGE_STROKE_COLOR = BORDER_SECONDARY_TOKEN; export const DIMMED_NODE_OPACITY = 0.32; export const DIMMED_EDGE_OPACITY = 0.12; export const DIMMED_EDGE_LABEL_OPACITY = 0.16; -export const NODE_FILL_DEFAULT = 'var(--color-bg-primary)'; +export const NODE_FILL_DEFAULT = 'var(--tw-color-bg-primary)'; export const NODE_BORDER_COLOR = BORDER_SECONDARY_TOKEN; -export const NODE_SELECTED_STROKE = 'var(--color-border-brand)'; +export const NODE_SELECTED_STROKE = 'var(--tw-color-border-brand)'; export const NODE_SELECTED_LINE_WIDTH = 2.5; export const NODE_SELECTED_HALO_LINE_WIDTH = 4; -export const NODE_SELECTED_HALO_FILL = 'var(--color-bg-brand-primary)'; +export const NODE_SELECTED_HALO_FILL = 'var(--tw-color-bg-brand-primary)'; export const NODE_BORDER_RADIUS = 9; export const NODE_PADDING_V = 9; export const NODE_PADDING_H = 12; @@ -374,25 +392,26 @@ export const NODE_LABEL_PADDING: [number, number, number, number] = [ export const COMBO_FILL_DEFAULT = NODE_FILL_DEFAULT; export const COMBO_BODY_FILL_OPACITY = '22'; export const COMBO_LABEL_BG_OPACITY = '40'; -export const NODE_LABEL_FILL = 'var(--color-text-primary)'; -export const NODE_LABEL_FILL_INVERSE = 'var(--color-text-white)'; +export const NODE_LABEL_FILL = 'var(--tw-color-text-primary)'; +export const NODE_LABEL_FILL_INVERSE = 'var(--tw-color-text-white)'; export const BRAND_BLUE_FALLBACK = '#3b82f6'; export const COMBO_COLOR_FALLBACK = '#94a3b8'; -export const DATA_MODE_ASSET_COUNT_BADGE_BG = 'var(--color-bg-primary-solid)'; -export const DATA_MODE_LOAD_MORE_BADGE_BG = 'var(--color-bg-brand-solid)'; +export const DATA_MODE_ASSET_COUNT_BADGE_BG = + 'var(--tw-color-bg-primary-solid)'; +export const DATA_MODE_LOAD_MORE_BADGE_BG = 'var(--tw-color-bg-brand-solid)'; export const NODE_LABEL_FONT_SIZE = 11; export const NODE_LABEL_FONT_WEIGHT = 600; export const NODE_SHADOW_COLOR = BORDER_SECONDARY_TOKEN; export const NODE_SHADOW_BLUR = 2; export const NODE_SHADOW_OFFSET_Y = 1; -export const EDGE_LABEL_FILL = 'var(--color-text-tertiary)'; +export const EDGE_LABEL_FILL = 'var(--tw-color-text-tertiary)'; export const EDGE_LABEL_FONT_SIZE = 10; export const EDGE_LABEL_FONT_WEIGHT = 600; export const EDGE_LABEL_FONT_FAMILY = 'Inter'; export const EDGE_LABEL_LINE_HEIGHT = 16; export const EDGE_LABEL_LETTER_SPACING = 0; -export const EDGE_LABEL_BG_FILL = 'var(--color-bg-secondary)'; +export const EDGE_LABEL_BG_FILL = 'var(--tw-color-bg-secondary)'; export const EDGE_LABEL_BG_STROKE = NODE_FILL_DEFAULT; export const EDGE_LABEL_BG_RADIUS = 3; export const EDGE_LABEL_BG_SHADOW_COLOR = BORDER_SECONDARY_TOKEN; @@ -544,11 +563,11 @@ export const NODE_LABEL_FILL_FALLBACK = '#1e293b'; // A metric node is a governed Metric entity drawn next to the concept it // measures, not a concept: muted and dashed so it never reads as authorable. export const STUDIO_METRIC_NODE_KIND = 'metric'; -export const METRIC_NODE_FILL = 'var(--color-bg-secondary)'; +export const METRIC_NODE_FILL = 'var(--tw-color-bg-secondary)'; export const METRIC_NODE_FILL_FALLBACK = '#FAFAFA'; export const METRIC_NODE_STROKE = BORDER_PRIMARY_TOKEN; export const METRIC_NODE_STROKE_FALLBACK = BORDER_PRIMARY_FALLBACK; -export const METRIC_NODE_MUTED_COLOR = 'var(--color-text-tertiary)'; +export const METRIC_NODE_MUTED_COLOR = 'var(--tw-color-text-tertiary)'; export const METRIC_NODE_MUTED_COLOR_FALLBACK = '#535862'; export const METRIC_NODE_LINE_DASH = [4, 3]; export const NODE_SHADOW_COLOR_FALLBACK = 'rgba(0,0,0,0.12)'; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.test.tsx index 6f735c71c164..76985979b480 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.test.tsx @@ -312,7 +312,7 @@ describe('OntologyExplorer Studio data controls', () => { ).not.toBeInTheDocument(); }); - it('resolves palette tokens before painting custom SVG relationships', () => { + it('preserves palette tokens so custom SVG relationships follow theme changes', () => { const state = createExplorerState({ explorationMode: 'data', graphDataToShow: { @@ -339,7 +339,7 @@ describe('OntologyExplorer Studio data controls', () => { expect(screen.getByTestId('ontology-data-semantic-edge')).toHaveAttribute( 'stroke', - '#7a5af8' + 'var(--tw-color-utility-purple-500)' ); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.tsx index 8a0ed015a9d5..6a3728a14a2d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyExplorer.tsx @@ -93,9 +93,9 @@ const SearchInputIcon = ({ className }: { className?: string }) => (