mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-04 05:16:47 +00:00
chore(ui): update add button styling for expandable cards (#21197)
* chore(ui): update add button styling for expandable cards * fix tests * fix test for expandable card * fix data test id * fix playwright tests * fix tests * fix metric playwright * fix test
This commit is contained in:
parent
76834ce90a
commit
b74816ceed
@ -211,10 +211,14 @@ entities.forEach((EntityClass) => {
|
||||
});
|
||||
|
||||
test('Tag Add, Update and Remove', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await entity.tag(page, 'PersonalData.Personal', 'PII.None');
|
||||
});
|
||||
|
||||
test('Glossary Term Add, Update and Remove', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await entity.glossaryTerm(
|
||||
page,
|
||||
EntityDataClass.glossaryTerm1.responseData,
|
||||
|
@ -224,7 +224,7 @@ export class EntityClass {
|
||||
await page
|
||||
.getByTestId('KnowledgePanel.Tags')
|
||||
.getByTestId('tags-container')
|
||||
.getByTestId('Add')
|
||||
.getByTestId('add-tag')
|
||||
.isVisible();
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ export class EntityClass {
|
||||
await page
|
||||
.locator(`[${rowSelector}="${rowId}"]`)
|
||||
.getByTestId('tags-container')
|
||||
.getByTestId('Add')
|
||||
.getByTestId('add-tag')
|
||||
.isVisible();
|
||||
}
|
||||
|
||||
@ -281,7 +281,7 @@ export class EntityClass {
|
||||
await page
|
||||
.getByTestId('KnowledgePanel.GlossaryTerms')
|
||||
.getByTestId('glossary-container')
|
||||
.getByTestId('Add')
|
||||
.getByTestId('add-tag')
|
||||
.isVisible();
|
||||
}
|
||||
|
||||
@ -321,7 +321,7 @@ export class EntityClass {
|
||||
await page
|
||||
.locator(`[${rowSelector}="${rowId}"]`)
|
||||
.getByTestId('glossary-container')
|
||||
.getByTestId('Add')
|
||||
.getByTestId('add-tag')
|
||||
.isVisible();
|
||||
}
|
||||
|
||||
|
@ -532,14 +532,14 @@ export const checkDataConsumerPermissions = async (page: Page) => {
|
||||
// Check right panel add tags button
|
||||
await expect(
|
||||
page.locator(
|
||||
'[data-testid="KnowledgePanel.Tags"] [data-testid="tags-container"] [data-testid="entity-tags"] .tag-chip-add-button'
|
||||
'[data-testid="KnowledgePanel.Tags"] [data-testid="tags-container"] [data-testid="add-tag"]'
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
// Check right panel add glossary term button
|
||||
await expect(
|
||||
page.locator(
|
||||
'[data-testid="KnowledgePanel.GlossaryTerms"] [data-testid="glossary-container"] [data-testid="entity-tags"] .tag-chip-add-button'
|
||||
'[data-testid="KnowledgePanel.GlossaryTerms"] [data-testid="glossary-container"] [data-testid="add-tag"]'
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
@ -617,14 +617,14 @@ export const checkStewardPermissions = async (page: Page) => {
|
||||
// Check right panel add tags button
|
||||
await expect(
|
||||
page.locator(
|
||||
'[data-testid="KnowledgePanel.Tags"] [data-testid="tags-container"] [data-testid="entity-tags"] .tag-chip-add-button'
|
||||
'[data-testid="KnowledgePanel.Tags"] [data-testid="tags-container"] [data-testid="add-tag"]'
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
// Check right panel add glossary term button
|
||||
await expect(
|
||||
page.locator(
|
||||
'[data-testid="KnowledgePanel.GlossaryTerms"] [data-testid="glossary-container"] [data-testid="entity-tags"] .tag-chip-add-button'
|
||||
'[data-testid="KnowledgePanel.GlossaryTerms"] [data-testid="glossary-container"] [data-testid="add-tag"]'
|
||||
)
|
||||
).toBeVisible();
|
||||
|
||||
|
@ -14,13 +14,14 @@ import { Typography } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||
import { TabSpecificField } from '../../../enums/entity.enum';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { getOwnerVersionLabel } from '../../../utils/EntityVersionUtils';
|
||||
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../common/TagButton/TagButton.component';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../common/IconButtons/EditIconButton';
|
||||
import { UserTeamSelectableList } from '../../common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
|
||||
|
||||
@ -48,15 +49,22 @@ export const OwnerLabelV2 = <
|
||||
<Typography.Text className="text-sm font-medium">
|
||||
{t('label.owner-plural')}
|
||||
</Typography.Text>
|
||||
{(permissions.EditOwners || permissions.EditAll) &&
|
||||
data.owners &&
|
||||
data.owners.length > 0 && (
|
||||
<UserTeamSelectableList
|
||||
hasPermission={permissions.EditOwners || permissions.EditAll}
|
||||
listHeight={200}
|
||||
multiple={{ user: true, team: false }}
|
||||
owner={data.owners}
|
||||
onUpdate={handleUpdatedOwner}>
|
||||
{(permissions.EditOwners || permissions.EditAll) && (
|
||||
<UserTeamSelectableList
|
||||
hasPermission={permissions.EditOwners || permissions.EditAll}
|
||||
listHeight={200}
|
||||
multiple={{ user: true, team: false }}
|
||||
owner={data.owners}
|
||||
onUpdate={handleUpdatedOwner}>
|
||||
{isEmpty(data.owners) ? (
|
||||
<PlusIconButton
|
||||
data-testid="add-owner"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.owner-plural'),
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-owner"
|
||||
@ -65,8 +73,9 @@ export const OwnerLabelV2 = <
|
||||
entity: t('label.owner-plural'),
|
||||
})}
|
||||
/>
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
)}
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
[data, permissions, handleUpdatedOwner]
|
||||
@ -85,24 +94,6 @@ export const OwnerLabelV2 = <
|
||||
TabSpecificField.OWNERS,
|
||||
permissions.EditOwners || permissions.EditAll
|
||||
)}
|
||||
|
||||
{data.owners?.length === 0 &&
|
||||
(permissions.EditOwners || permissions.EditAll) && (
|
||||
<UserTeamSelectableList
|
||||
hasPermission={permissions.EditOwners || permissions.EditAll}
|
||||
listHeight={200}
|
||||
multiple={{ user: true, team: false }}
|
||||
owner={data.owners}
|
||||
onUpdate={(updatedUser) => handleUpdatedOwner(updatedUser)}>
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
dataTestId="add-owner"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
/>
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
</ExpandableCard>
|
||||
);
|
||||
};
|
||||
|
@ -13,14 +13,15 @@
|
||||
import { Typography } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import React, { useMemo } from 'react';
|
||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||
import { TabSpecificField } from '../../../enums/entity.enum';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { ChangeDescription } from '../../../generated/type/changeEvent';
|
||||
import { getOwnerVersionLabel } from '../../../utils/EntityVersionUtils';
|
||||
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../common/TagButton/TagButton.component';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../common/IconButtons/EditIconButton';
|
||||
import { UserTeamSelectableList } from '../../common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
|
||||
|
||||
@ -70,7 +71,7 @@ export const ReviewerLabelV2 = <
|
||||
data-testid="heading-name">
|
||||
{t('label.reviewer-plural')}
|
||||
</Typography.Text>
|
||||
{hasEditReviewerAccess && hasReviewers && (
|
||||
{hasEditReviewerAccess && (
|
||||
<UserTeamSelectableList
|
||||
previewSelected
|
||||
hasPermission={hasEditReviewerAccess}
|
||||
@ -80,14 +81,24 @@ export const ReviewerLabelV2 = <
|
||||
owner={assignedReviewers ?? []}
|
||||
popoverProps={{ placement: 'topLeft' }}
|
||||
onUpdate={handleReviewerSave}>
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-reviewer-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.reviewer-plural'),
|
||||
})}
|
||||
/>
|
||||
{hasReviewers ? (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-reviewer-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.reviewer-plural'),
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<PlusIconButton
|
||||
data-testid="Add"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.reviewer-plural'),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
</div>
|
||||
@ -102,33 +113,16 @@ export const ReviewerLabelV2 = <
|
||||
}}
|
||||
dataTestId="glossary-reviewer"
|
||||
isExpandDisabled={!hasReviewers}>
|
||||
<div data-testid="glossary-reviewer-name">
|
||||
{getOwnerVersionLabel(
|
||||
data,
|
||||
isVersionView ?? false,
|
||||
TabSpecificField.REVIEWERS,
|
||||
hasEditReviewerAccess
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasEditReviewerAccess && !hasReviewers && (
|
||||
<UserTeamSelectableList
|
||||
previewSelected
|
||||
hasPermission={hasEditReviewerAccess}
|
||||
label={t('label.reviewer-plural')}
|
||||
listHeight={200}
|
||||
multiple={{ user: true, team: false }}
|
||||
owner={assignedReviewers ?? []}
|
||||
popoverProps={{ placement: 'topLeft' }}
|
||||
onUpdate={handleReviewerSave}>
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
/>
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
{hasReviewers ? (
|
||||
<div data-testid="glossary-reviewer-name">
|
||||
{getOwnerVersionLabel(
|
||||
data,
|
||||
isVersionView ?? false,
|
||||
TabSpecificField.REVIEWERS,
|
||||
hasEditReviewerAccess
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</ExpandableCard>
|
||||
);
|
||||
};
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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 { DataProduct } from '../../../generated/entity/domains/dataProduct';
|
||||
import { Paging } from '../../../generated/type/paging';
|
||||
import { DataProductSelectOption } from '../DataProductsSelectList/DataProductSelectList.interface';
|
||||
|
||||
export type DataProductsSelectFormProps = {
|
||||
placeholder: string;
|
||||
defaultValue: string[];
|
||||
onChange?: (value: string[]) => void;
|
||||
onSubmit: (values: DataProduct[]) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
fetchApi: (
|
||||
search: string,
|
||||
page: number
|
||||
) => Promise<{
|
||||
data: DataProductSelectOption[];
|
||||
paging: Paging;
|
||||
}>;
|
||||
};
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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 } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import DataProductsSelectForm from './DataProductsSelectForm';
|
||||
|
||||
const mockOnSubmit = jest.fn();
|
||||
const mockOnCancel = jest.fn();
|
||||
|
||||
describe('Data Products Form Page', () => {
|
||||
it('renders without errors', () => {
|
||||
const { getByTestId } = render(
|
||||
<DataProductsSelectForm
|
||||
defaultValue={[]}
|
||||
fetchApi={() => Promise.resolve({ data: [], paging: { total: 0 } })}
|
||||
placeholder="Select products"
|
||||
onCancel={mockOnCancel}
|
||||
onSubmit={mockOnSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
// Ensure that the component renders without errors
|
||||
expect(getByTestId('data-product-selector')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onCancel function when Cancel button is clicked', () => {
|
||||
const { getByTestId } = render(
|
||||
<DataProductsSelectForm
|
||||
defaultValue={[]}
|
||||
fetchApi={() => Promise.resolve({ data: [], paging: { total: 0 } })}
|
||||
placeholder="Select products"
|
||||
onCancel={mockOnCancel}
|
||||
onSubmit={mockOnSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
const cancelButton = getByTestId('cancelAssociatedTag');
|
||||
userEvent.click(cancelButton);
|
||||
|
||||
// Ensure that the onCancel function is called when the Cancel button is clicked
|
||||
expect(mockOnCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls onSubmit when the Save button is clicked', () => {
|
||||
const { getByTestId } = render(
|
||||
<DataProductsSelectForm
|
||||
defaultValue={[]}
|
||||
fetchApi={() => Promise.resolve({ data: [], paging: { total: 0 } })}
|
||||
placeholder="Select products"
|
||||
onCancel={mockOnCancel}
|
||||
onSubmit={mockOnSubmit}
|
||||
/>
|
||||
);
|
||||
const selectRef = getByTestId('data-product-selector').querySelector(
|
||||
'.ant-select-selector'
|
||||
);
|
||||
if (selectRef) {
|
||||
userEvent.click(selectRef);
|
||||
}
|
||||
|
||||
// Simulate the Save button click
|
||||
userEvent.click(getByTestId('saveAssociatedTag'));
|
||||
|
||||
// Check if onSubmit was called with the selected value
|
||||
expect(mockOnSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnSubmit).toHaveBeenCalledWith(expect.any(Array));
|
||||
});
|
||||
});
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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 { CheckOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { Button, Col, Row, Space } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { DataProductsSelectRef } from '../DataProductsSelectList/DataProductSelectList.interface';
|
||||
import DataProductsSelectList from '../DataProductsSelectList/DataProductsSelectList';
|
||||
import { DataProductsSelectFormProps } from './DataProductsSelectForm.interface';
|
||||
|
||||
const DataProductsSelectForm = ({
|
||||
fetchApi,
|
||||
defaultValue,
|
||||
placeholder,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: DataProductsSelectFormProps) => {
|
||||
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
||||
const selectRef = useRef<DataProductsSelectRef>(null);
|
||||
|
||||
const onSave = () => {
|
||||
setIsSubmitLoading(true);
|
||||
const value = selectRef.current?.getSelectValue() ?? [];
|
||||
onSubmit(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gutter={[0, 8]}>
|
||||
<Col className="gutter-row d-flex justify-end" span={24}>
|
||||
<Space align="center">
|
||||
<Button
|
||||
className="p-x-05"
|
||||
data-testid="cancelAssociatedTag"
|
||||
disabled={isSubmitLoading}
|
||||
icon={<CloseOutlined size={12} />}
|
||||
size="small"
|
||||
onClick={onCancel}
|
||||
/>
|
||||
<Button
|
||||
className="p-x-05"
|
||||
data-testid="saveAssociatedTag"
|
||||
icon={<CheckOutlined size={12} />}
|
||||
loading={isSubmitLoading}
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={onSave}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
<Col className="gutter-row" span={24}>
|
||||
<DataProductsSelectList
|
||||
defaultValue={defaultValue}
|
||||
fetchOptions={fetchApi}
|
||||
mode="multiple"
|
||||
placeholder={placeholder}
|
||||
ref={selectRef}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataProductsSelectForm;
|
@ -18,7 +18,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ReactComponent as DataProductIcon } from '../../../assets/svg/ic-data-product.svg';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../constants/constants';
|
||||
import { TAG_CONSTANT, TAG_START_WITH } from '../../../constants/Tag.constants';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { DataProduct } from '../../../generated/entity/domains/dataProduct';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
@ -26,9 +25,11 @@ import { fetchDataProductsElasticSearch } from '../../../rest/dataProductAPI';
|
||||
import { getEntityName } from '../../../utils/EntityUtils';
|
||||
import { getEntityDetailsPath } from '../../../utils/RouterUtils';
|
||||
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../common/IconButtons/EditIconButton';
|
||||
import TagsV1 from '../../Tag/TagsV1/TagsV1.component';
|
||||
import DataProductsSelectForm from '../DataProductSelectForm/DataProductsSelectForm';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../common/IconButtons/EditIconButton';
|
||||
import DataProductsSelectList from '../DataProductsSelectList/DataProductsSelectList';
|
||||
interface DataProductsContainerProps {
|
||||
showHeader?: boolean;
|
||||
hasPermission: boolean;
|
||||
@ -77,11 +78,13 @@ const DataProductsContainer = ({
|
||||
|
||||
const autoCompleteFormSelectContainer = useMemo(() => {
|
||||
return (
|
||||
<DataProductsSelectForm
|
||||
<DataProductsSelectList
|
||||
open
|
||||
defaultValue={(dataProducts ?? []).map(
|
||||
(item) => item.fullyQualifiedName ?? ''
|
||||
)}
|
||||
fetchApi={fetchAPI}
|
||||
fetchOptions={fetchAPI}
|
||||
mode="multiple"
|
||||
placeholder={t('label.data-product-plural')}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSave}
|
||||
@ -139,6 +142,14 @@ const DataProductsContainer = ({
|
||||
<Typography.Text className={classNames('text-sm font-medium')}>
|
||||
{t('label.data-product-plural')}
|
||||
</Typography.Text>
|
||||
{showAddTagButton && (
|
||||
<PlusIconButton
|
||||
data-testid="add-data-product"
|
||||
size="small"
|
||||
title={t('label.add-data-product')}
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
)}
|
||||
{hasPermission && !isUndefined(activeDomain) && (
|
||||
<Row gutter={12}>
|
||||
{!isEmpty(dataProducts) && (
|
||||
@ -159,41 +170,26 @@ const DataProductsContainer = ({
|
||||
</Space>
|
||||
)
|
||||
);
|
||||
}, [showHeader, dataProducts, hasPermission]);
|
||||
|
||||
const addTagButton = useMemo(
|
||||
() =>
|
||||
showAddTagButton ? (
|
||||
<Col
|
||||
className="m-t-xss"
|
||||
data-testid="add-data-product"
|
||||
onClick={handleAddClick}>
|
||||
<TagsV1 startWith={TAG_START_WITH.PLUS} tag={TAG_CONSTANT} />
|
||||
</Col>
|
||||
) : null,
|
||||
[showAddTagButton]
|
||||
);
|
||||
}, [showHeader, dataProducts, hasPermission, showAddTagButton]);
|
||||
|
||||
const cardProps = useMemo(() => {
|
||||
return {
|
||||
title: header,
|
||||
};
|
||||
}, [header]);
|
||||
}, [header, showAddTagButton, isEditMode]);
|
||||
|
||||
return (
|
||||
<ExpandableCard
|
||||
cardProps={cardProps}
|
||||
dataTestId="data-products-container"
|
||||
isExpandDisabled={isEmpty(dataProducts)}>
|
||||
{!isEditMode && (
|
||||
{isEditMode ? (
|
||||
autoCompleteFormSelectContainer
|
||||
) : isEmpty(renderDataProducts) ? null : (
|
||||
<Row data-testid="data-products-list">
|
||||
<Col className="flex flex-wrap gap-2">
|
||||
{addTagButton}
|
||||
{renderDataProducts}
|
||||
</Col>
|
||||
<Col className="flex flex-wrap gap-2">{renderDataProducts}</Col>
|
||||
</Row>
|
||||
)}
|
||||
{isEditMode && autoCompleteFormSelectContainer}
|
||||
</ExpandableCard>
|
||||
);
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { SelectProps } from 'antd';
|
||||
import { DataProduct } from '../../../generated/entity/domains/dataProduct';
|
||||
import { Paging } from '../../../generated/type/paging';
|
||||
|
||||
@ -18,12 +19,13 @@ export type DataProductSelectOption = {
|
||||
value: DataProduct;
|
||||
};
|
||||
|
||||
export interface DataProductsSelectListProps {
|
||||
export interface DataProductsSelectListProps extends SelectProps {
|
||||
mode?: 'multiple';
|
||||
placeholder?: string;
|
||||
debounceTimeout?: number;
|
||||
defaultValue?: string[];
|
||||
onChange?: (newValue: DataProduct[]) => void;
|
||||
onSubmit?: (newValue: DataProduct[]) => Promise<void>;
|
||||
onCancel?: () => void;
|
||||
fetchOptions: (
|
||||
search: string,
|
||||
page: number
|
||||
|
@ -10,17 +10,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Select, Space, Tooltip, Typography } from 'antd';
|
||||
import { Button, Select, Space, Tooltip, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { debounce } from 'lodash';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataProduct } from '../../../generated/entity/domains/dataProduct';
|
||||
import { Paging } from '../../../generated/type/paging';
|
||||
import { getEntityName } from '../../../utils/EntityUtils';
|
||||
@ -30,160 +24,171 @@ import Loader from '../../common/Loader/Loader';
|
||||
import {
|
||||
DataProductSelectOption,
|
||||
DataProductsSelectListProps,
|
||||
DataProductsSelectRef,
|
||||
} from './DataProductSelectList.interface';
|
||||
|
||||
const DataProductsSelectList = forwardRef<
|
||||
DataProductsSelectRef,
|
||||
DataProductsSelectListProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
mode,
|
||||
onChange,
|
||||
fetchOptions,
|
||||
debounceTimeout = 800,
|
||||
defaultValue,
|
||||
...props
|
||||
}: DataProductsSelectListProps,
|
||||
ref
|
||||
) => {
|
||||
const selectRef = useRef(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasContentLoading, setHasContentLoading] = useState(false);
|
||||
const [options, setOptions] = useState<DataProductSelectOption[]>([]);
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [selectedValue, setSelectedValue] = useState<DataProduct[]>([]);
|
||||
const DataProductsSelectList = ({
|
||||
mode,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
fetchOptions,
|
||||
debounceTimeout = 800,
|
||||
defaultValue,
|
||||
...props
|
||||
}: DataProductsSelectListProps) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasContentLoading, setHasContentLoading] = useState(false);
|
||||
const [options, setOptions] = useState<DataProductSelectOption[]>([]);
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [selectedValue, setSelectedValue] = useState<DataProduct[]>([]);
|
||||
const { t } = useTranslation();
|
||||
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
||||
|
||||
const loadOptions = useCallback(
|
||||
async (value: string) => {
|
||||
setOptions([]);
|
||||
setIsLoading(true);
|
||||
const onSave = async () => {
|
||||
setIsSubmitLoading(true);
|
||||
try {
|
||||
await onSubmit?.(selectedValue);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadOptions = useCallback(
|
||||
async (value: string) => {
|
||||
setOptions([]);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await fetchOptions(value, 1);
|
||||
setOptions(res.data);
|
||||
setPaging(res.paging);
|
||||
setSearchValue(value);
|
||||
setCurrentPage(1);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchOptions]
|
||||
);
|
||||
|
||||
const debounceFetcher = useMemo(
|
||||
() => debounce(loadOptions, debounceTimeout),
|
||||
[loadOptions, debounceTimeout]
|
||||
);
|
||||
|
||||
const selectOptions = useMemo(() => {
|
||||
return options.map((item) => {
|
||||
return {
|
||||
label: item.label,
|
||||
displayName: (
|
||||
<Space className="w-full" direction="vertical" size={0}>
|
||||
<Typography.Paragraph ellipsis className="text-grey-muted m-0 p-0">
|
||||
{getEntityName(item.value.domain)}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Text ellipsis>
|
||||
{getEntityName(item.value)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
),
|
||||
value: item.value.fullyQualifiedName,
|
||||
};
|
||||
});
|
||||
}, [options]);
|
||||
|
||||
const onScroll = async (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { currentTarget } = e;
|
||||
if (
|
||||
currentTarget.scrollTop + currentTarget.offsetHeight ===
|
||||
currentTarget.scrollHeight
|
||||
) {
|
||||
if (options.length < paging.total) {
|
||||
try {
|
||||
const res = await fetchOptions(value, 1);
|
||||
setOptions(res.data);
|
||||
setHasContentLoading(true);
|
||||
const res = await fetchOptions(searchValue, currentPage + 1);
|
||||
setOptions((prev) => [...prev, ...res.data]);
|
||||
setPaging(res.paging);
|
||||
setSearchValue(value);
|
||||
setCurrentPage(1);
|
||||
setCurrentPage((prev) => prev + 1);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchOptions]
|
||||
);
|
||||
|
||||
const debounceFetcher = useMemo(
|
||||
() => debounce(loadOptions, debounceTimeout),
|
||||
[loadOptions, debounceTimeout]
|
||||
);
|
||||
|
||||
const selectOptions = useMemo(() => {
|
||||
return options.map((item) => {
|
||||
return {
|
||||
label: item.label,
|
||||
displayName: (
|
||||
<Space className="w-full" direction="vertical" size={0}>
|
||||
<Typography.Paragraph
|
||||
ellipsis
|
||||
className="text-grey-muted m-0 p-0">
|
||||
{getEntityName(item.value.domain)}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Text ellipsis>
|
||||
{getEntityName(item.value)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
),
|
||||
value: item.value.fullyQualifiedName,
|
||||
};
|
||||
});
|
||||
}, [options]);
|
||||
|
||||
const onScroll = async (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { currentTarget } = e;
|
||||
if (
|
||||
currentTarget.scrollTop + currentTarget.offsetHeight ===
|
||||
currentTarget.scrollHeight
|
||||
) {
|
||||
if (options.length < paging.total) {
|
||||
try {
|
||||
setHasContentLoading(true);
|
||||
const res = await fetchOptions(searchValue, currentPage + 1);
|
||||
setOptions((prev) => [...prev, ...res.data]);
|
||||
setPaging(res.paging);
|
||||
setCurrentPage((prev) => prev + 1);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setHasContentLoading(false);
|
||||
}
|
||||
setHasContentLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const dropdownRender = (menu: React.ReactElement) => (
|
||||
<>
|
||||
{menu}
|
||||
{hasContentLoading ? <Loader size="small" /> : null}
|
||||
</>
|
||||
);
|
||||
const dropdownRender = (menu: React.ReactElement) => (
|
||||
<>
|
||||
{menu}
|
||||
{hasContentLoading ? <Loader size="small" /> : null}
|
||||
<Space className="p-sm p-b-xss p-l-xs custom-dropdown-render" size={8}>
|
||||
<Button
|
||||
className="update-btn"
|
||||
data-testid="saveAssociatedTag"
|
||||
loading={isSubmitLoading}
|
||||
size="small"
|
||||
onClick={onSave}>
|
||||
{t('label.update')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="cancelAssociatedTag"
|
||||
size="small"
|
||||
onClick={onCancel}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
|
||||
const onSelectChange = (value: string[]) => {
|
||||
const entityObj = value.reduce((result: DataProduct[], item) => {
|
||||
const option = options.find((option) => option.label === item);
|
||||
if (option) {
|
||||
result.push(option.value);
|
||||
}
|
||||
const onSelectChange = (value: string[]) => {
|
||||
const entityObj = value.reduce((result: DataProduct[], item) => {
|
||||
const option = options.find((option) => option.label === item);
|
||||
if (option) {
|
||||
result.push(option.value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, []);
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
setSelectedValue(entityObj as DataProduct[]);
|
||||
};
|
||||
setSelectedValue(entityObj as DataProduct[]);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getSelectValue() {
|
||||
return selectedValue;
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<Select
|
||||
autoFocus
|
||||
showSearch
|
||||
className="w-full"
|
||||
data-testid="data-product-selector"
|
||||
defaultValue={defaultValue}
|
||||
dropdownRender={dropdownRender}
|
||||
filterOption={false}
|
||||
mode={mode}
|
||||
notFoundContent={isLoading ? <Loader size="small" /> : null}
|
||||
optionLabelProp="label"
|
||||
ref={selectRef}
|
||||
tagRender={tagRender}
|
||||
onChange={onSelectChange}
|
||||
onFocus={() => loadOptions('')}
|
||||
onPopupScroll={onScroll}
|
||||
onSearch={debounceFetcher}
|
||||
{...props}>
|
||||
{selectOptions.map(({ label, value, displayName }) => (
|
||||
<Select.Option data-testid={`tag-${value}`} key={label} value={value}>
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
mouseEnterDelay={1.5}
|
||||
placement="leftTop"
|
||||
title={label}
|
||||
trigger="hover">
|
||||
{displayName}
|
||||
</Tooltip>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<Select
|
||||
autoFocus
|
||||
showSearch
|
||||
className="w-full"
|
||||
data-testid="data-product-selector"
|
||||
defaultValue={defaultValue}
|
||||
dropdownRender={dropdownRender}
|
||||
filterOption={false}
|
||||
mode={mode}
|
||||
notFoundContent={isLoading ? <Loader size="small" /> : null}
|
||||
optionLabelProp="label"
|
||||
tagRender={tagRender}
|
||||
onChange={onSelectChange}
|
||||
onFocus={() => loadOptions('')}
|
||||
onPopupScroll={onScroll}
|
||||
onSearch={debounceFetcher}
|
||||
{...props}>
|
||||
{selectOptions.map(({ label, value, displayName }) => (
|
||||
<Select.Option data-testid={`tag-${value}`} key={label} value={value}>
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
mouseEnterDelay={1.5}
|
||||
placement="leftTop"
|
||||
title={label}
|
||||
trigger="hover">
|
||||
{displayName}
|
||||
</Tooltip>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataProductsSelectList;
|
||||
|
@ -12,24 +12,24 @@
|
||||
*/
|
||||
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Button, Col, Drawer, Row, Space, Tooltip, Typography } from 'antd';
|
||||
import { Col, Drawer, Row, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg';
|
||||
import { ReactComponent as IconUser } from '../../../../assets/svg/user.svg';
|
||||
import { DE_ACTIVE_COLOR } from '../../../../constants/constants';
|
||||
import { EntityType } from '../../../../enums/entity.enum';
|
||||
import { Query } from '../../../../generated/entity/data/query';
|
||||
import { TagLabel } from '../../../../generated/type/tagLabel';
|
||||
import { TagLabel, TagSource } from '../../../../generated/type/tagLabel';
|
||||
import { getEntityName } from '../../../../utils/EntityUtils';
|
||||
import { getUserPath } from '../../../../utils/RouterUtils';
|
||||
import DescriptionV1 from '../../../common/EntityDescription/DescriptionV1';
|
||||
import ExpandableCard from '../../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
|
||||
import Loader from '../../../common/Loader/Loader';
|
||||
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
|
||||
import ProfilePicture from '../../../common/ProfilePicture/ProfilePicture';
|
||||
import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import TagsInput from '../../../TagsInput/TagsInput.component';
|
||||
import TagsContainerV2 from '../../../Tag/TagsContainerV2/TagsContainerV2';
|
||||
import { TableQueryRightPanelProps } from './TableQueryRightPanel.interface';
|
||||
|
||||
const TableQueryRightPanel = ({
|
||||
@ -79,66 +79,72 @@ const TableQueryRightPanel = ({
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<Row className="m-y-md p-x-md w-full" gutter={[16, 40]}>
|
||||
<Row className="m-y-md p-x-md w-full" gutter={[16, 20]}>
|
||||
<Col span={24}>
|
||||
<Space className="relative" direction="vertical" size={4}>
|
||||
<Space align="center" className="w-full" size={0}>
|
||||
<Typography.Text className="right-panel-label">
|
||||
{t('label.owner-plural')}
|
||||
</Typography.Text>
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: (
|
||||
<Space align="center" className="w-full" size={0}>
|
||||
<Typography.Text className="right-panel-label">
|
||||
{t('label.owner-plural')}
|
||||
</Typography.Text>
|
||||
|
||||
{(EditAll || EditOwners) && (
|
||||
<UserTeamSelectableList
|
||||
hasPermission={EditAll || EditOwners}
|
||||
multiple={{ user: true, team: false }}
|
||||
owner={query.owners}
|
||||
onUpdate={(updatedUsers) =>
|
||||
handleUpdateOwner(updatedUsers)
|
||||
}>
|
||||
<Tooltip
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.owner-lowercase-plural'),
|
||||
})}>
|
||||
<Button
|
||||
className="cursor-pointer flex-center"
|
||||
data-testid="edit-owner"
|
||||
icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />}
|
||||
size="small"
|
||||
type="text"
|
||||
/>
|
||||
</Tooltip>
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
</Space>
|
||||
{(EditAll || EditOwners) && (
|
||||
<UserTeamSelectableList
|
||||
hasPermission={EditAll || EditOwners}
|
||||
multiple={{ user: true, team: false }}
|
||||
owner={query.owners}
|
||||
onUpdate={(updatedUsers) =>
|
||||
handleUpdateOwner(updatedUsers)
|
||||
}>
|
||||
<EditIconButton
|
||||
data-testid="edit-owner"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.owner-lowercase-plural'),
|
||||
})}
|
||||
/>
|
||||
</UserTeamSelectableList>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
}}>
|
||||
<OwnerLabel hasPermission={false} owners={query.owners} />
|
||||
</Space>
|
||||
</ExpandableCard>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Space direction="vertical" size={4}>
|
||||
<DescriptionV1
|
||||
description={query?.description || ''}
|
||||
entityFullyQualifiedName={query?.fullyQualifiedName}
|
||||
entityType={EntityType.QUERY}
|
||||
hasEditAccess={EditDescription || EditAll}
|
||||
showCommentsIcon={false}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<TagsInput
|
||||
editable={EditAll || EditTags}
|
||||
tags={query?.tags || []}
|
||||
onTagsUpdate={handleTagSelection}
|
||||
<DescriptionV1
|
||||
wrapInCard
|
||||
className="w-full"
|
||||
description={query?.description || ''}
|
||||
entityFullyQualifiedName={query?.fullyQualifiedName}
|
||||
entityType={EntityType.QUERY}
|
||||
hasEditAccess={EditDescription || EditAll}
|
||||
showCommentsIcon={false}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Space className="m-b-md" direction="vertical" size={4}>
|
||||
<Typography.Text
|
||||
className="right-panel-label"
|
||||
data-testid="users">
|
||||
{t('label.user-plural')}
|
||||
</Typography.Text>
|
||||
<TagsContainerV2
|
||||
newLook
|
||||
permission={EditAll || EditTags}
|
||||
selectedTags={query?.tags || []}
|
||||
showTaskHandler={false}
|
||||
tagType={TagSource.Classification}
|
||||
onSelectionChange={handleTagSelection}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: (
|
||||
<Typography.Text
|
||||
className="right-panel-label"
|
||||
data-testid="users">
|
||||
{t('label.user-plural')}
|
||||
</Typography.Text>
|
||||
),
|
||||
}}>
|
||||
{query.users && query.users.length ? (
|
||||
<Space wrap size={6}>
|
||||
{query.users.map((user) => (
|
||||
@ -161,15 +167,19 @@ const TableQueryRightPanel = ({
|
||||
})}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</Space>
|
||||
</ExpandableCard>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Space className="m-b-md" direction="vertical" size={4}>
|
||||
<Typography.Text
|
||||
className="right-panel-label"
|
||||
data-testid="used-by">
|
||||
{t('label.used-by')}
|
||||
</Typography.Text>
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: (
|
||||
<Typography.Text
|
||||
className="right-panel-label"
|
||||
data-testid="used-by">
|
||||
{t('label.used-by')}
|
||||
</Typography.Text>
|
||||
),
|
||||
}}>
|
||||
{query.usedBy && query.usedBy.length ? (
|
||||
<Space wrap size={6}>
|
||||
{query.usedBy.map((user) => (
|
||||
@ -186,7 +196,7 @@ const TableQueryRightPanel = ({
|
||||
})}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
</Space>
|
||||
</ExpandableCard>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
@ -83,13 +83,13 @@ jest.mock('../../../common/EntityDescription/DescriptionV1', () => {
|
||||
</div>
|
||||
));
|
||||
});
|
||||
jest.mock('../../../TagsInput/TagsInput.component', () => {
|
||||
return jest.fn().mockImplementation(({ onTagsUpdate }) => (
|
||||
jest.mock('../../../Tag/TagsContainerV2/TagsContainerV2', () => {
|
||||
return jest.fn().mockImplementation(({ onSelectionChange }) => (
|
||||
<div>
|
||||
TagsInput.component
|
||||
<button
|
||||
data-testid="update-tags-button"
|
||||
onClick={() => onTagsUpdate(mockNewTag)}>
|
||||
onClick={() => onSelectionChange(mockNewTag)}>
|
||||
{' '}
|
||||
Update Tags
|
||||
</button>
|
||||
|
@ -51,7 +51,6 @@ const TableTags = <T extends TableUnion>({
|
||||
entityType={entityType}
|
||||
permission={hasTagEditAccess && !isReadOnly}
|
||||
selectedTags={tags}
|
||||
showHeader={false}
|
||||
showInlineEditButton={showInlineEditTagButton}
|
||||
sizeCap={TAG_LIST_SIZE}
|
||||
tagType={type}
|
||||
|
@ -15,14 +15,15 @@ import classNames from 'classnames';
|
||||
import { t } from 'i18next';
|
||||
import { cloneDeep, includes, isEmpty, isEqual } from 'lodash';
|
||||
import { default as React, useMemo } from 'react';
|
||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||
import { TabSpecificField } from '../../../enums/entity.enum';
|
||||
import { Domain } from '../../../generated/entity/domains/domain';
|
||||
import { EntityReference } from '../../../generated/tests/testCase';
|
||||
import { getOwnerVersionLabel } from '../../../utils/EntityVersionUtils';
|
||||
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../common/TagButton/TagButton.component';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../common/IconButtons/EditIconButton';
|
||||
import { UserSelectableList } from '../../common/UserSelectableList/UserSelectableList.component';
|
||||
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
|
||||
|
||||
@ -69,53 +70,44 @@ export const DomainExpertWidget = () => {
|
||||
data-testid="domain-expert-heading-name">
|
||||
{t('label.expert-plural')}
|
||||
</Typography.Text>
|
||||
{editOwnerPermission && domain.experts && domain.experts.length > 0 && (
|
||||
{editOwnerPermission && (
|
||||
<UserSelectableList
|
||||
hasPermission
|
||||
popoverProps={{ placement: 'topLeft' }}
|
||||
selectedUsers={domain.experts ?? []}
|
||||
onUpdate={handleExpertsUpdate}>
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-expert-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.expert-plural'),
|
||||
})}
|
||||
/>
|
||||
{isEmpty(domain.experts) ? (
|
||||
<PlusIconButton
|
||||
data-testid="Add"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.expert-plural'),
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-expert-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.expert-plural'),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</UserSelectableList>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<div>
|
||||
{getOwnerVersionLabel(
|
||||
domain,
|
||||
isVersionView ?? false,
|
||||
TabSpecificField.EXPERTS,
|
||||
editAllPermission
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{editOwnerPermission && domain.experts?.length === 0 && (
|
||||
<UserSelectableList
|
||||
hasPermission={editOwnerPermission}
|
||||
popoverProps={{ placement: 'topLeft' }}
|
||||
selectedUsers={domain.experts ?? []}
|
||||
onUpdate={handleExpertsUpdate}>
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
/>
|
||||
</UserSelectableList>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
const content = isEmpty(domain.experts) ? null : (
|
||||
<div>
|
||||
{getOwnerVersionLabel(
|
||||
domain,
|
||||
isVersionView ?? false,
|
||||
TabSpecificField.EXPERTS,
|
||||
editAllPermission
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -15,7 +15,6 @@ import { Space, Typography } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { cloneDeep, isEmpty, isEqual } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ReactComponent as PlusIcon } from '../../../../assets/svg/plus-primary.svg';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../../constants/constants';
|
||||
import { EntityField } from '../../../../constants/Feeds.constants';
|
||||
import {
|
||||
@ -30,8 +29,10 @@ import {
|
||||
} from '../../../../utils/EntityVersionUtils';
|
||||
import { renderReferenceElement } from '../../../../utils/GlossaryUtils';
|
||||
import ExpandableCard from '../../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../../common/TagButton/TagButton.component';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../../common/IconButtons/EditIconButton';
|
||||
import { useGenericContext } from '../../../Customization/GenericProvider/GenericProvider';
|
||||
import GlossaryTermReferencesModal from '../GlossaryTermReferencesModal.component';
|
||||
|
||||
@ -130,47 +131,49 @@ const GlossaryTermReferences = () => {
|
||||
<Typography.Text className="text-sm font-medium">
|
||||
{t('label.reference-plural')}
|
||||
</Typography.Text>
|
||||
{references.length > 0 && permissions.EditAll && (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-button"
|
||||
disabled={!permissions.EditAll}
|
||||
size="small"
|
||||
onClick={() => setIsViewMode(false)}
|
||||
/>
|
||||
)}
|
||||
{permissions.EditAll &&
|
||||
(isEmpty(references) ? (
|
||||
<PlusIconButton
|
||||
data-testid="term-references-add-button"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.reference-plural'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setIsViewMode(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-button"
|
||||
disabled={!permissions.EditAll}
|
||||
size="small"
|
||||
onClick={() => setIsViewMode(false)}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
);
|
||||
|
||||
return (
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: header,
|
||||
}}
|
||||
dataTestId="references-container"
|
||||
isExpandDisabled={isEmpty(references)}>
|
||||
{isVersionView ? (
|
||||
getVersionReferenceElements()
|
||||
) : (
|
||||
<div className="d-flex flex-wrap">
|
||||
{references.map((ref) => renderReferenceElement(ref))}
|
||||
{permissions.EditAll && references.length === 0 && (
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
dataTestId="term-references-add-button"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
onClick={() => {
|
||||
setIsViewMode(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!permissions.EditAll && references.length === 0 && (
|
||||
<div>{NO_DATA_PLACEHOLDER}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<>
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: header,
|
||||
}}
|
||||
dataTestId="references-container"
|
||||
isExpandDisabled={isEmpty(references)}>
|
||||
{isVersionView ? (
|
||||
getVersionReferenceElements()
|
||||
) : !permissions.EditAll || !isEmpty(references) ? (
|
||||
<div className="d-flex flex-wrap">
|
||||
{references.map((ref) => renderReferenceElement(ref))}
|
||||
{!permissions.EditAll && references.length === 0 && (
|
||||
<div>{NO_DATA_PLACEHOLDER}</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</ExpandableCard>
|
||||
|
||||
<GlossaryTermReferencesModal
|
||||
isVisible={!isViewMode}
|
||||
@ -180,7 +183,7 @@ const GlossaryTermReferences = () => {
|
||||
}}
|
||||
onSave={handleReferencesSave}
|
||||
/>
|
||||
</ExpandableCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,6 @@ import { Button, Select, Space, Typography } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { cloneDeep, isEmpty, isEqual } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ReactComponent as PlusIcon } from '../../../../assets/svg/plus-primary.svg';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../../constants/constants';
|
||||
import { EntityField } from '../../../../constants/Feeds.constants';
|
||||
import { GlossaryTerm } from '../../../../generated/entity/data/glossaryTerm';
|
||||
@ -27,7 +26,10 @@ import {
|
||||
getDiffByFieldName,
|
||||
} from '../../../../utils/EntityVersionUtils';
|
||||
import ExpandableCard from '../../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../../common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../../common/TagButton/TagButton.component';
|
||||
import { useGenericContext } from '../../../Customization/GenericProvider/GenericProvider';
|
||||
|
||||
@ -42,32 +44,22 @@ const GlossaryTermSynonyms = () => {
|
||||
permissions,
|
||||
} = useGenericContext<GlossaryTerm>();
|
||||
|
||||
const getSynonyms = () => (
|
||||
<div className="d-flex flex-wrap">
|
||||
{synonyms.map((synonym) => (
|
||||
<TagButton
|
||||
className="glossary-synonym-tag"
|
||||
key={synonym}
|
||||
label={synonym}
|
||||
/>
|
||||
))}
|
||||
{permissions.EditAll && synonyms.length === 0 && (
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
dataTestId="synonym-add-button"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
onClick={() => {
|
||||
setIsViewMode(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!permissions.EditAll && synonyms.length === 0 && (
|
||||
<div>{NO_DATA_PLACEHOLDER}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const getSynonyms = () =>
|
||||
!permissions.EditAll || !isEmpty(synonyms) ? (
|
||||
<div className="d-flex flex-wrap">
|
||||
{synonyms.map((synonym) => (
|
||||
<TagButton
|
||||
className="glossary-synonym-tag"
|
||||
key={synonym}
|
||||
label={synonym}
|
||||
/>
|
||||
))}
|
||||
|
||||
{!permissions.EditAll && synonyms.length === 0 && (
|
||||
<div>{NO_DATA_PLACEHOLDER}</div>
|
||||
)}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const getSynonymsContainer = useCallback(() => {
|
||||
if (!isVersionView) {
|
||||
@ -174,17 +166,30 @@ const GlossaryTermSynonyms = () => {
|
||||
<Typography.Text className="text-sm font-medium">
|
||||
{t('label.synonym-plural')}
|
||||
</Typography.Text>
|
||||
{permissions.EditAll && synonyms.length > 0 && isViewMode && (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.synonym-plural'),
|
||||
})}
|
||||
onClick={() => setIsViewMode(false)}
|
||||
/>
|
||||
)}
|
||||
{permissions.EditAll &&
|
||||
isViewMode &&
|
||||
(isEmpty(synonyms) ? (
|
||||
<PlusIconButton
|
||||
data-testid="synonym-add-button"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.synonym-plural'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setIsViewMode(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.synonym-plural'),
|
||||
})}
|
||||
onClick={() => setIsViewMode(false)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -18,7 +18,6 @@ import { isArray, isEmpty, isUndefined } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ReactComponent as IconTerm } from '../../../../assets/svg/book.svg';
|
||||
import { ReactComponent as PlusIcon } from '../../../../assets/svg/plus-primary.svg';
|
||||
import TagSelectForm from '../../../../components/Tag/TagsSelectForm/TagsSelectForm.component';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../../constants/constants';
|
||||
import { EntityField } from '../../../../constants/Feeds.constants';
|
||||
@ -41,7 +40,10 @@ import { VersionStatus } from '../../../../utils/EntityVersionUtils.interface';
|
||||
import { getGlossaryPath } from '../../../../utils/RouterUtils';
|
||||
import { SelectOption } from '../../../common/AsyncSelectList/AsyncSelectList.interface';
|
||||
import ExpandableCard from '../../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../../common/IconButtons/EditIconButton';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../../common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../../common/TagButton/TagButton.component';
|
||||
import { useGenericContext } from '../../../Customization/GenericProvider/GenericProvider';
|
||||
|
||||
@ -193,21 +195,8 @@ const RelatedTerms = () => {
|
||||
() =>
|
||||
isVersionView ? (
|
||||
getVersionRelatedTerms()
|
||||
) : (
|
||||
) : !permissions.EditAll || !isEmpty(selectedOption) ? (
|
||||
<div className="d-flex flex-wrap">
|
||||
{permissions.EditAll && selectedOption.length === 0 && (
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
dataTestId="related-term-add-button"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
onClick={() => {
|
||||
setIsIconVisible(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedOption.map((entity: EntityReference) =>
|
||||
getRelatedTermElement(entity)
|
||||
)}
|
||||
@ -216,7 +205,7 @@ const RelatedTerms = () => {
|
||||
<div>{NO_DATA_PLACEHOLDER}</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
) : null,
|
||||
[
|
||||
permissions,
|
||||
selectedOption,
|
||||
@ -231,17 +220,29 @@ const RelatedTerms = () => {
|
||||
<Typography.Text className="text-sm font-medium">
|
||||
{t('label.related-term-plural')}
|
||||
</Typography.Text>
|
||||
{permissions.EditAll && selectedOption.length > 0 && (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.related-term-plural'),
|
||||
})}
|
||||
onClick={() => setIsIconVisible(false)}
|
||||
/>
|
||||
)}
|
||||
{permissions.EditAll &&
|
||||
(isEmpty(selectedOption) ? (
|
||||
<PlusIconButton
|
||||
data-testid="related-term-add-button"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.related-term-plural'),
|
||||
})}
|
||||
onClick={() => {
|
||||
setIsIconVisible(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-button"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.related-term-plural'),
|
||||
})}
|
||||
onClick={() => setIsIconVisible(false)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Button, Col, Row, Space, Typography } from 'antd';
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty } from 'lodash';
|
||||
@ -18,7 +18,6 @@ import React, { FC, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../constants/constants';
|
||||
import { TAG_CONSTANT, TAG_START_WITH } from '../../../constants/Tag.constants';
|
||||
import { Metric } from '../../../generated/entity/data/metric';
|
||||
import { EntityReference } from '../../../generated/type/entityReference';
|
||||
import entityUtilClassBase from '../../../utils/EntityUtilClassBase';
|
||||
@ -26,10 +25,12 @@ import { getEntityName } from '../../../utils/EntityUtils';
|
||||
import { getEntityIcon } from '../../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../common/IconButtons/EditIconButton';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../common/IconButtons/EditIconButton';
|
||||
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
|
||||
import { DataAssetOption } from '../../DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface';
|
||||
import TagsV1 from '../../Tag/TagsV1/TagsV1.component';
|
||||
import './related-metrics.less';
|
||||
import { RelatedMetricsForm } from './RelatedMetricsForm';
|
||||
|
||||
@ -160,59 +161,51 @@ const RelatedMetrics: FC<RelatedMetricsProps> = ({
|
||||
{t('label.related-metric-plural')}
|
||||
</Typography.Text>
|
||||
{!isEdit &&
|
||||
!isEmpty(relatedMetrics) &&
|
||||
permissions.EditAll &&
|
||||
!metricDetails.deleted && (
|
||||
!metricDetails.deleted &&
|
||||
(isEmpty(relatedMetrics) ? (
|
||||
<PlusIconButton
|
||||
data-testid="add-related-metrics-container"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.related-metric-plural'),
|
||||
})}
|
||||
onClick={() => setIsEdit(true)}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-related-metrics"
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity: t('label.related-metric-plural'),
|
||||
})}
|
||||
onClick={() => setIsEdit(true)}
|
||||
/>
|
||||
)}
|
||||
))}
|
||||
</Space>
|
||||
);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{isEmpty(relatedMetrics) &&
|
||||
!isEdit &&
|
||||
permissions.EditAll &&
|
||||
!metricDetails.deleted && (
|
||||
<Col
|
||||
className="m-t-xss"
|
||||
data-testid="add-related-metrics-container"
|
||||
onClick={() => setIsEdit(true)}>
|
||||
<TagsV1 startWith={TAG_START_WITH.PLUS} tag={TAG_CONSTANT} />
|
||||
</Col>
|
||||
)}
|
||||
<Col span={24}>
|
||||
{isEdit ? (
|
||||
<RelatedMetricsForm
|
||||
defaultValue={defaultValue}
|
||||
initialOptions={initialOptions}
|
||||
metricFqn={metricDetails.fullyQualifiedName ?? ''}
|
||||
onCancel={() => setIsEdit(false)}
|
||||
onSubmit={handleRelatedMetricUpdate}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isEmpty(relatedMetrics) &&
|
||||
(metricDetails.deleted || isInSummaryPanel) ? (
|
||||
<Typography.Text>{NO_DATA_PLACEHOLDER}</Typography.Text>
|
||||
) : (
|
||||
<div
|
||||
className="metric-entity-list-body"
|
||||
data-testid="metric-entity-list-body">
|
||||
{getRelatedMetricListing(visibleRelatedMetrics)}
|
||||
{isShowMore && getRelatedMetricListing(hiddenRelatedMetrics)}
|
||||
{!isEmpty(hiddenRelatedMetrics) && showMoreLessElement}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
</>
|
||||
const content = isEdit ? (
|
||||
<RelatedMetricsForm
|
||||
defaultValue={defaultValue}
|
||||
initialOptions={initialOptions}
|
||||
metricFqn={metricDetails.fullyQualifiedName ?? ''}
|
||||
onCancel={() => setIsEdit(false)}
|
||||
onSubmit={handleRelatedMetricUpdate}
|
||||
/>
|
||||
) : isEmpty(relatedMetrics) && (metricDetails.deleted || isInSummaryPanel) ? (
|
||||
<Typography.Text>{NO_DATA_PLACEHOLDER}</Typography.Text>
|
||||
) : (
|
||||
!isEmpty(relatedMetrics) && (
|
||||
<div
|
||||
className="metric-entity-list-body"
|
||||
data-testid="metric-entity-list-body">
|
||||
{getRelatedMetricListing(visibleRelatedMetrics)}
|
||||
{isShowMore && getRelatedMetricListing(hiddenRelatedMetrics)}
|
||||
{!isEmpty(hiddenRelatedMetrics) && showMoreLessElement}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
@ -221,7 +214,7 @@ const RelatedMetrics: FC<RelatedMetricsProps> = ({
|
||||
title: header,
|
||||
}}
|
||||
isExpandDisabled={isEmpty(relatedMetrics)}>
|
||||
<Row gutter={[0, 8]}>{content}</Row>
|
||||
{content}
|
||||
</ExpandableCard>
|
||||
);
|
||||
};
|
||||
|
@ -26,7 +26,6 @@ export type TagsContainerV2Props = {
|
||||
columnData?: {
|
||||
fqn: string;
|
||||
};
|
||||
showHeader?: boolean;
|
||||
showBottomEditButton?: boolean;
|
||||
showInlineEditButton?: boolean;
|
||||
children?: ReactElement;
|
||||
|
@ -43,6 +43,7 @@ import ExpandableCard from '../../common/ExpandableCard/ExpandableCard';
|
||||
import {
|
||||
CommentIconButton,
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
RequestIconButton,
|
||||
} from '../../common/IconButtons/EditIconButton';
|
||||
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
|
||||
@ -65,7 +66,6 @@ const TagsContainerV2 = ({
|
||||
tagType,
|
||||
displayType,
|
||||
layoutType,
|
||||
showHeader = true,
|
||||
showBottomEditButton,
|
||||
showInlineEditButton,
|
||||
columnData,
|
||||
@ -173,29 +173,34 @@ const TagsContainerV2 = ({
|
||||
const addTagButton = useMemo(
|
||||
() =>
|
||||
showAddTagButton ? (
|
||||
<Col className="m-t-xss" onClick={handleAddClick}>
|
||||
<TagsV1
|
||||
startWith={TAG_START_WITH.PLUS}
|
||||
tag={isGlossaryType ? GLOSSARY_CONSTANT : TAG_CONSTANT}
|
||||
tagType={tagType}
|
||||
/>
|
||||
</Col>
|
||||
<PlusIconButton
|
||||
className="m-t-xss"
|
||||
data-testid="add-tag"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: isGlossaryType
|
||||
? t('label.glossary-term')
|
||||
: t('label.tag-plural'),
|
||||
})}
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
) : null,
|
||||
[showAddTagButton]
|
||||
[showAddTagButton, handleAddClick, t, isGlossaryType]
|
||||
);
|
||||
|
||||
const renderTags = useMemo(
|
||||
() => (
|
||||
<Col span={24}>
|
||||
<TagsViewer
|
||||
displayType={displayType}
|
||||
showNoDataPlaceholder={showNoDataPlaceholder}
|
||||
sizeCap={sizeCap}
|
||||
tagType={tagType}
|
||||
tags={tags?.[tagType] ?? []}
|
||||
/>
|
||||
</Col>
|
||||
),
|
||||
() =>
|
||||
isEmpty(tags?.[tagType]) && !showNoDataPlaceholder ? null : (
|
||||
<Col span={24}>
|
||||
<TagsViewer
|
||||
displayType={displayType}
|
||||
showNoDataPlaceholder={showNoDataPlaceholder}
|
||||
sizeCap={sizeCap}
|
||||
tagType={tagType}
|
||||
tags={tags?.[tagType] ?? []}
|
||||
/>
|
||||
</Col>
|
||||
),
|
||||
[displayType, showNoDataPlaceholder, tags?.[tagType], layoutType]
|
||||
);
|
||||
|
||||
@ -267,46 +272,43 @@ const TagsContainerV2 = ({
|
||||
|
||||
const header = useMemo(() => {
|
||||
return (
|
||||
showHeader && (
|
||||
<Space>
|
||||
<Typography.Text
|
||||
className={classNames({
|
||||
'text-sm font-medium': newLook,
|
||||
'right-panel-label': !newLook,
|
||||
})}>
|
||||
{isGlossaryType ? t('label.glossary-term') : t('label.tag-plural')}
|
||||
</Typography.Text>
|
||||
{permission && (
|
||||
<>
|
||||
{!isEmpty(tags?.[tagType]) && !isEditTags && (
|
||||
<EditIconButton
|
||||
data-testid="edit-button"
|
||||
newLook={newLook}
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity:
|
||||
tagType === TagSource.Classification
|
||||
? t('label.tag-plural')
|
||||
: t('label.glossary-term'),
|
||||
})}
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
)}
|
||||
{showTaskHandler && (
|
||||
<>
|
||||
{tagType === TagSource.Classification && requestTagElement}
|
||||
{conversationThreadElement}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
<Space>
|
||||
<Typography.Text
|
||||
className={classNames({
|
||||
'text-sm font-medium': newLook,
|
||||
'right-panel-label': !newLook,
|
||||
})}>
|
||||
{isGlossaryType ? t('label.glossary-term') : t('label.tag-plural')}
|
||||
</Typography.Text>
|
||||
{permission && (
|
||||
<>
|
||||
{addTagButton ?? (
|
||||
<EditIconButton
|
||||
data-testid="edit-button"
|
||||
newLook={newLook}
|
||||
size="small"
|
||||
title={t('label.edit-entity', {
|
||||
entity:
|
||||
tagType === TagSource.Classification
|
||||
? t('label.tag-plural')
|
||||
: t('label.glossary-term'),
|
||||
})}
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
)}
|
||||
{showTaskHandler && (
|
||||
<>
|
||||
{tagType === TagSource.Classification && requestTagElement}
|
||||
{conversationThreadElement}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
}, [
|
||||
tags,
|
||||
tagType,
|
||||
showHeader,
|
||||
isEditTags,
|
||||
permission,
|
||||
showTaskHandler,
|
||||
@ -372,13 +374,21 @@ const TagsContainerV2 = ({
|
||||
} else {
|
||||
return isHoriZontalLayout ? (
|
||||
horizontalLayout
|
||||
) : (
|
||||
) : showInlineEditButton || !isEmpty(renderTags) || !newLook ? (
|
||||
<Row data-testid="entity-tags">
|
||||
{addTagButton}
|
||||
{showAddTagButton && (
|
||||
<Col className="m-t-xss" onClick={handleAddClick}>
|
||||
<TagsV1
|
||||
startWith={TAG_START_WITH.PLUS}
|
||||
tag={isGlossaryType ? GLOSSARY_CONSTANT : TAG_CONSTANT}
|
||||
tagType={tagType}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
{renderTags}
|
||||
{showInlineEditButton && <Col>{editTagButton}</Col>}
|
||||
{showInlineEditButton ? <Col>{editTagButton}</Col> : null}
|
||||
</Row>
|
||||
);
|
||||
) : null;
|
||||
}
|
||||
}, [
|
||||
isEditTags,
|
||||
@ -429,17 +439,7 @@ const TagsContainerV2 = ({
|
||||
}}
|
||||
dataTestId={isGlossaryType ? 'glossary-container' : 'tags-container'}
|
||||
isExpandDisabled={isEmpty(tags?.[tagType])}>
|
||||
{suggestionDataRender ?? (
|
||||
<>
|
||||
{tagBody}
|
||||
{(children || showBottomEditButton) && (
|
||||
<Space align="baseline" className="m-t-xs w-full" size="middle">
|
||||
{showBottomEditButton && !showInlineEditButton && editTagButton}
|
||||
{children}
|
||||
</Space>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{suggestionDataRender ?? tagBody}
|
||||
</ExpandableCard>
|
||||
);
|
||||
}
|
||||
@ -448,8 +448,6 @@ const TagsContainerV2 = ({
|
||||
<div
|
||||
className="w-full tags-container"
|
||||
data-testid={isGlossaryType ? 'glossary-container' : 'tags-container'}>
|
||||
{header}
|
||||
|
||||
{suggestionDataRender ?? (
|
||||
<>
|
||||
{tagBody}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 Collate.
|
||||
* Copyright 2025 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
|
||||
@ -10,136 +10,129 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { LabelType, State, TagSource } from '../../generated/type/tagLabel';
|
||||
import {
|
||||
LabelType,
|
||||
State,
|
||||
TagLabel,
|
||||
TagSource,
|
||||
} from '../../generated/type/tagLabel';
|
||||
import TagsContainerV2 from '../Tag/TagsContainerV2/TagsContainerV2';
|
||||
import TagsInput from './TagsInput.component';
|
||||
|
||||
const mockOnTagsUpdate = jest.fn();
|
||||
jest.mock('../../components/Tag/TagsContainerV2/TagsContainerV2', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="tags-container">Mocked TagsContainerV2</div>
|
||||
));
|
||||
});
|
||||
|
||||
const tags = [
|
||||
{
|
||||
tagFQN: 'tag1',
|
||||
displayName: 'Tag 1',
|
||||
labelType: LabelType.Automated,
|
||||
source: TagSource.Classification,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
{
|
||||
tagFQN: 'tag2',
|
||||
displayName: 'Tag 2',
|
||||
description: 'This is a sample tag description.',
|
||||
labelType: LabelType.Derived,
|
||||
source: TagSource.Glossary,
|
||||
state: State.Suggested,
|
||||
},
|
||||
];
|
||||
describe('TagsInput Component', () => {
|
||||
const mockTags: TagLabel[] = [
|
||||
{
|
||||
tagFQN: 'test.tag1',
|
||||
source: TagSource.Classification,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
{
|
||||
tagFQN: 'test.tag2',
|
||||
source: TagSource.Classification,
|
||||
labelType: LabelType.Manual,
|
||||
state: State.Confirmed,
|
||||
},
|
||||
];
|
||||
|
||||
describe('TagsInput', () => {
|
||||
it('should render TagsInput along with tagsViewer in version view', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TagsInput
|
||||
isVersionView
|
||||
editable={false}
|
||||
tags={tags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
const mockOnTagsUpdate = jest.fn();
|
||||
|
||||
expect(
|
||||
await screen.findByTestId('tags-input-container')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('label.tag-plural')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Tag 1')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Tag 2')).toBeInTheDocument();
|
||||
it('renders without crashing', () => {
|
||||
render(
|
||||
<TagsInput editable tags={mockTags} onTagsUpdate={mockOnTagsUpdate} />,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('tags-input-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render tags container when not in in version view', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable={false}
|
||||
tags={tags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('renders in version view mode', () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable
|
||||
isVersionView
|
||||
tags={mockTags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByTestId('tags-input-container')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('tags-container')).toBeInTheDocument();
|
||||
|
||||
expect(await screen.findByText('label.tag-plural')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Tag 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.tag-plural')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render edit button when no editable', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TagsInput editable tags={tags} onTagsUpdate={mockOnTagsUpdate} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
it('renders tags in version view mode', () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable
|
||||
isVersionView
|
||||
tags={mockTags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('edit-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render edit button when no editable', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable={false}
|
||||
tags={tags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
expect(await screen.queryByTestId('edit-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render tags if tags is empty', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable={false}
|
||||
tags={[]}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('tags-container')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('entity-tags')).toBeInTheDocument();
|
||||
expect(await screen.findByText('--')).toBeInTheDocument();
|
||||
mockTags.forEach((tag) => {
|
||||
expect(screen.getByText(tag.tagFQN)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render add tags if tags is empty and has permission', async () => {
|
||||
await act(async () => {
|
||||
render(<TagsInput editable tags={[]} onTagsUpdate={mockOnTagsUpdate} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
it('renders TagsContainerV2 when not in version view', () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable
|
||||
isVersionView={false}
|
||||
tags={mockTags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('entity-tags')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('add-tag')).toBeInTheDocument();
|
||||
// Verify TagsContainerV2 is rendered
|
||||
expect(screen.getByTestId('tags-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles empty tags array', () => {
|
||||
render(<TagsInput editable tags={[]} onTagsUpdate={mockOnTagsUpdate} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('tags-input-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables tag editing when editable is false', () => {
|
||||
render(
|
||||
<TagsInput
|
||||
editable={false}
|
||||
tags={mockTags}
|
||||
onTagsUpdate={mockOnTagsUpdate}
|
||||
/>,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
|
||||
expect(TagsContainerV2).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
permission: false,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('handles undefined tags prop', () => {
|
||||
render(<TagsInput editable onTagsUpdate={mockOnTagsUpdate} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('tags-input-container')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -39,10 +39,6 @@ describe('ExpandableCard', () => {
|
||||
|
||||
expect(screen.getByText('Test Card')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('test-content')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button')).toHaveAttribute(
|
||||
'title',
|
||||
'label.collapse'
|
||||
);
|
||||
});
|
||||
|
||||
it('renders with custom data-testid', () => {
|
||||
@ -104,7 +100,6 @@ describe('ExpandableCard', () => {
|
||||
const expandButton = screen.getByRole('button');
|
||||
|
||||
// Initial state (collapsed)
|
||||
expect(expandButton).toHaveAttribute('title', 'label.collapse');
|
||||
expect(expandButton.closest('.ant-card')).toHaveClass('expanded');
|
||||
|
||||
// Click to collapse
|
||||
@ -112,7 +107,6 @@ describe('ExpandableCard', () => {
|
||||
fireEvent.click(expandButton);
|
||||
});
|
||||
|
||||
expect(expandButton).toHaveAttribute('title', 'label.expand');
|
||||
expect(expandButton.closest('.ant-card')).not.toHaveClass('collapsed');
|
||||
|
||||
// Click to expand again
|
||||
@ -120,7 +114,6 @@ describe('ExpandableCard', () => {
|
||||
fireEvent.click(expandButton);
|
||||
});
|
||||
|
||||
expect(expandButton).toHaveAttribute('title', 'label.collapse');
|
||||
expect(expandButton.closest('.ant-card')).toHaveClass('expanded');
|
||||
});
|
||||
|
||||
@ -239,8 +232,9 @@ describe('ExpandableCard', () => {
|
||||
fireEvent.click(expandButton);
|
||||
});
|
||||
|
||||
// Should not throw any errors
|
||||
expect(expandButton).toHaveAttribute('title', 'label.expand');
|
||||
const card = screen.getByRole('button').closest('.ant-card');
|
||||
|
||||
expect(card).not.toHaveClass('expanded');
|
||||
});
|
||||
|
||||
it('works with minimal cardProps', () => {
|
||||
|
@ -44,6 +44,10 @@ const ExpandableCard = ({
|
||||
|
||||
return (
|
||||
<Card
|
||||
bodyStyle={{
|
||||
// This will prevent the card body from having padding when there is no content
|
||||
padding: children ? undefined : '0px',
|
||||
}}
|
||||
className={classNames(
|
||||
'new-header-border-card w-full',
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Icon from '@ant-design/icons';
|
||||
import Icon, { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, ButtonProps, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
@ -129,17 +129,42 @@ export const AlignRightIconButton = ({
|
||||
export const CardExpandCollapseIconButton = ({
|
||||
title,
|
||||
className,
|
||||
size,
|
||||
disabled,
|
||||
...props
|
||||
}: IconButtonPropsInternal) => {
|
||||
return (
|
||||
const button = (
|
||||
<Button
|
||||
className={classNames('bordered', className)}
|
||||
disabled={disabled}
|
||||
icon={<CardExpandCollapseIcon />}
|
||||
size={size}
|
||||
title={title}
|
||||
type="text"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip title={title}>
|
||||
{/* Adding span to fix the issue with className is not being applied for disabled button
|
||||
Refer this comment for more details https://github.com/ant-design/ant-design/issues/21404#issuecomment-586800984 */}
|
||||
{disabled ? <span className={className}>{button}</span> : button}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlusIconButton = ({
|
||||
title,
|
||||
className,
|
||||
size,
|
||||
...props
|
||||
}: IconButtonPropsInternal) => {
|
||||
return (
|
||||
<Tooltip title={title}>
|
||||
<Button
|
||||
className={classNames('bordered', className)}
|
||||
icon={<PlusOutlined />}
|
||||
size={size}
|
||||
{...props}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
@ -50,7 +50,7 @@ export const FrequentlyJoinedTables = () => {
|
||||
|
||||
const content = joinedTables.map((table) => (
|
||||
<Space
|
||||
className="w-full frequently-joint-data justify-between"
|
||||
className="w-full frequently-joint-data justify-between m-t-xss"
|
||||
data-testid="related-tables-data"
|
||||
key={table.name}
|
||||
size={4}>
|
||||
|
@ -16,10 +16,11 @@ import { isEmpty, map } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||
import ExpandableCard from '../../../components/common/ExpandableCard/ExpandableCard';
|
||||
import { EditIconButton } from '../../../components/common/IconButtons/EditIconButton';
|
||||
import TagButton from '../../../components/common/TagButton/TagButton.component';
|
||||
import {
|
||||
EditIconButton,
|
||||
PlusIconButton,
|
||||
} from '../../../components/common/IconButtons/EditIconButton';
|
||||
import { useGenericContext } from '../../../components/Customization/GenericProvider/GenericProvider';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||
import { EntityType, FqnPart } from '../../../enums/entity.enum';
|
||||
@ -63,30 +64,29 @@ const TableConstraints = () => {
|
||||
{t('label.table-constraints')}
|
||||
</Typography.Text>
|
||||
|
||||
{hasPermission && !isEmpty(data?.tableConstraints) && (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-table-constraint-button"
|
||||
size="small"
|
||||
onClick={handleOpenEditConstraintModal}
|
||||
/>
|
||||
)}
|
||||
{hasPermission &&
|
||||
(isEmpty(data?.tableConstraints) ? (
|
||||
<PlusIconButton
|
||||
data-testid="table-constraints-add-button"
|
||||
size="small"
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.table-constraints'),
|
||||
})}
|
||||
onClick={handleOpenEditConstraintModal}
|
||||
/>
|
||||
) : (
|
||||
<EditIconButton
|
||||
newLook
|
||||
data-testid="edit-table-constraint-button"
|
||||
size="small"
|
||||
onClick={handleOpenEditConstraintModal}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
);
|
||||
|
||||
const content = (
|
||||
const content = isEmpty(data?.tableConstraints) ? null : (
|
||||
<Space className="w-full new-header-border-card" direction="vertical">
|
||||
{hasPermission && isEmpty(data?.tableConstraints) && (
|
||||
<TagButton
|
||||
className="text-primary cursor-pointer"
|
||||
dataTestId="table-constraints-add-button"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}
|
||||
label={t('label.add')}
|
||||
tooltip=""
|
||||
onClick={handleOpenEditConstraintModal}
|
||||
/>
|
||||
)}
|
||||
|
||||
{data?.tableConstraints?.map(
|
||||
({ constraintType, columns, referredColumns }) => {
|
||||
if (constraintType === ConstraintType.PrimaryKey) {
|
||||
@ -163,6 +163,18 @@ const TableConstraints = () => {
|
||||
return null;
|
||||
}
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: header,
|
||||
}}
|
||||
isExpandDisabled={isEmpty(data?.tableConstraints)}>
|
||||
{content}
|
||||
</ExpandableCard>
|
||||
{isModalOpen && (
|
||||
<TableConstraintsModal
|
||||
constraint={data?.tableConstraints}
|
||||
@ -171,17 +183,7 @@ const TableConstraints = () => {
|
||||
onSave={handleSubmit}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
|
||||
return (
|
||||
<ExpandableCard
|
||||
cardProps={{
|
||||
title: header,
|
||||
}}
|
||||
isExpandDisabled={isEmpty(data?.tableConstraints)}>
|
||||
{content}
|
||||
</ExpandableCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user