UI : Fix the Webhook add button issue in settings (#7222)

* Fix the Webhook add button issue in settings

* addressing comments

* fixed failing cypress

* miner cypress fix

Co-authored-by: Shailesh Parmar <shailesh.parmar.webdev@gmail.com>
This commit is contained in:
Ashish Gupta 2022-09-06 07:36:07 +05:30 committed by GitHub
parent 23aa773ad3
commit 535f4b4f4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 219 additions and 208 deletions

View File

@ -111,7 +111,7 @@ describe('Glossary page should work properly', () => {
it('Create new glossary flow should work properly', () => {
// check for no data placeholder
cy.contains('glossaries').should('be.visible');
cy.contains('Add New Glossary').should('be.visible');
// Redirecting to add glossary page
cy.get('[data-testid="add-webhook-button"]').should('be.visible').click();
@ -456,6 +456,6 @@ describe('Glossary page should work properly', () => {
.should('be.visible');
cy.get('.Toastify__close-button > svg > path').should('be.visible').click();
cy.wait(500);
cy.contains('glossaries').should('be.visible');
cy.contains('Add New Glossary').should('be.visible');
});
});

View File

@ -11,9 +11,10 @@
* limitations under the License.
*/
import { Button, Col, Row, Space, Table, Tooltip } from 'antd';
import { Button, Col, Row, Space, Switch, Table, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { getBots } from '../../axiosAPIs/botsAPI';
@ -22,6 +23,7 @@ import {
INITIAL_PAGING_VALUE,
PAGE_SIZE,
} from '../../constants/constants';
import { BOTS_DOCS } from '../../constants/docs.constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
import { EntityType } from '../../enums/entity.enum';
import { Bot } from '../../generated/entity/bot';
@ -32,6 +34,7 @@ import { checkPermission } from '../../utils/PermissionsUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import DeleteWidgetModal from '../common/DeleteWidget/DeleteWidgetModal';
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
import NextPrevious from '../common/next-previous/NextPrevious';
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
import Loader from '../Loader/Loader';
@ -39,7 +42,11 @@ import { usePermissionProvider } from '../PermissionProvider/PermissionProvider'
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import { BotListV1Props } from './BotListV1.interfaces';
const BotListV1 = ({ showDeleted }: BotListV1Props) => {
const BotListV1 = ({
showDeleted,
handleAddBotClick,
handleShowDeleted,
}: BotListV1Props) => {
const { permissions } = usePermissionProvider();
const [botUsers, setBotUsers] = useState<Bot[]>([]);
const [paging, setPaging] = useState<Paging>({} as Paging);
@ -47,6 +54,14 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE);
const [handleErrorPlaceholder, setHandleErrorPlaceholder] = useState(false);
const createPermission = checkPermission(
Operation.Create,
ResourceEntity.BOT,
permissions
);
const deletePermission = useMemo(
() => checkPermission(Operation.Delete, ResourceEntity.BOT, permissions),
[permissions]
@ -66,6 +81,11 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
});
setPaging(paging);
setBotUsers(data);
if (!showDeleted && isEmpty(data)) {
setHandleErrorPlaceholder(true);
} else {
setHandleErrorPlaceholder(false);
}
} catch (error) {
showErrorToast((error as AxiosError).message);
} finally {
@ -152,45 +172,88 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
fetchBots(showDeleted);
}, [showDeleted]);
return (
<>
<Row>
<Col span={24}>
<Table
columns={columns}
dataSource={botUsers}
loading={{
spinning: loading,
indicator: <Loader size="small" />,
}}
pagination={false}
size="small"
/>
</Col>
<Col span={24}>
{paging.total > PAGE_SIZE && (
<NextPrevious
currentPage={currentPage}
pageSize={PAGE_SIZE}
paging={paging}
pagingHandler={handlePageChange}
totalCount={paging.total}
return handleErrorPlaceholder ? (
<ErrorPlaceHolder
buttons={
<div className="tw-text-lg tw-text-center">
<Button
ghost
title="Add Team"
type="primary"
onClick={handleAddBotClick}>
Add Bot
</Button>
</div>
}
doc={BOTS_DOCS}
heading="Bot"
type="ADD_DATA"
/>
) : (
<Row gutter={[16, 16]}>
<Col flex={1} />
<Col>
<Space size={16}>
<Space align="end" size={5}>
<Switch
checked={showDeleted}
id="switch-deleted"
size="small"
onClick={handleShowDeleted}
/>
)}
</Col>
</Row>
<label htmlFor="switch-deleted">Show deleted</label>
</Space>
<DeleteWidgetModal
afterDeleteAction={handleDeleteAction}
entityId={selectedUser?.id || ''}
entityName={selectedUser?.displayName || ''}
entityType={EntityType.BOT}
visible={Boolean(selectedUser)}
onCancel={() => {
setSelectedUser(undefined);
}}
/>
</>
<Tooltip
title={createPermission ? 'Add Bot' : NO_PERMISSION_FOR_ACTION}>
<Button
disabled={!createPermission}
type="primary"
onClick={handleAddBotClick}>
Add Bot
</Button>
</Tooltip>
</Space>
</Col>
<Col span={24}>
<Row>
<Col span={24}>
<Table
columns={columns}
dataSource={botUsers}
loading={{
spinning: loading,
indicator: <Loader size="small" />,
}}
pagination={false}
size="small"
/>
</Col>
<Col span={24}>
{paging.total > PAGE_SIZE && (
<NextPrevious
currentPage={currentPage}
pageSize={PAGE_SIZE}
paging={paging}
pagingHandler={handlePageChange}
totalCount={paging.total}
/>
)}
</Col>
</Row>
<DeleteWidgetModal
afterDeleteAction={handleDeleteAction}
entityId={selectedUser?.id || ''}
entityName={selectedUser?.displayName || ''}
entityType={EntityType.BOT}
visible={Boolean(selectedUser)}
onCancel={() => {
setSelectedUser(undefined);
}}
/>
</Col>
</Row>
);
};

View File

@ -13,4 +13,6 @@
export interface BotListV1Props {
showDeleted: boolean;
handleAddBotClick: () => void;
handleShowDeleted: (checked: boolean) => void;
}

View File

@ -562,11 +562,19 @@ const GlossaryV1 = ({
) : (
<PageLayoutV1>
<ErrorPlaceHolder
buttonId="add-webhook-button"
buttonLabel="Add New Glossary"
buttonListener={handleAddGlossaryClick}
buttons={
<ButtonAntd
ghost
className="tw-h-8 tw-rounded tw-my-3"
data-testid="add-webhook-button"
disabled={!createGlossaryPermission}
type="primary"
onClick={handleAddGlossaryClick}>
Add New Glossary
</ButtonAntd>
}
doc={GLOSSARIES_DOCS}
heading="glossaries"
heading="Glossary"
type="ADD_DATA"
/>
</PageLayoutV1>

View File

@ -103,59 +103,61 @@ const SampleDataTable: FunctionComponent<Props> = ({ sampleData }: Props) => {
</button>
) : null}
<div
className="tw-table-responsive tw-overflow-x-auto tw-table-container"
ref={tableRef}>
<>
{sampleData?.rows?.length && sampleData?.columns?.length ? (
<table
className="tw-min-w-max tw-w-full tw-table-auto"
data-testid="sample-data-table">
<thead>
<tr className="tableHead-row">
{sampleData.columns.map((column) => {
<div
className="tw-table-responsive tw-overflow-x-auto tw-table-container"
ref={tableRef}>
<table
className="tw-min-w-max tw-w-full tw-table-auto"
data-testid="sample-data-table">
<thead>
<tr className="tableHead-row">
{sampleData.columns.map((column) => {
return (
<th
className="tableHead-cell"
data-testid="column-name"
key={column.name}>
<Space direction="vertical" size={0}>
<span>{column.name}</span>
<span className="tw-text-grey-muted">
({lowerCase(column.dataType)})
</span>
</Space>
</th>
);
})}
</tr>
</thead>
<tbody className="tw-text-gray-600 tw-text-sm">
{sampleData?.rows?.map((row, rowIndex) => {
return (
<th
className="tableHead-cell"
data-testid="column-name"
key={column.name}>
<Space direction="vertical" size={0}>
<span>{column.name}</span>
<span className="tw-text-grey-muted">
({lowerCase(column.dataType)})
</span>
</Space>
</th>
<tr
className={classNames(
'tableBody-row',
!isEven(rowIndex + 1) ? 'odd-row' : null
)}
data-testid="row"
key={rowIndex}>
{row.map((data, index) => {
return (
<td
className="tableBody-cell"
data-testid="cell"
key={index}>
<RowData data={data} />
</td>
);
})}
</tr>
);
})}
</tr>
</thead>
<tbody className="tw-text-gray-600 tw-text-sm">
{sampleData?.rows?.map((row, rowIndex) => {
return (
<tr
className={classNames(
'tableBody-row',
!isEven(rowIndex + 1) ? 'odd-row' : null
)}
data-testid="row"
key={rowIndex}>
{row.map((data, index) => {
return (
<td
className="tableBody-cell"
data-testid="cell"
key={index}>
<RowData data={data} />
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</tbody>
</table>
</div>
) : (
<div className="tw-flex tw-flex-col tw-justify-center tw-font-medium tw-items-center tw-p-8">
<div className="tw-w-full tw-flex tw-flex-col tw-justify-center tw-font-medium tw-items-center tw-p-8">
<div className="tw-mt-12">
<img alt="No Service" src={NoDataFoundPlaceHolder} width={120} />
</div>
@ -180,7 +182,7 @@ const SampleDataTable: FunctionComponent<Props> = ({ sampleData }: Props) => {
</div>
</div>
)}
</div>
</>
</div>
);
};

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card, Col, Row, Tooltip, Typography } from 'antd';
import { Button as ButtonAntd, Card, Col, Row, Tooltip } from 'antd';
import { isEmpty } from 'lodash';
import React, { Fragment, useMemo } from 'react';
import { Link, useHistory } from 'react-router-dom';
@ -23,10 +23,7 @@ import {
} from '../../constants/constants';
import { CONNECTORS_DOCS } from '../../constants/docs.constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
import {
AddPlaceHolder,
servicesDisplayName,
} from '../../constants/services.const';
import { servicesDisplayName } from '../../constants/services.const';
import { ServiceCategory } from '../../enums/service.enum';
import { Operation } from '../../generated/entity/policies/policy';
import { Paging } from '../../generated/type/paging';
@ -43,6 +40,7 @@ import {
getResourceEntityFromServiceCategory,
} from '../../utils/ServiceUtils';
import { Button } from '../buttons/Button/Button';
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
import NextPrevious from '../common/next-previous/NextPrevious';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
@ -64,8 +62,6 @@ const Services = ({
currentPage,
onPageChange,
}: ServicesProps) => {
const { Paragraph, Link: AntdLink } = Typography;
const { isAuthDisabled } = useAuthContext();
const history = useHistory();
const handleAddServiceClick = () => {
@ -187,41 +183,27 @@ const Services = ({
)}
</Fragment>
) : (
<div className="tw-flex tw-items-center tw-flex-col">
<div className="tw-mt-24">
<img alt="No Service" src={AddPlaceHolder} width={120} />
</div>
<div className="tw-mt-8 tw-max-w-x tw-text-center">
<Paragraph style={{ marginBottom: '4px' }}>
{' '}
Adding a new {servicesDisplayName[serviceName]} is easy, just give
it a spin!
</Paragraph>
<Paragraph>
{' '}
Still need help? Refer to our{' '}
<AntdLink href={CONNECTORS_DOCS} target="_blank">
docs
</AntdLink>{' '}
for more information.
</Paragraph>
<div className="tw-text-lg tw-text-center">
<Col span={24}>
<ErrorPlaceHolder
buttons={
<NonAdminAction
position="bottom"
title={TITLE_FOR_NON_ADMIN_ACTION}>
<Button
<ButtonAntd
ghost
data-testid="add-service-button"
size="small"
theme="primary"
variant="outlined"
type="primary"
onClick={handleAddServiceClick}>
Add new {servicesDisplayName[serviceName]}
</Button>
</NonAdminAction>{' '}
</div>
</div>
</div>
</ButtonAntd>
</NonAdminAction>
}
doc={CONNECTORS_DOCS}
heading={servicesDisplayName[serviceName]}
type="ADD_DATA"
/>
</Col>
)}
</Row>
);

View File

@ -26,6 +26,7 @@ import {
TITLE_FOR_NON_ADMIN_ACTION,
TITLE_FOR_NON_OWNER_ACTION,
} from '../../constants/constants';
import { TEAMS_DOCS } from '../../constants/docs.constants';
import { ADMIN_ONLY_ACCESSIBLE_SECTION } from '../../enums/common.enum';
import { EntityType } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum';
@ -672,7 +673,9 @@ const TeamDetails = ({
</NonAdminAction>
</div>
}
doc={TEAMS_DOCS}
heading="Teams"
type="ADD_DATA"
/>
)}

View File

@ -38,6 +38,7 @@ import {
getTeamAndUserDetailsPath,
PAGE_SIZE,
} from '../../constants/constants';
import { TEAMS_DOCS } from '../../constants/docs.constants';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
@ -993,7 +994,9 @@ const TeamDetailsV1 = ({
</Button>
</div>
}
doc={TEAMS_DOCS}
heading="Teams"
type="ADD_DATA"
/>
)}

View File

@ -11,13 +11,14 @@
* limitations under the License.
*/
import { Col, Row, Select, Space, Tooltip } from 'antd';
import { Button as ButtonAntd, Col, Row, Select, Space, Tooltip } from 'antd';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { isEmpty, isNil } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { deleteWebhook } from '../../axiosAPIs/webhookAPI';
import { PAGE_SIZE } from '../../constants/constants';
import { WEBHOOK_DOCS } from '../../constants/docs.constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
import { WebhookType } from '../../generated/api/events/createWebhook';
import { Webhook } from '../../generated/entity/events/webhook';
@ -85,20 +86,22 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
? 'Add Webhook'
: NO_PERMISSION_FOR_ACTION
}>
<Button
<ButtonAntd
ghost
className={classNames('tw-h-8 tw-rounded tw-my-3')}
data-testid="add-webhook-button"
disabled={!addWebhookPermission}
size="small"
theme="primary"
variant="contained"
type="primary"
onClick={onAddWebhook}>
Add {WEBHOOKS_INTEGRATION[webhookType]}
</Button>
</ButtonAntd>
</Tooltip>
</p>
}
doc={WEBHOOK_DOCS}
heading={message}
type="ADD_DATA"
/>
);
},
@ -125,9 +128,7 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
}, [data, selectedStatus]);
if (data.length === 0) {
return fetchErrorPlaceHolder(
`No ${WEBHOOKS_INTEGRATION[webhookType]} found`
);
return fetchErrorPlaceHolder(WEBHOOKS_INTEGRATION[webhookType]);
}
return (

View File

@ -13,11 +13,8 @@
import { Typography } from 'antd';
import React from 'react';
import {
default as AddPlaceHolder,
default as NoDataFoundPlaceHolder,
} from '../../../assets/img/no-data-placeholder.svg';
import { Button } from '../../buttons/Button/Button';
import AddPlaceHolder from '../../../assets/img/add-placeholder.svg';
import NoDataFoundPlaceHolder from '../../../assets/img/no-data-placeholder.svg';
type Props = {
children?: React.ReactNode;
@ -30,16 +27,7 @@ type Props = {
buttonId?: string;
};
const ErrorPlaceHolder = ({
doc,
type,
children,
heading,
buttonLabel,
buttonListener,
buttons,
buttonId,
}: Props) => {
const ErrorPlaceHolder = ({ doc, type, children, heading, buttons }: Props) => {
const { Paragraph, Link } = Typography;
return type === 'ADD_DATA' ? (
@ -48,7 +36,7 @@ const ErrorPlaceHolder = ({
{' '}
<img data-testid="no-data-image" src={AddPlaceHolder} width="100" />
</div>
<div className="tw-flex tw-flex-col tw-items-center tw-mt-10 tw-text-base tw-font-medium">
<div className="tw-flex tw-flex-col tw-items-center tw-mt-9 tw-text-base tw-font-medium">
<Paragraph style={{ marginBottom: '4px' }}>
{' '}
Adding a new {heading} is easy, just give it a spin!
@ -62,19 +50,7 @@ const ErrorPlaceHolder = ({
for more information.
</Paragraph>
<div className="tw-text-lg tw-text-center">
{buttons ? (
buttons
) : (
<Button
data-testId={buttonId}
size="small"
theme="primary"
onClick={buttonListener}>
{buttonLabel}
</Button>
)}
</div>
<div className="tw-text-lg tw-text-center">{buttons}</div>
</div>
</>
) : (

View File

@ -12,3 +12,11 @@ export const CONNECTORS_DOCS =
export const WORKFLOWS_METADATA_DOCS =
'https://docs.open-metadata.org/openmetadata/ingestion/workflows/metadata';
export const BOTS_DOCS =
'https://docs.open-metadata.org/main-concepts/metadata-standard/schemas/entity/bot';
export const TEAMS_DOCS = 'https://docs.open-metadata.org/openmetadata/users';
export const WEBHOOK_DOCS =
'https://docs.open-metadata.org/developers/webhooks';

View File

@ -11,19 +11,12 @@
* limitations under the License.
*/
import { Button, Col, Row, Space, Switch, Tooltip } from 'antd';
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import BotListV1 from '../../components/BotListV1/BotListV1.component';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
import { getCreateUserPath } from '../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { checkPermission } from '../../utils/PermissionsUtils';
export const BotsPageV1 = () => {
const { permissions } = usePermissionProvider();
const history = useHistory();
const [showDeleted, setShowDeleted] = useState(false);
@ -35,42 +28,12 @@ export const BotsPageV1 = () => {
setShowDeleted(checked);
};
const createPermission = checkPermission(
Operation.Create,
ResourceEntity.BOT,
permissions
);
return (
<Row gutter={[16, 16]}>
<Col flex={1} />
<Col>
<Space size={16}>
<Space align="end" size={5}>
<Switch
checked={showDeleted}
id="switch-deleted"
size="small"
onClick={handleShowDeleted}
/>
<label htmlFor="switch-deleted">Show deleted</label>
</Space>
<Tooltip
title={createPermission ? 'Add Bot' : NO_PERMISSION_FOR_ACTION}>
<Button
disabled={!createPermission}
type="primary"
onClick={handleAddBotClick}>
Add Bot
</Button>
</Tooltip>
</Space>
</Col>
<Col span={24}>
<BotListV1 showDeleted={showDeleted} />
</Col>
</Row>
<BotListV1
handleAddBotClick={handleAddBotClick}
handleShowDeleted={handleShowDeleted}
showDeleted={showDeleted}
/>
);
};