mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-03 03:59:12 +00:00
chore(ui): glossary import optimization around files under BulkImportPage (#20991)
* glossary import optimization around files under BulkImportPage * fix unit test * fix the special character glossary breaking for import
This commit is contained in:
parent
1447767d0c
commit
b3d7f97590
@ -155,7 +155,7 @@ test.describe('Glossary Bulk Import Export', () => {
|
||||
|
||||
await page.click('[data-testid="manage-button"]');
|
||||
await page.click('[data-testid="import-button-description"]');
|
||||
const fileInput = await page.$('[type="file"]');
|
||||
const fileInput = page.getByTestId('upload-file-widget');
|
||||
await fileInput?.setInputFiles([
|
||||
'downloads/' + glossary1.data.displayName + '.csv',
|
||||
]);
|
||||
|
||||
@ -35,8 +35,6 @@ const EntityImportRouter = () => {
|
||||
setIsLoading(true);
|
||||
const entityPermission = await getEntityPermissionByFqn(entityType, fqn);
|
||||
setEntityPermission(entityPermission);
|
||||
} catch (error) {
|
||||
// will not show logs
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ import { EntityType } from '../../enums/entity.enum';
|
||||
import { useFqn } from '../../hooks/useFqn';
|
||||
import { getBulkEditCSVExportEntityApi } from '../../utils/EntityBulkEdit/EntityBulkEditUtils';
|
||||
import entityUtilClassBase from '../../utils/EntityUtilClassBase';
|
||||
import { getEncodedFqn } from '../../utils/StringsUtils';
|
||||
import Banner from '../common/Banner/Banner';
|
||||
import { ImportStatus } from '../common/EntityImport/ImportStatus/ImportStatus.component';
|
||||
import Loader from '../common/Loader/Loader';
|
||||
@ -64,7 +63,7 @@ const BulkEditEntity = ({
|
||||
|
||||
useEffect(() => {
|
||||
triggerExportForBulkEdit({
|
||||
name: getEncodedFqn(fqn),
|
||||
name: fqn,
|
||||
onExport: getBulkEditCSVExportEntityApi(entityType),
|
||||
exportTypes: [ExportTypes.CSV],
|
||||
});
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
} from '@inovua/reactdatagrid-community/types';
|
||||
import { VALIDATION_STEP } from '../../constants/BulkImport.constant';
|
||||
import { CSVImportResult } from '../../generated/type/csvImportResult';
|
||||
import { CSVImportJobType } from '../BulkImport/BulkEntityImport.interface';
|
||||
import { CSVImportJobType } from '../../pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.interface';
|
||||
import { TitleBreadcrumbProps } from '../common/TitleBreadcrumb/TitleBreadcrumb.interface';
|
||||
|
||||
export interface BulkEditEntityProps {
|
||||
|
||||
@ -1,544 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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 ReactDataGrid from '@inovua/reactdatagrid-community';
|
||||
import '@inovua/reactdatagrid-community/index.css';
|
||||
import {
|
||||
TypeColumn,
|
||||
TypeComputedProps,
|
||||
} from '@inovua/reactdatagrid-community/types';
|
||||
import { Button, Card, Col, Row, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, {
|
||||
MutableRefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePapaParse } from 'react-papaparse';
|
||||
|
||||
import { capitalize } from 'lodash';
|
||||
import {
|
||||
ENTITY_IMPORT_STEPS,
|
||||
VALIDATION_STEP,
|
||||
} from '../../constants/BulkImport.constant';
|
||||
import { SOCKET_EVENTS } from '../../constants/constants';
|
||||
import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocketProvider';
|
||||
import { CSVImportResult } from '../../generated/type/csvImportResult';
|
||||
import {
|
||||
getCSVStringFromColumnsAndDataSource,
|
||||
getEntityColumnsAndDataSourceFromCSV,
|
||||
} from '../../utils/CSV/CSV.utils';
|
||||
import csvUtilsClassBase from '../../utils/CSV/CSVUtilsClassBase';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import Banner from '../common/Banner/Banner';
|
||||
import { ImportStatus } from '../common/EntityImport/ImportStatus/ImportStatus.component';
|
||||
import Stepper from '../Settings/Services/Ingestion/IngestionStepper/IngestionStepper.component';
|
||||
import { UploadFile } from '../UploadFile/UploadFile';
|
||||
import './bulk-entity-import.style.less';
|
||||
import {
|
||||
BulkImportProps,
|
||||
CSVImportAsyncWebsocketResponse,
|
||||
CSVImportJobType,
|
||||
} from './BulkEntityImport.interface';
|
||||
|
||||
let inEdit = false;
|
||||
|
||||
const BulkEntityImport = ({
|
||||
entityType,
|
||||
fqn,
|
||||
onValidateCsvString,
|
||||
onSuccess,
|
||||
}: BulkImportProps) => {
|
||||
const { socket } = useWebSocketConnector();
|
||||
const [activeAsyncImportJob, setActiveAsyncImportJob] =
|
||||
useState<CSVImportJobType>();
|
||||
const activeAsyncImportJobRef = useRef<CSVImportJobType>();
|
||||
|
||||
const [activeStep, setActiveStep] = useState<VALIDATION_STEP>(
|
||||
VALIDATION_STEP.UPLOAD
|
||||
);
|
||||
|
||||
const activeStepRef = useRef<VALIDATION_STEP>(VALIDATION_STEP.UPLOAD);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [validationData, setValidationData] = useState<CSVImportResult>();
|
||||
const [columns, setColumns] = useState<TypeColumn[]>([]);
|
||||
const [dataSource, setDataSource] = useState<Record<string, string>[]>([]);
|
||||
const { readString } = usePapaParse();
|
||||
const [validateCSVData, setValidateCSVData] =
|
||||
useState<{ columns: TypeColumn[]; dataSource: Record<string, string>[] }>();
|
||||
const [gridRef, setGridRef] = useState<
|
||||
MutableRefObject<TypeComputedProps | null>
|
||||
>({ current: null });
|
||||
|
||||
const filterColumns = useMemo(
|
||||
() =>
|
||||
columns?.filter(
|
||||
(col) =>
|
||||
!csvUtilsClassBase.hideImportsColumnList().includes(col.name ?? '')
|
||||
),
|
||||
[columns]
|
||||
);
|
||||
|
||||
const focusToGrid = useCallback(() => {
|
||||
setGridRef((ref) => {
|
||||
ref.current?.focus();
|
||||
|
||||
return ref;
|
||||
});
|
||||
}, [setGridRef]);
|
||||
|
||||
const handleActiveStepChange = useCallback(
|
||||
(step: VALIDATION_STEP) => {
|
||||
setActiveStep(step);
|
||||
activeStepRef.current = step;
|
||||
},
|
||||
[setActiveStep, activeStepRef]
|
||||
);
|
||||
|
||||
const onCSVReadComplete = useCallback(
|
||||
(results: { data: string[][] }) => {
|
||||
// results.data is returning data with unknown type
|
||||
const { columns, dataSource } = getEntityColumnsAndDataSourceFromCSV(
|
||||
results.data as string[][],
|
||||
entityType
|
||||
);
|
||||
setDataSource(dataSource);
|
||||
setColumns(columns);
|
||||
|
||||
handleActiveStepChange(VALIDATION_STEP.EDIT_VALIDATE);
|
||||
setTimeout(focusToGrid, 500);
|
||||
},
|
||||
[entityType, setDataSource, setColumns, handleActiveStepChange, focusToGrid]
|
||||
);
|
||||
|
||||
const handleLoadData = useCallback(
|
||||
async (e: ProgressEvent<FileReader>) => {
|
||||
try {
|
||||
const result = e.target?.result as string;
|
||||
|
||||
const validationResponse = await onValidateCsvString(result, true);
|
||||
const jobData: CSVImportJobType = {
|
||||
...validationResponse,
|
||||
type: 'initialLoad',
|
||||
initialResult: result,
|
||||
};
|
||||
|
||||
setActiveAsyncImportJob(jobData);
|
||||
activeAsyncImportJobRef.current = jobData;
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
},
|
||||
[onCSVReadComplete]
|
||||
);
|
||||
|
||||
const onEditComplete = useCallback(
|
||||
({ value, columnId, rowId }) => {
|
||||
const data = [...dataSource];
|
||||
data[rowId][columnId] = value;
|
||||
|
||||
setDataSource(data);
|
||||
},
|
||||
[dataSource]
|
||||
);
|
||||
|
||||
const handleBack = () => {
|
||||
if (activeStep === VALIDATION_STEP.UPDATE) {
|
||||
handleActiveStepChange(VALIDATION_STEP.EDIT_VALIDATE);
|
||||
} else {
|
||||
handleActiveStepChange(VALIDATION_STEP.UPLOAD);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValidate = async () => {
|
||||
setIsValidating(true);
|
||||
setValidateCSVData(undefined);
|
||||
try {
|
||||
// Call the validate API
|
||||
const csvData = getCSVStringFromColumnsAndDataSource(columns, dataSource);
|
||||
|
||||
const response = await onValidateCsvString(
|
||||
csvData,
|
||||
activeStep === VALIDATION_STEP.EDIT_VALIDATE
|
||||
);
|
||||
|
||||
const jobData: CSVImportJobType = {
|
||||
...response,
|
||||
type: 'onValidate',
|
||||
};
|
||||
|
||||
setActiveAsyncImportJob(jobData);
|
||||
activeAsyncImportJobRef.current = jobData;
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
setIsValidating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onEditStart = () => {
|
||||
inEdit = true;
|
||||
};
|
||||
|
||||
const onEditStop = () => {
|
||||
requestAnimationFrame(() => {
|
||||
inEdit = false;
|
||||
gridRef.current?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (inEdit) {
|
||||
if (event.key === 'Escape') {
|
||||
const [rowIndex, colIndex] = gridRef.current?.computedActiveCell ?? [
|
||||
0, 0,
|
||||
];
|
||||
const column = gridRef.current?.getColumnBy(colIndex);
|
||||
|
||||
gridRef.current?.cancelEdit?.({
|
||||
rowIndex,
|
||||
columnId: column?.name ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
const grid = gridRef.current;
|
||||
if (!grid) {
|
||||
return;
|
||||
}
|
||||
let [rowIndex, colIndex] = grid.computedActiveCell ?? [0, 0];
|
||||
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
const column = grid.getColumnBy(colIndex);
|
||||
grid.startEdit?.({ columnId: column.name ?? '', rowIndex });
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
if (event.key !== 'Tab') {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const direction = event.shiftKey ? -1 : 1;
|
||||
|
||||
const columns = grid.visibleColumns;
|
||||
const rowCount = grid.count;
|
||||
|
||||
colIndex += direction;
|
||||
if (colIndex === -1) {
|
||||
colIndex = columns.length - 1;
|
||||
rowIndex -= 1;
|
||||
}
|
||||
if (colIndex === columns.length) {
|
||||
rowIndex += 1;
|
||||
colIndex = 0;
|
||||
}
|
||||
if (rowIndex < 0 || rowIndex === rowCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid?.setActiveCell([rowIndex, colIndex]);
|
||||
};
|
||||
|
||||
const handleAddRow = useCallback(() => {
|
||||
setDataSource((data) => {
|
||||
setTimeout(() => {
|
||||
gridRef.current?.scrollToId(data.length + '');
|
||||
gridRef.current?.focus();
|
||||
}, 1);
|
||||
|
||||
return [...data, { id: data.length + '' }];
|
||||
});
|
||||
}, [gridRef]);
|
||||
|
||||
const handleRetryCsvUpload = () => {
|
||||
setValidationData(undefined);
|
||||
|
||||
handleActiveStepChange(VALIDATION_STEP.UPLOAD);
|
||||
};
|
||||
|
||||
const handleResetImportJob = useCallback(() => {
|
||||
setActiveAsyncImportJob(undefined);
|
||||
activeAsyncImportJobRef.current = undefined;
|
||||
}, [setActiveAsyncImportJob, activeAsyncImportJobRef]);
|
||||
|
||||
const handleImportWebsocketResponseWithActiveStep = useCallback(
|
||||
(importResults: CSVImportResult) => {
|
||||
const activeStep = activeStepRef.current;
|
||||
|
||||
if (activeStep === VALIDATION_STEP.UPDATE) {
|
||||
if (importResults?.status === 'failure') {
|
||||
setValidationData(importResults);
|
||||
readString(importResults?.importResultsCsv ?? '', {
|
||||
worker: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (results) => {
|
||||
// results.data is returning data with unknown type
|
||||
setValidateCSVData(
|
||||
getEntityColumnsAndDataSourceFromCSV(
|
||||
results.data as string[][],
|
||||
entityType
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
handleActiveStepChange(VALIDATION_STEP.UPDATE);
|
||||
setIsValidating(false);
|
||||
} else {
|
||||
showSuccessToast(
|
||||
t('message.entity-details-updated', {
|
||||
entityType: capitalize(entityType),
|
||||
fqn,
|
||||
})
|
||||
);
|
||||
onSuccess();
|
||||
handleResetImportJob();
|
||||
setIsValidating(false);
|
||||
}
|
||||
} else if (activeStep === VALIDATION_STEP.EDIT_VALIDATE) {
|
||||
setValidationData(importResults);
|
||||
handleActiveStepChange(VALIDATION_STEP.UPDATE);
|
||||
readString(importResults?.importResultsCsv ?? '', {
|
||||
worker: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (results) => {
|
||||
// results.data is returning data with unknown type
|
||||
setValidateCSVData(
|
||||
getEntityColumnsAndDataSourceFromCSV(
|
||||
results.data as string[][],
|
||||
entityType
|
||||
)
|
||||
);
|
||||
},
|
||||
});
|
||||
handleResetImportJob();
|
||||
setIsValidating(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
activeStepRef,
|
||||
entityType,
|
||||
fqn,
|
||||
onSuccess,
|
||||
handleResetImportJob,
|
||||
handleActiveStepChange,
|
||||
]
|
||||
);
|
||||
|
||||
const handleImportWebsocketResponse = useCallback(
|
||||
(websocketResponse: CSVImportAsyncWebsocketResponse) => {
|
||||
if (!websocketResponse.jobId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeImportJob = activeAsyncImportJobRef.current;
|
||||
|
||||
if (websocketResponse.jobId === activeImportJob?.jobId) {
|
||||
setActiveAsyncImportJob((job) => {
|
||||
if (!job) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
...job,
|
||||
...websocketResponse,
|
||||
};
|
||||
});
|
||||
|
||||
if (websocketResponse.status === 'COMPLETED') {
|
||||
const importResults = websocketResponse.result;
|
||||
|
||||
// If the job is complete and the status is either failure or aborted
|
||||
// then reset the validation data and active step
|
||||
if (['failure', 'aborted'].includes(importResults?.status ?? '')) {
|
||||
setValidationData(importResults);
|
||||
|
||||
handleActiveStepChange(VALIDATION_STEP.UPLOAD);
|
||||
|
||||
handleResetImportJob();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If the job is complete and the status is success
|
||||
// and job was for initial load then check if the initial result is available
|
||||
// and then read the initial result
|
||||
if (
|
||||
activeImportJob.type === 'initialLoad' &&
|
||||
activeImportJob.initialResult
|
||||
) {
|
||||
readString(activeImportJob.initialResult, {
|
||||
worker: true,
|
||||
skipEmptyLines: true,
|
||||
complete: onCSVReadComplete,
|
||||
});
|
||||
|
||||
handleResetImportJob();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handleImportWebsocketResponseWithActiveStep(importResults);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
activeStepRef,
|
||||
activeAsyncImportJobRef,
|
||||
onCSVReadComplete,
|
||||
setActiveAsyncImportJob,
|
||||
handleResetImportJob,
|
||||
handleActiveStepChange,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (socket) {
|
||||
socket.on(SOCKET_EVENTS.CSV_IMPORT_CHANNEL, (importResponse) => {
|
||||
if (importResponse) {
|
||||
const importResponseData = JSON.parse(
|
||||
importResponse
|
||||
) as CSVImportAsyncWebsocketResponse;
|
||||
|
||||
handleImportWebsocketResponse(importResponseData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket && socket.off(SOCKET_EVENTS.CSV_IMPORT_CHANNEL);
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Stepper activeStep={activeStep} steps={ENTITY_IMPORT_STEPS} />
|
||||
</Col>
|
||||
{activeAsyncImportJob?.jobId && (
|
||||
<Col span={24}>
|
||||
<Banner
|
||||
className="border-radius"
|
||||
isLoading={!activeAsyncImportJob.error}
|
||||
message={
|
||||
activeAsyncImportJob.error ?? activeAsyncImportJob.message ?? ''
|
||||
}
|
||||
type={activeAsyncImportJob.error ? 'error' : 'success'}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
<Col span={24}>
|
||||
{activeStep === 0 && (
|
||||
<>
|
||||
{validationData?.abortReason ? (
|
||||
<Card className="m-t-lg">
|
||||
<Space
|
||||
align="center"
|
||||
className="w-full justify-center p-lg text-center"
|
||||
direction="vertical"
|
||||
size={16}>
|
||||
<Typography.Text
|
||||
className="text-center"
|
||||
data-testid="abort-reason">
|
||||
<strong className="d-block">{t('label.aborted')}</strong>{' '}
|
||||
{validationData.abortReason}
|
||||
</Typography.Text>
|
||||
<Space size={16}>
|
||||
<Button
|
||||
ghost
|
||||
data-testid="cancel-button"
|
||||
type="primary"
|
||||
onClick={handleRetryCsvUpload}>
|
||||
{t('label.back')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</Card>
|
||||
) : (
|
||||
<UploadFile fileType=".csv" onCSVUploaded={handleLoadData} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeStep === 1 && (
|
||||
<ReactDataGrid
|
||||
editable
|
||||
columns={filterColumns}
|
||||
dataSource={dataSource}
|
||||
defaultActiveCell={[0, 0]}
|
||||
handle={setGridRef}
|
||||
idProperty="id"
|
||||
loading={isValidating}
|
||||
minRowHeight={30}
|
||||
showZebraRows={false}
|
||||
style={{ height: 'calc(100vh - 245px)' }}
|
||||
onEditComplete={onEditComplete}
|
||||
onEditStart={onEditStart}
|
||||
onEditStop={onEditStop}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
)}
|
||||
{activeStep === 2 && validationData && (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<ImportStatus csvImportResult={validationData} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{validateCSVData && (
|
||||
<ReactDataGrid
|
||||
idProperty="id"
|
||||
loading={isValidating}
|
||||
style={{ height: 'calc(100vh - 300px)' }}
|
||||
{...validateCSVData}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
{activeStep > 0 && (
|
||||
<Col span={24}>
|
||||
{activeStep === 1 && (
|
||||
<Button data-testid="add-row-btn" onClick={handleAddRow}>
|
||||
{`+ ${t('label.add-row')}`}
|
||||
</Button>
|
||||
)}
|
||||
<div className="float-right import-footer">
|
||||
{activeStep > 0 && (
|
||||
<Button disabled={isValidating} onClick={handleBack}>
|
||||
{t('label.previous')}
|
||||
</Button>
|
||||
)}
|
||||
{activeStep < 3 && (
|
||||
<Button
|
||||
className="m-l-sm"
|
||||
disabled={isValidating}
|
||||
type="primary"
|
||||
onClick={handleValidate}>
|
||||
{activeStep === 2 ? t('label.update') : t('label.next')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkEntityImport;
|
||||
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
.reserve-right-sidebar {
|
||||
.import-footer {
|
||||
// Right side padding 20 + 64 width of sidebar
|
||||
padding-right: 84px;
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,7 @@ import { DE_ACTIVE_COLOR } from '../../../constants/constants';
|
||||
import { ExportTypes } from '../../../constants/Export.constants';
|
||||
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface';
|
||||
import { EntityAction, EntityType } from '../../../enums/entity.enum';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { Glossary } from '../../../generated/entity/data/glossary';
|
||||
import {
|
||||
GlossaryTerm,
|
||||
@ -58,12 +58,14 @@ import {
|
||||
patchGlossaryTerm,
|
||||
} from '../../../rest/glossaryAPI';
|
||||
import { getEntityDeleteMessage } from '../../../utils/CommonUtils';
|
||||
import { getEntityVoteStatus } from '../../../utils/EntityUtils';
|
||||
import {
|
||||
getEntityImportPath,
|
||||
getEntityVoteStatus,
|
||||
} from '../../../utils/EntityUtils';
|
||||
import Fqn from '../../../utils/Fqn';
|
||||
import { checkPermission } from '../../../utils/PermissionsUtils';
|
||||
import {
|
||||
getGlossaryPath,
|
||||
getGlossaryPathWithAction,
|
||||
getGlossaryTermsVersionsPath,
|
||||
getGlossaryVersionsPath,
|
||||
} from '../../../utils/RouterUtils';
|
||||
@ -211,12 +213,7 @@ const GlossaryHeader = ({
|
||||
}, [fqn]);
|
||||
|
||||
const handleGlossaryImport = () =>
|
||||
history.push(
|
||||
getGlossaryPathWithAction(
|
||||
selectedData.fullyQualifiedName ?? '',
|
||||
EntityAction.IMPORT
|
||||
)
|
||||
);
|
||||
history.push(getEntityImportPath(EntityType.GLOSSARY_TERM, fqn));
|
||||
|
||||
const handleVersionClick = async () => {
|
||||
let path: string;
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
import { AxiosError } from 'axios';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { cloneDeep, isEmpty } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { withActivityFeed } from '../../components/AppRouter/withActivityFeed';
|
||||
@ -49,7 +49,6 @@ import GlossaryTermModal from './GlossaryTermModal/GlossaryTermModal.component';
|
||||
import GlossaryTermsV1 from './GlossaryTerms/GlossaryTermsV1.component';
|
||||
import { GlossaryV1Props } from './GlossaryV1.interfaces';
|
||||
import './glossaryV1.less';
|
||||
import ImportGlossary from './ImportGlossary/ImportGlossary';
|
||||
import { ModifiedGlossary, useGlossaryStore } from './useGlossary.store';
|
||||
|
||||
const GlossaryV1 = ({
|
||||
@ -101,11 +100,6 @@ const GlossaryV1 = ({
|
||||
|
||||
const { id, fullyQualifiedName } = activeGlossary ?? {};
|
||||
|
||||
const isImportAction = useMemo(
|
||||
() => action === EntityAction.IMPORT,
|
||||
[action]
|
||||
);
|
||||
|
||||
const fetchGlossaryTerm = async (
|
||||
params?: ListGlossaryTermsParams,
|
||||
refresh?: boolean
|
||||
@ -336,9 +330,7 @@ const GlossaryV1 = ({
|
||||
setIsTabExpanded(!isTabExpanded);
|
||||
};
|
||||
|
||||
return isImportAction ? (
|
||||
<ImportGlossary glossaryName={selectedData.fullyQualifiedName ?? ''} />
|
||||
) : (
|
||||
return (
|
||||
<>
|
||||
{(isLoading || isPermissionLoading) && <Loader />}
|
||||
|
||||
|
||||
@ -11,13 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
act,
|
||||
findByText,
|
||||
getByTestId,
|
||||
queryByText,
|
||||
render,
|
||||
} from '@testing-library/react';
|
||||
import { findByText, queryByText, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
mockedGlossaries,
|
||||
@ -26,7 +20,7 @@ import {
|
||||
import GlossaryV1 from './GlossaryV1.component';
|
||||
import { GlossaryV1Props } from './GlossaryV1.interfaces';
|
||||
|
||||
let params = {
|
||||
const params = {
|
||||
glossaryName: 'GlossaryName',
|
||||
action: '',
|
||||
};
|
||||
@ -115,12 +109,6 @@ jest.mock('../ActivityFeed/FeedEditor/FeedEditor', () => {
|
||||
return jest.fn().mockReturnValue(<p>FeedEditor</p>);
|
||||
});
|
||||
|
||||
jest.mock('./ImportGlossary/ImportGlossary', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(<div data-testid="import-glossary">ImportGlossary</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../components/AppRouter/withActivityFeed', () => ({
|
||||
withActivityFeed: jest.fn().mockImplementation((component) => component),
|
||||
}));
|
||||
@ -185,16 +173,4 @@ describe('Test Glossary component', () => {
|
||||
expect(glossaryTerm).toBeInTheDocument();
|
||||
expect(glossaryDetails).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render import glossary component', async () => {
|
||||
params = { ...params, action: 'import' };
|
||||
|
||||
await act(async () => {
|
||||
const { container } = render(<GlossaryV1 {...mockProps} />);
|
||||
|
||||
const importGlossary = getByTestId(container, 'import-glossary');
|
||||
|
||||
expect(importGlossary).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { CSVImportResult } from '../../../generated/type/csvImportResult';
|
||||
import { importGlossaryInCSVFormat } from '../../../rest/glossaryAPI';
|
||||
import ImportGlossary from './ImportGlossary';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
const glossaryName = 'Glossary1';
|
||||
const mockCsvImportResult = {
|
||||
dryRun: true,
|
||||
status: 'success',
|
||||
numberOfRowsProcessed: 3,
|
||||
numberOfRowsPassed: 3,
|
||||
numberOfRowsFailed: 0,
|
||||
importResultsCsv: `status,details,parent,name*,displayName,description,synonyms,relatedTerms,references,tags\r
|
||||
success,Entity created,,Glossary2 Term,Glossary2 Term displayName,Description for Glossary2 Term,,,,\r
|
||||
success,Entity created,,Glossary2 term2,Glossary2 term2,Description data.,,,,\r`,
|
||||
} as CSVImportResult;
|
||||
|
||||
jest.mock('../../common/TitleBreadcrumb/TitleBreadcrumb.component', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="breadcrumb">Breadcrumb</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../common/Loader/Loader', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="loader">Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../PageLayoutV1/PageLayoutV1', () => {
|
||||
return jest.fn().mockImplementation(({ children }) => <p>{children}</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../BulkImport/BulkEntityImport.component', () =>
|
||||
jest.fn().mockImplementation(({ onSuccess, onValidateCsvString }) => (
|
||||
<div>
|
||||
<button onClick={onSuccess}>SuccessButton</button>
|
||||
<button
|
||||
onClick={() => onValidateCsvString('markdown: This is test', true)}>
|
||||
ValidateCsvButton
|
||||
</button>
|
||||
<p>BulkEntityImport</p>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../../../rest/glossaryAPI', () => ({
|
||||
importGlossaryInCSVFormat: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockCsvImportResult)),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/RouterUtils', () => ({
|
||||
getGlossaryPath: jest.fn().mockImplementation((fqn) => `/glossary/${fqn}`),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../common/EntityImport/EntityImport.component', () => ({
|
||||
EntityImport: jest.fn().mockImplementation(({ children, onImport }) => {
|
||||
return (
|
||||
<div data-testid="entity-import">
|
||||
{children}{' '}
|
||||
<button data-testid="import" onClick={onImport}>
|
||||
import
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Import Glossary', () => {
|
||||
it('Should render the all components', async () => {
|
||||
render(<ImportGlossary glossaryName={glossaryName} />);
|
||||
|
||||
expect(await screen.findByTestId('breadcrumb')).toBeInTheDocument();
|
||||
expect(screen.getByText('BulkEntityImport')).toBeInTheDocument();
|
||||
expect(screen.getByText('SuccessButton')).toBeInTheDocument();
|
||||
expect(screen.getByText('ValidateCsvButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should redirect the page when onSuccess get triggered', async () => {
|
||||
render(<ImportGlossary glossaryName={glossaryName} />);
|
||||
|
||||
const successButton = screen.getByText('SuccessButton');
|
||||
await act(async () => {
|
||||
fireEvent.click(successButton);
|
||||
});
|
||||
|
||||
expect(mockPush).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the importGlossaryInCSVFormat api when validate props is trigger', async () => {
|
||||
render(<ImportGlossary glossaryName={glossaryName} />);
|
||||
|
||||
const successButton = screen.getByText('ValidateCsvButton');
|
||||
await act(async () => {
|
||||
fireEvent.click(successButton);
|
||||
});
|
||||
|
||||
expect(importGlossaryInCSVFormat).toHaveBeenCalledWith(
|
||||
'Glossary1',
|
||||
'markdown: This is test',
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Col, Row } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { importGlossaryInCSVFormat } from '../../../rest/glossaryAPI';
|
||||
import { getGlossaryPath } from '../../../utils/RouterUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import BulkEntityImport from '../../BulkImport/BulkEntityImport.component';
|
||||
import TitleBreadcrumb from '../../common/TitleBreadcrumb/TitleBreadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from '../../common/TitleBreadcrumb/TitleBreadcrumb.interface';
|
||||
import PageLayoutV1 from '../../PageLayoutV1/PageLayoutV1';
|
||||
import './import-glossary.less';
|
||||
|
||||
interface Props {
|
||||
glossaryName: string;
|
||||
}
|
||||
|
||||
const ImportGlossary: FC<Props> = ({ glossaryName }) => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
const breadcrumbList: TitleBreadcrumbProps['titleLinks'] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: t('label.glossary-plural'),
|
||||
url: getGlossaryPath(),
|
||||
activeTitle: false,
|
||||
},
|
||||
{
|
||||
name: glossaryName,
|
||||
url: getGlossaryPath(glossaryName),
|
||||
},
|
||||
],
|
||||
[glossaryName]
|
||||
);
|
||||
|
||||
const handleGlossaryRedirection = () => {
|
||||
history.push(getGlossaryPath(glossaryName));
|
||||
};
|
||||
|
||||
const handleImportCsv = async (data: string, dryRun = true) => {
|
||||
try {
|
||||
const response = await importGlossaryInCSVFormat(
|
||||
glossaryName,
|
||||
data,
|
||||
dryRun
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayoutV1
|
||||
pageTitle={t('label.import-entity', {
|
||||
entity: t('label.glossary-term-plural'),
|
||||
})}>
|
||||
<Row className="import-glossary p-x-lg" gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<TitleBreadcrumb titleLinks={breadcrumbList} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<BulkEntityImport
|
||||
entityType={EntityType.GLOSSARY_TERM}
|
||||
fqn={glossaryName}
|
||||
onSuccess={handleGlossaryRedirection}
|
||||
onValidateCsvString={handleImportCsv}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</PageLayoutV1>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportGlossary;
|
||||
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
@bg-white: #ffffff;
|
||||
@border-color: #6b728066;
|
||||
@check-mark-color: #28a744;
|
||||
@fail-color: #cb2531;
|
||||
|
||||
.ant-upload.file-dragger-wrapper {
|
||||
border-color: @border-color;
|
||||
border-width: 2px;
|
||||
&:not(.ant-upload-disabled) {
|
||||
&:hover {
|
||||
border-color: @border-color;
|
||||
}
|
||||
}
|
||||
background: @bg-white;
|
||||
height: 360px;
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.glossary-preview-footer {
|
||||
box-shadow: 1px -2px 4px rgba(0, 0, 0, 0.12);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.import-glossary {
|
||||
.browse-text {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
.passed-row {
|
||||
color: @check-mark-color;
|
||||
}
|
||||
|
||||
.failed-row {
|
||||
color: @fail-color;
|
||||
}
|
||||
@ -29,11 +29,11 @@ import {
|
||||
CSVImportResult,
|
||||
Status,
|
||||
} from '../../../generated/type/csvImportResult';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import {
|
||||
CSVImportAsyncWebsocketResponse,
|
||||
CSVImportJobType,
|
||||
} from '../../BulkImport/BulkEntityImport.interface';
|
||||
} from '../../../pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.interface';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import Stepper from '../../Settings/Services/Ingestion/IngestionStepper/IngestionStepper.component';
|
||||
import { UploadFile } from '../../UploadFile/UploadFile';
|
||||
import Banner from '../Banner/Banner';
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { CSVImportResult } from '../../../generated/type/csvImportResult';
|
||||
import { CSVImportAsyncResponse } from '../../BulkImport/BulkEntityImport.interface';
|
||||
import { CSVImportAsyncResponse } from '../../../pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.interface';
|
||||
|
||||
export interface EntityImportProps {
|
||||
entityName: string;
|
||||
|
||||
@ -20,6 +20,7 @@ export const SUPPORTED_BULK_IMPORT_EDIT_ENTITY = [
|
||||
ResourceEntity.DATABASE_SERVICE,
|
||||
ResourceEntity.DATABASE,
|
||||
ResourceEntity.DATABASE_SCHEMA,
|
||||
ResourceEntity.GLOSSARY_TERM,
|
||||
];
|
||||
|
||||
export enum VALIDATION_STEP {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 Collate.
|
||||
* Copyright 2025 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
|
||||
@ -10,18 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { CSVImportResult } from '../../generated/type/csvImportResult';
|
||||
|
||||
export interface BulkImportProps {
|
||||
entityType: EntityType;
|
||||
fqn: string;
|
||||
onValidateCsvString: (
|
||||
data: string,
|
||||
dryRun?: boolean
|
||||
) => Promise<CSVImportAsyncResponse | undefined>;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
import { CSVImportResult } from '../../../generated/type/csvImportResult';
|
||||
|
||||
export type CSVImportAsyncResponse = {
|
||||
jobId: string;
|
||||
@ -18,7 +18,7 @@ import {
|
||||
} from '@inovua/reactdatagrid-community/types';
|
||||
import { Button, Card, Col, Row, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { capitalize, isEmpty } from 'lodash';
|
||||
import React, {
|
||||
MutableRefObject,
|
||||
useCallback,
|
||||
@ -31,10 +31,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { usePapaParse } from 'react-papaparse';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import BulkEditEntity from '../../../components/BulkEditEntity/BulkEditEntity.component';
|
||||
import {
|
||||
CSVImportAsyncWebsocketResponse,
|
||||
CSVImportJobType,
|
||||
} from '../../../components/BulkImport/BulkEntityImport.interface';
|
||||
import Banner from '../../../components/common/Banner/Banner';
|
||||
import { ImportStatus } from '../../../components/common/EntityImport/ImportStatus/ImportStatus.component';
|
||||
import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
|
||||
@ -52,23 +48,25 @@ import { useWebSocketConnector } from '../../../context/WebSocketProvider/WebSoc
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { CSVImportResult } from '../../../generated/type/csvImportResult';
|
||||
import { useFqn } from '../../../hooks/useFqn';
|
||||
import {
|
||||
importEntityInCSVFormat,
|
||||
importServiceInCSVFormat,
|
||||
} from '../../../rest/importExportAPI';
|
||||
import {
|
||||
getCSVStringFromColumnsAndDataSource,
|
||||
getEntityColumnsAndDataSourceFromCSV,
|
||||
} from '../../../utils/CSV/CSV.utils';
|
||||
import csvUtilsClassBase from '../../../utils/CSV/CSVUtilsClassBase';
|
||||
import { isBulkEditRoute } from '../../../utils/EntityBulkEdit/EntityBulkEditUtils';
|
||||
import {
|
||||
getBulkEntityBreadcrumbList,
|
||||
getImportedEntityType,
|
||||
getImportValidateAPIEntityType,
|
||||
validateCsvString,
|
||||
} from '../../../utils/EntityImport/EntityImportUtils';
|
||||
import entityUtilClassBase from '../../../utils/EntityUtilClassBase';
|
||||
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
|
||||
import './bulk-entity-import-page.less';
|
||||
import {
|
||||
CSVImportAsyncWebsocketResponse,
|
||||
CSVImportJobType,
|
||||
} from './BulkEntityImportPage.interface';
|
||||
|
||||
let inEdit = false;
|
||||
|
||||
@ -100,6 +98,15 @@ const BulkEntityImportPage = () => {
|
||||
>({ current: null });
|
||||
const [entity, setEntity] = useState<DataAssetsHeaderProps['dataAsset']>();
|
||||
|
||||
const filterColumns = useMemo(
|
||||
() =>
|
||||
columns?.filter(
|
||||
(col) =>
|
||||
!csvUtilsClassBase.hideImportsColumnList().includes(col.name ?? '')
|
||||
),
|
||||
[columns]
|
||||
);
|
||||
|
||||
const fetchEntityData = useCallback(async () => {
|
||||
try {
|
||||
const response = await entityUtilClassBase.getEntityByFqn(
|
||||
@ -107,7 +114,7 @@ const BulkEntityImportPage = () => {
|
||||
fqn
|
||||
);
|
||||
setEntity(response);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// not show error here
|
||||
}
|
||||
}, [entityType, fqn]);
|
||||
@ -216,10 +223,7 @@ const BulkEntityImportPage = () => {
|
||||
// Call the validate API
|
||||
const csvData = getCSVStringFromColumnsAndDataSource(columns, dataSource);
|
||||
|
||||
const api =
|
||||
entityType === EntityType.DATABASE_SERVICE
|
||||
? importServiceInCSVFormat
|
||||
: importEntityInCSVFormat;
|
||||
const api = getImportValidateAPIEntityType(entityType);
|
||||
|
||||
const response = await api({
|
||||
entityType,
|
||||
@ -351,7 +355,7 @@ const BulkEntityImportPage = () => {
|
||||
} else {
|
||||
showSuccessToast(
|
||||
t('message.entity-details-updated', {
|
||||
entityType,
|
||||
entityType: capitalize(entityType),
|
||||
fqn,
|
||||
})
|
||||
);
|
||||
@ -572,7 +576,7 @@ const BulkEntityImportPage = () => {
|
||||
{activeStep === 1 && (
|
||||
<ReactDataGrid
|
||||
editable
|
||||
columns={columns}
|
||||
columns={filterColumns}
|
||||
dataSource={dataSource}
|
||||
defaultActiveCell={[0, 0]}
|
||||
handle={setGridRef}
|
||||
|
||||
@ -256,10 +256,12 @@ export const exportDatabaseDetailsInCSV = async (
|
||||
recursive?: boolean;
|
||||
}
|
||||
) => {
|
||||
// FQN should be encoded already and we should not encode the fqn here to avoid double encoding
|
||||
const res = await APIClient.get(`databases/name/${fqn}/exportAsync`, {
|
||||
params,
|
||||
});
|
||||
const res = await APIClient.get(
|
||||
`databases/name/${getEncodedFqn(fqn)}/exportAsync`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
@ -287,10 +289,12 @@ export const exportDatabaseSchemaDetailsInCSV = async (
|
||||
recursive?: boolean;
|
||||
}
|
||||
) => {
|
||||
// FQN should be encoded already and we should not encode the fqn here to avoid double encoding
|
||||
const res = await APIClient.get(`databaseSchemas/name/${fqn}/exportAsync`, {
|
||||
params,
|
||||
});
|
||||
const res = await APIClient.get(
|
||||
`databaseSchemas/name/${getEncodedFqn(fqn)}/exportAsync`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { PagingResponse } from 'Models';
|
||||
import { CSVImportAsyncResponse } from '../components/BulkImport/BulkEntityImport.interface';
|
||||
import { CSVExportResponse } from '../components/Entity/EntityExportModalProvider/EntityExportModalProvider.interface';
|
||||
import { VotingDataProps } from '../components/Entity/Voting/voting.interface';
|
||||
import { ES_MAX_PAGE_SIZE, PAGE_SIZE_MEDIUM } from '../constants/constants';
|
||||
@ -172,28 +171,6 @@ export const exportGlossaryInCSVFormat = async (glossaryName: string) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const importGlossaryInCSVFormat = async (
|
||||
glossaryName: string,
|
||||
data: string,
|
||||
dryRun = true
|
||||
) => {
|
||||
const configOptions = {
|
||||
headers: { 'Content-type': 'text/plain' },
|
||||
};
|
||||
const response = await APIClient.put<
|
||||
string,
|
||||
AxiosResponse<CSVImportAsyncResponse>
|
||||
>(
|
||||
`/glossaries/name/${getEncodedFqn(
|
||||
glossaryName
|
||||
)}/importAsync?dryRun=${dryRun}`,
|
||||
data,
|
||||
configOptions
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getGlossaryVersionsList = async (id: string) => {
|
||||
const url = `/glossaries/${id}/versions`;
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { CSVImportAsyncResponse } from '../components/BulkImport/BulkEntityImport.interface';
|
||||
import { EntityType } from '../enums/entity.enum';
|
||||
import { CSVImportAsyncResponse } from '../pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.interface';
|
||||
import { getEncodedFqn } from '../utils/StringsUtils';
|
||||
import APIClient from './index';
|
||||
|
||||
@ -71,3 +71,23 @@ export const importServiceInCSVFormat = async ({
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const importGlossaryInCSVFormat = async ({
|
||||
name,
|
||||
data,
|
||||
dryRun = true,
|
||||
}: importEntityInCSVFormatRequestParams) => {
|
||||
const configOptions = {
|
||||
headers: { 'Content-type': 'text/plain' },
|
||||
};
|
||||
const response = await APIClient.put<
|
||||
string,
|
||||
AxiosResponse<CSVImportAsyncResponse>
|
||||
>(
|
||||
`/glossaries/name/${getEncodedFqn(name)}/importAsync?dryRun=${dryRun}`,
|
||||
data,
|
||||
configOptions
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@ -193,8 +193,7 @@ export const exportDatabaseServiceDetailsInCSV = async (
|
||||
}
|
||||
) => {
|
||||
const res = await APIClient.get(
|
||||
// FQN should be encoded already and we should not encode the fqn here to avoid double encoding
|
||||
`services/databaseServices/name/${fqn}/exportAsync`,
|
||||
`services/databaseServices/name/${getEncodedFqn(fqn)}/exportAsync`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
|
||||
@ -239,10 +239,12 @@ export const exportTableDetailsInCSV = async (
|
||||
recursive?: boolean;
|
||||
}
|
||||
) => {
|
||||
// FQN should be encoded already and we should not encode the fqn here to avoid double encoding
|
||||
const res = await APIClient.get(`tables/name/${fqn}/exportAsync`, {
|
||||
params,
|
||||
});
|
||||
const res = await APIClient.get(
|
||||
`tables/name/${getEncodedFqn(fqn)}/exportAsync`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@ -14,13 +14,13 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { PagingResponse, RestoreRequestType } from 'Models';
|
||||
import { CSVImportAsyncResponse } from '../components/BulkImport/BulkEntityImport.interface';
|
||||
import { CSVExportResponse } from '../components/Entity/EntityExportModalProvider/EntityExportModalProvider.interface';
|
||||
import { CreateTeam } from '../generated/api/teams/createTeam';
|
||||
import { EntityReference } from '../generated/entity/data/table';
|
||||
import { Team } from '../generated/entity/teams/team';
|
||||
import { TeamHierarchy } from '../generated/entity/teams/teamHierarchy';
|
||||
import { ListParams } from '../interface/API.interface';
|
||||
import { CSVImportAsyncResponse } from '../pages/EntityImport/BulkEntityImportPage/BulkEntityImportPage.interface';
|
||||
import { getEncodedFqn } from '../utils/StringsUtils';
|
||||
import APIClient from './index';
|
||||
|
||||
|
||||
@ -16,10 +16,12 @@ import { DataAssetsHeaderProps } from '../../components/DataAssets/DataAssetsHea
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import {
|
||||
importEntityInCSVFormat,
|
||||
importGlossaryInCSVFormat,
|
||||
importServiceInCSVFormat,
|
||||
} from '../../rest/importExportAPI';
|
||||
import { getEntityBreadcrumbs } from '../EntityUtils';
|
||||
import { getEntityBreadcrumbs, getEntityName } from '../EntityUtils';
|
||||
import i18n from '../i18next/LocalUtil';
|
||||
import { getGlossaryPath } from '../RouterUtils';
|
||||
|
||||
type ParsedDataType<T> = Array<T>;
|
||||
|
||||
@ -67,7 +69,19 @@ export const getBulkEntityBreadcrumbList = (
|
||||
isBulkEdit: boolean
|
||||
): TitleBreadcrumbProps['titleLinks'] => {
|
||||
return [
|
||||
...getEntityBreadcrumbs(entity, entityType, true),
|
||||
...(entityType === EntityType.GLOSSARY_TERM
|
||||
? [
|
||||
{
|
||||
name: i18n.t('label.glossary-plural'),
|
||||
url: getGlossaryPath(),
|
||||
activeTitle: false,
|
||||
},
|
||||
{
|
||||
name: getEntityName(entity),
|
||||
url: getGlossaryPath(entity.fullyQualifiedName),
|
||||
},
|
||||
]
|
||||
: getEntityBreadcrumbs(entity, entityType, true)),
|
||||
{
|
||||
name: i18n.t(`label.${isBulkEdit ? 'bulk-edit' : 'import'}`),
|
||||
url: '',
|
||||
@ -76,16 +90,26 @@ export const getBulkEntityBreadcrumbList = (
|
||||
];
|
||||
};
|
||||
|
||||
export const getImportValidateAPIEntityType = (entityType: EntityType) => {
|
||||
switch (entityType) {
|
||||
case EntityType.DATABASE_SERVICE:
|
||||
return importServiceInCSVFormat;
|
||||
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
return importGlossaryInCSVFormat;
|
||||
|
||||
default:
|
||||
return importEntityInCSVFormat;
|
||||
}
|
||||
};
|
||||
|
||||
export const validateCsvString = async (
|
||||
csvData: string,
|
||||
entityType: EntityType,
|
||||
fqn: string,
|
||||
isBulkEdit: boolean
|
||||
) => {
|
||||
const api =
|
||||
entityType === EntityType.DATABASE_SERVICE
|
||||
? importServiceInCSVFormat
|
||||
: importEntityInCSVFormat;
|
||||
const api = getImportValidateAPIEntityType(entityType);
|
||||
|
||||
const response = await api({
|
||||
entityType,
|
||||
|
||||
@ -45,6 +45,7 @@ import {
|
||||
getDatabaseDetailsByFQN,
|
||||
getDatabaseSchemaDetailsByFQN,
|
||||
} from '../rest/databaseAPI';
|
||||
import { getGlossariesByName } from '../rest/glossaryAPI';
|
||||
import { getServiceByFQN } from '../rest/serviceAPI';
|
||||
import { getTableDetailsByFQN } from '../rest/tableAPI';
|
||||
import { ExtraDatabaseDropdownOptions } from './Database/Database.util';
|
||||
@ -68,7 +69,6 @@ import {
|
||||
getTeamsWithFqnPath,
|
||||
getUserPath,
|
||||
} from './RouterUtils';
|
||||
import { getEncodedFqn } from './StringsUtils';
|
||||
import { ExtraTableDropdownOptions } from './TableUtils';
|
||||
import { getTestSuiteDetailsPath } from './TestSuiteUtils';
|
||||
|
||||
@ -292,6 +292,9 @@ class EntityUtilClassBase {
|
||||
return getDatabaseDetailsByFQN(fqn, { fields });
|
||||
case EntityType.DATABASE_SCHEMA:
|
||||
return getDatabaseSchemaDetailsByFQN(fqn, { fields });
|
||||
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
return getGlossariesByName(fqn, { fields });
|
||||
default:
|
||||
return getTableDetailsByFQN(fqn, { fields });
|
||||
}
|
||||
@ -408,29 +411,19 @@ class EntityUtilClassBase {
|
||||
| APICollection
|
||||
): ItemType[] {
|
||||
const isEntityDeleted = _entityDetails?.deleted ?? false;
|
||||
// We are encoding here since we are getting the decoded fqn from the OSS code
|
||||
const encodedFqn = getEncodedFqn(_fqn);
|
||||
switch (_entityType) {
|
||||
case EntityType.TABLE:
|
||||
return [
|
||||
...ExtraTableDropdownOptions(
|
||||
encodedFqn,
|
||||
_permission,
|
||||
isEntityDeleted
|
||||
),
|
||||
...ExtraTableDropdownOptions(_fqn, _permission, isEntityDeleted),
|
||||
];
|
||||
case EntityType.DATABASE:
|
||||
return [
|
||||
...ExtraDatabaseDropdownOptions(
|
||||
encodedFqn,
|
||||
_permission,
|
||||
isEntityDeleted
|
||||
),
|
||||
...ExtraDatabaseDropdownOptions(_fqn, _permission, isEntityDeleted),
|
||||
];
|
||||
case EntityType.DATABASE_SCHEMA:
|
||||
return [
|
||||
...ExtraDatabaseSchemaDropdownOptions(
|
||||
encodedFqn,
|
||||
_fqn,
|
||||
_permission,
|
||||
isEntityDeleted
|
||||
),
|
||||
@ -438,7 +431,7 @@ class EntityUtilClassBase {
|
||||
case EntityType.DATABASE_SERVICE:
|
||||
return [
|
||||
...ExtraDatabaseServiceDropdownOptions(
|
||||
encodedFqn,
|
||||
_fqn,
|
||||
_permission,
|
||||
isEntityDeleted
|
||||
),
|
||||
|
||||
@ -2504,7 +2504,7 @@ export const getEntityImportPath = (entityType: EntityType, fqn: string) => {
|
||||
return ROUTES.ENTITY_IMPORT.replace(
|
||||
PLACEHOLDER_ROUTE_ENTITY_TYPE,
|
||||
entityType
|
||||
).replace(PLACEHOLDER_ROUTE_FQN, fqn);
|
||||
).replace(PLACEHOLDER_ROUTE_FQN, getEncodedFqn(fqn));
|
||||
};
|
||||
|
||||
export const getEntityBulkEditPath = (entityType: EntityType, fqn: string) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user