Skip to content

Commit

Permalink
fix(ui): expand invalid state upon glossary term add (#18968)
Browse files Browse the repository at this point in the history
* fix(ui): expand invalid state upon glossary term add
invalid expand state for glossary term update

* fix tests

* update glossary store upon changes in tree

* fix tests
  • Loading branch information
chirag-madlani authored Dec 16, 2024
1 parent 6c6b760 commit 254fce4
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ const GlossaryTermTab = ({
useGlossaryStore();
const { t } = useTranslation();

const glossaryTerms = (glossaryChildTerms as ModifiedGlossaryTerm[]) ?? [];
const glossaryTerms = useMemo(
() => (glossaryChildTerms as ModifiedGlossaryTerm[]) ?? [],
[glossaryChildTerms]
);

const [movedGlossaryTerm, setMovedGlossaryTerm] =
useState<MoveGlossaryTermType>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,12 @@ const GlossaryV1 = ({

const [editMode, setEditMode] = useState(false);

const { activeGlossary, glossaryChildTerms, setGlossaryChildTerms } =
useGlossaryStore();
const {
activeGlossary,
glossaryChildTerms,
setGlossaryChildTerms,
insertNewGlossaryTermToChildTerms,
} = useGlossaryStore();

const { id, fullyQualifiedName } = activeGlossary ?? {};

Expand Down Expand Up @@ -129,11 +133,9 @@ const GlossaryV1 = ({
const { data } = await getFirstLevelGlossaryTerms(
params?.glossary ?? params?.parent ?? ''
);
const children = data.map((data) =>
data.childrenCount ?? 0 > 0 ? { ...data, children: [] } : data
);

setGlossaryChildTerms(children as ModifiedGlossary[]);
// We are considering childrenCount fot expand collapse state
// Hence don't need any intervention to list response here
setGlossaryChildTerms(data as ModifiedGlossary[]);
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
Expand Down Expand Up @@ -232,7 +234,11 @@ const GlossaryV1 = ({
entity: t('label.glossary-term'),
});
} else {
updateGlossaryTermInStore(response);
updateGlossaryTermInStore({
...response,
// Since patch didn't respond with childrenCount preserve it from currentData
childrenCount: currentData.childrenCount,
});
setIsEditModalOpen(false);
}
} catch (error) {
Expand All @@ -257,29 +263,38 @@ const GlossaryV1 = ({
}
};

const onTermModalSuccess = useCallback(() => {
loadGlossaryTerms(true);
if (!isGlossaryActive && tab !== 'terms') {
history.push(
getGlossaryTermDetailsPath(
selectedData.fullyQualifiedName || '',
EntityTabs.TERMS
)
);
}
setIsEditModalOpen(false);
}, [isGlossaryActive, tab, selectedData]);
const onTermModalSuccess = useCallback(
(term: GlossaryTerm) => {
// Setting loading so that nested terms are rendered again on table with change
setIsTermsLoading(true);
// Update store with newly created term
insertNewGlossaryTermToChildTerms(term);
if (!isGlossaryActive && tab !== 'terms') {
history.push(
getGlossaryTermDetailsPath(
selectedData.fullyQualifiedName || '',
EntityTabs.TERMS
)
);
}
// Close modal and set loading to false
setIsEditModalOpen(false);
setIsTermsLoading(false);
},
[isGlossaryActive, tab, selectedData]
);

const handleGlossaryTermAdd = async (formData: GlossaryTermForm) => {
try {
await addGlossaryTerm({
const term = await addGlossaryTerm({
...formData,
glossary:
activeGlossaryTerm?.glossary?.name ||
(selectedData.fullyQualifiedName ?? ''),
parent: activeGlossaryTerm?.fullyQualifiedName,
});
onTermModalSuccess();

onTermModalSuccess(term);
} catch (error) {
if (
(error as AxiosError).response?.status === HTTP_STATUS_CODE.CONFLICT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
*/
import { create } from 'zustand';
import { Glossary } from '../../generated/entity/data/glossary';
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
import { GlossaryTermWithChildren } from '../../rest/glossaryAPI';
import { findAndUpdateNested } from '../../utils/GlossaryUtils';

export type ModifiedGlossary = Glossary & {
children?: GlossaryTermWithChildren[];
Expand All @@ -27,6 +29,7 @@ export const useGlossaryStore = create<{
updateGlossary: (glossary: Glossary) => void;
updateActiveGlossary: (glossary: Partial<ModifiedGlossary>) => void;
setGlossaryChildTerms: (glossaryChildTerms: ModifiedGlossary[]) => void;
insertNewGlossaryTermToChildTerms: (glossary: GlossaryTerm) => void;
}>()((set, get) => ({
glossaries: [],
activeGlossary: {} as ModifiedGlossary,
Expand Down Expand Up @@ -67,6 +70,14 @@ export const useGlossaryStore = create<{
glossaries[index] = updatedGlossary;
}
},
insertNewGlossaryTermToChildTerms: (glossary: GlossaryTerm) => {
const { glossaryChildTerms } = get();

// Typically used to updated the glossary term list in the glossary page
set({
glossaryChildTerms: findAndUpdateNested(glossaryChildTerms, glossary),
});
},
setGlossaryChildTerms: (glossaryChildTerms: ModifiedGlossary[]) => {
set({ glossaryChildTerms });
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const getGlossaryTermByFQN = async (fqn = '', params?: ListParams) => {

export const addGlossaryTerm = async (
data: CreateGlossaryTerm
): Promise<AxiosResponse> => {
): Promise<GlossaryTerm> => {
const url = '/glossaryTerms';

const response = await APIClient.post(url, data);
Expand Down
136 changes: 136 additions & 0 deletions openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
* limitations under the License.
*/
import { ModifiedGlossaryTerm } from '../components/Glossary/GlossaryTermTab/GlossaryTermTab.interface';
import { ModifiedGlossary } from '../components/Glossary/useGlossary.store';
import { EntityType } from '../enums/entity.enum';
import { Glossary } from '../generated/entity/data/glossary';
import { GlossaryTerm } from '../generated/entity/data/glossaryTerm';
import {
MOCKED_GLOSSARY_TERMS,
MOCKED_GLOSSARY_TERMS_1,
Expand All @@ -22,6 +24,7 @@ import {
import {
buildTree,
filterTreeNodeOptions,
findAndUpdateNested,
findExpandableKeys,
findExpandableKeysForArray,
getQueryFilterToExcludeTerm,
Expand Down Expand Up @@ -219,3 +222,136 @@ describe('Glossary Utils', () => {
expect(filteredOptions).toEqual(expected_glossary);
});
});

describe('findAndUpdateNested', () => {
it('should add new term to the correct parent', () => {
const terms: ModifiedGlossary[] = [
{
fullyQualifiedName: 'parent1',
children: [],
id: 'parent1',
name: 'parent1',
description: 'parent1',
},
{
fullyQualifiedName: 'parent2',
children: [],
id: 'parent2',
name: 'parent2',
description: 'parent2',
},
];

const newTerm: GlossaryTerm = {
fullyQualifiedName: 'child1',
parent: {
fullyQualifiedName: 'parent1',
id: 'parent1',
type: 'Glossary',
},
id: 'child1',
name: 'child1',
description: 'child1',
glossary: {
fullyQualifiedName: 'child1',
id: 'child1',
name: 'child1',
description: 'child1',
type: 'Glossary',
},
};

const updatedTerms = findAndUpdateNested(terms, newTerm);

expect(updatedTerms[0].children).toHaveLength(1);
expect(updatedTerms?.[0].children?.[0]).toEqual(newTerm);
});

it('should add new term to nested parent', () => {
const terms: ModifiedGlossary[] = [
{
fullyQualifiedName: 'parent1',
children: [
{
fullyQualifiedName: 'child1',
children: [],
glossary: {
fullyQualifiedName: 'child1',
id: 'child1',
name: 'child1',
description: 'child1',
type: 'Glossary',
},
id: 'child1',
name: 'child1',
description: 'child1',
},
],
id: 'parent1',
name: 'parent1',
description: 'parent1',
},
];

const newTerm: GlossaryTerm = {
fullyQualifiedName: 'child2',
parent: { fullyQualifiedName: 'child1', id: 'child1', type: 'Glossary' },
id: 'child2',
name: 'child2',
description: 'child2',
glossary: {
fullyQualifiedName: 'child2',
id: 'child2',
name: 'child2',
description: 'child2',
type: 'Glossary',
},
};

const updatedTerms = findAndUpdateNested(terms, newTerm);

expect(
updatedTerms?.[0].children && updatedTerms?.[0].children[0].children
).toHaveLength(1);
expect(
updatedTerms?.[0].children &&
updatedTerms?.[0].children[0].children &&
updatedTerms?.[0].children[0].children[0]
).toEqual(newTerm);
});

it('should not modify terms if parent is not found', () => {
const terms: ModifiedGlossary[] = [
{
fullyQualifiedName: 'parent1',
children: [],
id: 'parent1',
name: 'parent1',
description: 'parent1',
},
];

const newTerm: GlossaryTerm = {
fullyQualifiedName: 'child1',
parent: {
fullyQualifiedName: 'nonexistent',
id: 'nonexistent',
type: 'Glossary',
},
id: 'child1',
name: 'child1',
description: 'child1',
glossary: {
fullyQualifiedName: 'child1',
id: 'child1',
name: 'child1',
description: 'child1',
type: 'Glossary',
},
};

const updatedTerms = findAndUpdateNested(terms, newTerm);

expect(updatedTerms).toEqual(terms);
});
});
32 changes: 32 additions & 0 deletions openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,35 @@ export const renderReferenceElement = (
</Tag>
);
};

export const findAndUpdateNested = (
terms: ModifiedGlossary[],
newTerm: GlossaryTerm
): ModifiedGlossary[] => {
// If new term has no parent, it's a top level term
// So just update 0 level terms no need to iterate over it
if (!newTerm.parent) {
return [...terms, newTerm as ModifiedGlossary];
}

// If parent is there means term is created within a term
// So we need to find the parent term and update it's children
return terms.map((term) => {
if (term.fullyQualifiedName === newTerm.parent?.fullyQualifiedName) {
return {
...term,
children: [...(term.children || []), newTerm] as GlossaryTerm[],
} as ModifiedGlossary;
} else if ('children' in term && term.children?.length) {
return {
...term,
children: findAndUpdateNested(
term.children as ModifiedGlossary[],
newTerm
),
} as ModifiedGlossary;
}

return term;
});
};

0 comments on commit 254fce4

Please sign in to comment.