mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-22 17:01:41 +00:00
ui : supported delete functionality for sample data (#12871)
This commit is contained in:
parent
795294c87f
commit
c6d03a6eaf
@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<path fill="#37352F" d="M20.184 2.813H16.67v-.704C16.669.946 15.722 0 14.559 0H8.934C7.771 0 6.825.946 6.825 2.11v.703H3.309c-1.163 0-2.109.946-2.109 2.109 0 .934.61 1.728 1.453 2.004l1.254 15.14A2.122 2.122 0 0 0 6.01 24h11.474c1.089 0 2.012-.85 2.102-1.935L20.84 6.926a2.113 2.113 0 0 0 1.454-2.004c0-1.163-.947-2.11-2.11-2.11ZM8.231 2.109c0-.387.316-.703.703-.703h5.625c.388 0 .704.316.704.703v.704H8.23v-.704Zm9.954 19.84a.707.707 0 0 1-.7.645H6.01a.707.707 0 0 1-.701-.645L4.073 7.031h15.348L18.184 21.95Zm2-16.324H3.308a.704.704 0 0 1 0-1.406h16.875a.704.704 0 0 1 0 1.406Z"/><path fill="#37352F" d="M9.186 20.44 8.483 9.098a.704.704 0 0 0-1.404.087l.704 11.344a.703.703 0 1 0 1.403-.087ZM12 8.438a.703.703 0 0 0-.703.703v11.343a.703.703 0 0 0 1.406 0V9.141A.703.703 0 0 0 12 8.437ZM16.262 8.439a.704.704 0 0 0-.745.658l-.703 11.344a.703.703 0 0 0 1.403.087l.704-11.344a.703.703 0 0 0-.659-.745Z"/>
|
||||
<path fill="currentColor" d="M20.184 2.813H16.67v-.704C16.669.946 15.722 0 14.559 0H8.934C7.771 0 6.825.946 6.825 2.11v.703H3.309c-1.163 0-2.109.946-2.109 2.109 0 .934.61 1.728 1.453 2.004l1.254 15.14A2.122 2.122 0 0 0 6.01 24h11.474c1.089 0 2.012-.85 2.102-1.935L20.84 6.926a2.113 2.113 0 0 0 1.454-2.004c0-1.163-.947-2.11-2.11-2.11ZM8.231 2.109c0-.387.316-.703.703-.703h5.625c.388 0 .704.316.704.703v.704H8.23v-.704Zm9.954 19.84a.707.707 0 0 1-.7.645H6.01a.707.707 0 0 1-.701-.645L4.073 7.031h15.348L18.184 21.95Zm2-16.324H3.308a.704.704 0 0 1 0-1.406h16.875a.704.704 0 0 1 0 1.406Z"/><path fill="currentColor" d="M9.186 20.44 8.483 9.098a.704.704 0 0 0-1.404.087l.704 11.344a.703.703 0 1 0 1.403-.087ZM12 8.438a.703.703 0 0 0-.703.703v11.343a.703.703 0 0 0 1.406 0V9.141A.703.703 0 0 0 12 8.437ZM16.262 8.439a.704.704 0 0 0-.745.658l-.703 11.344a.703.703 0 0 0 1.403.087l.704-11.344a.703.703 0 0 0-.659-.745Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 994 B |
@ -11,19 +11,39 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Space, Table as AntdTable, Typography } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Space,
|
||||
Table as AntdTable,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import AppState from 'AppState';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { ManageButtonItemLabel } from 'components/common/ManageButtonContentItem/ManageButtonContentItem.component';
|
||||
import EntityDeleteModal from 'components/Modals/EntityDeleteModal/EntityDeleteModal';
|
||||
import { useTourProvider } from 'components/TourProvider/TourProvider';
|
||||
import { DROPDOWN_ICON_SIZE_PROPS } from 'constants/ManageButton.constants';
|
||||
import { mockDatasetData } from 'constants/mockTourData.constants';
|
||||
import { LOADING_STATE } from 'enums/common.enum';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty, lowerCase } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getSampleDataByTableId } from 'rest/tableAPI';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
deleteSampleDataByTableId,
|
||||
getSampleDataByTableId,
|
||||
} from 'rest/tableAPI';
|
||||
import { ReactComponent as IconDelete } from '../../assets/svg/ic-delete.svg';
|
||||
import { ReactComponent as IconDropdown } from '../../assets/svg/menu.svg';
|
||||
import { WORKFLOWS_PROFILER_DOCS } from '../../constants/docs.constants';
|
||||
import { Table } from '../../generated/entity/data/table';
|
||||
import { withLoader } from '../../hoc/withLoader';
|
||||
import { Transi18next } from '../../utils/CommonUtils';
|
||||
import { getEntityDeleteMessage, Transi18next } from '../../utils/CommonUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import Loader from '../Loader/Loader';
|
||||
@ -34,11 +54,38 @@ import {
|
||||
SampleDataType,
|
||||
} from './sample.interface';
|
||||
import './SampleDataTable.style.less';
|
||||
const SampleDataTable = ({ isTableDeleted, tableId }: SampleDataProps) => {
|
||||
|
||||
const SampleDataTable = ({
|
||||
isTableDeleted,
|
||||
tableId,
|
||||
ownerId,
|
||||
permissions,
|
||||
}: SampleDataProps) => {
|
||||
const { isTourPage } = useTourProvider();
|
||||
|
||||
const [sampleData, setSampleData] = useState<SampleData>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
|
||||
const [deleteState, setDeleteState] = useState(LOADING_STATE.INITIAL);
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
|
||||
const currentUser = useMemo(
|
||||
() => AppState.getCurrentUserDetails(),
|
||||
[AppState.userDetails]
|
||||
);
|
||||
|
||||
const hasPermission = useMemo(
|
||||
() =>
|
||||
permissions.EditAll ||
|
||||
permissions.EditSampleData ||
|
||||
currentUser?.id === ownerId,
|
||||
[ownerId, permissions, currentUser]
|
||||
);
|
||||
|
||||
const handleDeleteModal = useCallback(
|
||||
() => setIsDeleteModalOpen((prev) => !prev),
|
||||
[]
|
||||
);
|
||||
|
||||
const getSampleDataWithType = (table: Table) => {
|
||||
const { sampleData, columns } = table;
|
||||
@ -91,6 +138,52 @@ const SampleDataTable = ({ isTableDeleted, tableId }: SampleDataProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteSampleData = async () => {
|
||||
setDeleteState(LOADING_STATE.WAITING);
|
||||
|
||||
try {
|
||||
await deleteSampleDataByTableId(tableId);
|
||||
handleDeleteModal();
|
||||
fetchSampleData();
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.delete-entity-error', {
|
||||
entity: t('label.sample-data'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setDeleteState(LOADING_STATE.SUCCESS);
|
||||
}
|
||||
};
|
||||
|
||||
const manageButtonContent: ItemType[] = [
|
||||
{
|
||||
label: (
|
||||
<ManageButtonItemLabel
|
||||
description={t('message.delete-entity-type-action-description', {
|
||||
entityType: t('label.sample-data'),
|
||||
})}
|
||||
icon={
|
||||
<IconDelete
|
||||
className="m-t-xss"
|
||||
{...DROPDOWN_ICON_SIZE_PROPS}
|
||||
name="Delete"
|
||||
/>
|
||||
}
|
||||
id="delete-button"
|
||||
name={t('label.delete')}
|
||||
/>
|
||||
),
|
||||
key: 'delete-button',
|
||||
onClick: (e) => {
|
||||
e.domEvent.stopPropagation();
|
||||
setShowActions(false);
|
||||
handleDeleteModal();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (!isTableDeleted && tableId && !isTourPage) {
|
||||
@ -114,7 +207,7 @@ const SampleDataTable = ({ isTableDeleted, tableId }: SampleDataProps) => {
|
||||
|
||||
if (isEmpty(sampleData?.rows) && isEmpty(sampleData?.columns)) {
|
||||
return (
|
||||
<ErrorPlaceHolder>
|
||||
<ErrorPlaceHolder className="error-placeholder">
|
||||
<Typography.Paragraph>
|
||||
<Transi18next
|
||||
i18nKey="message.view-sample-data-entity"
|
||||
@ -142,6 +235,30 @@ const SampleDataTable = ({ isTableDeleted, tableId }: SampleDataProps) => {
|
||||
})}
|
||||
data-testid="sample-data"
|
||||
id="sampleDataDetails">
|
||||
<Space className="m-b-md justify-end w-full">
|
||||
{hasPermission && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: manageButtonContent,
|
||||
}}
|
||||
open={showActions}
|
||||
overlayClassName="manage-dropdown-list-container"
|
||||
overlayStyle={{ width: '350px' }}
|
||||
placement="bottomRight"
|
||||
trigger={['click']}
|
||||
onOpenChange={setShowActions}>
|
||||
<Tooltip placement="right">
|
||||
<Button
|
||||
className="flex-center px-1.5"
|
||||
data-testid="sample-data-manage-button"
|
||||
onClick={() => setShowActions(true)}>
|
||||
<IconDropdown className="anticon self-center " />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
<AntdTable
|
||||
bordered
|
||||
columns={sampleData?.columns}
|
||||
@ -152,8 +269,20 @@ const SampleDataTable = ({ isTableDeleted, tableId }: SampleDataProps) => {
|
||||
scroll={{ x: true }}
|
||||
size="small"
|
||||
/>
|
||||
|
||||
{isDeleteModalOpen && (
|
||||
<EntityDeleteModal
|
||||
bodyText={getEntityDeleteMessage(t('label.sample-data'), '')}
|
||||
entityName={t('label.sample-data')}
|
||||
entityType={EntityType.SAMPLE_DATA}
|
||||
loadingState={deleteState}
|
||||
visible={isDeleteModalOpen}
|
||||
onCancel={handleDeleteModal}
|
||||
onConfirm={handleDeleteSampleData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withLoader<SampleDataProps>(SampleDataTable);
|
||||
export default withLoader<SampleDataProps>(observer(SampleDataTable));
|
||||
|
@ -11,11 +11,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@border-color: #e5e7eb;
|
||||
|
||||
.no-data-placeholder {
|
||||
width: 100%;
|
||||
padding: 32px;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: 4px;
|
||||
.error-placeholder {
|
||||
height: calc(100vh - 255px);
|
||||
}
|
||||
|
@ -12,11 +12,27 @@
|
||||
*/
|
||||
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import React from 'react';
|
||||
import { getSampleDataByTableId } from 'rest/tableAPI';
|
||||
import { MOCK_TABLE } from '../../mocks/TableData.mock';
|
||||
import SampleDataTable from './SampleDataTable.component';
|
||||
|
||||
const mockProps = {
|
||||
tableId: 'id',
|
||||
ownerId: 'ownerId',
|
||||
permissions: {
|
||||
Create: true,
|
||||
Delete: true,
|
||||
ViewAll: true,
|
||||
EditAll: true,
|
||||
EditDescription: true,
|
||||
EditDisplayName: true,
|
||||
EditCustomFields: true,
|
||||
} as OperationPermission,
|
||||
};
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
Link: jest.fn().mockImplementation(({ children }) => <span>{children}</span>),
|
||||
useLocation: jest.fn().mockImplementation(() => ({ pathname: 'test' })),
|
||||
@ -36,6 +52,10 @@ jest.mock('../common/error-with-placeholder/ErrorPlaceHolder', () => {
|
||||
);
|
||||
});
|
||||
|
||||
jest.mock('components/Modals/EntityDeleteModal/EntityDeleteModal', () => {
|
||||
return jest.fn().mockReturnValue(<p>EntityDeleteModal</p>);
|
||||
});
|
||||
|
||||
describe('Test SampleDataTable Component', () => {
|
||||
it('Render error placeholder if the columns passed are empty', async () => {
|
||||
(getSampleDataByTableId as jest.Mock).mockImplementationOnce(() =>
|
||||
@ -43,7 +63,7 @@ describe('Test SampleDataTable Component', () => {
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<SampleDataTable tableId="id" />);
|
||||
render(<SampleDataTable {...mockProps} />);
|
||||
});
|
||||
|
||||
const errorPlaceholder = screen.getByTestId('error-placeholder');
|
||||
@ -53,11 +73,51 @@ describe('Test SampleDataTable Component', () => {
|
||||
|
||||
it('Renders all the data that was sent to the component', async () => {
|
||||
await act(async () => {
|
||||
render(<SampleDataTable tableId="id" />);
|
||||
render(<SampleDataTable {...mockProps} />);
|
||||
});
|
||||
|
||||
const deleteButton = screen.getByTestId('sample-data-manage-button');
|
||||
const table = screen.getByTestId('sample-data-table');
|
||||
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
expect(table).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Sample Data menu dropdown should not be present when not have permission', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<SampleDataTable
|
||||
{...mockProps}
|
||||
permissions={{
|
||||
...mockProps.permissions,
|
||||
EditAll: false,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('sample-data-manage-button')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Render Delete Modal when delete sample data button is clicked', async () => {
|
||||
await act(async () => {
|
||||
render(<SampleDataTable {...mockProps} />);
|
||||
});
|
||||
|
||||
const dropdown = screen.getByTestId('sample-data-manage-button');
|
||||
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
|
||||
userEvent.click(dropdown);
|
||||
|
||||
const deleteButton = screen.getByTestId('delete-button-details-container');
|
||||
|
||||
userEvent.click(deleteButton);
|
||||
|
||||
const deleteModal = screen.getByText('EntityDeleteModal');
|
||||
|
||||
expect(deleteModal).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
|
||||
export type SampleDataType =
|
||||
| string
|
||||
@ -29,4 +30,6 @@ export interface SampleData {
|
||||
export interface SampleDataProps {
|
||||
isTableDeleted?: boolean;
|
||||
tableId: string;
|
||||
ownerId: string;
|
||||
permissions: OperationPermission;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Popover } from 'antd';
|
||||
import { Button, Popover, Space } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, {
|
||||
@ -25,11 +25,12 @@ import { useHistory } from 'react-router-dom';
|
||||
import { getUserByName } from 'rest/userAPI';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import AppState from '../../../AppState';
|
||||
import { ReactComponent as IconTeams } from '../../../assets/svg/teams-grey.svg';
|
||||
import { ReactComponent as IconUsers } from '../../../assets/svg/user.svg';
|
||||
import { getUserPath, TERM_ADMIN } from '../../../constants/constants';
|
||||
import { User } from '../../../generated/entity/teams/user';
|
||||
import { EntityReference } from '../../../generated/type/entityReference';
|
||||
import { getNonDeletedTeams } from '../../../utils/CommonUtils';
|
||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
|
||||
@ -44,9 +45,9 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const getData = () => {
|
||||
const userdetails = AppState.userDataProfiles[userName];
|
||||
if (userdetails) {
|
||||
setUserData(userdetails);
|
||||
const userDetails = AppState.userDataProfiles[userName];
|
||||
if (userDetails) {
|
||||
setUserData(userDetails);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
if (type === 'user') {
|
||||
@ -68,21 +69,24 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const teams = getNonDeletedTeams(userData.teams ?? []);
|
||||
|
||||
return teams?.length ? (
|
||||
<p className="m-t-xs">
|
||||
<SVGIcons alt="icon" className="w-4" icon={Icons.TEAMS_GREY} />
|
||||
<span className="m-r-xs m-l-xss align-middle font-medium">
|
||||
{t('label.team-plural')}
|
||||
</span>
|
||||
<span className="d-flex flex-wrap m-t-xss">
|
||||
{teams.map((team, i) => (
|
||||
<div className="m-t-xs">
|
||||
<p className="d-flex items-center">
|
||||
<IconTeams height={16} width={16} />
|
||||
<span className="m-r-xs m-l-xss align-middle font-medium">
|
||||
{t('label.team-plural')}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p className="d-flex flex-wrap m-t-xss">
|
||||
{teams.map((team) => (
|
||||
<span
|
||||
className="bg-grey rounded-4 p-x-xs text-grey-body text-xs"
|
||||
key={i}>
|
||||
className="bg-grey rounded-4 p-x-xs text-grey-body text-xs m-b-xss"
|
||||
key={team.id}>
|
||||
{team?.displayName ?? team?.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -91,24 +95,29 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const isAdmin = userData?.isAdmin;
|
||||
|
||||
return roles?.length ? (
|
||||
<p className="m-t-xs">
|
||||
<SVGIcons alt="icon" className="w-4" icon={Icons.USERS} />
|
||||
<span className="m-r-xs m-l-xss align-middle font-medium">
|
||||
{t('label.role-plural')}
|
||||
</span>
|
||||
<div className="m-t-xs">
|
||||
<p className="d-flex items-center">
|
||||
<IconUsers height={16} width={16} />
|
||||
<span className="m-r-xs m-l-xss align-middle font-medium">
|
||||
{t('label.role-plural')}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<span className="d-flex flex-wrap m-t-xss">
|
||||
{isAdmin && (
|
||||
<span className="bg-grey rounded-4 p-x-xs text-xs">
|
||||
<span className="bg-grey rounded-4 p-x-xs text-xs m-b-xss">
|
||||
{TERM_ADMIN}
|
||||
</span>
|
||||
)}
|
||||
{roles.map((role, i) => (
|
||||
<span className="bg-grey rounded-4 p-x-xs text-xs" key={i}>
|
||||
{roles.map((role) => (
|
||||
<span
|
||||
className="bg-grey rounded-4 p-x-xs text-xs m-b-xss"
|
||||
key={role.id}>
|
||||
{role?.displayName ?? role?.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -117,25 +126,24 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const displayName = getEntityName(userData as unknown as EntityReference);
|
||||
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<div className="m-r-xs">
|
||||
<ProfilePicture id="" name={userName} width="24" />
|
||||
</div>
|
||||
<Space align="center">
|
||||
<ProfilePicture id="" name={userName} width="24" />
|
||||
<div className="self-center">
|
||||
<button
|
||||
className="text-info"
|
||||
<Button
|
||||
className="text-info p-0"
|
||||
type="link"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTitleClickHandler(getUserPath(name));
|
||||
}}>
|
||||
<span className="font-medium m-r-xs">{displayName}</span>
|
||||
</button>
|
||||
</Button>
|
||||
{displayName !== name ? (
|
||||
<span className="text-grey-muted">{name}</span>
|
||||
) : null}
|
||||
{isEmpty(userData) && <span>{userName}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -47,6 +47,7 @@ export enum EntityType {
|
||||
SUBSCRIPTION = 'subscription',
|
||||
USER_NAME = 'username',
|
||||
CHART = 'chart',
|
||||
SAMPLE_DATA = 'sampleData',
|
||||
}
|
||||
|
||||
export enum AssetsType {
|
||||
|
@ -563,6 +563,8 @@ const TableDetailsPageV1 = () => {
|
||||
) : (
|
||||
<SampleDataTableComponent
|
||||
isTableDeleted={tableDetails?.deleted}
|
||||
ownerId={tableDetails?.owner?.id ?? ''}
|
||||
permissions={tablePermissions}
|
||||
tableId={tableDetails?.id ?? ''}
|
||||
/>
|
||||
),
|
||||
|
@ -246,3 +246,7 @@ export const getTableList = async (params?: TableListParams) => {
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteSampleDataByTableId = async (id: string) => {
|
||||
return await APIClient.delete<Table>(`/tables/${id}/sampleData`);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user