This is an automated email from the ASF dual-hosted git repository.
pawarprasad123 pushed a commit to branch atlas-2.6
in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/atlas-2.6 by this push:
new 5f9508273 ATLAS-5306: Atlas React UI: SearchResult Table:
Classification toolti… (#665)
5f9508273 is described below
commit 5f950827339de6faa030381ba1c2a3c0e1850c64
Author: Brijesh Bhalala <[email protected]>
AuthorDate: Thu Jun 11 17:38:25 2026 +0530
ATLAS-5306: Atlas React UI: SearchResult Table: Classification toolti…
(#665)
* ATLAS-5306: Atlas React UI: SearchResult Table: Classification tooltip is
missing parent/super-type name
* ATLAS-5306: Atlas React UI: SearchResult Table: Classification tooltip is
missing parent/super-type name
* ATLAS-5306: Atlas React UI: SearchResult Table: Classification tooltip is
missing parent/super-type name
( cherry-picked from the commit 911079f8f57118845bf74984726b1ece617aeb49)
---
dashboard/src/components/DialogShowMoreLess.tsx | 111 +++++++++++++--------
.../__tests__/DialogShowMoreLess.test.tsx | 25 +++++
dashboard/src/views/SearchResult/SearchResult.tsx | 11 +-
.../SearchResult/__tests__/SearchResult.test.tsx | 34 ++++++-
4 files changed, 129 insertions(+), 52 deletions(-)
diff --git a/dashboard/src/components/DialogShowMoreLess.tsx
b/dashboard/src/components/DialogShowMoreLess.tsx
index 03492d077..ebe35f9f2 100644
--- a/dashboard/src/components/DialogShowMoreLess.tsx
+++ b/dashboard/src/components/DialogShowMoreLess.tsx
@@ -16,7 +16,7 @@
*/
import { LightTooltip } from "./muiComponents";
-import { Chip, IconButton, Menu, MenuItem, Typography } from "@mui/material";
+import { Chip, IconButton, Menu, MenuItem, Typography, Box } from
"@mui/material";
import { extractKeyValueFromEntity, isEmpty, serverError } from "@utils/Utils";
import { useRef, useState } from "react";
import ErrorRoundedIcon from "@mui/icons-material/ErrorRounded";
@@ -37,7 +37,8 @@ import { fetchGlossaryDetails } from
"@redux/slice/glossaryDetailsSlice";
import { fetchDetailPageData } from "@redux/slice/detailPageSlice";
import { fetchGlossaryData } from "@redux/slice/glossarySlice";
-const CHIP_MAX_WIDTH = "200px";
+const CHIP_MAX_WIDTH = "6.25rem";
+const CLASSIFICATION = "Classification";
const ITEM_HEIGHT = 48;
export interface DialogShowMoreLessProps {
@@ -129,7 +130,7 @@ const DialogShowMoreLess = ({
* classification is on this entity; propagated tags use another entityGuid.
*/
const canShowDeleteOnClassificationChip = (tag: any) => {
- if (colName !== "Classification") {
+ if (colName !== CLASSIFICATION) {
return true;
}
if (!tag) {
@@ -155,7 +156,7 @@ const DialogShowMoreLess = ({
const handleRemove = async (): Promise<void> => {
try {
setRemoveLoader(true);
- if (colName == "Classification") {
+ if (colName == CLASSIFICATION) {
await removeApiMethod(
detailPage ? entity.guid : value.guid,
currentValue.selectedValue
@@ -216,12 +217,11 @@ const DialogShowMoreLess = ({
setOpenModal(false);
toast.dismiss(toastId.current);
toastId.current = toast.success(
- `${colName} ${
- colName == "Term" ? "association" : currentValue.selectedValue
+ `${colName} ${colName == "Term" ? "association" :
currentValue.selectedValue
} was removed successfully`
);
const isSchemaClassificationFlow =
- colName === "Classification" &&
+ colName === CLASSIFICATION &&
typeof onSchemaChildEntityRefresh === "function";
if (!isEmpty(guid)) {
if (!isEmpty(gType)) {
@@ -256,8 +256,8 @@ const DialogShowMoreLess = ({
const checkSuperTypes = (classificationName: string) => {
let tagObj = !isEmpty(classificationData.classificationDefs)
? classificationData.classificationDefs.find((obj: { name: string }) => {
- return obj.name == classificationName;
- })
+ return obj.name == classificationName;
+ })
: {};
return !isEmpty(tagObj?.superTypes)
@@ -268,7 +268,10 @@ const DialogShowMoreLess = ({
};
const getLabel = (label: string, optionalLabel?: string) => {
- if (columnVal == "Classifications" || columnVal == "self") {
+ if (colName == CLASSIFICATION) {
+ return checkSuperTypes(label);
+ } else if (colName == "Propagated Classification") {
+ // Re-using checkSuperTypes since it does the same as getTagParentList
return checkSuperTypes(label);
} else {
return label || optionalLabel;
@@ -280,7 +283,7 @@ const DialogShowMoreLess = ({
text: string | undefined,
data: any | undefined
) => {
- if (colName == "Classification" || colName == "Propagated Classification")
{
+ if (colName == CLASSIFICATION || colName == "Propagated Classification") {
let keys = Array.from(searchParams.keys());
for (let i = 0; i < keys.length; i++) {
// if (keys[i] != "searchType") {
@@ -330,7 +333,7 @@ const DialogShowMoreLess = ({
};
const assignTitle = () => {
- if (colName == "Classification") {
+ if (colName == CLASSIFICATION) {
return "Add Classification";
} else if (colName == "Term") {
return "Add Term";
@@ -338,7 +341,7 @@ const DialogShowMoreLess = ({
};
const removeTitle = () => {
- if (colName == "Classification") {
+ if (colName == CLASSIFICATION) {
return "Remove Classification Assignment";
} else if (colName == "Term") {
return "Remove Term Assignment";
@@ -350,9 +353,18 @@ const DialogShowMoreLess = ({
return (
<>
{value?.[columnVal]?.length > 0 ? (
- <div
+ <Box
className="tag-list"
- style={{ flexWrap: isShowMoreLess ? "nowrap" : "wrap" }}
+ sx={
+ isShowMoreLess
+ ? {
+ display: "grid",
+ gridTemplateColumns: "minmax(2.1875rem, max-content)
max-content max-content",
+ alignItems: "center",
+ width: "100%"
+ }
+ : { display: "flex", flexWrap: "wrap", alignItems: "center" }
+ }
>
{isShowMoreLess && (
<LightTooltip
@@ -370,32 +382,36 @@ const DialogShowMoreLess = ({
value[columnVal][0][displayText],
optionalDisplayText,
(colName == "Term" || colName == "Category") &&
- value[columnVal][0]
+ value[columnVal][0]
)}
</EllipsisText>
}
onDelete={
!isEmpty(removeApiMethod) &&
- canShowDeleteOnClassificationChip(value[columnVal][0])
+ canShowDeleteOnClassificationChip(value[columnVal][0])
? () => {
- handleDelete(value[columnVal][0][displayText]);
- }
+ handleDelete(value[columnVal][0][displayText]);
+ }
: undefined
}
size="small"
variant="outlined"
sx={{
"& .MuiChip-label": {
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
display: "block",
- overflow: "ellipsis",
- maxWidth: "145px"
+ minWidth: 0,
+ flexShrink: 1
},
-
- maxWidth: CHIP_MAX_WIDTH
+ maxWidth: CHIP_MAX_WIDTH,
+ minWidth: 0,
+ overflow: "hidden"
}}
clickable
data-cy="tagClick"
- />{" "}
+ />
</LightTooltip>
)}
{!isShowMoreLess &&
@@ -414,28 +430,30 @@ const DialogShowMoreLess = ({
obj[displayText] || obj,
optionalDisplayText,
(colName == "Term" || colName == "Category") &&
- value[columnVal][index]
+ value[columnVal][index]
)}
</EllipsisText>
}
onDelete={
!isEmpty(removeApiMethod) &&
- canShowDeleteOnClassificationChip(obj)
+ canShowDeleteOnClassificationChip(obj)
? () => {
- handleDelete(obj[displayText] || obj);
- }
+ handleDelete(obj[displayText] || obj);
+ }
: undefined
}
size="small"
variant="outlined"
sx={{
"& .MuiChip-label": {
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
display: "block",
- overflow: "ellipsis",
- maxWidth: "180px"
+ minWidth: 0
},
-
- maxWidth: CHIP_MAX_WIDTH
+ maxWidth: CHIP_MAX_WIDTH,
+ minWidth: 0
}}
clickable
/>
@@ -451,7 +469,7 @@ const DialogShowMoreLess = ({
onClick={handleClick}
aria-controls={open ? "long-menu" : undefined}
aria-expanded={open ? "true" : undefined}
- aria-haspopup="true"
+ sx={{ flexShrink: 0, padding: "2px" }}
>
<MoreHorizIcon />
</IconButton>
@@ -464,7 +482,7 @@ const DialogShowMoreLess = ({
color="primary"
size="small"
onClick={() => {
- if (colName == "Classification") {
+ if (colName == CLASSIFICATION) {
setTagModal(true);
} else if (colName == "Term") {
setTermModal(true);
@@ -474,6 +492,7 @@ const DialogShowMoreLess = ({
setAttributeModal(true);
}
}}
+ sx={{ flexShrink: 0, padding: "2px" }}
>
<AddCircleOutlineIcon fontSize="small" />
</IconButton>
@@ -509,17 +528,17 @@ const DialogShowMoreLess = ({
obj[displayText],
optionalDisplayText,
(colName == "Term" || colName == "Category") &&
- value[columnVal][index]
+ value[columnVal][index]
)}
</EllipsisText>
}
className="chip-items"
onDelete={
!isEmpty(removeApiMethod) &&
- canShowDeleteOnClassificationChip(obj)
+ canShowDeleteOnClassificationChip(obj)
? () => {
- handleDelete(obj[displayText] || obj);
- }
+ handleDelete(obj[displayText] || obj);
+ }
: undefined
}
size="small"
@@ -527,11 +546,14 @@ const DialogShowMoreLess = ({
clickable
sx={{
"& .MuiChip-label": {
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
display: "block",
- overflow: "ellipsis",
- maxWidth: "180px"
+ minWidth: 0
},
- maxWidth: CHIP_MAX_WIDTH
+ maxWidth: CHIP_MAX_WIDTH,
+ minWidth: 0
}}
/>
</LightTooltip>
@@ -540,7 +562,7 @@ const DialogShowMoreLess = ({
}
})}
</Menu>
- </div>
+ </Box>
) : (
!readOnly && (
<LightTooltip title={assignTitle()}>
@@ -549,7 +571,7 @@ const DialogShowMoreLess = ({
color="primary"
size="small"
onClick={() => {
- if (colName == "Classification") {
+ if (colName == CLASSIFICATION) {
setTagModal(true);
} else if (colName == "Term") {
setTermModal(true);
@@ -559,6 +581,7 @@ const DialogShowMoreLess = ({
setAttributeModal(true);
}
}}
+ sx={{ flexShrink: 0, padding: "2px" }}
>
<AddCircleOutlineIcon fontSize="small" />
</IconButton>
@@ -592,7 +615,7 @@ const DialogShowMoreLess = ({
</CustomModal>
)}
- {tagModal && colName == "Classification" && (
+ {tagModal && colName == CLASSIFICATION && (
<AddTag
open={tagModal}
isAdd={true}
diff --git a/dashboard/src/components/__tests__/DialogShowMoreLess.test.tsx
b/dashboard/src/components/__tests__/DialogShowMoreLess.test.tsx
index fe30545e5..575eca7e7 100644
--- a/dashboard/src/components/__tests__/DialogShowMoreLess.test.tsx
+++ b/dashboard/src/components/__tests__/DialogShowMoreLess.test.tsx
@@ -28,6 +28,7 @@
import React from 'react'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import '@testing-library/jest-dom'
import DialogShowMoreLess from '../DialogShowMoreLess'
const toastSuccess = jest.fn()
@@ -45,6 +46,9 @@ jest.mock('../muiComponents', () => ({
}))
jest.mock('@mui/material', () => ({
+ Box: ({ children, className, sx }: any) => (
+ <div className={className} style={sx}
data-testid="box">{children}</div>
+ ),
Chip: ({ label, onDelete, className, sx, clickable, size, variant,
color }: any) => (
<div
data-testid="chip"
@@ -365,6 +369,27 @@ describe('DialogShowMoreLess', () => {
const chip = screen.getByText('PII@(a,
b)').closest('[data-testid="chip"]')
expect(chip).toBeTruthy()
})
+
+ it('applies truncation styles to classification chip', () => {
+ render(
+ <DialogShowMoreLess
+ value={{
+ guid: 'g1',
+ Classifications: [
+ { name: 'PII',
entityGuid: 'g1', entityStatus: 'ACTIVE' }
+ ]
+ }}
+ columnVal="Classifications"
+ colName="Classification"
+ displayText="name"
+ isShowMoreLess={true}
+ />
+ )
+
+ const chip = screen.getByText('PII@(a,
b)').closest('[data-testid="chip"]')
+ expect(chip).toBeTruthy()
+ expect(chip).toHaveStyle({ minWidth: 0 })
+ })
})
describe('Term rendering', () => {
diff --git a/dashboard/src/views/SearchResult/SearchResult.tsx
b/dashboard/src/views/SearchResult/SearchResult.tsx
index 5de7ed7f1..f14ac32fb 100644
--- a/dashboard/src/views/SearchResult/SearchResult.tsx
+++ b/dashboard/src/views/SearchResult/SearchResult.tsx
@@ -362,8 +362,7 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams, hideFilters }:
<LightTooltip title={name}>
{entity.guid != "-1" ? (
<Link
- className={`entity-name nav-link text-decoration-none ${
- entityDef.status && entityStateReadOnly[entityDef.status]
+ className={`entity-name nav-link text-decoration-none
${entityDef.status && entityStateReadOnly[entityDef.status]
? "text-red"
: "text-blue"
}`}
@@ -435,7 +434,8 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams, hideFilters }:
accessorKey: "owner",
cell: (info: any) => <span>{info.getValue()}</span>,
header: "Owner",
- show: true
+ show: true,
+ size: 100
},
{
accessorFn: (row: any) => row.attributes.description,
@@ -468,7 +468,8 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams, hideFilters }:
);
},
header: "Type",
- show: true
+ show: true,
+ size: 100
},
{
accessorFn: (row: any) => row.classificationNames[0],
@@ -1078,6 +1079,7 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams, hideFilters }:
</Stack>
)}
+ <div className="search-result-table-wrapper">
<TableLayout
fetchData={fetchSearchResult}
data={
@@ -1143,6 +1145,7 @@ const SearchResult = ({ classificationParams,
glossaryTypeParams, hideFilters }:
showGoToPage={true}
totalCount={totalCount}
/>
+ </div>
</Stack>
);
};
diff --git a/dashboard/src/views/SearchResult/__tests__/SearchResult.test.tsx
b/dashboard/src/views/SearchResult/__tests__/SearchResult.test.tsx
index 0f62bd223..398b362ee 100644
--- a/dashboard/src/views/SearchResult/__tests__/SearchResult.test.tsx
+++ b/dashboard/src/views/SearchResult/__tests__/SearchResult.test.tsx
@@ -23,7 +23,7 @@ import { MemoryRouter } from 'react-router-dom';
import { configureStore } from '@reduxjs/toolkit';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import SearchResult from '../SearchResult';
-import * as searchApiMethod from '@api/apiMethods/searchApiMethod';
+import * as searchApiMethod from '../../../api/apiMethods/searchApiMethod';
import { toast } from 'react-toastify';
const theme = createTheme();
@@ -41,7 +41,7 @@ jest.mock('@api/apiUrlLinks/commonApiUrl', () => ({
}));
// Mock API methods
-jest.mock('@api/apiMethods/searchApiMethod');
+jest.mock('../../../api/apiMethods/searchApiMethod');
jest.mock('@api/apiMethods/classificationApiMethod', () => ({
removeClassification: jest.fn()
}));
@@ -259,14 +259,14 @@ jest.mock('@utils/Utils', () => {
}
}
// Filter out any invalid entries
- const validArr = arr.filter(item => item != null);
+ const validArr = arr.filter((item: any) => item != null);
if (validArr.length === 0) return [];
if (!Array.isArray(keys) || keys.length === 0) {
const result = [...validArr];
return Array.isArray(result) ? result : [];
}
try {
- const sorted = [...validArr].sort((a, b) => {
+ const sorted = [...validArr].sort((a: any, b: any) => {
if (!a || !b) return 0;
for (const key of keys) {
const aVal = a?.[key] || '';
@@ -1838,6 +1838,32 @@ describe('SearchResult', () => {
}, { timeout: 15000 });
}, 30000);
+ it('should render table within search-result-table-wrapper class', async
() => {
+ await act(async () => {
+ renderWithProviders(<SearchResult />);
+ });
+
+ await waitFor(() => {
+
expect(document.querySelector('.search-result-table-wrapper')).toBeInTheDocument();
+ }, { timeout: 15000 });
+ }, 30000);
+
+ it('should configure Owner and Type columns with specific sizes', async ()
=> {
+ await act(async () => {
+ renderWithProviders(<SearchResult />);
+ });
+
+ await waitFor(() => {
+ expect(document.querySelector('.table')).toBeInTheDocument();
+ const ownerColumn = capturedColumns.find((col: any) => col.header ===
'Owner');
+ expect(ownerColumn).toBeDefined();
+ expect(ownerColumn?.size).toBe(100);
+
+ const typeColumn = capturedColumns.find((col: any) => col.header ===
'Type');
+ expect(typeColumn).toBeDefined();
+ expect(typeColumn?.size).toBe(100);
+ }, { timeout: 15000 });
+ }, 30000);
it('should show assign filters when classificationParams is present',
async () => {
await act(async () => {