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:
Sachin Chaurasiya 2022-07-14 11:44:17 +05:30 committed by GitHub
parent 954635b6dc
commit c40e9ff685
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 162 additions and 50 deletions

View File

@ -15,7 +15,7 @@ import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popover } from 'antd';
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 React, { Fragment, useEffect, useState } from 'react';
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 {
Column,
ColumnJoins,
ColumnTest,
JoinedWith,
Table,
} from '../../generated/entity/data/table';
import { ThreadType } from '../../generated/entity/feed/thread';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
@ -59,6 +57,7 @@ import {
import { getTagCategories, getTaglist } from '../../utils/TagsUtils';
import {
getRequestDescriptionPath,
getRequestTagsPath,
getUpdateDescriptionPath,
} from '../../utils/TasksUtils';
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 TagsContainer from '../tags-container/tags-container';
import TagsViewer from '../tags-viewer/tags-viewer';
interface Props {
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;
}
import { TABLE_HEADERS } from './EntityTable.constant';
import { EntityTableProps } from './EntityTable.interface';
const EntityTable = ({
tableColumns,
@ -98,35 +82,11 @@ const EntityTable = ({
entityFqn,
tableConstraints,
entityFieldTasks,
}: Props) => {
}: EntityTableProps) => {
const { isAdminUser, userPermissions } = useAuth();
const { isAuthDisabled } = useAuthContext();
const history = useHistory();
const columns = React.useMemo(
() => [
{
Header: 'Name',
accessor: 'name',
},
{
Header: 'Type',
accessor: 'dataTypeDisplay',
},
{
Header: 'Data Quality',
accessor: 'columnTests',
},
{
Header: 'Description',
accessor: 'description',
},
{
Header: 'Tags',
accessor: 'tags',
},
],
[]
);
const columns = TABLE_HEADERS;
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 = (
columnName: 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(() => {
if (!searchText) {
setSearchedColumns(tableColumns);
@ -677,6 +667,7 @@ const EntityTable = ({
/>
</NonAdminAction>
<div className="tw-mt-1">
{getRequestTagsElement(cell)}
{getFieldThreadElement(
getColumnName(cell),
'tags',
@ -689,6 +680,23 @@ const EntityTable = ({
)}${ENTITY_LINK_SEPARATOR}tags`,
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>
)}

View File

@ -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',
},
];

View File

@ -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;
}

View File

@ -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', () => {
it('Check if it has all child elements', async () => {
const { container } = render(<EntityTable {...mockEntityTableProp} />, {

View File

@ -18,7 +18,7 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Card } from 'antd';
import classNames from 'classnames';
import { cloneDeep, isEmpty, isUndefined, lowerCase } from 'lodash';
import { cloneDeep, isEmpty, lowerCase } from 'lodash';
import {
AggregationType,
Bucket,
@ -131,6 +131,9 @@ const Explore: React.FC<ExploreProps> = ({
const [selectedAdvancedFields, setSelectedAdvancedField] = useState<
Array<AdvanceField>
>([]);
const [isInitialFilterSet, setIsInitialFilterSet] = useState<boolean>(
!isEmpty(initialFilter)
);
const onAdvancedFieldSelect = (value: string) => {
const flag = selectedAdvancedFields.some((field) => field.key === value);
@ -505,7 +508,11 @@ const Explore: React.FC<ExploreProps> = ({
const getData = () => {
if (!isMounting.current && previsouIndex === getCurrentIndex(tab)) {
if (isInitialFilterSet) {
forceSetAgg.current = isInitialFilterSet;
} else {
forceSetAgg.current = !isFilterSet;
}
fetchTableData();
}
};
@ -565,7 +572,7 @@ const Explore: React.FC<ExploreProps> = ({
if (searchResult) {
updateSearchResults(searchResult.resSearchResults);
setCount(searchResult.resSearchResults.data.hits.total.value);
if (forceSetAgg.current || !isUndefined(initialFilter)) {
if (forceSetAgg.current) {
setAggregations(
searchResult.resSearchResults.data.hits.hits.length > 0
? getAggregationList(
@ -573,6 +580,7 @@ const Explore: React.FC<ExploreProps> = ({
)
: getAggregationListFromQS(location.search)
);
setIsInitialFilterSet(false);
} else {
const aggServiceType = getAggregationList(
searchResult.resAggServiceType.data.aggregations,

View File

@ -178,7 +178,7 @@ const FacetFilter: FunctionComponent<FacetProp> = ({
className="sidebar-my-data-holder mt-2 mb-3"
data-testid="show-deleted-cntnr">
<div
className="filter-group tw-justify-between tw-mb-2"
className="filter-group tw-justify-between tw-mb-1"
data-testid="filter-container-deleted">
<div className="tw-flex">
<div className="filters-title tw-w-36 tw-truncate custom-checkbox-label">