Skip to content

Commit 277a726

Browse files
ATLAS-5349: Ability to Export Apache Atlas Business Glossary to Excel/CSV from UI
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 40de707 commit 277a726

38 files changed

Lines changed: 4543 additions & 128 deletions

dashboard/src/api/apiMethods/__tests__/glossaryApiMethod.test.ts

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
import {
2929
removeTerm,
3030
getGlossary,
31+
getGlossaryTermsForGlossary,
32+
getGlossaryCategoriesForGlossary,
33+
searchGlossary,
34+
createGlossaryExportFile,
35+
getGlossaryDownloadStatus,
3136
getGlossaryImportTmpl,
3237
getGlossaryImport,
3338
getGlossaryType,
@@ -57,7 +62,10 @@ import {
5762
assignTermtoEntitiesUrl,
5863
assignTermtoCategoryUrl,
5964
assignGlossaryTypeUrl,
60-
removeTermorCatgeoryUrl
65+
removeTermorCatgeoryUrl,
66+
glossaryTermsUrl,
67+
glossaryCategoriesUrl,
68+
glossarySearchUrl
6169
} from '../../../api/apiUrlLinks/glossaryUrl'
6270

6371
// Mock dependencies
@@ -81,6 +89,11 @@ const mockAssignTermtoEntitiesUrl = jest.fn((termId: string) => `/api/glossary/t
8189
const mockAssignTermtoCategoryUrl = jest.fn((categoryId: string) => `/api/glossary/category/${categoryId}`)
8290
const mockAssignGlossaryTypeUrl = jest.fn((guid: string) => `/api/glossary/type/${guid}`)
8391
const mockRemoveTermorCatgeoryUrl = jest.fn((guid: string, type: string) => `/api/glossary/${type}/${guid}`)
92+
const mockGlossaryTermsUrl = jest.fn((guid: string) => `/api/glossary/${guid}/terms`)
93+
const mockGlossaryCategoriesUrl = jest.fn((guid: string) => `/api/glossary/${guid}/categories`)
94+
const mockGlossarySearchUrl = jest.fn(() => '/api/glossary/search')
95+
const mockGlossaryCreateFileUrl = jest.fn(() => '/api/glossary/create_file')
96+
const mockGlossaryDownloadStatusUrl = jest.fn(() => '/api/glossary/download/status')
8497

8598
jest.mock('../../../api/apiUrlLinks/glossaryUrl', () => ({
8699
removeTermUrl: (...args: any[]) => mockRemoveTermUrl(...args),
@@ -95,7 +108,12 @@ jest.mock('../../../api/apiUrlLinks/glossaryUrl', () => ({
95108
assignTermtoEntitiesUrl: (...args: any[]) => mockAssignTermtoEntitiesUrl(...args),
96109
assignTermtoCategoryUrl: (...args: any[]) => mockAssignTermtoCategoryUrl(...args),
97110
assignGlossaryTypeUrl: (...args: any[]) => mockAssignGlossaryTypeUrl(...args),
98-
removeTermorCatgeoryUrl: (...args: any[]) => mockRemoveTermorCatgeoryUrl(...args)
111+
removeTermorCatgeoryUrl: (...args: any[]) => mockRemoveTermorCatgeoryUrl(...args),
112+
glossaryTermsUrl: (...args: any[]) => mockGlossaryTermsUrl(...args),
113+
glossaryCategoriesUrl: (...args: any[]) => mockGlossaryCategoriesUrl(...args),
114+
glossarySearchUrl: (...args: any[]) => mockGlossarySearchUrl(...args),
115+
glossaryCreateFileUrl: (...args: any[]) => mockGlossaryCreateFileUrl(...args),
116+
glossaryDownloadStatusUrl: (...args: any[]) => mockGlossaryDownloadStatusUrl(...args)
99117
}))
100118

101119
describe('glossaryApiMethod', () => {
@@ -132,6 +150,8 @@ describe('glossaryApiMethod', () => {
132150
mockAssignTermtoCategoryUrl.mockImplementation((categoryId: string) => `/api/glossary/category/${categoryId}`)
133151
mockAssignGlossaryTypeUrl.mockImplementation((guid: string) => `/api/glossary/type/${guid}`)
134152
mockRemoveTermorCatgeoryUrl.mockImplementation((guid: string, type: string) => `/api/glossary/${type}/${guid}`)
153+
mockGlossaryTermsUrl.mockImplementation((guid: string) => `/api/glossary/${guid}/terms`)
154+
mockGlossaryCategoriesUrl.mockImplementation((guid: string) => `/api/glossary/${guid}/categories`)
135155
})
136156

137157
describe('removeTerm', () => {
@@ -163,6 +183,85 @@ describe('glossaryApiMethod', () => {
163183
})
164184
})
165185

186+
describe('getGlossaryTermsForGlossary', () => {
187+
it('should call _get with terms URL and list params', async () => {
188+
const result = await getGlossaryTermsForGlossary('g-1')
189+
190+
expect(mockGlossaryTermsUrl).toHaveBeenCalledWith('g-1')
191+
expect(mockGet).toHaveBeenCalledWith('/api/glossary/g-1/terms', {
192+
method: 'GET',
193+
params: { limit: -1, offset: 0, sort: 'ASC' }
194+
})
195+
expect(result).toEqual(mockResponse)
196+
})
197+
})
198+
199+
describe('getGlossaryCategoriesForGlossary', () => {
200+
it('should call _get with categories URL and list params', async () => {
201+
const result = await getGlossaryCategoriesForGlossary('g-2')
202+
203+
expect(mockGlossaryCategoriesUrl).toHaveBeenCalledWith('g-2')
204+
expect(mockGet).toHaveBeenCalledWith('/api/glossary/g-2/categories', {
205+
method: 'GET',
206+
params: { limit: -1, offset: 0, sort: 'ASC' }
207+
})
208+
expect(result).toEqual(mockResponse)
209+
})
210+
})
211+
212+
describe('searchGlossary', () => {
213+
it('should POST search parameters to glossary search URL', async () => {
214+
const params = {
215+
limit: 25,
216+
offset: 0,
217+
sortBy: 'glossaryName',
218+
sortOrder: 'ASCENDING'
219+
}
220+
const result = await searchGlossary(params)
221+
222+
expect(mockGlossarySearchUrl).toHaveBeenCalled()
223+
expect(mockPost).toHaveBeenCalledWith('/api/glossary/search', {
224+
method: 'POST',
225+
params: {},
226+
data: params
227+
})
228+
expect(result).toEqual(mockResponse)
229+
})
230+
})
231+
232+
describe('createGlossaryExportFile', () => {
233+
it('should POST export parameters to glossary create_file URL', async () => {
234+
const params = {
235+
limit: 0,
236+
offset: 0,
237+
sortBy: 'glossaryName',
238+
sortOrder: 'DESCENDING'
239+
}
240+
const result = await createGlossaryExportFile(params)
241+
242+
expect(mockGlossaryCreateFileUrl).toHaveBeenCalled()
243+
expect(mockPost).toHaveBeenCalledWith('/api/glossary/create_file', {
244+
method: 'POST',
245+
params: {},
246+
data: params
247+
})
248+
expect(result).toEqual(mockResponse)
249+
})
250+
})
251+
252+
describe('getGlossaryDownloadStatus', () => {
253+
it('should GET glossary download status URL', async () => {
254+
const result = await getGlossaryDownloadStatus({})
255+
256+
expect(mockGlossaryDownloadStatusUrl).toHaveBeenCalled()
257+
expect(mockGet).toHaveBeenCalledWith('/api/glossary/download/status', {
258+
method: 'GET',
259+
params: {}
260+
})
261+
expect(result).toEqual(mockResponse)
262+
})
263+
})
264+
166265
describe('getGlossaryImportTmpl', () => {
167266
it('should call _get with correct URL and params', async () => {
168267
const params = { type: 'glossary' }

dashboard/src/api/apiMethods/glossaryApiMethod.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import {
2727
glossaryImportUrl,
2828
glossaryTypeUrl,
2929
glossaryUrl,
30+
glossaryTermsUrl,
31+
glossaryCategoriesUrl,
32+
glossarySearchUrl,
33+
glossaryCreateFileUrl,
34+
glossaryDownloadStatusUrl,
3035
removeTermorCatgeoryUrl,
3136
removeTermUrl
3237
} from "../apiUrlLinks/glossaryUrl";
@@ -93,6 +98,48 @@ const getGlossaryType = (glossaryType: string, guid: string) => {
9398
return _get(glossaryTypeUrl(glossaryType, guid), config);
9499
};
95100

101+
const getGlossaryTermsForGlossary = (glossaryGuid: string) => {
102+
const config: configs = {
103+
method: "GET",
104+
params: { limit: -1, offset: 0, sort: "ASC" }
105+
};
106+
return _get(glossaryTermsUrl(glossaryGuid), config);
107+
};
108+
109+
const getGlossaryCategoriesForGlossary = (glossaryGuid: string) => {
110+
const config: configs = {
111+
method: "GET",
112+
params: { limit: -1, offset: 0, sort: "ASC" }
113+
};
114+
return _get(glossaryCategoriesUrl(glossaryGuid), config);
115+
};
116+
117+
const searchGlossary = (params: object) => {
118+
const config: importConfigs = {
119+
method: "POST",
120+
params: {},
121+
data: params
122+
};
123+
return _post(glossarySearchUrl(), config);
124+
};
125+
126+
const createGlossaryExportFile = (params: object) => {
127+
const config: importConfigs = {
128+
method: "POST",
129+
params: {},
130+
data: params
131+
};
132+
return _post(glossaryCreateFileUrl(), config);
133+
};
134+
135+
const getGlossaryDownloadStatus = (params: object) => {
136+
const config: configs = {
137+
method: "GET",
138+
params: params
139+
};
140+
return _get(glossaryDownloadStatusUrl(), config);
141+
};
142+
96143
const createGlossary = (params: any) => {
97144
const config: importConfigs = {
98145
method: "POST",
@@ -182,6 +229,11 @@ const removeTermorCategory = (
182229
export {
183230
removeTerm,
184231
getGlossary,
232+
getGlossaryTermsForGlossary,
233+
getGlossaryCategoriesForGlossary,
234+
searchGlossary,
235+
createGlossaryExportFile,
236+
getGlossaryDownloadStatus,
185237
getGlossaryImportTmpl,
186238
getGlossaryImport,
187239
getGlossaryType,

dashboard/src/api/apiUrlLinks/__tests__/glossaryUrl.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ import {
4747
assignTermtoEntitiesUrl,
4848
assignTermtoCategoryUrl,
4949
assignGlossaryTypeUrl,
50-
removeTermorCatgeoryUrl
50+
removeTermorCatgeoryUrl,
51+
glossaryTermsUrl,
52+
glossaryCategoriesUrl,
53+
glossarySearchUrl,
54+
glossaryCreateFileUrl,
55+
glossaryDownloadStatusUrl,
56+
glossaryDownloadFileUrl
5157
} from '../glossaryUrl'
5258
import { getBaseApiUrl } from '../commonApiUrl'
5359

@@ -289,6 +295,54 @@ describe('glossaryUrl', () => {
289295
})
290296
})
291297

298+
describe('glossaryTermsUrl', () => {
299+
it('should return terms URL for a glossary guid', () => {
300+
const result = glossaryTermsUrl('glossary-guid-1')
301+
expect(result).toBe(
302+
'/mock-base-url/api/atlas/v2/glossary/glossary-guid-1/terms'
303+
)
304+
})
305+
})
306+
307+
describe('glossaryCategoriesUrl', () => {
308+
it('should return categories URL for a glossary guid', () => {
309+
const result = glossaryCategoriesUrl('glossary-guid-2')
310+
expect(result).toBe(
311+
'/mock-base-url/api/atlas/v2/glossary/glossary-guid-2/categories'
312+
)
313+
})
314+
})
315+
316+
describe('glossarySearchUrl', () => {
317+
it('should return glossary search URL', () => {
318+
const result = glossarySearchUrl()
319+
expect(result).toBe('/mock-base-url/api/atlas/v2/glossary/search')
320+
})
321+
})
322+
323+
describe('glossaryCreateFileUrl', () => {
324+
it('should return glossary create_file URL', () => {
325+
const result = glossaryCreateFileUrl()
326+
expect(result).toBe('/mock-base-url/api/atlas/v2/glossary/create_file')
327+
})
328+
})
329+
330+
describe('glossaryDownloadStatusUrl', () => {
331+
it('should return glossary download status URL', () => {
332+
const result = glossaryDownloadStatusUrl()
333+
expect(result).toBe('/mock-base-url/api/atlas/v2/glossary/download/status')
334+
})
335+
})
336+
337+
describe('glossaryDownloadFileUrl', () => {
338+
it('should return glossary download file URL', () => {
339+
const result = glossaryDownloadFileUrl('glossary-export.csv')
340+
expect(result).toBe(
341+
'/mock-base-url/api/atlas/v2/glossary/download/glossary-export.csv'
342+
)
343+
})
344+
})
345+
292346
describe('removeTermorCatgeoryUrl', () => {
293347
it('should return correct URL for removing term', () => {
294348
const guid = 'term-guid-123'

dashboard/src/api/apiUrlLinks/glossaryUrl.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,30 @@ const glossaryTypeUrl = (glossaryType: string, guid: string) => {
4040
return `${glossaryUrl()}/${glossaryType}/${guid}`;
4141
};
4242

43+
const glossaryTermsUrl = (glossaryGuid: string) => {
44+
return `${glossaryUrl()}/${glossaryGuid}/terms`;
45+
};
46+
47+
const glossaryCategoriesUrl = (glossaryGuid: string) => {
48+
return `${glossaryUrl()}/${glossaryGuid}/categories`;
49+
};
50+
51+
const glossarySearchUrl = () => {
52+
return `${glossaryUrl()}/search`;
53+
};
54+
55+
const glossaryCreateFileUrl = () => {
56+
return `${glossaryUrl()}/create_file`;
57+
};
58+
59+
const glossaryDownloadStatusUrl = () => {
60+
return `${glossaryUrl()}/download/status`;
61+
};
62+
63+
const glossaryDownloadFileUrl = (fileName: string) => {
64+
return `${glossaryUrl()}/download/${fileName}`;
65+
};
66+
4367
const editGlossaryUrl = (guid: string) => {
4468
return `${glossaryUrl()}/${guid}`;
4569
};
@@ -83,6 +107,12 @@ export {
83107
glossaryImportTempUrl,
84108
glossaryImportUrl,
85109
glossaryTypeUrl,
110+
glossaryTermsUrl,
111+
glossaryCategoriesUrl,
112+
glossarySearchUrl,
113+
glossaryCreateFileUrl,
114+
glossaryDownloadStatusUrl,
115+
glossaryDownloadFileUrl,
86116
editGlossaryUrl,
87117
deleteGlossaryorTermUrl,
88118
createTermorCategoryUrl,

dashboard/src/components/ImportDialog.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ interface CustomModalProps {
6262
open: boolean;
6363
onClose: () => void;
6464
title: string;
65+
onImportSuccess?: () => void;
6566
}
6667

6768
export const ImportDialog: React.FC<CustomModalProps> = ({
6869
open,
6970
onClose,
70-
title
71+
title,
72+
onImportSuccess
7173
}) => {
7274
const [fileData, setFileData] = useState<any>([]);
7375
const [progress, setProgress] = useState(0);
@@ -106,6 +108,7 @@ export const ImportDialog: React.FC<CustomModalProps> = ({
106108
`File: ${fileData.name} imported successfully`
107109
);
108110

111+
onImportSuccess?.();
109112
onClose();
110113
setErrorDetails(false);
111114
setFileData([]);

0 commit comments

Comments
 (0)