From 732d9147d5a850502f7e5d24d2abebf4de6e5487 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Fri, 21 Aug 2026 17:16:31 +0530 Subject: [PATCH 1/6] fix: paginate glossary terms and fix expand/collapse scroll behaviour Load glossary terms in pages instead of requesting the full list at once, and keep the collapsed-view infinite scroll working after using Expand/ Collapse all. Fixes #29916 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../GlossaryTermTab.component.tsx | 101 ++++++++++++------ .../GlossaryTermTab/GlossaryTermTab.test.tsx | 6 +- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index cde323ae0e2f..a11d82988f59 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -56,7 +56,6 @@ import ErrorPlaceHolder from '../../../components/common/ErrorWithPlaceholder/Er import { OwnerLabel } from '../../../components/common/OwnerLabel/OwnerLabel.component'; import StatusBadge from '../../../components/common/StatusBadge/StatusBadge.component'; import { - API_RES_MAX_SIZE, DE_ACTIVE_COLOR, NO_DATA_PLACEHOLDER, PAGE_SIZE_LARGE, @@ -414,50 +413,75 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { setIsTableLoading(true); setIsExpandingAll(true); const key = isGlossary ? 'glossary' : 'parent'; - const { data } = await getGlossaryTerms({ - [key]: activeGlossary?.id || '', - limit: API_RES_MAX_SIZE, - fields: [ - TabSpecificField.OWNERS, - TabSpecificField.PARENT, - TabSpecificField.CHILDREN, - ], - }); - setGlossaryChildTerms(buildTree(data) as ModifiedGlossary[]); - const keys = data.reduce((prev, curr) => { - if (curr.children?.length) { - prev.push(curr.fullyQualifiedName ?? ''); - } - return prev; - }, [] as string[]); + try { + const allTerms: GlossaryTerm[] = []; + let after: string | undefined; + + do { + const { data, paging } = await getGlossaryTerms({ + [key]: activeGlossary?.id || '', + limit: PAGE_SIZE_LARGE, + after, + fields: [ + TabSpecificField.OWNERS, + TabSpecificField.PARENT, + TabSpecificField.CHILDREN, + ], + }); + allTerms.push(...data); + after = paging?.after; + } while (after); + + setGlossaryChildTerms(buildTree(allTerms) as ModifiedGlossary[]); + const keys = allTerms.reduce((prev, curr) => { + if (curr.children?.length) { + prev.push(curr.fullyQualifiedName ?? ''); + } + + return prev; + }, [] as string[]); - setExpandedRowKeys(keys); - setIsTableLoading(false); - setIsExpandingAll(false); + setExpandedRowKeys(keys); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsTableLoading(false); + setIsExpandingAll(false); + } }; const fetchAllTasks = useCallback(async () => { - if (!activeGlossary?.fullyQualifiedName) { + const glossaryFqn = activeGlossary?.fullyQualifiedName; + if (!glossaryFqn) { return; } try { - const { data } = await listTasks({ - status: TaskEntityStatus.Open, - category: TaskCategory.Approval, - type: TaskEntityType.RequestApproval, - limit: API_RES_MAX_SIZE, - fields: 'about,assignees', - }); + const tasks: Task[] = []; + let after: string | undefined; + + do { + const { data, paging } = await listTasks({ + status: TaskEntityStatus.Open, + category: TaskCategory.Approval, + type: TaskEntityType.RequestApproval, + aboutEntity: glossaryFqn, + limit: PAGE_SIZE_LARGE, + after, + fields: 'about,assignees', + }); + tasks.push(...data); + after = paging?.after; + } while (after); // Glossary approvals are now workflow-managed RequestApproval tasks created // for each glossary term, not legacy glossary-root tasks. - const tasksByTerm = data.reduce( + const tasksByTerm = tasks.reduce( (acc: Record, task: Task) => { const termFQN = task.about?.fullyQualifiedName; const isGlossaryTermTask = task.about?.type === EntityType.GLOSSARY_TERM && - termFQN?.startsWith(`${activeGlossary.fullyQualifiedName}.`); + termFQN?.startsWith(`${glossaryFqn}.`); if (isGlossaryTermTask && termFQN) { const entityLink = `<#E::${EntityType.GLOSSARY_TERM}::${termFQN}>`; @@ -489,10 +513,12 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { currentFQN && !isLoadingMore && currentFQN !== previousGlossaryFQN && - !toggleExpandBtn && !searchTerm // Don't fetch if there's an active search ) { - // Clear existing terms when switching glossaries + // Clear existing terms when switching glossaries. Always reset to the + // collapsed, paginated view so the new glossary starts scrollable — a + // stuck expand-all flag must not carry over from the previous glossary. + setToggleExpandBtn(false); setGlossaryChildTerms([]); handlePagingChange((prev) => ({ ...prev, after: undefined })); setPreviousGlossaryFQN(currentFQN); @@ -502,7 +528,6 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { activeGlossary?.fullyQualifiedName, isLoadingMore, previousGlossaryFQN, - toggleExpandBtn, searchTerm, ]); @@ -1130,8 +1155,14 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { }; const toggleExpandAll = useCallback(async () => { - setToggleExpandBtn((prev) => !prev); - if (expandedRowKeys.length === expandableKeys.length) { + // `toggleExpandBtn` marks whether the fully-expanded tree is currently + // shown; the scroll handlers gate the collapsed-view infinite scroll on + // `!toggleExpandBtn`. Reflect the resulting mode rather than blindly + // flipping — a blind toggle leaves the flag stuck `true` after an odd + // number of clicks, which permanently disables scroll-to-load-more. + const isCollapsing = expandedRowKeys.length === expandableKeys.length; + setToggleExpandBtn(!isCollapsing); + if (isCollapsing) { // Collapse all - immediate UI update setExpandedRowKeys([]); fetchAllTerms(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx index c352296323fb..49096e7d4f8b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx @@ -761,7 +761,8 @@ describe('Test GlossaryTermTab component', () => { status: 'Open', category: 'Approval', type: 'RequestApproval', - limit: 100000, + aboutEntity: mockedGlossaryTerms[0].fullyQualifiedName, + limit: 50, fields: 'about,assignees', }) ); @@ -779,7 +780,8 @@ describe('Test GlossaryTermTab component', () => { status: 'Open', category: 'Approval', type: 'RequestApproval', - limit: 100000, + aboutEntity: mockedGlossaryTerms[0].fullyQualifiedName, + limit: 50, fields: 'about,assignees', }) ); From 4637fbfb885480b1d4a5be74cd16d23f1bf23449 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Fri, 21 Aug 2026 17:30:57 +0530 Subject: [PATCH 2/6] fix: guard glossary term fetches against stale switches and non-terminating pagination - Discard in-flight term/task responses when the glossary has changed so a previous glossary's data can no longer repopulate the table. - Stop the pagination loops on no forward progress (empty page or a cursor that does not advance) instead of relying on the server nulling the token. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../GlossaryTermTab.component.tsx | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index a11d82988f59..b705129dde44 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -203,6 +203,12 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { const [searchTerm, setSearchTerm] = useState(''); const searchTermRef = useRef(searchTerm); searchTermRef.current = searchTerm; + // Live ref to the active glossary so an in-flight request can verify the + // glossary is still the current one before applying its response — the + // component stays mounted across glossary switches, so an uncancelled request + // for the previous glossary must not repopulate the table. + const activeGlossaryFqnRef = useRef(activeGlossary?.fullyQualifiedName); + activeGlossaryFqnRef.current = activeGlossary?.fullyQualifiedName; const [searchInput, setSearchInput] = useState(''); const [searchPaging, setSearchPaging] = useState<{ offset: number; @@ -299,6 +305,7 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { const requestSeq = ++fetchRequestSeqRef.current; const fetchSearchTerm = searchTerm; const fetchStatusKey = selectedStatus.join(','); + const fetchGlossaryFqn = activeGlossary?.fullyQualifiedName; initializeLoadingStates(loadMore); try { @@ -355,16 +362,17 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { })); } - // Apply the response only when it still matches the active search context. - // A response computed for a different (now-outdated) search term — e.g. a - // listing request that was in flight when the user typed a query, or a - // stale search-mode fetch after the query changed — is discarded so it - // cannot repopulate or clear the table against the user's current intent. + // Apply the response only when it still matches the active context — the + // same search term, status filter, and glossary it was issued for. A + // response computed for a now-outdated context (a request in flight when + // the user typed a query, changed the filter, or switched glossaries) is + // discarded so it cannot repopulate the table against the user's intent. if ( !data || !Array.isArray(data) || fetchSearchTerm !== searchTermRef.current || - fetchStatusKey !== selectedStatusRef.current.join(',') + fetchStatusKey !== selectedStatusRef.current.join(',') || + fetchGlossaryFqn !== activeGlossaryFqnRef.current ) { return; } @@ -413,6 +421,7 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { setIsTableLoading(true); setIsExpandingAll(true); const key = isGlossary ? 'glossary' : 'parent'; + const requestedGlossaryFqn = activeGlossary?.fullyQualifiedName; try { const allTerms: GlossaryTerm[] = []; @@ -430,9 +439,19 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { ], }); allTerms.push(...data); - after = paging?.after; + // Stop on no forward progress — an empty page or a cursor that did not + // advance — so termination never depends solely on the server nulling + // the token (a stuck cursor would otherwise loop forever). + const nextAfter = paging?.after; + const hasProgress = data.length > 0 && nextAfter !== after; + after = hasProgress ? nextAfter : undefined; } while (after); + // Discard the result if the user switched glossaries while paging. + if (requestedGlossaryFqn !== activeGlossaryFqnRef.current) { + return; + } + setGlossaryChildTerms(buildTree(allTerms) as ModifiedGlossary[]); const keys = allTerms.reduce((prev, curr) => { if (curr.children?.length) { @@ -471,9 +490,18 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { fields: 'about,assignees', }); tasks.push(...data); - after = paging?.after; + // Stop on no forward progress (empty page or non-advancing cursor) so + // termination never depends solely on the server nulling the token. + const nextAfter = paging?.after; + const hasProgress = data.length > 0 && nextAfter !== after; + after = hasProgress ? nextAfter : undefined; } while (after); + // Discard the result if the user switched glossaries while paging. + if (glossaryFqn !== activeGlossaryFqnRef.current) { + return; + } + // Glossary approvals are now workflow-managed RequestApproval tasks created // for each glossary term, not legacy glossary-root tasks. const tasksByTerm = tasks.reduce( From d98465501c82d4fbbba07ab8baf4023add6ae09f Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Fri, 21 Aug 2026 17:43:06 +0530 Subject: [PATCH 3/6] fix: reject stale glossary term responses before advancing pagination state Move the stale-context guard in fetchAllTerms ahead of the search-offset and continuation-cursor updates so a response for a now-inactive search term, status filter, or glossary can no longer advance the pagination state that the active glossary's scrolling depends on. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../GlossaryTermTab.component.tsx | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index b705129dde44..9ab341da6e27 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -317,34 +317,23 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { ? selectedStatus.filter((s) => s !== 'all').join(',') : undefined; - // Use search API if search term is present + // Fetch the page first and defer every shared-state mutation until the + // response is confirmed current by the stale-context guard below. + let searchOffset = 0; if (searchTerm) { - const currentOffset = loadMore ? searchPaging.offset : 0; + searchOffset = loadMore ? searchPaging.offset : 0; const response = await searchGlossaryTermsPaginated({ q: searchTerm, glossaryFqn: activeGlossary?.fullyQualifiedName, limit: PAGE_SIZE_LARGE, - offset: currentOffset, + offset: searchOffset, fields: 'children,relatedTerms,reviewers,owners,tags,usageCount,domains,extension,childrenCount', entityStatus: entityStatusParam, }); data = response.data; pagingResponse = response.paging; - - // Update search pagination state - const newOffset = currentOffset + PAGE_SIZE_LARGE; - const hasMore = - data.length === PAGE_SIZE_LARGE && - (pagingResponse?.total === undefined || - newOffset < pagingResponse?.total); - setSearchPaging({ - offset: newOffset, - total: pagingResponse?.total, - hasMore, - }); } else { - // Use regular listing API when no search term const response = await getFirstLevelGlossaryTermsPaginated( activeGlossary?.fullyQualifiedName || '', PAGE_SIZE_LARGE, @@ -353,20 +342,12 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { ); data = response.data; pagingResponse = response.paging; - - // Update regular paging state for next page - handlePagingChange((prev) => ({ - ...prev, - after: pagingResponse?.after, - total: pagingResponse?.total || prev.total, - })); } - // Apply the response only when it still matches the active context — the - // same search term, status filter, and glossary it was issued for. A - // response computed for a now-outdated context (a request in flight when - // the user typed a query, changed the filter, or switched glossaries) is - // discarded so it cannot repopulate the table against the user's intent. + // Discard stale responses BEFORE touching any shared pagination state, so + // a request issued for a now-inactive context (search term, status + // filter, or glossary) cannot advance the search offset or continuation + // cursor that the active glossary's scrolling relies on. if ( !data || !Array.isArray(data) || @@ -377,6 +358,26 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { return; } + // Advance pagination state now that the response is known to be current. + if (searchTerm) { + const newOffset = searchOffset + PAGE_SIZE_LARGE; + const hasMore = + data.length === PAGE_SIZE_LARGE && + (pagingResponse?.total === undefined || + newOffset < pagingResponse?.total); + setSearchPaging({ + offset: newOffset, + total: pagingResponse?.total, + hasMore, + }); + } else { + handlePagingChange((prev) => ({ + ...prev, + after: pagingResponse?.after, + total: pagingResponse?.total || prev.total, + })); + } + if (data.length === 0 && isStatusFilterActive) { const countResponse = await getFirstLevelGlossaryTermsPaginated( activeGlossary?.fullyQualifiedName || '', From 06d49d8ef43b669a8fdc444750e0f7d536ee0f61 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Fri, 21 Aug 2026 18:07:00 +0530 Subject: [PATCH 4/6] fix: give expand-all the same stale-request discipline as the term list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetchExpadedTree now shares the fetchRequestSeqRef sequence used by fetchAllTerms, so a superseded expand-all applies no rows and clears no loading flags — a stale expand-all can no longer release a newer operation's loading state. fetchAllTerms also honours the sequence before applying rows so a stale first-level load cannot clobber a newer expand-all, and a fresh first-level fetch clears the expand indicator it supersedes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../GlossaryTermTab.component.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index 9ab341da6e27..a66b54c84d70 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -288,6 +288,10 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { } setIsTableLoading(true); + // A fresh first-level fetch supersedes any in-flight expand-all, so clear + // its indicator; the superseded expand-all's own cleanup is sequence-gated + // and will not touch this loading state. + setIsExpandingAll(false); if (searchTerm) { setSearchPaging({ offset: 0, total: undefined, hasMore: true }); @@ -351,6 +355,7 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { if ( !data || !Array.isArray(data) || + requestSeq !== fetchRequestSeqRef.current || fetchSearchTerm !== searchTermRef.current || fetchStatusKey !== selectedStatusRef.current.join(',') || fetchGlossaryFqn !== activeGlossaryFqnRef.current @@ -419,6 +424,10 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { }; const fetchExpadedTree = async () => { + // Share the same monotonic sequence as fetchAllTerms so whichever term + // load starts last owns the shared table + loading state; a superseded + // expand-all then applies no rows and clears no loading flags. + const requestSeq = ++fetchRequestSeqRef.current; setIsTableLoading(true); setIsExpandingAll(true); const key = isGlossary ? 'glossary' : 'parent'; @@ -448,8 +457,12 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { after = hasProgress ? nextAfter : undefined; } while (after); - // Discard the result if the user switched glossaries while paging. - if (requestedGlossaryFqn !== activeGlossaryFqnRef.current) { + // Discard the result if the glossary changed under us, or if a newer term + // load superseded this one while it was paging. + if ( + requestedGlossaryFqn !== activeGlossaryFqnRef.current || + requestSeq !== fetchRequestSeqRef.current + ) { return; } @@ -464,10 +477,15 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { setExpandedRowKeys(keys); } catch (error) { - showErrorToast(error as AxiosError); + if (requestSeq === fetchRequestSeqRef.current) { + showErrorToast(error as AxiosError); + } } finally { - setIsTableLoading(false); - setIsExpandingAll(false); + // Only the latest term load owns the shared loading flags. + if (requestSeq === fetchRequestSeqRef.current) { + setIsTableLoading(false); + setIsExpandingAll(false); + } } }; const fetchAllTasks = useCallback(async () => { From 3a2dbf147223c7bdf6fc98455760f943acfbc403 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Fri, 21 Aug 2026 18:16:11 +0530 Subject: [PATCH 5/6] fix: re-validate the term response after the follow-up count request The status-filtered empty-page path issues a second request (the total count) after the initial staleness guard. Extract the staleness check into a reusable isSuperseded() helper and re-run it after that count request so a response that went stale mid-flight cannot overwrite the active context's term list, count, or expanded rows. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../GlossaryTermTab.component.tsx | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index a66b54c84d70..4162c455072c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -348,22 +348,37 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { pagingResponse = response.paging; } - // Discard stale responses BEFORE touching any shared pagination state, so - // a request issued for a now-inactive context (search term, status - // filter, or glossary) cannot advance the search offset or continuation - // cursor that the active glossary's scrolling relies on. - if ( - !data || - !Array.isArray(data) || + // A response is applied only while it still matches the context it was + // issued for (same request sequence, search term, status filter, and + // glossary). Re-checked at every await boundary — including the follow-up + // count request below — so a request that goes stale mid-flight touches + // no shared state (pagination cursor, term list, or count). + const isSuperseded = () => requestSeq !== fetchRequestSeqRef.current || fetchSearchTerm !== searchTermRef.current || fetchStatusKey !== selectedStatusRef.current.join(',') || - fetchGlossaryFqn !== activeGlossaryFqnRef.current - ) { + fetchGlossaryFqn !== activeGlossaryFqnRef.current; + + if (!data || !Array.isArray(data) || isSuperseded()) { return; } - // Advance pagination state now that the response is known to be current. + // A status-filtered empty page needs a follow-up total for the empty + // state. Fetch it before applying anything and re-validate afterwards so + // the previous context's empty result cannot overwrite the active one. + let totalCount = data.length; + if (data.length === 0 && isStatusFilterActive) { + const countResponse = await getFirstLevelGlossaryTermsPaginated( + fetchGlossaryFqn || '', + 0 + ); + if (isSuperseded()) { + return; + } + totalCount = countResponse.paging?.total ?? 0; + } + + // Advance pagination state now that the response is confirmed current. if (searchTerm) { const newOffset = searchOffset + PAGE_SIZE_LARGE; const hasMore = @@ -383,15 +398,7 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { })); } - if (data.length === 0 && isStatusFilterActive) { - const countResponse = await getFirstLevelGlossaryTermsPaginated( - activeGlossary?.fullyQualifiedName || '', - 0 - ); - setTotalTermsCount(countResponse.paging?.total ?? 0); - } else { - setTotalTermsCount(data.length); - } + setTotalTermsCount(totalCount); const newTerms = data as ModifiedGlossary[]; From 05cd6d642d775bcdee0420dd8061d48b7f04f0d0 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Fri, 21 Aug 2026 18:25:30 +0530 Subject: [PATCH 6/6] fix: reload terms on glossary switch even while a search is active The glossary-switch effect skipped fetching when a search term was set, and the search effect depends only on search term/status (not the glossary), so switching glossaries mid-search left the new glossary empty or showing the previous one's state. Drop the search-term guard so a glossary change always reloads; fetchAllTerms re-runs the search scoped to the newly active glossary. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../GlossaryTermTab.component.tsx | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index 4162c455072c..dc475194b001 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -563,27 +563,18 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { useEffect(() => { const currentFQN = activeGlossary?.fullyQualifiedName; - if ( - currentFQN && - !isLoadingMore && - currentFQN !== previousGlossaryFQN && - !searchTerm // Don't fetch if there's an active search - ) { - // Clear existing terms when switching glossaries. Always reset to the - // collapsed, paginated view so the new glossary starts scrollable — a - // stuck expand-all flag must not carry over from the previous glossary. + if (currentFQN && !isLoadingMore && currentFQN !== previousGlossaryFQN) { + // Reload whenever the glossary changes — including while a search is + // active, in which case fetchAllTerms re-runs the search scoped to the + // new glossary. Reset to the collapsed, paginated view so the new + // glossary starts scrollable and no stuck expand-all flag carries over. setToggleExpandBtn(false); setGlossaryChildTerms([]); handlePagingChange((prev) => ({ ...prev, after: undefined })); setPreviousGlossaryFQN(currentFQN); fetchAllTerms(); } - }, [ - activeGlossary?.fullyQualifiedName, - isLoadingMore, - previousGlossaryFQN, - searchTerm, - ]); + }, [activeGlossary?.fullyQualifiedName, isLoadingMore, previousGlossaryFQN]); // Clear terms when component unmounts useEffect(() => {