fix(ui): fix the errorPlaceholder visible when component is in loading state (#11387)

* fix the errorPlaceholder visible when component is in loading state

* changes as per commets
This commit is contained in:
Ashish Gupta 2023-05-04 12:09:22 +05:30 committed by GitHub
parent f374bb38b4
commit f2a62dd60f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 651 additions and 558 deletions

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { PlusOutlined } from '@ant-design/icons';
import { import {
Button, Button,
Card, Card,
@ -21,6 +22,8 @@ import {
Typography, Typography,
} from 'antd'; } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import { ERROR_PLACEHOLDER_TYPE, SIZE } from 'enums/common.enum';
import { isEmpty, isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react'; import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -276,27 +279,27 @@ const KPIChart: FC<Props> = ({ chartFilter, kpiList }) => {
<Space <Space
className="w-full justify-center items-center" className="w-full justify-center items-center"
direction="vertical"> direction="vertical">
<Typography.Text> <ErrorPlaceHolder
{t('message.no-kpi-available-add-new-one')} button={
</Typography.Text> <AntdTooltip
<AntdTooltip title={!isAdminUser && t('message.no-permission-for-action')}>
title={ <Button
isAdminUser ghost
? t('label.add-entity', { icon={<PlusOutlined />}
type="primary"
onClick={handleAddKpi}>
{t('label.add-entity', {
entity: t('label.kpi-uppercase'), entity: t('label.kpi-uppercase'),
}) })}
: t('message.no-permission-for-action') </Button>
}> </AntdTooltip>
<Button }
className="tw-border-primary tw-text-primary" className="p-y-lg"
disabled={!isAdminUser} permission={isAdminUser}
type="default" size={SIZE.MEDIUM}
onClick={handleAddKpi}> type={ERROR_PLACEHOLDER_TYPE.ASSIGN}>
{t('label.add-entity', { {t('message.no-kpi-available-add-new-one')}
entity: t('label.kpi-uppercase'), </ErrorPlaceHolder>
})}
</Button>
</AntdTooltip>
</Space> </Space>
)} )}
</Card> </Card>

View File

