diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/calendar.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/calendar.svg index b7fa71e12ec..c79063fe208 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/calendar.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/calendar.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/filter.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/filter.svg index 7f7be1ee132..8f4a9852135 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/filter.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/filter.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.test.tsx new file mode 100644 index 00000000000..33417efd714 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.test.tsx @@ -0,0 +1,108 @@ +/* + * 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 ExecutionsTab from './Execution.component'; + +jest.mock('./ListView/ListViewTab.component', () => + jest.fn().mockImplementation(() =>
ListViewTab
) +); + +jest.mock('./TreeView/TreeViewTab.component', () => + jest.fn().mockImplementation(() =>
TreeViewTab
) +); + +jest.mock('../../assets/svg/calendar.svg', () => + jest.fn().mockImplementation(() =>
Calendar
) +); + +jest.mock('../../assets/svg/filter.svg', () => + jest.fn().mockImplementation(() =>
FilterIcon
) +); + +jest.mock('../../utils/ToastUtils', () => ({ + showErrorToast: jest.fn(), +})); + +jest.mock('../../utils/date-time/DateTimeUtils', () => ({ + getCurrentMillis: jest.fn().mockImplementation((data) => data), + getEpochMillisForPastDays: jest + .fn() + .mockImplementation(() =>
StatusIndicator
), +})); + +jest.mock('../../rest/pipelineAPI', () => ({ + getPipelineStatus: jest.fn().mockImplementation(() => + Promise.resolve({ + data: [], + }) + ), +})); + +const mockProps = { + pipelineFQN: 'pipelineFQN', + tasks: [], +}; + +describe('Test Execution Component', () => { + it('Should render component properly', async () => { + render(); + + expect(screen.getByTestId('execution-tab')).toBeInTheDocument(); + expect(screen.getByTestId('radio-switch')).toBeInTheDocument(); + expect(screen.getByTestId('status-button')).toBeInTheDocument(); + expect(screen.getByTestId('data-range-picker-button')).toBeInTheDocument(); + expect(screen.getByTestId('data-range-picker')).toBeInTheDocument(); + }); + + it('Should render ListViewTab component', async () => { + render(); + + expect(screen.getByText('ListViewTab')).toBeInTheDocument(); + }); + + it('Should render TreeViewTab component on tabView change', async () => { + render(); + + expect(screen.getByText('ListViewTab')).toBeInTheDocument(); + + const treeRadioButton = screen.getByText('Tree'); + + act(() => { + fireEvent.click(treeRadioButton); + }); + + expect(screen.getByText('TreeViewTab')).toBeInTheDocument(); + }); + + it('Should render data picker button only on Tree View Tab', async () => { + render(); + + expect(screen.getByText('ListViewTab')).toBeInTheDocument(); + + expect(screen.getByTestId('data-range-picker-button')).toBeInTheDocument(); + + const treeRadioButton = screen.getByText('Tree'); + + act(() => { + fireEvent.click(treeRadioButton); + }); + + expect(screen.getByText('TreeViewTab')).toBeInTheDocument(); + + expect( + screen.queryByTestId('data-range-picker-button') + ).not.toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.tsx index ce6b1d2ba49..ca3db77d4d3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Execution/Execution.component.tsx @@ -11,13 +11,12 @@ * limitations under the License. */ -import { CloseCircleOutlined } from '@ant-design/icons'; +import Icon, { CloseCircleOutlined } from '@ant-design/icons'; import { Button, Col, DatePicker, Dropdown, - Menu, MenuProps, Radio, Row, @@ -27,7 +26,7 @@ import { RangePickerProps } from 'antd/lib/date-picker'; import { AxiosError } from 'axios'; import classNames from 'classnames'; import { isNaN, map } from 'lodash'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as Calendar } from '../../assets/svg/calendar.svg'; import { ReactComponent as FilterIcon } from '../../assets/svg/filter.svg'; @@ -84,22 +83,19 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { } }; - const handleMenuClick: MenuProps['onClick'] = (event) => { - if (event?.key) { - setStatus(MenuOptions[event.key as keyof typeof MenuOptions]); - } - }; + const handleMenuClick: MenuProps['onClick'] = useCallback( + (event) => setStatus(MenuOptions[event.key as keyof typeof MenuOptions]), + [] + ); - const menu = useMemo( - () => ( - ({ - key: key, - label: value, - }))} - onClick={handleMenuClick} - /> - ), + const statusMenuItems = useMemo( + () => ({ + items: map(MenuOptions, (value, key) => ({ + key: key, + label: value, + })), + onClick: handleMenuClick, + }), [handleMenuClick] ); @@ -123,6 +119,12 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { } setDatesSelected(true); + } else { + setDatesSelected(false); + setStartTime( + getEpochMillisForPastDays(EXECUTION_FILTER_RANGE.last365days.days) + ); + setEndTime(getCurrentMillis()); } }; @@ -131,7 +133,7 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { }, [pipelineFQN, datesSelected, startTime, endTime]); return ( - + @@ -139,18 +141,20 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { setView(e.target.value)} /> - - {view === PIPELINE_EXECUTION_TABS.LIST_VIEW ? ( @@ -161,12 +165,13 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { 'range-picker-button-width delay-100': !datesSelected && !isClickedCalendar, })} + data-testid="data-range-picker-button" + icon={} type="primary" onClick={() => { setIsClickedCalendar(true); }}> - - + {!datesSelected && ( )} @@ -176,6 +181,7 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { bordered={false} className="executions-date-picker" clearIcon={} + data-testid="data-range-picker" open={isClickedCalendar} placeholder={['', '']} suffixIcon={null} @@ -184,7 +190,7 @@ const ExecutionsTab = ({ pipelineFQN, tasks }: ExecutionProps) => { setIsClickedCalendar(isOpen); }} /> - + ) : null} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.test.tsx new file mode 100644 index 00000000000..833c2775a31 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import React from 'react'; +import { StatusType } from '../../../generated/entity/data/pipeline'; +import { EXECUTION_LIST_MOCK } from '../../../mocks/PipelineVersion.mock'; +import ListView from './ListViewTab.component'; + +jest.mock('../../common/ErrorWithPlaceholder/FilterTablePlaceHolder', () => + jest.fn().mockImplementation(() =>
FilterTablePlaceHolder
) +); + +jest.mock('../../../utils/executionUtils', () => ({ + getTableViewData: jest.fn().mockImplementation((data) => data), + StatusIndicator: jest + .fn() + .mockImplementation(() =>
StatusIndicator
), +})); + +const mockProps = { + executions: EXECUTION_LIST_MOCK, + status: StatusType.Successful, + loading: false, +}; + +describe('Test ListViewTab Component', () => { + it('Should render loader in table component', async () => { + render(); + + expect(screen.getByTestId('skeleton-table')).toBeInTheDocument(); + }); + + it('Should render component properly if not loading', async () => { + render(); + + expect(screen.getByTestId('list-view-table')).toBeInTheDocument(); + }); + + it('Should render NoDataPlaceholder if no data present', async () => { + render(); + + expect(screen.getByTestId('list-view-table')).toBeInTheDocument(); + + expect(screen.getByText('FilterTablePlaceHolder')).toBeInTheDocument(); + }); + + it('Should render columns if data present', async () => { + render(); + + expect(screen.getByTestId('list-view-table')).toBeInTheDocument(); + + expect( + screen.queryByText('FilterTablePlaceHolder') + ).not.toBeInTheDocument(); + + expect(screen.getAllByText('StatusIndicator')).toHaveLength(7); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.tsx index 48ff1e07916..b29f2b1d06c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Execution/ListView/ListViewTab.component.tsx @@ -11,7 +11,6 @@ * limitations under the License. */ -import { Table } from 'antd'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -23,6 +22,7 @@ import { StatusIndicator, } from '../../../utils/executionUtils'; import FilterTablePlaceHolder from '../../common/ErrorWithPlaceholder/FilterTablePlaceHolder'; +import Table from '../../common/Table/Table'; interface ListViewProps { executions: Array | undefined; @@ -65,13 +65,14 @@ const ListView = ({ executions, status, loading }: ListViewProps) => { bordered className="h-full" columns={columns} + data-testid="list-view-table" dataSource={tableData} loading={loading} locale={{ emptyText: , }} pagination={false} - rowKey={(record) => record.name.concat(record.timestamp as string)} + rowKey={(record) => `${record.name}-${record.status}-${record.key}`} /> ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/mocks/PipelineVersion.mock.ts b/openmetadata-ui/src/main/resources/ui/src/mocks/PipelineVersion.mock.ts index 407b1769897..97506bbfaa2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/mocks/PipelineVersion.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/mocks/PipelineVersion.mock.ts @@ -12,7 +12,10 @@ */ import { PipelineVersionProp } from '../components/PipelineVersion/PipelineVersion.interface'; -import { PipelineServiceType } from '../generated/entity/data/pipeline'; +import { + PipelineServiceType, + StatusType, +} from '../generated/entity/data/pipeline'; import { ENTITY_PERMISSIONS } from '../mocks/Permissions.mock'; import { mockBackHandler, @@ -144,3 +147,104 @@ export const mockColumnDiffPipelineVersionMockProps = { ...pipelineVersionMockProps, currentVersionData: mockColumnDiffPipelineData, }; + +export const EXECUTION_LIST_MOCK = [ + { + timestamp: 1697265270340, + executionStatus: StatusType.Pending, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Pending, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Pending, + }, + ], + }, + { + timestamp: 1697265270200, + executionStatus: StatusType.Pending, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Failed, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Pending, + }, + ], + }, + { + timestamp: 1697265269958, + executionStatus: StatusType.Pending, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Pending, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Successful, + }, + ], + }, + { + timestamp: 1697265269825, + executionStatus: StatusType.Failed, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Failed, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Successful, + }, + ], + }, + { + timestamp: 1697265269683, + executionStatus: StatusType.Failed, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Successful, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Failed, + }, + ], + }, + { + timestamp: 1697265269509, + executionStatus: StatusType.Successful, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Successful, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Successful, + }, + ], + }, + { + timestamp: 1697265269363, + executionStatus: StatusType.Failed, + taskStatus: [ + { + name: 'dim_address_task', + executionStatus: StatusType.Failed, + }, + { + name: 'assert_table_exists', + executionStatus: StatusType.Failed, + }, + ], + }, +]; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/executionUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/executionUtils.tsx index 615976a822d..63a31b75d12 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/executionUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/executionUtils.tsx @@ -35,6 +35,7 @@ export interface ViewDataInterface { timestamp?: string; executionStatus?: StatusType; type?: string; + key: number; } export const StatusIndicator = ({ status }: StatusIndicatorInterface) => ( @@ -73,6 +74,7 @@ export const getTableViewData = ( timestamp: formatDateTime(execution.timestamp), executionStatus: execute.executionStatus, type: '--', + key: execution.timestamp, }); }); });