From 2620af8e2a35d159d7ab2597940880cbe015ea28 Mon Sep 17 00:00:00 2001 From: purnimagarg1 <139125209+purnimagarg1@users.noreply.github.com> Date: Sat, 31 May 2025 06:14:02 +0530 Subject: [PATCH] feat(ui/ingestion): add empty and loading states for sources and secrets tables (#13646) Co-authored-by: Chris Collins --- .../ExecutionRequestDetailsModal.tsx | 6 +- .../src/app/ingestV2/EmptySources.tsx | 44 +++++++++++ .../src/app/ingestV2/secret/SecretsList.tsx | 73 +++++++++---------- .../ingestV2/source/IngestionSourceList.tsx | 54 ++++++++------ .../ingestV2/source/IngestionSourceTable.tsx | 3 + .../ExecutionRequestDetailsModal.tsx | 23 ++---- 6 files changed, 121 insertions(+), 82 deletions(-) create mode 100644 datahub-web-react/src/app/ingestV2/EmptySources.tsx diff --git a/datahub-web-react/src/app/ingest/source/executions/ExecutionRequestDetailsModal.tsx b/datahub-web-react/src/app/ingest/source/executions/ExecutionRequestDetailsModal.tsx index 8d3c9b39d0..7821fbd434 100644 --- a/datahub-web-react/src/app/ingest/source/executions/ExecutionRequestDetailsModal.tsx +++ b/datahub-web-react/src/app/ingest/source/executions/ExecutionRequestDetailsModal.tsx @@ -178,14 +178,14 @@ export const ExecutionDetailsModal = ({ urn, open, onClose }: Props) => { bodyStyle={modalBodyStyle} title={ - Sync Details + Execution Run Details } open={open} onCancel={onClose} > - {!data && loading && } - {error && message.error('Failed to load sync details :(')} + {!data && loading && } + {error && message.error('Failed to load execution run details :(')}
Status diff --git a/datahub-web-react/src/app/ingestV2/EmptySources.tsx b/datahub-web-react/src/app/ingestV2/EmptySources.tsx new file mode 100644 index 0000000000..bc14063c97 --- /dev/null +++ b/datahub-web-react/src/app/ingestV2/EmptySources.tsx @@ -0,0 +1,44 @@ +import { Text } from '@components'; +import React from 'react'; +import styled from 'styled-components'; + +import { EmptyContainer } from '@app/govern/structuredProperties/styledComponents'; +import EmptyFormsImage from '@src/images/empty-forms.svg?react'; + +export const TextContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +interface Props { + sourceType?: string; + isEmptySearchResult?: boolean; +} + +const EmptySources = ({ sourceType, isEmptySearchResult }: Props) => { + return ( + + {isEmptySearchResult ? ( + + + No search results! + + + Try another search query with at least 3 characters... + + + ) : ( + <> + + + {`No ${sourceType || 'sources'} yet!`} + + + )} + + ); +}; + +export default EmptySources; diff --git a/datahub-web-react/src/app/ingestV2/secret/SecretsList.tsx b/datahub-web-react/src/app/ingestV2/secret/SecretsList.tsx index 575a9b1f14..0b280b370d 100644 --- a/datahub-web-react/src/app/ingestV2/secret/SecretsList.tsx +++ b/datahub-web-react/src/app/ingestV2/secret/SecretsList.tsx @@ -1,11 +1,12 @@ import { Icon, Pagination, SearchBar, Table, colors } from '@components'; -import { Empty, Modal, Typography, message } from 'antd'; +import { Typography, message } from 'antd'; import * as QueryString from 'query-string'; import React, { useEffect, useState } from 'react'; import { useLocation } from 'react-router'; import styled from 'styled-components'; import TabToolbar from '@app/entity/shared/components/styled/TabToolbar'; +import EmptySources from '@app/ingestV2/EmptySources'; import { SecretBuilderModal } from '@app/ingestV2/secret/SecretBuilderModal'; import { addSecretToListSecretsCache, @@ -13,8 +14,8 @@ import { updateSecretInListSecretsCache, } from '@app/ingestV2/secret/cacheUtils'; import { SecretBuilderState } from '@app/ingestV2/secret/types'; -import { Message } from '@app/shared/Message'; import { scrollToTop } from '@app/shared/searchUtils'; +import { ConfirmationModal } from '@app/sharedV2/modals/ConfirmationModal'; import { useCreateSecretMutation, @@ -79,12 +80,6 @@ const TextContainer = styled(Typography.Text)` color: ${colors.gray[1700]}; `; -const EmptyState = () => ( -
- -
-); - type TableDataType = { urn: string; name: string; @@ -111,6 +106,7 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo const start = (page - 1) * pageSize; const [editSecret, setEditSecret] = useState(undefined); + const [showConfirmDelete, setShowConfirmDelete] = useState(false); const [deleteSecretMutation] = useDeleteSecretMutation(); const [createSecretMutation] = useCreateSecretMutation(); @@ -143,6 +139,8 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo message.error({ content: `Failed to remove secret: \n ${e.message || ''}`, duration: 3 }); } }); + setShowConfirmDelete(false); + refetch(); }; const onChangePage = (newPage: number) => { @@ -232,18 +230,8 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo }); }; - const onDeleteSecret = (urn: string) => { - Modal.confirm({ - title: `Confirm Secret Removal`, - content: `Are you sure you want to remove this secret? Sources that use it may no longer work as expected.`, - onOk() { - deleteSecret(urn); - }, - onCancel() {}, - okText: 'Yes', - maskClosable: true, - closable: true, - }); + const handleDeleteClose = () => { + setShowConfirmDelete(false); }; const onEditSecret = (urnData: any) => { @@ -301,21 +289,30 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo title: '', key: 'actions', render: (record: TableDataType) => ( - - - - + <> + + + + + deleteSecret(record.urn)} + handleClose={handleDeleteClose} + /> + ), width: '100px', }, @@ -330,7 +327,6 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo return ( <> - {!data && loading && } {error && message.error({ content: `Failed to load secrets! \n ${error.message || ''}`, duration: 3 })} @@ -342,8 +338,8 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo /> - {tableData.length === 0 ? ( - + {!loading && totalSecrets === 0 ? ( + ) : ( <> @@ -353,6 +349,7 @@ export const SecretsList = ({ showCreateModal: isCreatingSecret, setShowCreateMo rowKey="urn" isScrollable style={{ tableLayout: 'fixed' }} + isLoading={loading} /> - {!data && loading && } {error && ( )} @@ -484,29 +484,35 @@ export const IngestionSourceList = ({ showCreateModal, setShowCreateModal }: Pro - - - - - - - + {!loading && totalSources === 0 ? ( + + ) : ( + <> + + + + + + + + )} void; onDelete: (urn: string) => void; onChangeSort: (field: string, order: SorterResult['order']) => void; + isLoading?: boolean; } function IngestionSourceTable({ @@ -36,6 +37,7 @@ function IngestionSourceTable({ onView, onDelete, onChangeSort, + isLoading, }: Props) { const tableData = sources.map((source) => ({ urn: source.urn, @@ -121,6 +123,7 @@ function IngestionSourceTable({ data={tableData} isScrollable handleSortColumnChange={handleSortColumnChange} + isLoading={isLoading} /> ); } diff --git a/datahub-web-react/src/app/ingestV2/source/executions/ExecutionRequestDetailsModal.tsx b/datahub-web-react/src/app/ingestV2/source/executions/ExecutionRequestDetailsModal.tsx index ad5f3682fe..1dc0c92070 100644 --- a/datahub-web-react/src/app/ingestV2/source/executions/ExecutionRequestDetailsModal.tsx +++ b/datahub-web-react/src/app/ingestV2/source/executions/ExecutionRequestDetailsModal.tsx @@ -1,6 +1,6 @@ import { DownloadOutlined } from '@ant-design/icons'; -import { Icon, Pill } from '@components'; -import { Button, Modal, Typography, message } from 'antd'; +import { Icon, Modal, Pill } from '@components'; +import { Button, Typography, message } from 'antd'; import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import YAML from 'yamljs'; @@ -25,11 +25,6 @@ import { Message } from '@app/shared/Message'; import { useGetIngestionExecutionRequestQuery } from '@graphql/ingestion.generated'; import { ExecutionRequestResult } from '@types'; -const StyledTitle = styled(Typography.Title)` - padding: 0px; - margin: 0px; -`; - const Section = styled.div` display: flex; flex-direction: column; @@ -54,8 +49,6 @@ const SubHeaderParagraph = styled(Typography.Paragraph)` margin-bottom: 0px; `; -const HeaderSection = styled.div``; - const StatusSection = styled.div` border-bottom: 1px solid ${ANTD_GRAY[4]}; padding: 16px; @@ -180,19 +173,15 @@ export const ExecutionDetailsModal = ({ urn, open, onClose }: Props) => { return ( Close} style={modalStyle} bodyStyle={modalBodyStyle} - title={ - - Sync Details - - } + title="Execution Run Details" open={open} onCancel={onClose} + buttons={[{ text: 'Close', variant: 'outline', onClick: onClose }]} > - {!data && loading && } - {error && message.error('Failed to load sync details :(')} + {!data && loading && } + {error && message.error('Failed to load execution run details :(')}
Status