@ -11,11 +11,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { CheckOutlined } from '@ant-design/icons'; import { CheckOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Col, Popover, Row, Table, Tooltip } from 'antd'; import { Button, Col, Popover, Row, Table, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import cronstrue from 'cronstrue'; import cronstrue from 'cronstrue';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { isEmpty } from 'lodash';
import React, { Fragment, useEffect, useMemo, useState } from 'react'; import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useLocation, useParams } from 'react-router-dom'; import { useHistory, useLocation, useParams } from 'react-router-dom';
@ -550,13 +553,44 @@ const TestSuitePipelineTab = () => {
currTriggerId, currTriggerId,
]); ]);
const errorPlaceholder = useMemo(
() => (
<ErrorPlaceHolder
button={
<Button
ghost
className="p-x-lg"
data-testid="add-placeholder-button"
icon={<PlusOutlined />}
type="primary"
onClick={() => {
history.push(getTestSuiteIngestionPath(testSuiteFQN));
}}>
{t('label.add')}
</Button>
}
className="mt-24"
heading={t('label.pipeline')}
permission={createPermission}
type={ERROR_PLACEHOLDER_TYPE.ASSIGN}
/>
),
[testSuiteFQN]
);
if (isLoading || isFetchingStatus) { if (isLoading || isFetchingStatus) {
return <Loader />; return <Loader />;
} }
return !isAirflowAvailable ? ( if (!isAirflowAvailable) {
<ErrorPlaceHolderIngestion /> return <ErrorPlaceHolderIngestion />;
) : ( }
if (isEmpty(testSuitePipelines)) {
return errorPlaceholder;
}
return (
<TestCaseCommonTabContainer <TestCaseCommonTabContainer
buttonName={t('label.add-entity', { buttonName={t('label.add-entity', {
entity: t('label.ingestion'), entity: t('label.ingestion'),

View File

@ -159,7 +159,7 @@ const AlertsPage = () => {
[] []
); );
if (isEmpty(alerts)) { if (isEmpty(alerts) && !loading) {
return ( return (
<ErrorPlaceHolder <ErrorPlaceHolder
permission permission
@ -200,7 +200,7 @@ const AlertsPage = () => {
bordered bordered
columns={columns} columns={columns}
dataSource={alerts} dataSource={alerts}
loading={{ spinning: loading, indicator: <Loader /> }} loading={{ spinning: loading, indicator: <Loader size="small" /> }}
pagination={false} pagination={false}
rowKey="id" rowKey="id"
size="middle" size="middle"

View File

@ -188,7 +188,7 @@ const KPIList = () => {
if (isEmpty(kpiList)) { if (isEmpty(kpiList)) {
return ( return (
<div className="mt-24 w-full"> <div className="m-t-lg w-full">
<EmptyGraphPlaceholder /> <EmptyGraphPlaceholder />
</div> </div>
); );

View File

@ -331,6 +331,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
entity: t('label.database-schema'), entity: t('label.database-schema'),
}) })
); );
setError(errMsg); setError(errMsg);
showErrorToast(errMsg); showErrorToast(errMsg);
}) })
@ -638,7 +639,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
const getSchemaTableList = () => { const getSchemaTableList = () => {
return ( return (
<Col span={24}> <Col span={24}>
{isEmpty(tableData) && !showDeletedTables ? ( {isEmpty(tableData) && !showDeletedTables && !tableDataLoading ? (
<ErrorPlaceHolder <ErrorPlaceHolder
className="mt-0-important" className="mt-0-important"
type={ERROR_PLACEHOLDER_TYPE.NO_DATA} type={ERROR_PLACEHOLDER_TYPE.NO_DATA}
@ -650,10 +651,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
columns={tableColumn} columns={tableColumn}
data-testid="databaseSchema-tables" data-testid="databaseSchema-tables"
dataSource={tableData} dataSource={tableData}
loading={{
spinning: tableDataLoading,
indicator: <Loader size="small" />,
}}
locale={{ locale={{
emptyText: <FilterTablePlaceHolder />, emptyText: <FilterTablePlaceHolder />,
}} }}
@ -786,162 +783,168 @@ const DatabaseSchemaPage: FunctionComponent = () => {
appState.inPageSearchText = ''; appState.inPageSearchText = '';
}, []); }, []);
if (isLoading) {
return <Loader />;
}
if (error) {
return (
<ErrorPlaceHolder>
<p data-testid="error-message">{error}</p>
</ErrorPlaceHolder>
);
}
return ( return (
<Fragment> <Fragment>
{isLoading ? ( {databaseSchemaPermission.ViewAll ||
<Loader /> databaseSchemaPermission.ViewBasic ? (
) : error ? ( <PageContainerV1>
<ErrorPlaceHolder> <PageLayoutV1
<p data-testid="error-message">{error}</p> pageTitle={t('label.entity-detail-plural', {
</ErrorPlaceHolder> entity: getEntityName(databaseSchema),
) : ( })}>
<> {IsSchemaDetailsLoading ? (
{databaseSchemaPermission.ViewAll || <Skeleton
databaseSchemaPermission.ViewBasic ? ( active
<PageContainerV1> paragraph={{
<PageLayoutV1 rows: 3,
pageTitle={t('label.entity-detail-plural', { width: ['20%', '80%', '60%'],
entity: getEntityName(databaseSchema), }}
})}> />
{IsSchemaDetailsLoading ? ( ) : (
<Skeleton <>
active <Col span={24}>
paragraph={{ <EntityPageInfo
rows: 3, isRecursiveDelete
width: ['20%', '80%', '60%'], canDelete={databaseSchemaPermission.Delete}
}} currentOwner={databaseSchema?.owner}
deleted={databaseSchema?.deleted}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={databaseSchemaFQN}
entityId={databaseSchemaId}
entityName={databaseSchemaName}
entityType={EntityType.DATABASE_SCHEMA}
extraDropdownContent={extraDropdownContent}
extraInfo={extraInfo}
followersList={[]}
isTagEditable={
databaseSchemaPermission.EditAll ||
databaseSchemaPermission.EditTags
}
serviceType={databaseSchema?.serviceType ?? ''}
tags={tags}
tagsHandler={onTagUpdate}
tier={tier}
titleLinks={slashedTableName}
updateOwner={
databaseSchemaPermission.EditOwner ||
databaseSchemaPermission.EditAll
? handleUpdateOwner
: undefined
}
onRestoreEntity={handleRestoreDatabaseSchema}
onThreadLinkSelect={onThreadLinkSelect}
/> />
) : (
<>
<Col span={24}>
<EntityPageInfo
isRecursiveDelete
canDelete={databaseSchemaPermission.Delete}
currentOwner={databaseSchema?.owner}
deleted={databaseSchema?.deleted}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={databaseSchemaFQN}
entityId={databaseSchemaId}
entityName={databaseSchemaName}
entityType={EntityType.DATABASE_SCHEMA}
extraDropdownContent={extraDropdownContent}
extraInfo={extraInfo}
followersList={[]}
isTagEditable={
databaseSchemaPermission.EditAll ||
databaseSchemaPermission.EditTags
}
serviceType={databaseSchema?.serviceType ?? ''}
tags={tags}
tagsHandler={onTagUpdate}
tier={tier}
titleLinks={slashedTableName}
updateOwner={
databaseSchemaPermission.EditOwner ||
databaseSchemaPermission.EditAll
? handleUpdateOwner
: undefined
}
onRestoreEntity={handleRestoreDatabaseSchema}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
</>
)}
<Col span={24}>
<Row className="m-t-xss">
<Col span={24}>
<TabsPane
activeTab={activeTab}
className="flex-initial"
setActiveTab={activeTabHandler}
tabs={tabs}
/>
</Col>
<Col className="p-y-md" span={24}>
{activeTab === 1 && (
<Card className="h-full">
<Row gutter={[16, 16]}>
<Col data-testid="description-container" span={24}>
<Description
description={description}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={databaseSchemaFQN}
entityName={databaseSchemaName}
entityType={EntityType.DATABASE_SCHEMA}
hasEditAccess={
databaseSchemaPermission.EditDescription ||
databaseSchemaPermission.EditAll
}
isEdit={isEdit}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
{getSchemaTableList()}
</Row>
</Card>
)}
{activeTab === 2 && (
<Card className="p-t-xss p-b-md">
<Row className="entity-feed-list" id="activityfeed">
<Col offset={4} span={16}>
<ActivityFeedList
hideFeedFilter
hideThreadFilter
isEntityFeed
withSidePanel
className=""
deletePostHandler={deletePostHandler}
entityName={databaseSchemaName}
feedList={entityThread}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
/>
</Col>
</Row>
</Card>
)}
<Col
data-testid="observer-element"
id="observer-element"
ref={elementRef as RefObject<HTMLDivElement>}
span={24}>
{getLoader()}
</Col>
</Col>
</Row>
</Col> </Col>
</>
)}
<Col span={24}>
<Row className="m-t-xss">
<Col span={24}> <Col span={24}>
{threadLink ? ( <TabsPane
<ActivityThreadPanel activeTab={activeTab}
createThread={createThread} className="flex-initial"
deletePostHandler={deletePostHandler} setActiveTab={activeTabHandler}
open={Boolean(threadLink)} tabs={tabs}
postFeedHandler={postFeedHandler} />
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/>
) : null}
</Col> </Col>
</PageLayoutV1> <Col className="p-y-md" span={24}>
</PageContainerV1> {activeTab === 1 && (
) : ( <Card className="h-full">
<ErrorPlaceHolder {tableDataLoading ? (
className="mt-24" <Loader />
type={ERROR_PLACEHOLDER_TYPE.PERMISSION} ) : (
/> <Row gutter={[16, 16]}>
)} <Col data-testid="description-container" span={24}>
</> <Description
description={description}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={databaseSchemaFQN}
entityName={databaseSchemaName}
entityType={EntityType.DATABASE_SCHEMA}
hasEditAccess={
databaseSchemaPermission.EditDescription ||
databaseSchemaPermission.EditAll
}
isEdit={isEdit}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
{getSchemaTableList()}
</Row>
)}
</Card>
)}
{activeTab === 2 && (
<Card className="p-t-xss p-b-md">
<Row className="entity-feed-list" id="activityfeed">
<Col offset={4} span={16}>
<ActivityFeedList
hideFeedFilter
hideThreadFilter
isEntityFeed
withSidePanel
className=""
deletePostHandler={deletePostHandler}
entityName={databaseSchemaName}
feedList={entityThread}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
/>
</Col>
</Row>
</Card>
)}
<Col
data-testid="observer-element"
id="observer-element"
ref={elementRef as RefObject<HTMLDivElement>}
span={24}>
{getLoader()}
</Col>
</Col>
</Row>
</Col>
<Col span={24}>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/>
) : null}
</Col>
</PageLayoutV1>
</PageContainerV1>
) : (
<ErrorPlaceHolder
className="mt-24"
type={ERROR_PLACEHOLDER_TYPE.PERMISSION}
/>
)} )}
</Fragment> </Fragment>
); );

View File

@ -68,7 +68,7 @@ const TestSuitePage = () => {
const { isAdminUser } = useAuth(); const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
const [testSuites, setTestSuites] = useState<Array<TestSuite>>([]); const [testSuites, setTestSuites] = useState<Array<TestSuite>>([]);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(true);
const [testSuitePage, setTestSuitePage] = useState(INITIAL_PAGING_VALUE); const [testSuitePage, setTestSuitePage] = useState(INITIAL_PAGING_VALUE);
const [testSuitePaging, setTestSuitePaging] = useState<Paging>(pagingObject); const [testSuitePaging, setTestSuitePaging] = useState<Paging>(pagingObject);
const [selectedTestSuite, setSelectedTestSuite] = useState<TestSuite>(); const [selectedTestSuite, setSelectedTestSuite] = useState<TestSuite>();
@ -85,6 +85,7 @@ const TestSuitePage = () => {
}, [permissions]); }, [permissions]);
const handleShowDeleted = (checked: boolean) => { const handleShowDeleted = (checked: boolean) => {
setIsLoading(true);
setShowDeleted(checked); setShowDeleted(checked);
}; };
@ -232,11 +233,7 @@ const TestSuitePage = () => {
[createPermission] [createPermission]
); );
if (isLoading) { if (isEmpty(testSuites) && !showDeleted && !isLoading) {
return <Loader />;
}
if (isEmpty(testSuites) && !showDeleted) {
return <PageContainerV1>{errorPlaceHolder}</PageContainerV1>; return <PageContainerV1>{errorPlaceHolder}</PageContainerV1>;
} }
@ -284,7 +281,10 @@ const TestSuitePage = () => {
columns={columns} columns={columns}
data-testid="test-suite-table" data-testid="test-suite-table"
dataSource={testSuites} dataSource={testSuites}
loading={{ spinning: isLoading, indicator: <Loader /> }} loading={{
spinning: isLoading,
indicator: <Loader size="small" />,
}}
locale={{ locale={{
emptyText: <FilterTablePlaceHolder />, emptyText: <FilterTablePlaceHolder />,
}} }}
@ -306,6 +306,7 @@ const TestSuitePage = () => {
)} )}
</Row> </Row>
<DeleteWidgetModal <DeleteWidgetModal
isRecursiveDelete
afterDeleteAction={fetchTestSuites} afterDeleteAction={fetchTestSuites}
allowSoftDelete={!showDeleted} allowSoftDelete={!showDeleted}
entityId={selectedTestSuite?.id || ''} entityId={selectedTestSuite?.id || ''}

View File

@ -770,255 +770,273 @@ const DatabaseDetails: FunctionComponent = () => {
setIsEditable(false); setIsEditable(false);
}; };
const databaseTable = useMemo(() => {
if (schemaDataLoading) {
return <Loader />;
} else if (!isEmpty(schemaData)) {
return (
<Col span={24}>
<Table
bordered
className="table-shadow"
columns={tableColumn}
data-testid="database-databaseSchemas"
dataSource={schemaData}
loading={{
spinning: schemaDataLoading,
indicator: <Loader size="small" />,
}}
pagination={false}
rowKey="id"
size="small"
/>
{Boolean(
!isNil(databaseSchemaPaging.after) ||
!isNil(databaseSchemaPaging.before)
) && (
<NextPrevious
currentPage={currentPage}
pageSize={PAGE_SIZE}
paging={databaseSchemaPaging}
pagingHandler={databaseSchemaPagingHandler}
totalCount={databaseSchemaPaging.total}
/>
)}
</Col>
);
} else {
return <ErrorPlaceHolder />;
}
}, [
schemaDataLoading,
schemaData,
tableColumn,
databaseSchemaPaging,
currentPage,
databaseSchemaPagingHandler,
]);
if (isLoading) {
return <Loader />;
}
if (error) {
return (
<ErrorPlaceHolder>
<p data-testid="error-message">{error}</p>
</ErrorPlaceHolder>
);
}
return ( return (
<> <>
{isLoading ? ( {databasePermission.ViewAll || databasePermission.ViewBasic ? (
<Loader /> <PageContainerV1>
) : error ? ( <PageLayoutV1
<ErrorPlaceHolder> pageTitle={t('label.entity-detail-plural', {
<p data-testid="error-message">{error}</p> entity: getEntityName(database),
</ErrorPlaceHolder> })}>
) : ( {isDatabaseDetailsLoading ? (
<> <Skeleton
{databasePermission.ViewAll || databasePermission.ViewBasic ? ( active
<PageContainerV1> paragraph={{
<PageLayoutV1 rows: 3,
pageTitle={t('label.entity-detail-plural', { width: ['20%', '80%', '60%'],
entity: getEntityName(database), }}
})}> />
{isDatabaseDetailsLoading ? ( ) : (
<Skeleton <>
active <Col span={24}>
paragraph={{ {database && (
rows: 3, <EntityHeader
width: ['20%', '80%', '60%'], breadcrumb={slashedDatabaseName}
}} entityData={database}
/> entityType={EntityType.DATABASE}
) : ( extra={
<> <ManageButton
<Col span={24}> isRecursiveDelete
{database && ( allowSoftDelete={false}
<EntityHeader canDelete={databasePermission.Delete}
breadcrumb={slashedDatabaseName} entityFQN={databaseFQN}
entityData={database} entityId={databaseId}
entityName={databaseName}
entityType={EntityType.DATABASE} entityType={EntityType.DATABASE}
extra={
<ManageButton
isRecursiveDelete
allowSoftDelete={false}
canDelete={databasePermission.Delete}
entityFQN={databaseFQN}
entityId={databaseId}
entityName={databaseName}
entityType={EntityType.DATABASE}
/>
}
icon={
<img
className="h-8"
src={serviceTypeLogo(serviceType ?? '')}
/>
}
serviceName={database.service.name ?? ''}
/> />
)} }
</Col> icon={
<img
<Col className="m-t-xs" span={24}> className="h-8"
<Space src={serviceTypeLogo(serviceType ?? '')}
wrap />
align="center" }
data-testid="extrainfo" serviceName={database.service.name ?? ''}
size={4}>
{extraInfo.map((info, index) => (
<span
className="tw-flex tw-items-center"
data-testid={info.key || `info${index}`}
key={index}>
<EntitySummaryDetails
currentOwner={database?.owner}
data={info}
removeTier={handleRemoveTier}
tier={getTierTags(database?.tags ?? [])}
updateOwner={
databasePermission.EditOwner ||
databasePermission.EditAll
? handleUpdateOwner
: undefined
}
updateTier={
databasePermission.EditTags ||
databasePermission.EditAll
? handleUpdateTier
: undefined
}
/>
{extraInfo.length !== 1 &&
index < extraInfo.length - 1 ? (
<span className="tw-mx-1.5 tw-inline-block tw-text-gray-400">
{t('label.pipe-symbol')}
</span>
) : null}
</span>
))}
</Space>
</Col>
<Col className="m-t-xs" span={24}>
<Space
wrap
align="center"
data-testid="entity-tags"
size={6}
onClick={() => {
if (isTagEditable) {
// Fetch tags and terms only once
if (tagList.length === 0) {
fetchTags();
}
setIsEditable(true);
}
}}>
{!deleted && (
<TagsContainer
className="w-min-20"
dropDownHorzPosRight={false}
editable={isEditable}
isLoading={isTagLoading}
selectedTags={selectedTags}
showAddTagButton={
isTagEditable && isEmpty(selectedTags)
}
showEditTagButton={isTagEditable}
size="small"
tagList={tagList}
onCancel={() => {
handleTagSelection();
}}
onSelectionChange={(tags) => {
handleTagSelection(tags);
}}
/>
)}
</Space>
</Col>
</>
)}
<Col span={24}>
<Row className="m-t-md">
<Col span={24}>
<TabsPane
activeTab={activeTab}
className="flex-initial"
setActiveTab={activeTabHandler}
tabs={tabs}
/>
</Col>
<Col className="p-y-md" span={24}>
{activeTab === 1 && (
<Card className="h-full">
<Row gutter={[16, 16]}>
<Col data-testid="description-container" span={24}>
<Description
description={description}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={databaseFQN}
entityName={databaseName}
entityType={EntityType.DATABASE}
hasEditAccess={
databasePermission.EditDescription ||
databasePermission.EditAll
}
isEdit={isEdit}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
<Col span={24}>
<Table
bordered
className="table-shadow"
columns={tableColumn}
data-testid="database-databaseSchemas"
dataSource={schemaData}
loading={{
spinning: schemaDataLoading,
indicator: <Loader size="small" />,
}}
pagination={false}
rowKey="id"
size="small"
/>
{Boolean(
!isNil(databaseSchemaPaging.after) ||
!isNil(databaseSchemaPaging.before)
) && (
<NextPrevious
currentPage={currentPage}
pageSize={PAGE_SIZE}
paging={databaseSchemaPaging}
pagingHandler={databaseSchemaPagingHandler}
totalCount={databaseSchemaPaging.total}
/>
)}
</Col>
</Row>
</Card>
)}
{activeTab === 2 && (
<Card className="p-t-xss p-b-md">
<Row className="entity-feed-list" id="activityfeed">
<Col offset={4} span={16}>
<ActivityFeedList
hideFeedFilter
hideThreadFilter
isEntityFeed
withSidePanel
className=""
deletePostHandler={deletePostHandler}
entityName={databaseName}
feedList={entityThread}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
/>
</Col>
</Row>
</Card>
)}
<Col
data-testid="observer-element"
id="observer-element"
ref={elementRef as RefObject<HTMLDivElement>}
span={24}>
{getLoader()}
</Col>
</Col>
</Row>
</Col>
<Col span={24}>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/> />
) : null} )}
</Col> </Col>
</PageLayoutV1>
</PageContainerV1> <Col className="m-t-xs" span={24}>
) : ( <Space wrap align="center" data-testid="extrainfo" size={4}>
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} /> {extraInfo.map((info, index) => (
)} <span
</> className="tw-flex tw-items-center"
data-testid={info.key || `info${index}`}
key={index}>
<EntitySummaryDetails
currentOwner={database?.owner}
data={info}
removeTier={handleRemoveTier}
tier={getTierTags(database?.tags ?? [])}
updateOwner={
databasePermission.EditOwner ||
databasePermission.EditAll
? handleUpdateOwner
: undefined
}
updateTier={
databasePermission.EditTags ||
databasePermission.EditAll
? handleUpdateTier
: undefined
}
/>
{extraInfo.length !== 1 &&
index < extraInfo.length - 1 ? (
<span className="tw-mx-1.5 tw-inline-block tw-text-gray-400">
{t('label.pipe-symbol')}
</span>
) : null}
</span>
))}
</Space>
</Col>
<Col className="m-t-xs" span={24}>
<Space
wrap
align="center"
data-testid="entity-tags"
size={6}
onClick={() => {
if (isTagEditable) {
// Fetch tags and terms only once
if (tagList.length === 0) {
fetchTags();
}
setIsEditable(true);
}
}}>
{!deleted && (
<TagsContainer
className="w-min-20"
dropDownHorzPosRight={false}
editable={isEditable}
isLoading={isTagLoading}
selectedTags={selectedTags}
showAddTagButton={
isTagEditable && isEmpty(selectedTags)
}
showEditTagButton={isTagEditable}
size="small"
tagList={tagList}
onCancel={() => {
handleTagSelection();
}}
onSelectionChange={(tags) => {
handleTagSelection(tags);
}}
/>
)}
</Space>
</Col>
</>
)}
<Col span={24}>
<Row className="m-t-md">
<Col span={24}>
<TabsPane
activeTab={activeTab}
className="flex-initial"
setActiveTab={activeTabHandler}
tabs={tabs}
/>
</Col>
<Col className="p-y-md" span={24}>
{activeTab === 1 && (
<Card className="h-full">
<Row gutter={[16, 16]}>
<Col data-testid="description-container" span={24}>
<Description
description={description}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldThreadCount
)}
entityFqn={databaseFQN}
entityName={databaseName}
entityType={EntityType.DATABASE}
hasEditAccess={
databasePermission.EditDescription ||
databasePermission.EditAll
}
isEdit={isEdit}
onCancel={onCancel}
onDescriptionEdit={onDescriptionEdit}
onDescriptionUpdate={onDescriptionUpdate}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Col>
{databaseTable}
</Row>
</Card>
)}
{activeTab === 2 && (
<Card className="p-t-xss p-b-md">
<Row className="entity-feed-list" id="activityfeed">
<Col offset={4} span={16}>
<ActivityFeedList
hideFeedFilter
hideThreadFilter
isEntityFeed
withSidePanel
className=""
deletePostHandler={deletePostHandler}
entityName={databaseName}
feedList={entityThread}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
/>
</Col>
</Row>
</Card>
)}
<Col
data-testid="observer-element"
id="observer-element"
ref={elementRef as RefObject<HTMLDivElement>}
span={24}>
{getLoader()}
</Col>
</Col>
</Row>
</Col>
<Col span={24}>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/>
) : null}
</Col>
</PageLayoutV1>
</PageContainerV1>
) : (
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />
)} )}
</> </>
); );

