diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/ingestionPipelineAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/ingestionPipelineAPI.ts index b71b8300963..932219be21e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/ingestionPipelineAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/ingestionPipelineAPI.ts @@ -82,3 +82,9 @@ export const updateIngestionPipeline = ( export const checkAirflowStatus = (): Promise => { return APIClient.get('/services/ingestionPipelines/status'); }; + +export const getIngestionPipelineLogById = ( + id: string +): Promise => { + return APIClient.get(`/services/ingestionPipelines/logs/${id}/last`); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/Ingestion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/Ingestion.component.tsx index f9d282d5e5d..da54022c235 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/Ingestion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/Ingestion.component.tsx @@ -43,6 +43,7 @@ import Searchbar from '../common/searchbar/Searchbar'; import DropDownList from '../dropdown/DropDownList'; import Loader from '../Loader/Loader'; import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal'; +import IngestionLogsModal from '../Modals/IngestionLogsModal/IngestionLogsModal'; import { IngestionProps } from './ingestion.interface'; const Ingestion: React.FC = ({ @@ -68,6 +69,8 @@ const Ingestion: React.FC = ({ const [currTriggerId, setCurrTriggerId] = useState({ id: '', state: '' }); const [currDeployId, setCurrDeployId] = useState({ id: '', state: '' }); const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false); + const [isLogsModalOpen, setIsLogsModalOpen] = useState(false); + const [selectedPipeline, setSelectedPipeline] = useState(); const [deleteSelection, setDeleteSelection] = useState({ id: '', name: '', @@ -271,6 +274,10 @@ const Ingestion: React.FC = ({ : ingestionList; }, [searchText, ingestionList]); + const separator = ( + | + ); + const getStatuses = (ingestion: IngestionPipeline) => { const lastFiveIngestions = ingestion.pipelineStatuses ?.sort((a, b) => { @@ -326,7 +333,7 @@ const Ingestion: React.FC = ({ if (ingestion.deployed) { return ( )} + {separator} + {separator} + {separator} + + {isLogsModalOpen && + selectedPipeline && + ingestion.id === selectedPipeline?.id && ( + { + setIsLogsModalOpen(false); + setSelectedPipeline(undefined); + }} + /> + )} ))} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/IngestionLogsModal/IngestionLogsModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/IngestionLogsModal/IngestionLogsModal.test.tsx new file mode 100644 index 00000000000..487afc2dd84 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/IngestionLogsModal/IngestionLogsModal.test.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2021 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 { render, screen } from '@testing-library/react'; +import React from 'react'; +import { PipelineType } from '../../../generated/api/services/ingestionPipelines/createIngestionPipeline'; +import IngestionLogsModal from './IngestionLogsModal'; + +const mockProps = { + pipelineId: 'bb2ee1a9-653f-4925-a70c-fdbb3abc2d2c', + pipelinName: 'MyUnsplash_Service_metadata', + pipelineType: PipelineType.Metadata, + isModalOpen: true, + onClose: jest.fn(), +}; + +jest.mock('../../../axiosAPIs/ingestionPipelineAPI', () => ({ + getIngestionPipelineLogById: jest.fn().mockImplementation(() => + // eslint-disable-next-line @typescript-eslint/camelcase + Promise.resolve({ data: { ingestion_task: 'logs' } }) + ), +})); + +jest.mock('../../buttons/CopyToClipboardButton/CopyToClipboardButton', () => + jest.fn().mockReturnValue() +); + +describe('Test Ingestion Logs Modal component', () => { + it('Should render the component', async () => { + render(); + + const container = await screen.findByTestId('logs-modal'); + const logsBody = await screen.findByTestId('logs-body'); + const jumpToEndButton = await screen.findByTestId('jump-to-end-button'); + + expect(container).toBeInTheDocument(); + + expect(logsBody).toBeInTheDocument(); + + expect(jumpToEndButton).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/IngestionLogsModal/IngestionLogsModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/IngestionLogsModal/IngestionLogsModal.tsx new file mode 100644 index 00000000000..0ce2451c0be --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/IngestionLogsModal/IngestionLogsModal.tsx @@ -0,0 +1,146 @@ +/* + * Copyright 2021 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 { Viewer } from '@toast-ui/react-editor'; +import { Button, Empty, Modal } from 'antd'; +import { AxiosError, AxiosResponse } from 'axios'; +import classNames from 'classnames'; +import { isNil } from 'lodash'; +import React, { FC, Fragment, useEffect, useState } from 'react'; +import { getIngestionPipelineLogById } from '../../../axiosAPIs/ingestionPipelineAPI'; +import { PipelineType } from '../../../generated/entity/services/ingestionPipelines/ingestionPipeline'; +import { showErrorToast } from '../../../utils/ToastUtils'; +import CopyToClipboardButton from '../../buttons/CopyToClipboardButton/CopyToClipboardButton'; +import Loader from '../../Loader/Loader'; + +interface IngestionLogsModalProps { + pipelineId: string; + pipelinName: string; + pipelineType: PipelineType; + isModalOpen: boolean; + onClose: () => void; +} + +const IngestionLogsModal: FC = ({ + pipelineId, + pipelinName, + pipelineType, + isModalOpen, + onClose, +}) => { + const [logs, setLogs] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isLogNotFound, setIsLogNotFound] = useState(false); + + const fetchLogs = (id: string) => { + setIsLoading(true); + getIngestionPipelineLogById(id) + .then((res: AxiosResponse) => { + switch (pipelineType) { + case PipelineType.Metadata: + setLogs(res.data?.ingestion_task || ''); + + break; + case PipelineType.Profiler: + setLogs(res.data?.profiler_task || ''); + + break; + case PipelineType.Usage: + setLogs(res.data?.usage_task || ''); + + break; + + default: + setLogs(''); + + break; + } + }) + .catch((err: AxiosError) => { + if (err.response?.status === 404) { + setIsLogNotFound(true); + } else { + showErrorToast(err); + } + }) + .finally(() => { + setIsLoading(false); + }); + }; + + const handleJumpToEnd = () => { + const logsBody = document.getElementById('logs-body') as HTMLElement; + if (!isNil(logsBody)) { + logsBody.scrollTop = logsBody.scrollHeight; + } + }; + + const modalTitle = ( +
+ {`Logs for ${pipelinName}`} +
+ ); + + useEffect(() => { + fetchLogs(pipelineId); + }, [pipelineId]); + + return ( + setLogs('')} + data-testid="logs-modal" + footer={null} + title={modalTitle} + visible={isModalOpen} + width={1200} + onCancel={onClose}> + {isLoading ? ( + + ) : ( + + {logs ? ( + + +
+ +
+
+ ) : ( + + )} +
+ )} +
+ ); +}; + +export default IngestionLogsModal;