mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-26 09:55:52 +00:00
✨ Activity Feed: Show "Request Tag" button for table column Tags (#6039)
* ✨ Activity Feed: Show "Request Tag" button for table column Tags
* Add support for task icon
* Addressing review comment
* Fix unit tests
* Fix unit test
* Fix explore page issue
This commit is contained in:
parent
954635b6dc
commit
c40e9ff685
@ -15,7 +15,7 @@ import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Popover } from 'antd';
|
import { Popover } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { cloneDeep, isNil, isUndefined, lowerCase } from 'lodash';
|
import { cloneDeep, isEmpty, isNil, isUndefined, lowerCase } from 'lodash';
|
||||||
import { EntityFieldThreads, EntityTags, TagOption } from 'Models';
|
import { EntityFieldThreads, EntityTags, TagOption } from 'Models';
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import React, { Fragment, useEffect, useState } from 'react';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
@ -28,10 +28,8 @@ import { SettledStatus } from '../../enums/axios.enum';
|
|||||||
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
ColumnJoins,
|
|
||||||
ColumnTest,
|
ColumnTest,
|
||||||
JoinedWith,
|
JoinedWith,
|
||||||
Table,
|
|
||||||
} from '../../generated/entity/data/table';
|
} from '../../generated/entity/data/table';
|
||||||
import { ThreadType } from '../../generated/entity/feed/thread';
|
import { ThreadType } from '../../generated/entity/feed/thread';
|
||||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||||
@ -59,6 +57,7 @@ import {
|
|||||||
import { getTagCategories, getTaglist } from '../../utils/TagsUtils';
|
import { getTagCategories, getTaglist } from '../../utils/TagsUtils';
|
||||||
import {
|
import {
|
||||||
getRequestDescriptionPath,
|
getRequestDescriptionPath,
|
||||||
|
getRequestTagsPath,
|
||||||
getUpdateDescriptionPath,
|
getUpdateDescriptionPath,
|
||||||
} from '../../utils/TasksUtils';
|
} from '../../utils/TasksUtils';
|
||||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||||
@ -67,23 +66,8 @@ import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPr
|
|||||||
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||||
import TagsContainer from '../tags-container/tags-container';
|
import TagsContainer from '../tags-container/tags-container';
|
||||||
import TagsViewer from '../tags-viewer/tags-viewer';
|
import TagsViewer from '../tags-viewer/tags-viewer';
|
||||||
|
import { TABLE_HEADERS } from './EntityTable.constant';
|
||||||
interface Props {
|
import { EntityTableProps } from './EntityTable.interface';
|
||||||
owner: Table['owner'];
|
|
||||||
tableColumns: ModifiedTableColumn[];
|
|
||||||
joins: Array<ColumnJoins>;
|
|
||||||
columnName: string;
|
|
||||||
hasEditAccess: boolean;
|
|
||||||
tableConstraints: Table['tableConstraints'];
|
|
||||||
searchText?: string;
|
|
||||||
isReadOnly?: boolean;
|
|
||||||
entityFqn?: string;
|
|
||||||
entityFieldThreads?: EntityFieldThreads[];
|
|
||||||
entityFieldTasks?: EntityFieldThreads[];
|
|
||||||
onUpdate?: (columns: ModifiedTableColumn[]) => void;
|
|
||||||
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
|
|
||||||
onEntityFieldSelect?: (value: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EntityTable = ({
|
const EntityTable = ({
|
||||||
tableColumns,
|
tableColumns,
|
||||||
@ -98,35 +82,11 @@ const EntityTable = ({
|
|||||||
entityFqn,
|
entityFqn,
|
||||||
tableConstraints,
|
tableConstraints,
|
||||||
entityFieldTasks,
|
entityFieldTasks,
|
||||||
}: Props) => {
|
}: EntityTableProps) => {
|
||||||
const { isAdminUser, userPermissions } = useAuth();
|
const { isAdminUser, userPermissions } = useAuth();
|
||||||
const { isAuthDisabled } = useAuthContext();
|
const { isAuthDisabled } = useAuthContext();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const columns = React.useMemo(
|
const columns = TABLE_HEADERS;
|
||||||
() => [
|
|
||||||
{
|
|
||||||
Header: 'Name',
|
|
||||||
accessor: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Type',
|
|
||||||
accessor: 'dataTypeDisplay',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Data Quality',
|
|
||||||
accessor: 'columnTests',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Description',
|
|
||||||
accessor: 'description',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Tags',
|
|
||||||
accessor: 'tags',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [searchedColumns, setSearchedColumns] = useState<ModifiedTableColumn[]>(
|
const [searchedColumns, setSearchedColumns] = useState<ModifiedTableColumn[]>(
|
||||||
[]
|
[]
|
||||||
@ -405,6 +365,15 @@ const EntityTable = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
const onRequestTagsHandler = (cell: any) => {
|
||||||
|
const field = EntityField.COLUMNS;
|
||||||
|
const value = getColumnName(cell);
|
||||||
|
history.push(
|
||||||
|
getRequestTagsPath(EntityType.TABLE, entityFqn as string, field, value)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const prepareConstraintIcon = (
|
const prepareConstraintIcon = (
|
||||||
columnName: string,
|
columnName: string,
|
||||||
columnConstraint?: string
|
columnConstraint?: string
|
||||||
@ -460,6 +429,27 @@ const EntityTable = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
|
const getRequestTagsElement = (cell: any) => {
|
||||||
|
const hasTags = !isEmpty(cell.value || []);
|
||||||
|
|
||||||
|
return !hasTags ? (
|
||||||
|
<button
|
||||||
|
className="tw-w-8 tw-h-8 tw-mr-1 tw-flex-none link-text focus:tw-outline-none tw-opacity-0 group-hover:tw-opacity-100 tw-align-top"
|
||||||
|
data-testid="request-tags"
|
||||||
|
onClick={() => onRequestTagsHandler(cell)}>
|
||||||
|
<Popover
|
||||||
|
destroyTooltipOnHide
|
||||||
|
content="Request tags"
|
||||||
|
overlayClassName="ant-popover-request-description"
|
||||||
|
trigger="hover"
|
||||||
|
zIndex={9999}>
|
||||||
|
<SVGIcons alt="request-tags" icon={Icons.REQUEST} width="16px" />
|
||||||
|
</Popover>
|
||||||
|
</button>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchText) {
|
if (!searchText) {
|
||||||
setSearchedColumns(tableColumns);
|
setSearchedColumns(tableColumns);
|
||||||
@ -677,6 +667,7 @@ const EntityTable = ({
|
|||||||
/>
|
/>
|
||||||
</NonAdminAction>
|
</NonAdminAction>
|
||||||
<div className="tw-mt-1">
|
<div className="tw-mt-1">
|
||||||
|
{getRequestTagsElement(cell)}
|
||||||
{getFieldThreadElement(
|
{getFieldThreadElement(
|
||||||
getColumnName(cell),
|
getColumnName(cell),
|
||||||
'tags',
|
'tags',
|
||||||
@ -689,6 +680,23 @@ const EntityTable = ({
|
|||||||
)}${ENTITY_LINK_SEPARATOR}tags`,
|
)}${ENTITY_LINK_SEPARATOR}tags`,
|
||||||
Boolean(cell.value.length)
|
Boolean(cell.value.length)
|
||||||
)}
|
)}
|
||||||
|
{getFieldThreadElement(
|
||||||
|
getColumnName(cell),
|
||||||
|
EntityField.TAGS,
|
||||||
|
entityFieldTasks as EntityFieldThreads[],
|
||||||
|
onThreadLinkSelect,
|
||||||
|
EntityType.TABLE,
|
||||||
|
entityFqn,
|
||||||
|
`${
|
||||||
|
EntityField.COLUMNS
|
||||||
|
}${ENTITY_LINK_SEPARATOR}${getColumnName(
|
||||||
|
cell
|
||||||
|
)}${ENTITY_LINK_SEPARATOR}${
|
||||||
|
EntityField.TAGS
|
||||||
|
}`,
|
||||||
|
Boolean(cell.value),
|
||||||
|
ThreadType.Task
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TABLE_HEADERS = [
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Type',
|
||||||
|
accessor: 'dataTypeDisplay',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Data Quality',
|
||||||
|
accessor: 'columnTests',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Description',
|
||||||
|
accessor: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Tags',
|
||||||
|
accessor: 'tags',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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 { EntityFieldThreads } from 'Models';
|
||||||
|
import { ThreadType } from '../../generated/api/feed/createThread';
|
||||||
|
import { ColumnJoins, Table } from '../../generated/entity/data/table';
|
||||||
|
import { ModifiedTableColumn } from '../../interface/dataQuality.interface';
|
||||||
|
|
||||||
|
export interface EntityTableProps {
|
||||||
|
owner: Table['owner'];
|
||||||
|
tableColumns: ModifiedTableColumn[];
|
||||||
|
joins: Array<ColumnJoins>;
|
||||||
|
columnName: string;
|
||||||
|
hasEditAccess: boolean;
|
||||||
|
tableConstraints: Table['tableConstraints'];
|
||||||
|
searchText?: string;
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
entityFqn?: string;
|
||||||
|
entityFieldThreads?: EntityFieldThreads[];
|
||||||
|
entityFieldTasks?: EntityFieldThreads[];
|
||||||
|
onUpdate?: (columns: ModifiedTableColumn[]) => void;
|
||||||
|
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
|
||||||
|
onEntityFieldSelect?: (value: string) => void;
|
||||||
|
}
|
@ -286,6 +286,33 @@ jest.mock('../../utils/TagsUtils', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('./EntityTable.constant', () => {
|
||||||
|
return {
|
||||||
|
TABLE_HEADERS: [
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Type',
|
||||||
|
accessor: 'dataTypeDisplay',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Data Quality',
|
||||||
|
accessor: 'columnTests',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Description',
|
||||||
|
accessor: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Tags',
|
||||||
|
accessor: 'tags',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('Test EntityTable Component', () => {
|
describe('Test EntityTable Component', () => {
|
||||||
it('Check if it has all child elements', async () => {
|
it('Check if it has all child elements', async () => {
|
||||||
const { container } = render(<EntityTable {...mockEntityTableProp} />, {
|
const { container } = render(<EntityTable {...mockEntityTableProp} />, {
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { cloneDeep, isEmpty, isUndefined, lowerCase } from 'lodash';
|
import { cloneDeep, isEmpty, lowerCase } from 'lodash';
|
||||||
import {
|
import {
|
||||||
AggregationType,
|
AggregationType,
|
||||||
Bucket,
|
Bucket,
|
||||||
@ -131,6 +131,9 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
const [selectedAdvancedFields, setSelectedAdvancedField] = useState<
|
const [selectedAdvancedFields, setSelectedAdvancedField] = useState<
|
||||||
Array<AdvanceField>
|
Array<AdvanceField>
|
||||||
>([]);
|
>([]);
|
||||||
|
const [isInitialFilterSet, setIsInitialFilterSet] = useState<boolean>(
|
||||||
|
!isEmpty(initialFilter)
|
||||||
|
);
|
||||||
|
|
||||||
const onAdvancedFieldSelect = (value: string) => {
|
const onAdvancedFieldSelect = (value: string) => {
|
||||||
const flag = selectedAdvancedFields.some((field) => field.key === value);
|
const flag = selectedAdvancedFields.some((field) => field.key === value);
|
||||||
@ -505,7 +508,11 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
|
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
if (!isMounting.current && previsouIndex === getCurrentIndex(tab)) {
|
if (!isMounting.current && previsouIndex === getCurrentIndex(tab)) {
|
||||||
|
if (isInitialFilterSet) {
|
||||||
|
forceSetAgg.current = isInitialFilterSet;
|
||||||
|
} else {
|
||||||
forceSetAgg.current = !isFilterSet;
|
forceSetAgg.current = !isFilterSet;
|
||||||
|
}
|
||||||
fetchTableData();
|
fetchTableData();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -565,7 +572,7 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
if (searchResult) {
|
if (searchResult) {
|
||||||
updateSearchResults(searchResult.resSearchResults);
|
updateSearchResults(searchResult.resSearchResults);
|
||||||
setCount(searchResult.resSearchResults.data.hits.total.value);
|
setCount(searchResult.resSearchResults.data.hits.total.value);
|
||||||
if (forceSetAgg.current || !isUndefined(initialFilter)) {
|
if (forceSetAgg.current) {
|
||||||
setAggregations(
|
setAggregations(
|
||||||
searchResult.resSearchResults.data.hits.hits.length > 0
|
searchResult.resSearchResults.data.hits.hits.length > 0
|
||||||
? getAggregationList(
|
? getAggregationList(
|
||||||
@ -573,6 +580,7 @@ const Explore: React.FC<ExploreProps> = ({
|
|||||||
)
|
)
|
||||||
: getAggregationListFromQS(location.search)
|
: getAggregationListFromQS(location.search)
|
||||||
);
|
);
|
||||||
|
setIsInitialFilterSet(false);
|
||||||
} else {
|
} else {
|
||||||
const aggServiceType = getAggregationList(
|
const aggServiceType = getAggregationList(
|
||||||
searchResult.resAggServiceType.data.aggregations,
|
searchResult.resAggServiceType.data.aggregations,
|
||||||
|
@ -178,7 +178,7 @@ const FacetFilter: FunctionComponent<FacetProp> = ({
|
|||||||
className="sidebar-my-data-holder mt-2 mb-3"
|
className="sidebar-my-data-holder mt-2 mb-3"
|
||||||
data-testid="show-deleted-cntnr">
|
data-testid="show-deleted-cntnr">
|
||||||
<div
|
<div
|
||||||
className="filter-group tw-justify-between tw-mb-2"
|
className="filter-group tw-justify-between tw-mb-1"
|
||||||
data-testid="filter-container-deleted">
|
data-testid="filter-container-deleted">
|
||||||
<div className="tw-flex">
|
<div className="tw-flex">
|
||||||
<div className="filters-title tw-w-36 tw-truncate custom-checkbox-label">
|
<div className="filters-title tw-w-36 tw-truncate custom-checkbox-label">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user