View File

@ -159,6 +159,7 @@ const ServicePage: FunctionComponent = () => {
const [serviceDetails, setServiceDetails] = useState<ServicesType>(); const [serviceDetails, setServiceDetails] = useState<ServicesType>();
const [data, setData] = useState<Array<ServicePageData>>([]); const [data, setData] = useState<Array<ServicePageData>>([]);
const [isLoading, setIsLoading] = useState(!isOpenMetadataService); const [isLoading, setIsLoading] = useState(!isOpenMetadataService);
const [isServiceLoading, setIsServiceLoading] = useState(true);
const [dataModel, setDataModel] = useState<Array<ServicePageData>>([]); const [dataModel, setDataModel] = useState<Array<ServicePageData>>([]);
const [dataModelPaging, setDataModelPaging] = useState<Paging>(pagingObject); const [dataModelPaging, setDataModelPaging] = useState<Paging>(pagingObject);
const [paging, setPaging] = useState<Paging>(pagingObject); const [paging, setPaging] = useState<Paging>(pagingObject);
@ -230,6 +231,23 @@ const ServicePage: FunctionComponent = () => {
}, },
]; ];
const isTestingDisabled = useMemo(
() =>
!servicePermission.EditAll ||
(serviceCategory === ServiceCategory.METADATA_SERVICES &&
serviceFQN === OPEN_METADATA) ||
isUndefined(connectionDetails),
[servicePermission, serviceCategory, serviceFQN, connectionDetails]
);
const goToEditConnection = () => {
history.push(getEditConnectionPath(serviceName || '', serviceFQN || ''));
};
const handleDelete = () => {
setDeleteWidgetVisible(true);
};
const activeTabHandler = (tabValue: number) => { const activeTabHandler = (tabValue: number) => {
setActiveTab(tabValue); setActiveTab(tabValue);
const currentTabIndex = tabValue - 1; const currentTabIndex = tabValue - 1;
@ -416,7 +434,7 @@ const ServicePage: FunctionComponent = () => {
}; };
const fetchDatabases = async (paging?: PagingWithoutTotal) => { const fetchDatabases = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const { data, paging: resPaging } = await getDatabases( const { data, paging: resPaging } = await getDatabases(
serviceFQN, serviceFQN,
@ -431,12 +449,12 @@ const ServicePage: FunctionComponent = () => {
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
const fetchTopics = async (paging?: PagingWithoutTotal) => { const fetchTopics = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const { data, paging: resPaging } = await getTopics( const { data, paging: resPaging } = await getTopics(
serviceFQN, serviceFQN,
@ -448,12 +466,12 @@ const ServicePage: FunctionComponent = () => {
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
const fetchDashboards = async (paging?: PagingWithoutTotal) => { const fetchDashboards = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const { data, paging: resPaging } = await getDashboards( const { data, paging: resPaging } = await getDashboards(
serviceFQN, serviceFQN,
@ -465,12 +483,12 @@ const ServicePage: FunctionComponent = () => {
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
const fetchDashboardsDataModel = async (paging?: PagingWithoutTotal) => { const fetchDashboardsDataModel = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const { data, paging: resPaging } = await getDataModels( const { data, paging: resPaging } = await getDataModels(
serviceFQN, serviceFQN,
@ -482,12 +500,12 @@ const ServicePage: FunctionComponent = () => {
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
const fetchPipeLines = async (paging?: PagingWithoutTotal) => { const fetchPipeLines = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const { data, paging: resPaging } = await getPipelines( const { data, paging: resPaging } = await getPipelines(
serviceFQN, serviceFQN,
@ -499,12 +517,12 @@ const ServicePage: FunctionComponent = () => {
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
const fetchMlModal = async (paging?: PagingWithoutTotal) => { const fetchMlModal = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const { data, paging: resPaging } = await getMlModels( const { data, paging: resPaging } = await getMlModels(
serviceFQN, serviceFQN,
@ -516,12 +534,12 @@ const ServicePage: FunctionComponent = () => {
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
const fetchContainers = async (paging?: PagingWithoutTotal) => { const fetchContainers = async (paging?: PagingWithoutTotal) => {
setIsLoading(true); setIsServiceLoading(true);
try { try {
const response = await getContainers({ const response = await getContainers({
service: serviceFQN, service: serviceFQN,
@ -536,7 +554,7 @@ const ServicePage: FunctionComponent = () => {
setData([]); setData([]);
setPaging(pagingObject); setPaging(pagingObject);
} finally { } finally {
setIsLoading(false); setIsServiceLoading(false);
} }
}; };
@ -836,7 +854,7 @@ const ServicePage: FunctionComponent = () => {
setCurrentPage(activePage ?? 1); setCurrentPage(activePage ?? 1);
}; };
const getIngestionTab = () => { const ingestionTab = useMemo(() => {
if (!isAirflowAvailable) { if (!isAirflowAvailable) {
return <ErrorPlaceHolderIngestion />; return <ErrorPlaceHolderIngestion />;
} else if (isUndefined(airflowEndpoint) || isUndefined(serviceDetails)) { } else if (isUndefined(airflowEndpoint) || isUndefined(serviceDetails)) {
@ -863,11 +881,90 @@ const ServicePage: FunctionComponent = () => {
</div> </div>
); );
} }
}; }, [
isAirflowAvailable,
airflowEndpoint,
serviceDetails,
deleteIngestionById,
deployIngestion,
handleEnableDisableIngestion,
ingestions,
ingestionPaging,
servicePermission,
serviceName,
serviceList,
serviceFQN,
triggerIngestionById,
getAllIngestionWorkflows,
]);
const getDataModalTab = () => ( const dataModalTab = useMemo(
<DataModelTable data={dataModel} isLoading={isLoading} /> () => <DataModelTable data={dataModel} isLoading={isLoading} />,
[dataModel, isLoading]
); );
const testConnectionTab = useMemo(() => {
return (
<>
<Space className="w-full my-4 justify-end">
<Tooltip
title={
servicePermission.EditAll
? t('label.edit-entity', {
entity: t('label.connection'),
})
: t('message.no-permission-for-action')
}>
<Button
ghost
data-testid="edit-connection-button"
disabled={!servicePermission.EditAll}
type="primary"
onClick={goToEditConnection}>
{t('label.edit-entity', {
entity: t('label.connection'),
})}
</Button>
</Tooltip>
{allowTestConn && isAirflowAvailable && (
<Tooltip
title={
servicePermission.EditAll
? t('label.test-entity', {
entity: t('label.connection'),
})
: t('message.no-permission-for-action')
}>
<TestConnection
connectionType={serviceDetails?.serviceType ?? ''}
formData={connectionDetails as ConfigData}
isTestingDisabled={isTestingDisabled}
serviceCategory={serviceCategory as ServiceCategory}
serviceName={serviceDetails?.name}
// validation is not required as we have all the data available and not in edit mode
shouldValidateForm={false}
showDetails={false}
/>
</Tooltip>
)}
</Space>
<ServiceConnectionDetails
connectionDetails={connectionDetails || {}}
serviceCategory={serviceCategory}
serviceFQN={serviceDetails?.serviceType || ''}
/>
</>
);
}, [
servicePermission.EditAll,
allowTestConn,
isAirflowAvailable,
serviceDetails,
connectionDetails,
isTestingDisabled,
serviceCategory,
]);
useEffect(() => { useEffect(() => {
if ( if (
servicePageTabs(getCountLabel(serviceName))[activeTab - 1].path !== tab servicePageTabs(getCountLabel(serviceName))[activeTab - 1].path !== tab
@ -876,18 +973,6 @@ const ServicePage: FunctionComponent = () => {
} }
}, [tab]); }, [tab]);
const goToEditConnection = () => {
history.push(getEditConnectionPath(serviceName || '', serviceFQN || ''));
};
const handleEditConnection = () => {
goToEditConnection();
};
const handleDelete = () => {
setDeleteWidgetVisible(true);
};
useEffect(() => { useEffect(() => {
if (!isOpenMetadataService) { if (!isOpenMetadataService) {
fetchServicePermission(); fetchServicePermission();
@ -960,18 +1045,53 @@ const ServicePage: FunctionComponent = () => {
]; ];
}, [serviceName]); }, [serviceName]);
const entityServiceTab = useMemo(() => {
if (isServiceLoading) {
return <Loader />;
} else if (!isEmpty(data) && !isServiceLoading) {
return (
<div data-testid="table-container">
<Table
bordered
className="mt-4 table-shadow"
columns={tableColumn}
components={tableComponent}
data-testid="service-children-table"
dataSource={data}
pagination={false}
rowKey="id"
size="small"
/>
{Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
<NextPrevious
currentPage={currentPage}
pageSize={PAGE_SIZE}
paging={paging}
pagingHandler={pagingHandler}
totalCount={paging.total}
/>
)}
</div>
);
} else {
return <ErrorPlaceHolder />;
}
}, [
isServiceLoading,
data,
paging,
tableColumn,
tableComponent,
currentPage,
pagingHandler,
]);
useEffect(() => { useEffect(() => {
if (isAirflowAvailable && !isOpenMetadataService) { if (isAirflowAvailable && !isOpenMetadataService) {
getAllIngestionWorkflows(); getAllIngestionWorkflows();
} }
}, [isAirflowAvailable]); }, [isAirflowAvailable]);
const isTestingDisabled =
!servicePermission.EditAll ||
(serviceCategory === ServiceCategory.METADATA_SERVICES &&
serviceFQN === OPEN_METADATA) ||
isUndefined(connectionDetails);
if (isLoading) { if (isLoading) {
return ( return (
<PageContainerV1> <PageContainerV1>
@ -1077,96 +1197,10 @@ const ServicePage: FunctionComponent = () => {
tabs={tabs} tabs={tabs}
/> />
<Col span={24}> <Col span={24}>
{activeTab === 1 && {activeTab === 1 && entityServiceTab}
(isEmpty(data) ? ( {activeTab === 4 && dataModalTab}
<ErrorPlaceHolder /> {activeTab === 2 && ingestionTab}
) : ( {activeTab === 3 && testConnectionTab}
<div data-testid="table-container">
<Table
bordered
className="mt-4 table-shadow"
columns={tableColumn}
components={tableComponent}
data-testid="service-children-table"
dataSource={data}
loading={{
spinning: isLoading,
indicator: <Loader size="small" />,
}}
pagination={false}
rowKey="id"
size="small"
/>
{Boolean(
!isNil(paging.after) || !isNil(paging.before)
) && (
<NextPrevious
currentPage={currentPage}
pageSize={PAGE_SIZE}
paging={paging}
pagingHandler={pagingHandler}
totalCount={paging.total}
/>
)}
</div>
))}
{activeTab === 4 && getDataModalTab()}
{activeTab === 2 && getIngestionTab()}
{activeTab === 3 && (
<>
<Space className="w-full my-4 justify-end">
<Tooltip
title={
servicePermission.EditAll
? t('label.edit-entity', {
entity: t('label.connection'),
})
: t('message.no-permission-for-action')
}>
<Button
ghost
data-testid="edit-connection-button"
disabled={!servicePermission.EditAll}
type="primary"
onClick={handleEditConnection}>
{t('label.edit-entity', {
entity: t('label.connection'),
})}
</Button>
</Tooltip>
{allowTestConn && isAirflowAvailable && (
<Tooltip
title={
servicePermission.EditAll
? t('label.test-entity', {
entity: t('label.connection'),
})
: t('message.no-permission-for-action')
}>
<TestConnection
connectionType={serviceDetails?.serviceType ?? ''}
formData={connectionDetails as ConfigData}
isTestingDisabled={isTestingDisabled}
serviceCategory={
serviceCategory as ServiceCategory
}
serviceName={serviceDetails?.name}
// validation is not required as we have all the data available and not in edit mode
shouldValidateForm={false}
showDetails={false}
/>
</Tooltip>
)}
</Space>
<ServiceConnectionDetails
connectionDetails={connectionDetails || {}}
serviceCategory={serviceCategory}
serviceFQN={serviceDetails?.serviceType || ''}
/>
</>
)}
</Col> </Col>
</Col> </Col>
</Row> </Row>