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 }) => (
);
const DEFAULT_GRAPH_BACKDROP_CLASS =
- 'tw:absolute tw:inset-0 tw:z-0 tw:bg-primary tw:[background-image:radial-gradient(circle,var(--color-border-secondary)_1px,transparent_1px)] tw:[background-size:14px_14px]';
+ 'tw:absolute tw:inset-0 tw:z-0 tw:bg-canvas tw:[background-image:radial-gradient(circle,var(--tw-color-border-secondary)_1px,transparent_1px)] tw:[background-size:14px_14px]';
const STUDIO_GRAPH_BACKDROP_CLASS =
- 'tw:absolute tw:inset-0 tw:z-0 tw:bg-primary tw:[background-image:radial-gradient(circle,var(--color-border-secondary)_1px,transparent_1px)] tw:[background-size:22px_22px]';
+ 'tw:absolute tw:inset-0 tw:z-0 tw:bg-canvas tw:[background-image:radial-gradient(circle,var(--tw-color-border-secondary)_1px,transparent_1px)] tw:[background-size:22px_22px]';
const ONTOLOGY_TOOLBAR_CARD_CLASS =
'tw:z-6 tw:border tw:border-utility-gray-blue-100 tw:shadow-md';
@@ -242,7 +242,7 @@ function LoadingGraphEmptyState() {
data-testid="ontology-graph-loading">
@@ -1039,7 +1039,7 @@ const OntologyExplorer: React.FC = ({
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyImportExportModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyImportExportModal.tsx
index b1d914b2cf08..950d3d937354 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyImportExportModal.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyImportExportModal.tsx
@@ -100,12 +100,12 @@ const FORMAT_MEDIA_TYPE: Record = {
turtle: 'text/turtle',
};
-const PREVIEW_BG = 'var(--color-bg-primary-solid)';
+const PREVIEW_BG = 'var(--tw-color-bg-primary-solid)';
const SYNTAX_COLOR = {
- default: 'var(--color-text-secondary_on-brand)',
- keyword: 'var(--color-utility-blue-400)',
- string: 'var(--color-utility-success-400)',
- term: 'var(--color-utility-orange-400)',
+ default: 'var(--tw-color-text-secondary_on-brand)',
+ keyword: 'var(--tw-color-utility-blue-400)',
+ string: 'var(--tw-color-utility-success-400)',
+ term: 'var(--tw-color-utility-orange-400)',
};
const SYNTAX_TOKEN =
/("(?:[^"\\]|\\.)*"(?:@[\w-]+)?)|(<[^>\s]*>)|(\b[A-Za-z][\w-]*:[\w/#.-]*)|(\ba\b)/g;
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyLibrary.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyLibrary.tsx
index ee3f7416f9cb..8cc72a2f62f0 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyLibrary.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyLibrary.tsx
@@ -51,7 +51,7 @@ interface OntologyLibraryProps {
const EMPTY_INSTALLATIONS: OntologyPackInstallation[] = [];
const LIBRARY_ICON_CLASS =
'tw:grid tw:size-8 tw:shrink-0 tw:place-items-center tw:rounded-[9px] ' +
- 'tw:bg-[linear-gradient(160deg,var(--color-utility-blue-light-500),var(--color-bg-brand-solid))] tw:text-white';
+ 'tw:bg-[linear-gradient(160deg,var(--tw-color-utility-blue-light-500),var(--tw-color-bg-brand-solid))] tw:text-white';
const INSTALLED_COUNT_CLASS =
'tw:inline-flex tw:shrink-0 tw:items-center tw:rounded-full tw:border ' +
'tw:border-utility-success-200 tw:bg-utility-success-50 tw:px-[11px] tw:py-1 ' +
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyNodeRelationsContent.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyNodeRelationsContent.tsx
index 429e571fe83a..77d209601cc9 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyNodeRelationsContent.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyNodeRelationsContent.tsx
@@ -261,7 +261,7 @@ export const OntologyNodeRelationsContent: React.FC<
);
const meta = 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/OntologyStudioQueryConsole.css b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyStudioQueryConsole.css
index ed6d9bcf4365..9d1365395c1b 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyStudioQueryConsole.css
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/OntologyStudioQueryConsole.css
@@ -14,11 +14,11 @@
.ontology-studio-query-editor .cm-editor {
height: auto;
min-height: 138px;
- background: var(--color-bg-primary);
- color: var(--color-text-primary);
+ background: var(--tw-color-bg-primary);
+ color: var(--tw-color-text-primary);
font-family: 'Geist Mono', ui-monospace, monospace;
- font-size: var(--om-font-size-xs);
- font-weight: var(--om-font-weight-regular);
+ font-size: var(--tw-text-xs);
+ font-weight: var(--tw-font-weight-normal);
line-height: 1.6;
}
@@ -32,7 +32,7 @@
}
.ontology-studio-query-editor .cm-content {
- padding: var(--om-space-12) var(--om-space-16);
+ padding: calc(var(--tw-spacing) * 3) calc(var(--tw-spacing) * 4);
}
.ontology-studio-query-editor .cm-line {
@@ -40,5 +40,5 @@
}
.ontology-studio-query-editor .cm-content > .cm-line:nth-child(-n + 2) span {
- color: var(--color-utility-purple-600);
+ color: var(--tw-color-utility-purple-600);
}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/PortOverlay.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/PortOverlay.component.tsx
index 49da27d06a9d..4050dfb90a27 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/PortOverlay.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/PortOverlay.component.tsx
@@ -153,7 +153,7 @@ const PortOverlay: React.FC = ({
style={{ overflow: 'visible' }}>
{
const color =
- params.glossaryColorMap[combo.glossaryId] ?? 'var(--color-gray-400)';
+ params.glossaryColorMap[combo.glossaryId] ?? 'var(--tw-color-gray-400)';
const isComboDimmed = Boolean(
params.searchGlossarySet &&
!params.searchGlossarySet.has(combo.glossaryId)
@@ -1458,7 +1463,7 @@ function buildGlossaryGroupCombos(params: BuildCombosParams): ComboData[] {
const name =
terms[0].group ?? (glossary ? glossary.displayName || glossary.name : '');
const color =
- params.glossaryColorMap[glossaryId] ?? 'var(--color-gray-400)';
+ params.glossaryColorMap[glossaryId] ?? 'var(--tw-color-gray-400)';
const isComboDimmed = Boolean(
params.searchGlossarySet && !params.searchGlossarySet.has(glossaryId)
);
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/hooks/useOntologyGraph.ts b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/hooks/useOntologyGraph.ts
index bcb596768118..27861789e548 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/hooks/useOntologyGraph.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/hooks/useOntologyGraph.ts
@@ -510,7 +510,12 @@ function getStudioNodeKindStyle(
}
return {
- stroke: ontNode?.type === 'glossaryTermIsolated' ? '#FEDF89' : '#E9EAEB',
+ stroke: getCanvasColor(
+ ontNode?.type === 'glossaryTermIsolated'
+ ? 'var(--tw-color-utility-warning-200)'
+ : 'var(--tw-color-border-secondary)',
+ '#E9EAEB'
+ ),
};
}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.test.ts b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.test.ts
index cd809280d27e..d20942bc5e1e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.test.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.test.ts
@@ -34,7 +34,10 @@ jest.mock('@antv/g6', () => ({
import { PaletteKey } from '../../../generated/entity/data/relationshipType';
import { createRelationshipTypeMock } from '../../../mocks/Ontology.mock';
-import { RELATION_META } from '../OntologyExplorer.constants';
+import {
+ COLOR_META_BY_HEX,
+ RELATION_META,
+} from '../OntologyExplorer.constants';
import {
buildComboStyle,
buildDataModeAssetNodeStyle,
@@ -62,16 +65,57 @@ const mockComputedStyle = (
const resolveSemanticColor = (color: string, fallback: string): string =>
({
- 'var(--color-bg-primary)': 'rgb(12, 14, 18)',
- 'var(--color-bg-secondary)': 'rgb(24, 27, 33)',
- 'var(--color-border-primary)': 'rgb(55, 58, 65)',
- 'var(--color-border-secondary)': 'rgb(34, 38, 47)',
- 'var(--color-text-primary)': 'rgb(247, 247, 247)',
- 'var(--color-text-tertiary)': 'rgb(148, 153, 162)',
- 'var(--color-text-white)': 'rgb(255, 255, 255)',
+ 'var(--tw-color-bg-primary)': 'rgb(12, 14, 18)',
+ 'var(--tw-color-bg-secondary)': 'rgb(24, 27, 33)',
+ 'var(--tw-color-border-primary)': 'rgb(55, 58, 65)',
+ 'var(--tw-color-border-secondary)': 'rgb(34, 38, 47)',
+ 'var(--tw-color-text-primary)': 'rgb(247, 247, 247)',
+ 'var(--tw-color-text-tertiary)': 'rgb(148, 153, 162)',
+ 'var(--tw-color-text-white)': 'rgb(255, 255, 255)',
}[color] ?? fallback);
describe('theme-aware graph styles', () => {
+ it('finds palette backgrounds for both saved hex colors and built-in theme tokens', () => {
+ expect(COLOR_META_BY_HEX['#1570ef'].color).toBe(
+ RELATION_META.relatedTo.color
+ );
+
+ for (const { color, background } of Object.values(RELATION_META)) {
+ expect(COLOR_META_BY_HEX[color]).toMatchObject({ color, background });
+ }
+ });
+
+ it('keeps a built-in studio badge border in the relation palette', () => {
+ const colors: Record = {
+ 'var(--tw-color-utility-error-200)': 'rgb(145, 32, 24)',
+ 'var(--tw-color-utility-error-700)': 'rgb(249, 112, 102)',
+ };
+ const style = getEdgeRelationLabelStyle(
+ 'SYNONYM',
+ 'synonym',
+ RELATION_META.synonym.color,
+ true,
+ (color, fallback) =>
+ colors[color] ?? resolveSemanticColor(color, fallback)
+ );
+
+ expect(style.labelBackgroundStroke).toBe('rgb(145, 32, 24)');
+ expect(style.labelFill).toBe('rgb(249, 112, 102)');
+ });
+
+ it('uses the active canvas surface for studio relation badges', () => {
+ const style = getEdgeRelationLabelStyle(
+ 'RELATED TO',
+ undefined,
+ undefined,
+ true,
+ resolveSemanticColor
+ );
+
+ expect(style.labelBackgroundFill).toBe('rgb(24, 27, 33)');
+ expect(style.labelBackgroundStroke).toBe('rgb(55, 58, 65)');
+ });
+
it('resolves semantic surface colors before returning a rectangular canvas node', () => {
const style = buildDefaultRectNodeStyle(
resolveSemanticColor,
@@ -147,7 +191,7 @@ describe('theme-aware graph styles', () => {
.mockReturnValue(null);
const style = buildComboStyle(
'Marketing',
- 'var(--color-border-primary)',
+ 'var(--tw-color-border-primary)',
0,
resolveSemanticColor
);
@@ -257,7 +301,9 @@ describe('getCanvasColor', () => {
.spyOn(globalThis, 'document', 'get')
.mockReturnValue(undefined as unknown as Document);
- expect(getCanvasColor('var(--color-missing)', '#abcdef')).toBe('#abcdef');
+ expect(getCanvasColor('var(--tw-color-missing)', '#abcdef')).toBe(
+ '#abcdef'
+ );
});
it('returns the input unchanged when it is not a var() token', () => {
@@ -270,12 +316,12 @@ describe('getCanvasColor', () => {
.spyOn(window, 'getComputedStyle')
.mockReturnValue(mockComputedStyle('rgb(1, 2, 3)'));
- const first = getCanvasColor('var(--color-cache-probe)', '#zzzzzz');
+ const first = getCanvasColor('var(--tw-color-cache-probe)', '#zzzzzz');
expect(first).toBe('rgb(1, 2, 3)');
spy.mockReturnValue(mockComputedStyle('rgb(9, 9, 9)'));
- const second = getCanvasColor('var(--color-cache-probe)', '#zzzzzz');
+ const second = getCanvasColor('var(--tw-color-cache-probe)', '#zzzzzz');
expect(second).toBe('rgb(1, 2, 3)');
});
@@ -285,7 +331,7 @@ describe('getCanvasColor', () => {
.spyOn(window, 'getComputedStyle')
.mockReturnValue(mockComputedStyle('rgba(0, 0, 0, 0)', '12px'));
- expect(getCanvasColor('var(--color-unresolvable)', '#fedcba')).toBe(
+ expect(getCanvasColor('var(--tw-color-unresolvable)', '#fedcba')).toBe(
'#fedcba'
);
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.ts b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.ts
index 11977c21daa1..5470d04ebfe2 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/graphStyles.ts
@@ -386,9 +386,12 @@ function parseColorToRgbTriplet(val: string): [number, number, number] | null {
function mixAccentLikeScale50(rgb: [number, number, number]): string {
const t = GLOSSARY_HEADER_MIX_ACCENT;
const clamp = (n: number) => Math.min(255, Math.max(0, n));
- const r = clamp(Math.round(rgb[0] * t + 255 * (1 - t)));
- const g = clamp(Math.round(rgb[1] * t + 255 * (1 - t)));
- const b = clamp(Math.round(rgb[2] * t + 255 * (1 - t)));
+ const surface = parseColorToRgbTriplet(
+ getCanvasColor(NODE_FILL_DEFAULT, '#ffffff')
+ ) ?? [255, 255, 255];
+ const r = clamp(Math.round(rgb[0] * t + surface[0] * (1 - t)));
+ const g = clamp(Math.round(rgb[1] * t + surface[1] * (1 - t)));
+ const b = clamp(Math.round(rgb[2] * t + surface[2] * (1 - t)));
const x = (n: number) => n.toString(16).padStart(2, '0');
return `#${x(r)}${x(g)}${x(b)}`;
@@ -396,10 +399,10 @@ function mixAccentLikeScale50(rgb: [number, number, number]): string {
/**
* Glossary combo header: light "50" wash matching the glossary accent (title + border stroke).
- * For `var(--color-*-scale)` strokes, resolves `var(--color-*-50)`. For hex palette strokes, blends toward white.
+ * Token strokes use the palette’s 50 shade. Stored custom colors blend toward the active surface.
*/
export function glossaryComboHeaderFill(stroke: string): string {
- const blendTowardWhite = (): string => {
+ const blendTowardSurface = (): string => {
const resolved = stroke.startsWith('var(')
? getCanvasColor(stroke, '#94a3b8')
: stroke;
@@ -409,13 +412,13 @@ export function glossaryComboHeaderFill(stroke: string): string {
};
if (stroke.startsWith('var(')) {
- const m = stroke.match(/^var\((--color-[a-z0-9-]+)-\d{2,3}\)$/i);
+ const m = /^var\((--tw-color-[a-z0-9-]+)-\d{2,3}\)$/i.exec(stroke);
if (m) {
- return getCanvasColor(`var(${m[1]}-50)`, blendTowardWhite());
+ return getCanvasColor(`var(${m[1]}-50)`, blendTowardSurface());
}
}
- return blendTowardWhite();
+ return blendTowardSurface();
}
export const LABEL_PLACEMENT_BOTTOM = 'bottom';
@@ -591,17 +594,24 @@ const STUDIO_EDGE_LABEL_PADDING: [number, number, number, number] = [
2, 7, 2, 7,
];
const STUDIO_EDGE_BORDER_BY_COLOR: Record = {
- '#079455': '#ABEFC6',
- '#0e9384': '#99E5D9',
- '#1570ef': '#D1E9FF',
- '#3538cd': '#C7D7FE',
- '#5925dc': '#D9D6FE',
- '#6172f3': '#C7D7FE',
- '#717680': '#E9EAEB',
- '#7a5af8': '#E3DEFC',
- '#c11574': '#FCCEEE',
- '#dc6803': '#FEDF89',
- '#e31b54': '#FECDD6',
+ // Built-in foregrounds use utility-700; their badge border uses the same palette's utility-200.
+ ...Object.fromEntries(
+ Object.values(COLOR_META_BY_HEX).map(({ color }) => [
+ color,
+ color.replace('-700)', '-200)'),
+ ])
+ ),
+ '#079455': 'var(--tw-color-utility-success-200)',
+ '#0e9384': 'var(--tw-color-utility-teal-200)',
+ '#1570ef': 'var(--tw-color-utility-brand-200)',
+ '#3538cd': 'var(--tw-color-utility-indigo-200)',
+ '#5925dc': 'var(--tw-color-utility-violet-200)',
+ '#6172f3': 'var(--tw-color-utility-indigo-200)',
+ '#717680': 'var(--tw-color-utility-gray-200)',
+ '#7a5af8': 'var(--tw-color-utility-purple-200)',
+ '#c11574': 'var(--tw-color-utility-pink-200)',
+ '#dc6803': 'var(--tw-color-utility-warning-200)',
+ '#e31b54': 'var(--tw-color-utility-rose-200)',
};
export function getEffectiveRelationColor(
@@ -648,7 +658,7 @@ const getEdgeLabelBackgroundFill = (
getColor: (cssVar: string, fallback: string) => string
): string => {
if (studioMode) {
- return '#FFFFFF';
+ return getColor(EDGE_LABEL_BG_FILL, '#FFFFFF');
}
return meta
@@ -663,7 +673,10 @@ const getEdgeLabelBackgroundStroke = (
getColor: (cssVar: string, fallback: string) => string
): string => {
if (studioMode) {
- return studioBorderColor ?? '#E9EAEB';
+ return getColor(
+ studioBorderColor ?? 'var(--tw-color-border-primary)',
+ '#E9EAEB'
+ );
}
return meta ? 'none' : getColor(EDGE_LABEL_BG_STROKE, '#FFF');
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/relationshipTypeUtils.ts b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/relationshipTypeUtils.ts
index 647cd26a9753..5e9c39d55722 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/relationshipTypeUtils.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/OntologyExplorer/utils/relationshipTypeUtils.ts
@@ -38,16 +38,16 @@ const HIERARCHICAL_PREDICATES = new Set([
]);
const PALETTE_COLOR_TOKENS: Record = {
- [PaletteKey.Amber]: 'var(--color-utility-warning-500)',
- [PaletteKey.Blue]: 'var(--color-utility-blue-500)',
- [PaletteKey.Gray]: 'var(--color-utility-gray-500)',
- [PaletteKey.Green]: 'var(--color-utility-success-500)',
- [PaletteKey.Indigo]: 'var(--color-utility-indigo-500)',
- [PaletteKey.Pink]: 'var(--color-utility-pink-500)',
- [PaletteKey.Purple]: 'var(--color-utility-purple-500)',
- [PaletteKey.Rose]: 'var(--color-fg-error-primary)',
- [PaletteKey.Teal]: 'var(--color-utility-success-600)',
- [PaletteKey.Violet]: 'var(--color-utility-purple-600)',
+ [PaletteKey.Amber]: 'var(--tw-color-utility-warning-500)',
+ [PaletteKey.Blue]: 'var(--tw-color-utility-blue-500)',
+ [PaletteKey.Gray]: 'var(--tw-color-utility-gray-500)',
+ [PaletteKey.Green]: 'var(--tw-color-utility-success-500)',
+ [PaletteKey.Indigo]: 'var(--tw-color-utility-indigo-500)',
+ [PaletteKey.Pink]: 'var(--tw-color-utility-pink-500)',
+ [PaletteKey.Purple]: 'var(--tw-color-utility-purple-500)',
+ [PaletteKey.Rose]: 'var(--tw-color-fg-error-primary)',
+ [PaletteKey.Teal]: 'var(--tw-color-utility-success-600)',
+ [PaletteKey.Violet]: 'var(--tw-color-utility-purple-600)',
};
const PALETTE_HEX_COLORS: Record = {
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryLeftPanel/GlossaryLeftPanel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryLeftPanel/GlossaryLeftPanel.component.tsx
index 0cfb1e408c60..c2407d5c912b 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryLeftPanel/GlossaryLeftPanel.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryLeftPanel/GlossaryLeftPanel.component.tsx
@@ -12,10 +12,12 @@
*/
import { Button, NavList, Typography } from '@openmetadata/ui-core-components';
-import { Glossary as GlossaryIcon } from '@openmetadata/ui-core-components/icons';
+import {
+ Glossary as GlossaryIcon,
+ Plus,
+} from '@openmetadata/ui-core-components/icons';
import { useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
-import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
import GlossaryV1Skeleton from '../../../components/common/Skeleton/GlossaryV1/GlossaryV1LeftPanelSkeleton.component';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../enums/permissions.enum';
@@ -96,7 +98,7 @@ const GlossaryLeftPanel = ({
className="tw:w-full"
color="secondary"
data-testid="add-glossary"
- iconLeading={ }
+ iconLeading={ }
size="sm"
onPress={onAddGlossary}>
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestDescriptionPage/RequestDescriptionPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestDescriptionPage/RequestDescriptionPage.tsx
index eef037f6fab9..3a8b356c2512 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestDescriptionPage/RequestDescriptionPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestDescriptionPage/RequestDescriptionPage.tsx
@@ -11,12 +11,21 @@
* limitations under the License.
*/
-import { Typography } from '@openmetadata/ui-core-components';
-import { Button, Form, FormProps, Input, Space } from 'antd';
-import { useForm } from 'antd/lib/form/Form';
+import {
+ Box,
+ Button,
+ Card,
+ FieldTypes,
+ FormField,
+ getField,
+ HintText,
+ HookForm,
+ Typography,
+} from '@openmetadata/ui-core-components';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
+import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
@@ -60,14 +69,15 @@ import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
import { useRequiredParams } from '../../../utils/useRequiredParams';
import Assignees from '../shared/Assignees';
import TaskPayloadSchemaFields from '../shared/TaskPayloadSchemaFields';
-import '../task-page.style.less';
import { EntityData, Option } from '../TasksPage.interface';
const RequestDescription = () => {
const { t } = useTranslation();
const location = useCustomLocation();
const navigate = useNavigate();
- const [form] = useForm();
+ const form = useForm<{ title: string; assignees: Option[] }>({
+ defaultValues: { title: '', assignees: [] },
+ });
const { entityType } = useRequiredParams<{ entityType: EntityType }>();
@@ -115,7 +125,10 @@ const RequestDescription = () => {
return getDescriptionTaskFieldPath(field, value);
};
- const onCreateTask: FormProps['onFinish'] = async (formValues) => {
+ const onCreateTask = async (formValues: {
+ title: string;
+ assignees: Option[];
+ }) => {
setIsLoading(true);
if (assignees.length) {
const data: CreateTask = {
@@ -175,11 +188,9 @@ const RequestDescription = () => {
setAssignees(defaultAssignee);
setOptions(defaultAssignee);
}
- form.setFieldsValue({
- title: taskMessage.trimEnd(),
- assignees: defaultAssignee,
- });
- }, [entityData]);
+ form.setValue('title', taskMessage.trimEnd());
+ form.setValue('assignees', defaultAssignee ?? []);
+ }, [entityData, form, taskMessage]);
useEffect(() => {
setPayload({
@@ -204,103 +215,109 @@ const RequestDescription = () => {
className="content-height-with-resizable-panel"
firstPanel={{
className: 'content-resizable-panel-container',
- cardClassName: 'max-width-md m-x-auto',
+ wrapInCard: false,
allowScroll: true,
minWidth: 700,
flex: 0.6,
children: (
-
-
-
-
-
- {t('label.create-entity', {
- entity: t('label.task'),
- })}
-
-
-
-
-
+
+
+
-
-
-
-
-
-
- {t('label.back')}
-
-
- {payload.newDescription
- ? t('label.suggest')
- : t('label.save')}
-
-
-
-
-
-
+
+
+ {t('label.create-entity', {
+ entity: t('label.task'),
+ })}
+
+
+ {getField({
+ name: 'title',
+ type: FieldTypes.TEXT,
+ label: t('label.task-entity', {
+ entity: t('label.title'),
+ }),
+ props: { 'data-testid': 'title', isDisabled: true },
+ })}
+
+ {({ field, fieldState }) => (
+
+ {
+ field.onChange(values);
+ setAssignees(values);
+ }}
+ onSearch={onSearch}
+ />
+ {fieldState.error?.message && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+
+
+
+
+ {t('label.back')}
+
+
+ {payload.newDescription
+ ? t('label.suggest')
+ : t('label.save')}
+
+
+
+
+
+
+
),
}}
pageTitle={t('label.request-description')}
secondPanel={{
+ wrapInCard: false,
className: 'content-resizable-panel-container',
minWidth: 60,
flex: 0.4,
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx
index 1bdbabeeea4d..ec2445020081 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx
@@ -11,12 +11,21 @@
* limitations under the License.
*/
-import { Typography } from '@openmetadata/ui-core-components';
-import { Button, Form, FormProps, Input, Space } from 'antd';
-import { useForm } from 'antd/lib/form/Form';
+import {
+ Box,
+ Button,
+ Card,
+ FieldTypes,
+ FormField,
+ getField,
+ HintText,
+ HookForm,
+ Typography,
+} from '@openmetadata/ui-core-components';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
+import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
@@ -61,14 +70,15 @@ import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
import { useRequiredParams } from '../../../utils/useRequiredParams';
import Assignees from '../shared/Assignees';
import TaskPayloadSchemaFields from '../shared/TaskPayloadSchemaFields';
-import '../task-page.style.less';
import { EntityData, Option } from '../TasksPage.interface';
const RequestTag = () => {
const { t } = useTranslation();
const location = useCustomLocation();
const navigate = useNavigate();
- const [form] = useForm();
+ const form = useForm<{ title: string; assignees: Option[] }>({
+ defaultValues: { title: '', assignees: [] },
+ });
const { entityType } = useRequiredParams<{ entityType: EntityType }>();
const { fqn } = useFqn();
const queryParams = new URLSearchParams(location.search);
@@ -114,7 +124,10 @@ const RequestTag = () => {
return getTagTaskFieldPath(field, value);
};
- const onCreateTask: FormProps['onFinish'] = async (formValues) => {
+ const onCreateTask = async (formValues: {
+ title: string;
+ assignees: Option[];
+ }) => {
setIsLoading(true);
const data: CreateTask = {
@@ -167,11 +180,9 @@ const RequestTag = () => {
setAssignees(defaultAssignee);
setOptions((prev) => [...defaultAssignee, ...prev]);
}
- form.setFieldsValue({
- title: taskMessage.trimEnd(),
- assignees: defaultAssignee,
- });
- }, [entityData]);
+ form.setValue('title', taskMessage.trimEnd());
+ form.setValue('assignees', defaultAssignee ?? []);
+ }, [entityData, form, taskMessage]);
useEffect(() => {
setPayload({
@@ -197,105 +208,110 @@ const RequestTag = () => {
-
-
-
- {t('label.create-entity', {
- entity: t('label.task'),
- })}
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
- {t('label.back')}
-
-
- {(payload.tagsToAdd as TagLabel[] | undefined)?.length
- ? t('label.suggest')
- : t('label.save')}
-
-
-
-
-
-
+
+
+ {t('label.create-entity', {
+ entity: t('label.task'),
+ })}
+
+
+ {getField({
+ name: 'title',
+ type: FieldTypes.TEXT,
+ label: t('label.task-entity', {
+ entity: t('label.title'),
+ }),
+ props: { 'data-testid': 'title', isDisabled: true },
+ })}
+
+ {({ field, fieldState }) => (
+
+ {
+ field.onChange(values);
+ setAssignees(values);
+ }}
+ onSearch={onSearch}
+ />
+ {fieldState.error?.message && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+
+
+
+
+ {t('label.back')}
+
+
+ {(payload.tagsToAdd as TagLabel[] | undefined)?.length
+ ? t('label.suggest')
+ : t('label.save')}
+
+
+
+
+
+
+
),
}}
pageTitle={t('label.request-tag-plural')}
secondPanel={{
- className: 'content-resizable-panel-container bg-white',
+ wrapInCard: false,
+ className: 'content-resizable-panel-container',
minWidth: 60,
flex: 0.4,
children: (
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx
index a0ee345c81d2..246d59085237 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx
@@ -11,12 +11,21 @@
* limitations under the License.
*/
-import { Typography } from '@openmetadata/ui-core-components';
-import { Button, Form, FormProps, Input, Space } from 'antd';
-import { useForm } from 'antd/lib/form/Form';
+import {
+ Box,
+ Button,
+ Card,
+ FieldTypes,
+ FormField,
+ getField,
+ HintText,
+ HookForm,
+ Typography,
+} from '@openmetadata/ui-core-components';
import { AxiosError } from 'axios';
import { isEmpty, isUndefined } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
+import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
@@ -26,7 +35,6 @@ import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBre
import ExploreSearchCard from '../../../components/ExploreV1/ExploreSearchCard/ExploreSearchCard';
import { SearchedDataProps } from '../../../components/SearchedData/SearchedData.interface';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
-import { VALIDATION_MESSAGES } from '../../../constants/constants';
import { TASK_SANITIZE_VALUE_REGEX } from '../../../constants/regex.constants';
import { EntityTabs, EntityType } from '../../../enums/entity.enum';
import { Glossary } from '../../../generated/entity/data/glossary';
@@ -65,13 +73,14 @@ import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
import { useRequiredParams } from '../../../utils/useRequiredParams';
import Assignees from '../shared/Assignees';
import TaskPayloadSchemaFields from '../shared/TaskPayloadSchemaFields';
-import '../task-page.style.less';
import { EntityData, Option } from '../TasksPage.interface';
const UpdateDescription = () => {
const location = useCustomLocation();
const navigate = useNavigate();
- const [form] = useForm();
+ const form = useForm<{ title: string; assignees: Option[] }>({
+ defaultValues: { title: '', assignees: [] },
+ });
const { entityType } = useRequiredParams<{ entityType: EntityType }>();
const { fqn } = useFqn();
@@ -144,7 +153,10 @@ const UpdateDescription = () => {
return getDescriptionTaskFieldPath(field, value);
};
- const onCreateTask: FormProps['onFinish'] = async (formValues) => {
+ const onCreateTask = async (formValues: {
+ title: string;
+ assignees: Option[];
+ }) => {
setIsLoading(true);
try {
@@ -200,12 +212,9 @@ const UpdateDescription = () => {
setAssignees(defaultAssignee);
setOptions(defaultAssignee);
}
- form.setFieldsValue({
- title: taskMessage.trimEnd(),
- assignees: defaultAssignee,
- description: getDescription(),
- });
- }, [entityData]);
+ form.setValue('title', taskMessage.trimEnd());
+ form.setValue('assignees', defaultAssignee ?? []);
+ }, [entityData, form, taskMessage]);
useEffect(() => {
const description = getDescription();
@@ -231,92 +240,107 @@ const UpdateDescription = () => {
className="content-height-with-resizable-panel"
firstPanel={{
className: 'content-resizable-panel-container',
- cardClassName: 'max-width-md m-x-auto',
+ wrapInCard: false,
allowScroll: true,
minWidth: 700,
flex: 0.6,
children: (
-
-
-
-
-
- {t('label.create-entity', {
- entity: t('label.task'),
- })}
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
- {t('label.back')}
-
-
- {t('label.save')}
-
-
-
-
-
-
+
+
+ {t('label.create-entity', {
+ entity: t('label.task'),
+ })}
+
+
+ {getField({
+ name: 'title',
+ type: FieldTypes.TEXT,
+ label: t('label.task-entity', {
+ entity: t('label.title'),
+ }),
+ props: { 'data-testid': 'title', isDisabled: true },
+ })}
+
+ {({ field, fieldState }) => (
+
+ {
+ field.onChange(values);
+ setAssignees(values);
+ }}
+ onSearch={onSearch}
+ />
+ {fieldState.error?.message && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+
+
+
+
+ {t('label.back')}
+
+
+ {t('label.save')}
+
+
+
+
+
+
+
),
}}
pageTitle={t('label.update-description')}
secondPanel={{
+ wrapInCard: false,
className: 'content-resizable-panel-container',
minWidth: 60,
flex: 0.4,
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx
index 6634d3c0111f..b3375422251e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx
@@ -11,12 +11,21 @@
* limitations under the License.
*/
-import { Typography } from '@openmetadata/ui-core-components';
-import { Button, Form, FormProps, Input, Space } from 'antd';
-import { useForm } from 'antd/lib/form/Form';
+import {
+ Box,
+ Button,
+ Card,
+ FieldTypes,
+ FormField,
+ getField,
+ HintText,
+ HookForm,
+ Typography,
+} from '@openmetadata/ui-core-components';
import { AxiosError } from 'axios';
import { isEmpty, isUndefined } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
+import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { ActivityFeedTabs } from '../../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
@@ -66,14 +75,15 @@ import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
import { useRequiredParams } from '../../../utils/useRequiredParams';
import Assignees from '../shared/Assignees';
import TaskPayloadSchemaFields from '../shared/TaskPayloadSchemaFields';
-import '../task-page.style.less';
import { EntityData, Option } from '../TasksPage.interface';
const UpdateTag = () => {
const location = useCustomLocation();
const { t } = useTranslation();
const navigate = useNavigate();
- const [form] = useForm();
+ const form = useForm<{ title: string; assignees: Option[] }>({
+ defaultValues: { title: '', assignees: [] },
+ });
const { entityType } = useRequiredParams<{ entityType: EntityType }>();
@@ -149,7 +159,10 @@ const UpdateTag = () => {
return getTagTaskFieldPath(field, value);
};
- const onCreateTask: FormProps['onFinish'] = async (formValues) => {
+ const onCreateTask = async (formValues: {
+ title: string;
+ assignees: Option[];
+ }) => {
setIsLoading(true);
const data: CreateTask = {
@@ -207,11 +220,9 @@ const UpdateTag = () => {
setAssignees(defaultAssignee);
setOptions(defaultAssignee);
}
- form.setFieldsValue({
- title: taskMessage.trimEnd(),
- assignees: defaultAssignee,
- });
- }, [entityData, columnObject]);
+ form.setValue('title', taskMessage.trimEnd());
+ form.setValue('assignees', defaultAssignee ?? []);
+ }, [entityData, form, taskMessage]);
useEffect(() => {
setPayload({
@@ -237,101 +248,110 @@ const UpdateTag = () => {
-
-
-
- {t('label.create-entity', {
- entity: t('label.task'),
- })}
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
- {t('label.back')}
-
-
- {t('label.save')}
-
-
-
-
-
-
+
+
+ {t('label.create-entity', {
+ entity: t('label.task'),
+ })}
+
+
+ {getField({
+ name: 'title',
+ type: FieldTypes.TEXT,
+ label: t('label.task-entity', {
+ entity: t('label.title'),
+ }),
+ props: { 'data-testid': 'title', isDisabled: true },
+ })}
+
+ {({ field, fieldState }) => (
+
+ {
+ field.onChange(values);
+ setAssignees(values);
+ }}
+ onSearch={onSearch}
+ />
+ {fieldState.error?.message && (
+
+ {fieldState.error.message}
+
+ )}
+
+ )}
+
+
+
+
+ {t('label.back')}
+
+
+ {t('label.save')}
+
+
+
+
+
+
+
),
}}
pageTitle={t('label.update-entity', {
entity: i18n.t('label.tag'),
})}
secondPanel={{
- className: 'content-resizable-panel-container bg-white',
+ wrapInCard: false,
+ className: 'content-resizable-panel-container',
minWidth: 60,
flex: 0.4,
children: (
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignee.less b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignee.less
deleted file mode 100644
index d3b3b9f4d2aa..000000000000
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignee.less
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2022 Collate.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-.assignee-tag {
- display: flex;
- align-items: center;
- padding: var(--om-space-2) var(--om-space-3);
- margin: var(--om-space-1) var(--om-space-2);
-}
-
-.assignee-item {
- .profile-image {
- vertical-align: middle;
- }
-}
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.test.tsx
index a423fe6d87c8..4aa6f082583a 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.test.tsx
@@ -11,7 +11,7 @@
* limitations under the License.
*/
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import Assignees from './Assignees';
const mockOptions = [
@@ -50,11 +50,39 @@ const mockProps = {
onChange: jest.fn(),
};
-jest.mock('../../../components/common/UserTag/UserTag.component', () => ({
- UserTag: jest.fn().mockReturnValue(UserTag
),
-}));
-
describe('Test assignees component', () => {
+ it('allows a required assignee selection with an empty search query to submit', () => {
+ const { container } = render(
+
+ );
+
+ expect(screen.getByRole('combobox')).toHaveValue('');
+ expect(container.querySelector('form')?.checkValidity()).toBe(true);
+ expect(screen.getByRole('combobox')).toHaveAttribute(
+ 'aria-required',
+ 'true'
+ );
+ });
+
+ it('preserves the selected assignee label and identity', async () => {
+ const onChange = jest.fn();
+ render( );
+
+ fireEvent.mouseDown(screen.getByRole('combobox'));
+ fireEvent.click(await screen.findByText('adam_matthews2'));
+
+ await waitFor(() =>
+ expect(onChange).toHaveBeenCalledWith([mockOptions[0]])
+ );
+ });
+
it('Should render the component', async () => {
render( );
@@ -62,7 +90,7 @@ describe('Test assignees component', () => {
fireEvent.change(container, { target: { value: 'adam_matthews2' } });
- const options = await screen.findByTestId(`adam_matthews2`);
+ const options = await screen.findByText('adam_matthews2');
expect(container).toBeInTheDocument();
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.tsx
index ed812acf4b62..7618402bdf26 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/Assignees.tsx
@@ -11,24 +11,26 @@
* limitations under the License.
*/
-import { Typography } from '@openmetadata/ui-core-components';
-import { Select, SelectProps } from 'antd';
-import { DefaultOptionType } from 'antd/lib/select';
-
-import { debounce, groupBy, isArray, isUndefined } from 'lodash';
-import { FC, useMemo } from 'react';
+import {
+ Autocomplete,
+ AutocompleteProps,
+ Avatar,
+} from '@openmetadata/ui-core-components';
+import { Users01 } from '@openmetadata/ui-core-components/icons';
+import { debounce, uniqBy } from 'lodash';
+import { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-import { ReactComponent as TeamIcon } from '../../../assets/svg/teams-grey.svg';
-import { UserTag } from '../../../components/common/UserTag/UserTag.component';
-import { UserTagSize } from '../../../components/common/UserTag/UserTag.interface';
-import { OwnerType } from '../../../enums/user.enum';
import { Option } from '../TasksPage.interface';
-import './Assignee.less';
interface Props
extends Omit<
- SelectProps,
- 'onChange' | 'onSearch' | 'value' | 'options'
+ AutocompleteProps,
+ | 'children'
+ | 'items'
+ | 'selectedItems'
+ | 'onSearchChange'
+ | 'onChange'
+ | 'value'
> {
options: Option[];
value: Option[];
@@ -38,7 +40,7 @@ interface Props
isSingleSelect?: boolean;
}
-const Assignees: FC = ({
+const Assignees = ({
value: assignees = [],
onSearch,
onChange,
@@ -46,98 +48,58 @@ const Assignees: FC = ({
disabled,
isSingleSelect = false,
...rest
-}) => {
+}: Props) => {
const { t } = useTranslation();
- const handleOnChange = (
- _values: Option[],
- newOptions?: DefaultOptionType | DefaultOptionType[]
- ) => {
- if (isUndefined(newOptions)) {
- onChange(newOptions as unknown as Option[]);
-
- return;
- }
-
- const normalizedOptions = isArray(newOptions) ? newOptions : [newOptions];
- const newValues = normalizedOptions.map((option) => ({
- label: option['data-label'],
- value: option.value,
- type: option.type,
- name: option.name,
- displayName: option.displayName,
- }));
-
- onChange(newValues as Option[]);
- };
+ const search = useMemo(() => debounce(onSearch, 300), [onSearch]);
+ useEffect(() => () => search.cancel(), [search]);
- const updatedOption = useMemo(() => {
- const groupByType = groupBy(options, (d) => d.type);
- const groupOptions = [];
- if (!isUndefined(groupByType.team)) {
- groupOptions.push({
- type: 'group',
- label: 'Teams',
- value: OwnerType.TEAM,
- options: groupByType.team.map((team) => ({
- ...team,
- label: (
-
-
- {team.label}
-
- ),
- })),
- });
- }
- if (!isUndefined(groupByType.user)) {
- groupOptions.push({
- type: 'group',
- label: 'Users',
- value: OwnerType.USER,
- options: groupByType.user.map((user) => ({
- ...user,
- label: (
-
-
-
- ),
- })),
- });
- }
-
- return groupOptions;
- }, [options]);
+ const toItem = (option: Option) => ({
+ id: option.value,
+ label: option.label,
+ supportingText: option.type === 'team' ? t('label.team') : t('label.user'),
+ icon:
+ option.type === 'team' ? (
+
+ ) : (
+
+ ),
+ });
+ // Selected identities must survive when a remote search replaces the option page.
+ const availableOptions = uniqBy([...options, ...assignees], 'value');
+ // Form rules validate selected identities; the search query clears after selection.
return (
-
+ data-testid="select-assignee"
+ filterOption={() => true}
+ isDisabled={disabled}
+ items={availableOptions.map(toItem)}
+ multiple={!isSingleSelect}
+ placeholder={rest.placeholder ?? t('label.select-to-search')}
+ selectedItems={assignees.map(toItem)}
+ validationBehavior="aria"
+ onItemCleared={(key) =>
+ onChange(assignees.filter((option) => option.value !== key))
+ }
+ onItemInserted={(key) => {
+ const option = availableOptions.find((item) => item.value === key);
+ if (option) {
+ onChange(
+ isSingleSelect ? [option] : uniqBy([...assignees, option], 'value')
+ );
+ }
+ }}
+ onSearchChange={search}>
+ {(item) => (
+ option.value === item.id)?.name
+ }
+ />
+ )}
+
);
};
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/DescriptionTabs.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/DescriptionTabs.tsx
index 57cd8823640c..0c4d08c81b7e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/DescriptionTabs.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/DescriptionTabs.tsx
@@ -11,7 +11,7 @@
* limitations under the License.
*/
-import { Tabs } from '@openmetadata/ui-core-components';
+import { Box, Tabs, Typography } from '@openmetadata/ui-core-components';
import { Change } from 'diff';
import { isEqual } from 'lodash';
import { lazy, useCallback, useRef, useState } from 'react';
@@ -62,7 +62,7 @@ export const DescriptionTabs = ({
setDiffs([]);
}
},
- [markdownRef]
+ [description]
);
return (
@@ -76,29 +76,31 @@ export const DescriptionTabs = ({
-
+
{description?.trim() ? (
) : (
-
+
{t('label.no-entity', { entity: t('label.description') })}
-
+
)}
-
+
{/* Kept mounted so the editor keeps its edits and the Diff tab can read them via markdownRef. */}
({
+ ...jest.requireActual('@openmetadata/ui-core-components'),
Badge: ({ children }: { children?: ReactNode }) => {children} ,
Box: ({ children }: { children?: ReactNode }) => {children}
,
Button: ({
@@ -64,15 +70,15 @@ jest.mock('./TagsTabs', () => ({
onClick={() =>
onChange([
{
- labelType: 'Manual',
+ labelType: LabelType.Manual,
source: TagSource.Classification,
- state: 'Confirmed',
+ state: State.Confirmed,
tagFQN: 'Classification.PersonalData.Personal',
},
{
- labelType: 'Manual',
+ labelType: LabelType.Manual,
source: TagSource.Classification,
- state: 'Confirmed',
+ state: State.Confirmed,
tagFQN: 'PII.Sensitive',
},
])
@@ -89,9 +95,9 @@ jest.mock('./TagSuggestion', () =>
onClick={() =>
onChange([
{
- labelType: 'Manual',
+ labelType: LabelType.Manual,
source: TagSource.Classification,
- state: 'Confirmed',
+ state: State.Confirmed,
tagFQN: 'Tier.Tier1',
},
])
@@ -102,20 +108,40 @@ jest.mock('./TagSuggestion', () =>
);
const PERSONAL_TAG: TagLabel = {
- labelType: 'Manual',
+ labelType: LabelType.Manual,
source: TagSource.Classification,
- state: 'Confirmed',
+ state: State.Confirmed,
tagFQN: 'Classification.PersonalData.Personal',
};
const CONFIDENTIAL_TAG: TagLabel = {
- labelType: 'Manual',
+ labelType: LabelType.Manual,
source: TagSource.Classification,
- state: 'Confirmed',
+ state: State.Confirmed,
tagFQN: 'Classification.PersonalData.Confidential',
};
describe('TaskPayloadSchemaFields', () => {
+ it('associates required schema fields with their accessible labels', () => {
+ render(
+
+ );
+
+ expect(
+ screen.getByRole('textbox', { name: /Review Notes/ })
+ ).toBeRequired();
+ });
+
it('updates description payload fields through the schema widget', () => {
const onChange = jest.fn();
const schema: JsonSchemaObject = {
@@ -193,9 +219,9 @@ describe('TaskPayloadSchemaFields', () => {
currentTags: [PERSONAL_TAG, CONFIDENTIAL_TAG],
tagsToAdd: [
{
- labelType: 'Manual',
+ labelType: LabelType.Manual,
source: TagSource.Classification,
- state: 'Confirmed',
+ state: State.Confirmed,
tagFQN: 'PII.Sensitive',
},
],
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskPayloadSchemaFields.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskPayloadSchemaFields.tsx
index 11c2283053da..fa682a771f78 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskPayloadSchemaFields.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskPayloadSchemaFields.tsx
@@ -10,27 +10,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Copyright 2026 Collate.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
import {
Badge,
Box,
Button,
+ Checkbox,
+ FormItemLabel,
+ Input,
+ Select,
+ TextArea,
Typography,
} from '@openmetadata/ui-core-components';
-import { Checkbox, Form, Input, InputNumber, Select } from 'antd';
import { uniqBy } from 'lodash';
import {
ReactNode,
@@ -112,7 +103,7 @@ const ClampedText = ({ text }: { text: string }) => {
{!expanded && isClamped && (
-
+
…
@@ -125,10 +116,7 @@ const ClampedText = ({ text }: { text: string }) => {
)}
{expanded && (
- setExpanded(false)}>
+ setExpanded(false)}>
{t('label.show-less')}
)}
@@ -182,7 +170,7 @@ function selectFieldWidgetKind(
const StringArrayDisplay = ({ items }: { items: string[] }) => {
if (items.length === 0) {
return (
-
+
--
);
@@ -221,7 +209,14 @@ const TaskPayloadSchemaFields = ({
}, [properties, uiSchema]);
const requiredFields = useMemo(
- () => new Set(Array.isArray(schema?.required) ? schema.required : []),
+ () =>
+ new Set(
+ Array.isArray(schema?.required)
+ ? schema.required.filter(
+ (field): field is string => typeof field === 'string'
+ )
+ : []
+ ),
[schema]
);
@@ -323,11 +318,11 @@ const TaskPayloadSchemaFields = ({
src={iconSrc}
/>
)}
-
+
{label}
-
+
:
@@ -339,7 +334,7 @@ const TaskPayloadSchemaFields = ({
const renderReadOnlyValue = (value: unknown) => {
if (value === null || value === undefined || value === '') {
return (
-
+
--
);
@@ -369,7 +364,7 @@ const TaskPayloadSchemaFields = ({
{description ? (
{description}
@@ -400,14 +395,14 @@ const TaskPayloadSchemaFields = ({
))
) : (
-
+
--
)}
{description ? (
{description}
@@ -446,18 +441,19 @@ const TaskPayloadSchemaFields = ({
}
return (
-
+
+
updateField(fieldName, value)}
/>
{description ? (
-
+
{description}
) : null}
-
+
);
};
@@ -484,7 +480,8 @@ const TaskPayloadSchemaFields = ({
}
return (
-
+
+
tag.tagFQN));
- onChange({
+ onChange?.({
...payload,
tagsToAdd: newTags.filter(
(tag) => !currentTagFqns.has(tag.tagFQN)
@@ -506,11 +503,11 @@ const TaskPayloadSchemaFields = ({
}}
/>
{description ? (
-
+
{description}
) : null}
-
+
);
};
@@ -530,17 +527,18 @@ const TaskPayloadSchemaFields = ({
}
return (
-
+
+
updateField(fieldName, newTags)}
/>
{description ? (
-
+
{description}
) : null}
-
+
);
};
@@ -560,30 +558,23 @@ const TaskPayloadSchemaFields = ({
}
return (
-
- ({
- label: value,
- value,
- }))}
- value={getFieldValue(fieldName) as string | undefined}
- onChange={(value) => updateField(fieldName, value)}
- />
-
+ label={label}
+ value={(getFieldValue(fieldName) as string | undefined) ?? null}
+ onChange={(value) => updateField(fieldName, value)}>
+ {(fieldSchema?.enum ?? []).map((value) => (
+
+ ))}
+
);
};
const renderNumberField: FieldRenderer = (
fieldName,
- _fieldSchema,
+ fieldSchema,
label,
description
) => {
@@ -596,14 +587,21 @@ const TaskPayloadSchemaFields = ({
);
}
+ const value = getFieldValue(fieldName);
+
return (
-
- updateField(fieldName, value)}
- />
-
+
+ updateField(fieldName, value === '' ? null : Number(value))
+ }
+ />
);
};
@@ -623,13 +621,13 @@ const TaskPayloadSchemaFields = ({
}
return (
-
- updateField(fieldName, event.target.checked)}>
- {description}
-
-
+ updateField(fieldName, value)}
+ />
);
};
@@ -649,21 +647,15 @@ const TaskPayloadSchemaFields = ({
}
return (
-
- updateField(fieldName, event.target.value)}
- />
-
+ label={label}
+ rows={4}
+ value={String(getFieldValue(fieldName, '') ?? '')}
+ onChange={(value) => updateField(fieldName, value)}
+ />
);
};
@@ -698,26 +690,23 @@ const TaskPayloadSchemaFields = ({
}
return (
-
- {
- try {
- updateField(fieldName, JSON.parse(event.target.value));
- } catch {
- updateField(fieldName, event.target.value);
- }
- }}
- />
- {description ? (
-
- {description}
-
- ) : null}
-
+