UI : fix request/update task after redirect from frequently joined tables (#17399)

* fix request/update task after redirect from frequently joined tables

* fix cypress test

* modify persona heading like entity header

* change css from fill to stretch and also fix cypress as per comments

* change the breadcrumb name to show display name if available like other entites

* fix advance search cypress

* fix search cypress

* fix unit tests

* Update the logic to fetch the column field for advanced search

* revert permission thing for database

---------

Co-authored-by: Chira Madlani <chirag@getcollate.io>
Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
Co-authored-by: karanh37 <karanh37@gmail.com>
Co-authored-by: Aniket Katkar <aniketkatkar97@gmail.com>
This commit is contained in:
Ashish Gupta 2024-08-15 00:04:48 +05:30 committed by GitHub
parent 75f62a7872
commit fc07324254
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 202 additions and 79 deletions

View File

@ -160,12 +160,14 @@ describe('Persona operations', { tags: 'Settings' }, () => {
verifyResponseStatusCode('@getPersonaDetails', 200); verifyResponseStatusCode('@getPersonaDetails', 200);
cy.get( cy.get('[data-testid="entity-header-name"]').should(
'[data-testid="page-header-container"] [data-testid="heading"]' 'contain',
).should('contain', PERSONA_DETAILS.displayName); PERSONA_DETAILS.name
cy.get( );
'[data-testid="page-header-container"] [data-testid="sub-heading"]' cy.get('[data-testid="entity-header-display-name"]').should(
).should('contain', PERSONA_DETAILS.name); 'contain',
PERSONA_DETAILS.displayName
);
cy.get( cy.get(
'[data-testid="viewer-container"] [data-testid="markdown-parser"]' '[data-testid="viewer-container"] [data-testid="markdown-parser"]'
).should('contain', PERSONA_DETAILS.description); ).should('contain', PERSONA_DETAILS.description);
@ -222,11 +224,14 @@ describe('Persona operations', { tags: 'Settings' }, () => {
updatePersonaDisplayName('Test Persona'); updatePersonaDisplayName('Test Persona');
cy.get('[data-testid="heading"]').should('contain', 'Test Persona'); cy.get('[data-testid="entity-header-display-name"]').should(
'contain',
'Test Persona'
);
updatePersonaDisplayName(PERSONA_DETAILS.displayName); updatePersonaDisplayName(PERSONA_DETAILS.displayName);
cy.get('[data-testid="heading"]').should( cy.get('[data-testid="entity-header-display-name"]').should(
'contain', 'contain',
PERSONA_DETAILS.displayName PERSONA_DETAILS.displayName
); );

View File

@ -504,7 +504,7 @@ describe('Glossary page should work properly', { tags: 'Governance' }, () => {
interceptURL('GET', `/api/v1/teams/**`, 'getTeams'); interceptURL('GET', `/api/v1/teams/**`, 'getTeams');
interceptURL( interceptURL(
'GET', 'GET',
`/api/v1/users?fields=teams%2Croles&limit=25&team=${appName}`, `/api/v1/users?fields=*&limit=25&team=${appName}`,
'teamUsers' 'teamUsers'
); );

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -30,6 +30,7 @@ import { EntityTags, TagFilterOptions } from 'Models';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ReactComponent as IconEdit } from '../../../assets/svg/edit-new.svg'; import { ReactComponent as IconEdit } from '../../../assets/svg/edit-new.svg';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { import {
DE_ACTIVE_COLOR, DE_ACTIVE_COLOR,
ICON_DIMENSION, ICON_DIMENSION,
@ -110,6 +111,16 @@ const SchemaTable = ({
const [editColumnDisplayName, setEditColumnDisplayName] = useState<Column>(); const [editColumnDisplayName, setEditColumnDisplayName] = useState<Column>();
const { getEntityPermissionByFqn } = usePermissionProvider(); const { getEntityPermissionByFqn } = usePermissionProvider();
const tableFqn = useMemo(
() =>
getPartialNameFromTableFQN(
decodedEntityFqn,
[FqnPart.Service, FqnPart.Database, FqnPart.Schema, FqnPart.Table],
FQN_SEPARATOR_CHAR
),
[decodedEntityFqn]
);
const sortByOrdinalPosition = useMemo( const sortByOrdinalPosition = useMemo(
() => sortBy(tableColumns, 'ordinalPosition'), () => sortBy(tableColumns, 'ordinalPosition'),
[tableColumns] [tableColumns]
@ -143,10 +154,10 @@ const SchemaTable = ({
); );
useEffect(() => { useEffect(() => {
if (!isEmpty(decodedEntityFqn)) { if (!isEmpty(tableFqn)) {
fetchResourcePermission(decodedEntityFqn); fetchResourcePermission(tableFqn);
} }
}, [decodedEntityFqn]); }, [tableFqn]);
const handleEditColumn = (column: Column): void => { const handleEditColumn = (column: Column): void => {
setEditColumn(column); setEditColumn(column);
@ -253,7 +264,7 @@ const SchemaTable = ({
field: record.description, field: record.description,
record, record,
}} }}
entityFqn={decodedEntityFqn} entityFqn={tableFqn}
entityType={EntityType.TABLE} entityType={EntityType.TABLE}
hasEditPermission={hasDescriptionEditAccess} hasEditPermission={hasDescriptionEditAccess}
index={index} index={index}
@ -422,7 +433,7 @@ const SchemaTable = ({
), ),
render: (tags: TagLabel[], record: Column, index: number) => ( render: (tags: TagLabel[], record: Column, index: number) => (
<TableTags<Column> <TableTags<Column>
entityFqn={decodedEntityFqn} entityFqn={tableFqn}
entityType={EntityType.TABLE} entityType={EntityType.TABLE}
handleTagSelection={handleTagSelection} handleTagSelection={handleTagSelection}
hasTagEditAccess={hasTagEditAccess} hasTagEditAccess={hasTagEditAccess}
@ -454,7 +465,7 @@ const SchemaTable = ({
), ),
render: (tags: TagLabel[], record: Column, index: number) => ( render: (tags: TagLabel[], record: Column, index: number) => (
<TableTags<Column> <TableTags<Column>
entityFqn={decodedEntityFqn} entityFqn={tableFqn}
entityType={EntityType.TABLE} entityType={EntityType.TABLE}
handleTagSelection={handleTagSelection} handleTagSelection={handleTagSelection}
hasTagEditAccess={hasTagEditAccess} hasTagEditAccess={hasTagEditAccess}
@ -490,7 +501,7 @@ const SchemaTable = ({
}, },
], ],
[ [
decodedEntityFqn, tableFqn,
isReadOnly, isReadOnly,
tableConstraints, tableConstraints,
hasTagEditAccess, hasTagEditAccess,

View File

@ -153,7 +153,7 @@ const TagsV1 = ({
{...tagProps}> {...tagProps}>
{/* Wrap only content to avoid redirect on closeable icons */} {/* Wrap only content to avoid redirect on closeable icons */}
<Link <Link
className="no-underline h-full w-full" className="no-underline h-full w-max-stretch"
data-testid="tag-redirect-link" data-testid="tag-redirect-link"
to={redirectLink}> to={redirectLink}>
{tagContent} {tagContent}

View File

@ -143,7 +143,10 @@ jest.mock('react-router-dom', () => ({
.mockImplementation(({ children }: { children: React.ReactNode }) => ( .mockImplementation(({ children }: { children: React.ReactNode }) => (
<p data-testid="link">{children}</p> <p data-testid="link">{children}</p>
)), )),
useHistory: jest.fn(), useHistory: () => ({
push: jest.fn(),
replace: jest.fn(),
}),
useParams: jest.fn().mockReturnValue({ useParams: jest.fn().mockReturnValue({
fqn: 'bigquery.shopify', fqn: 'bigquery.shopify',
}), }),

View File

@ -144,13 +144,18 @@ jest.mock('../../../utils/ToastUtils', () => ({
showErrorToast: jest.fn(), showErrorToast: jest.fn(),
})); }));
jest.mock(
'../../../components/Entity/EntityHeaderTitle/EntityHeaderTitle.component',
() => jest.fn().mockImplementation(() => <div>EntityHeaderTitle</div>)
);
describe('PersonaDetailsPage', () => { describe('PersonaDetailsPage', () => {
it('Component should render', async () => { it('Component should render', async () => {
render(<PersonaDetailsPage />); render(<PersonaDetailsPage />);
await waitForElementToBeRemoved(() => screen.getByTestId('loader')); await waitForElementToBeRemoved(() => screen.getByTestId('loader'));
expect(screen.getByTestId('persona-heading')).toBeInTheDocument(); expect(screen.getByText('EntityHeaderTitle')).toBeInTheDocument();
expect(screen.getByText('TitleBreadcrumb.component')).toBeInTheDocument(); expect(screen.getByText('TitleBreadcrumb.component')).toBeInTheDocument();
expect( expect(
await screen.findByText('ManageButton.component') await screen.findByText('ManageButton.component')

View File

@ -10,19 +10,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Tabs, Typography } from 'antd'; import Icon from '@ant-design/icons/lib/components/Icon';
import { Button, Col, Row, Tabs } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { ReactComponent as IconPersona } from '../../../assets/svg/ic-personas.svg';
import DescriptionV1 from '../../../components/common/EntityDescription/DescriptionV1'; import DescriptionV1 from '../../../components/common/EntityDescription/DescriptionV1';
import ManageButton from '../../../components/common/EntityPageInfos/ManageButton/ManageButton'; import ManageButton from '../../../components/common/EntityPageInfos/ManageButton/ManageButton';
import NoDataPlaceholder from '../../../components/common/ErrorWithPlaceholder/NoDataPlaceholder'; import NoDataPlaceholder from '../../../components/common/ErrorWithPlaceholder/NoDataPlaceholder';
import Loader from '../../../components/common/Loader/Loader'; import Loader from '../../../components/common/Loader/Loader';
import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component'; import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { UserSelectableList } from '../../../components/common/UserSelectableList/UserSelectableList.component'; import { UserSelectableList } from '../../../components/common/UserSelectableList/UserSelectableList.component';
import EntityHeaderTitle from '../../../components/Entity/EntityHeaderTitle/EntityHeaderTitle.component';
import { EntityName } from '../../../components/Modals/EntityNameModal/EntityNameModal.interface'; import { EntityName } from '../../../components/Modals/EntityNameModal/EntityNameModal.interface';
import PageLayoutV1 from '../../../components/PageLayoutV1/PageLayoutV1'; import PageLayoutV1 from '../../../components/PageLayoutV1/PageLayoutV1';
import { UsersTab } from '../../../components/Settings/Users/UsersTab/UsersTabs.component'; import { UsersTab } from '../../../components/Settings/Users/UsersTab/UsersTabs.component';
@ -182,14 +185,18 @@ export const PersonaDetailsPage = () => {
<Row className="m-b-md page-container" gutter={[0, 16]}> <Row className="m-b-md page-container" gutter={[0, 16]}>
<Col span={24}> <Col span={24}>
<div className="d-flex justify-between items-start"> <div className="d-flex justify-between items-start">
<div> <div className="w-full">
<TitleBreadcrumb titleLinks={breadcrumb} /> <TitleBreadcrumb titleLinks={breadcrumb} />
<Typography.Title
className="m-b-0 m-t-xs" <EntityHeaderTitle
data-testid="persona-heading" className="m-t-xs"
level={5}> displayName={personaDetails.displayName}
{getEntityName(personaDetails)} icon={
</Typography.Title> <Icon component={IconPersona} style={{ fontSize: '36px' }} />
}
name={personaDetails?.name}
serviceName={personaDetails.name}
/>
</div> </div>
<ManageButton <ManageButton
afterDeleteAction={handleAfterDeleteAction} afterDeleteAction={handleAfterDeleteAction}

View File

@ -140,14 +140,24 @@ const TableDetailsPageV1: React.FC = () => {
); );
const [testCaseSummary, setTestCaseSummary] = useState<TestSummary>(); const [testCaseSummary, setTestCaseSummary] = useState<TestSummary>();
const tableFqn = useMemo(
() =>
getPartialNameFromTableFQN(
datasetFQN,
[FqnPart.Service, FqnPart.Database, FqnPart.Schema, FqnPart.Table],
FQN_SEPARATOR_CHAR
),
[datasetFQN]
);
const extraDropdownContent = useMemo( const extraDropdownContent = useMemo(
() => () =>
entityUtilClassBase.getManageExtraOptions( entityUtilClassBase.getManageExtraOptions(
EntityType.TABLE, EntityType.TABLE,
datasetFQN, tableFqn,
tablePermissions tablePermissions
), ),
[tablePermissions, datasetFQN] [tablePermissions, tableFqn]
); );
const { viewUsagePermission, viewTestCasePermission } = useMemo( const { viewUsagePermission, viewTestCasePermission } = useMemo(
@ -160,16 +170,6 @@ const TableDetailsPageV1: React.FC = () => {
[tablePermissions] [tablePermissions]
); );
const tableFqn = useMemo(
() =>
getPartialNameFromTableFQN(
datasetFQN,
[FqnPart.Service, FqnPart.Database, FqnPart.Schema, FqnPart.Table],
FQN_SEPARATOR_CHAR
),
[datasetFQN]
);
const isViewTableType = useMemo( const isViewTableType = useMemo(
() => tableDetails?.tableType === TableType.View, () => tableDetails?.tableType === TableType.View,
[tableDetails?.tableType] [tableDetails?.tableType]
@ -560,7 +560,7 @@ const TableDetailsPageV1: React.FC = () => {
<DescriptionV1 <DescriptionV1
showSuggestions showSuggestions
description={tableDetails?.description} description={tableDetails?.description}
entityFqn={datasetFQN} entityFqn={tableFqn}
entityName={entityName} entityName={entityName}
entityType={EntityType.TABLE} entityType={EntityType.TABLE}
hasEditAccess={editDescriptionPermission} hasEditAccess={editDescriptionPermission}
@ -613,7 +613,7 @@ const TableDetailsPageV1: React.FC = () => {
editCustomAttributePermission editCustomAttributePermission
} }
editTagPermission={editTagsPermission} editTagPermission={editTagsPermission}
entityFQN={datasetFQN} entityFQN={tableFqn}
entityId={tableDetails?.id ?? ''} entityId={tableDetails?.id ?? ''}
entityType={EntityType.TABLE} entityType={EntityType.TABLE}
selectedTags={tableTags} selectedTags={tableTags}
@ -635,14 +635,22 @@ const TableDetailsPageV1: React.FC = () => {
</Row> </Row>
), ),
[ [
isTourPage,
tableTags,
joinedTables,
tableFqn,
isEdit, isEdit,
deleted,
tableDetails, tableDetails,
entityName, entityName,
onDescriptionEdit, onDescriptionEdit,
onDescriptionUpdate, onDescriptionUpdate,
testCaseSummary,
editTagsPermission, editTagsPermission,
editDescriptionPermission, editDescriptionPermission,
editAllPermission, editAllPermission,
viewAllPermission,
editCustomAttributePermission,
] ]
); );

View File

@ -58,6 +58,9 @@ jest.mock('../../../utils/TasksUtils', () => ({
getBreadCrumbList: jest.fn().mockReturnValue([]), getBreadCrumbList: jest.fn().mockReturnValue([]),
getTaskMessage: jest.fn().mockReturnValue('Task message'), getTaskMessage: jest.fn().mockReturnValue('Task message'),
getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE), getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE),
getTaskEntityFQN: jest
.fn()
.mockReturnValue('sample_data.ecommerce_db.shopify.dim_location'),
})); }));
jest.mock('../shared/Assignees', () => jest.mock('../shared/Assignees', () =>
jest.fn().mockImplementation(() => <div>Assignees.component</div>) jest.fn().mockImplementation(() => <div>Assignees.component</div>)

View File

@ -47,6 +47,7 @@ import {
fetchOptions, fetchOptions,
getBreadCrumbList, getBreadCrumbList,
getTaskAssignee, getTaskAssignee,
getTaskEntityFQN,
getTaskMessage, getTaskMessage,
} from '../../../utils/TasksUtils'; } from '../../../utils/TasksUtils';
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
@ -64,7 +65,7 @@ const RequestDescription = () => {
const { entityType } = useParams<{ entityType: EntityType }>(); const { entityType } = useParams<{ entityType: EntityType }>();
const { fqn: decodedEntityFQN } = useFqn(); const { fqn } = useFqn();
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const field = queryParams.get('field'); const field = queryParams.get('field');
@ -76,6 +77,11 @@ const RequestDescription = () => {
const [suggestion, setSuggestion] = useState<string>(''); const [suggestion, setSuggestion] = useState<string>('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const entityFQN = useMemo(
() => getTaskEntityFQN(entityType, fqn),
[fqn, entityType]
);
const taskMessage = useMemo( const taskMessage = useMemo(
() => () =>
getTaskMessage({ getTaskMessage({
@ -116,7 +122,7 @@ const RequestDescription = () => {
const data: CreateThread = { const data: CreateThread = {
from: currentUser?.name as string, from: currentUser?.name as string,
message: value.title || taskMessage, message: value.title || taskMessage,
about: getEntityFeedLink(entityType, decodedEntityFQN, getTaskAbout()), about: getEntityFeedLink(entityType, entityFQN, getTaskAbout()),
taskDetails: { taskDetails: {
assignees: assignees.map((assignee) => ({ assignees: assignees.map((assignee) => ({
id: assignee.value, id: assignee.value,
@ -138,7 +144,7 @@ const RequestDescription = () => {
history.push( history.push(
entityUtilClassBase.getEntityLink( entityUtilClassBase.getEntityLink(
entityType, entityType,
decodedEntityFQN, entityFQN,
EntityTabs.ACTIVITY_FEED, EntityTabs.ACTIVITY_FEED,
ActivityFeedTabs.TASKS ActivityFeedTabs.TASKS
) )
@ -152,8 +158,8 @@ const RequestDescription = () => {
}; };
useEffect(() => { useEffect(() => {
fetchEntityDetail(entityType, decodedEntityFQN, setEntityData); fetchEntityDetail(entityType, entityFQN, setEntityData);
}, [decodedEntityFQN, entityType]); }, [entityFQN, entityType]);
useEffect(() => { useEffect(() => {
const defaultAssignee = getTaskAssignee(entityData as Glossary); const defaultAssignee = getTaskAssignee(entityData as Glossary);

View File

@ -59,6 +59,9 @@ jest.mock('../../../utils/TasksUtils', () => ({
getBreadCrumbList: jest.fn().mockReturnValue([]), getBreadCrumbList: jest.fn().mockReturnValue([]),
getTaskMessage: jest.fn().mockReturnValue('Task message'), getTaskMessage: jest.fn().mockReturnValue('Task message'),
getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE), getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE),
getTaskEntityFQN: jest
.fn()
.mockReturnValue('sample_data.ecommerce_db.shopify.dim_location'),
})); }));
jest.mock('../shared/Assignees', () => jest.mock('../shared/Assignees', () =>
jest.fn().mockImplementation(() => <div>Assignees.component</div>) jest.fn().mockImplementation(() => <div>Assignees.component</div>)

View File

@ -46,6 +46,7 @@ import {
fetchOptions, fetchOptions,
getBreadCrumbList, getBreadCrumbList,
getTaskAssignee, getTaskAssignee,
getTaskEntityFQN,
getTaskMessage, getTaskMessage,
} from '../../../utils/TasksUtils'; } from '../../../utils/TasksUtils';
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
@ -61,7 +62,7 @@ const RequestTag = () => {
const history = useHistory(); const history = useHistory();
const [form] = useForm(); const [form] = useForm();
const { entityType } = useParams<{ entityType: EntityType }>(); const { entityType } = useParams<{ entityType: EntityType }>();
const { fqn: entityFQN } = useFqn(); const { fqn } = useFqn();
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const field = queryParams.get('field'); const field = queryParams.get('field');
@ -73,6 +74,11 @@ const RequestTag = () => {
const [suggestion] = useState<TagLabel[]>([]); const [suggestion] = useState<TagLabel[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const entityFQN = useMemo(
() => getTaskEntityFQN(entityType, fqn),
[fqn, entityType]
);
const taskMessage = useMemo( const taskMessage = useMemo(
() => () =>
getTaskMessage({ getTaskMessage({

View File

@ -80,6 +80,9 @@ jest.mock('../../../utils/TasksUtils', () => ({
description: mockTableData.columns[0].description, description: mockTableData.columns[0].description,
})), })),
getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE), getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE),
getTaskEntityFQN: jest
.fn()
.mockReturnValue('sample_data.ecommerce_db.shopify.dim_location'),
})); }));
jest.mock('../shared/Assignees', () => jest.mock('../shared/Assignees', () =>
jest.fn().mockImplementation(() => <div>Assignees.component</div>) jest.fn().mockImplementation(() => <div>Assignees.component</div>)

View File

@ -50,6 +50,7 @@ import {
getColumnObject, getColumnObject,
getEntityColumnsDetails, getEntityColumnsDetails,
getTaskAssignee, getTaskAssignee,
getTaskEntityFQN,
getTaskMessage, getTaskMessage,
} from '../../../utils/TasksUtils'; } from '../../../utils/TasksUtils';
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
@ -66,7 +67,7 @@ const UpdateDescription = () => {
const [form] = useForm(); const [form] = useForm();
const { entityType } = useParams<{ entityType: EntityType }>(); const { entityType } = useParams<{ entityType: EntityType }>();
const { fqn: entityFQN } = useFqn(); const { fqn } = useFqn();
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const field = queryParams.get('field'); const field = queryParams.get('field');
@ -78,6 +79,11 @@ const UpdateDescription = () => {
const [currentDescription, setCurrentDescription] = useState<string>(''); const [currentDescription, setCurrentDescription] = useState<string>('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const entityFQN = useMemo(
() => getTaskEntityFQN(entityType, fqn),
[fqn, entityType]
);
const sanitizeValue = useMemo( const sanitizeValue = useMemo(
() => value?.replaceAll(TASK_SANITIZE_VALUE_REGEX, '') ?? '', () => value?.replaceAll(TASK_SANITIZE_VALUE_REGEX, '') ?? '',
[value] [value]

View File

@ -90,6 +90,9 @@ jest.mock('../../../utils/TasksUtils', () => ({
getColumnObject: jest.fn().mockImplementation(() => ({ getColumnObject: jest.fn().mockImplementation(() => ({
tags: mockTableData.columns[0].tags, tags: mockTableData.columns[0].tags,
})), })),
getTaskEntityFQN: jest
.fn()
.mockReturnValue('sample_data.ecommerce_db.shopify.dim_location'),
getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE), getTaskAssignee: jest.fn().mockReturnValue(MOCK_TASK_ASSIGNEE),
})); }));
jest.mock('../shared/Assignees', () => jest.mock('../shared/Assignees', () =>

View File

@ -51,6 +51,7 @@ import {
getColumnObject, getColumnObject,
getEntityColumnsDetails, getEntityColumnsDetails,
getTaskAssignee, getTaskAssignee,
getTaskEntityFQN,
getTaskMessage, getTaskMessage,
} from '../../../utils/TasksUtils'; } from '../../../utils/TasksUtils';
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
@ -68,7 +69,7 @@ const UpdateTag = () => {
const { entityType } = useParams<{ entityType: EntityType }>(); const { entityType } = useParams<{ entityType: EntityType }>();
const { fqn: entityFQN } = useFqn(); const { fqn } = useFqn();
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const field = queryParams.get('field'); const field = queryParams.get('field');
@ -83,6 +84,11 @@ const UpdateTag = () => {
const [suggestion, setSuggestion] = useState<TagLabel[]>([]); const [suggestion, setSuggestion] = useState<TagLabel[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const entityFQN = useMemo(
() => getTaskEntityFQN(entityType, fqn),
[fqn, entityType]
);
const sanitizeValue = useMemo( const sanitizeValue = useMemo(
() => value?.replaceAll(TASK_SANITIZE_VALUE_REGEX, '') ?? '', () => value?.replaceAll(TASK_SANITIZE_VALUE_REGEX, '') ?? '',
[value] [value]

View File

@ -174,6 +174,9 @@
.w-max-full-140 { .w-max-full-140 {
max-width: calc(100% - 140px); max-width: calc(100% - 140px);
} }
.w-max-stretch {
max-width: stretch;
}
.w-auto { .w-auto {
width: auto; width: auto;

View File

@ -12,7 +12,7 @@
*/ */
import { t } from 'i18next'; import { t } from 'i18next';
import { sortBy } from 'lodash'; import { isEmpty, sortBy } from 'lodash';
import { import {
AsyncFetchListValues, AsyncFetchListValues,
AsyncFetchListValuesResult, AsyncFetchListValuesResult,
@ -97,19 +97,6 @@ class AdvancedSearchClassBase {
}, },
}, },
'columns.name.keyword': {
label: t('label.column'),
type: 'select',
mainWidgetProps: this.mainWidgetProps,
fieldSettings: {
asyncFetch: this.autocomplete({
searchIndex: SearchIndex.TABLE,
entityField: EntityFields.COLUMN,
}),
useAsyncSearch: true,
},
},
tableType: { tableType: {
label: t('label.table-type'), label: t('label.table-type'),
type: 'select', type: 'select',
@ -320,18 +307,6 @@ class AdvancedSearchClassBase {
useAsyncSearch: true, useAsyncSearch: true,
}, },
}, },
'columns.name.keyword': {
label: t('label.data-model-column'),
type: 'select',
mainWidgetProps: this.mainWidgetProps,
fieldSettings: {
asyncFetch: this.autocomplete({
searchIndex: SearchIndex.DASHBOARD_DATA_MODEL,
entityField: EntityFields.COLUMN,
}),
useAsyncSearch: true,
},
},
'project.keyword': { 'project.keyword': {
label: t('label.project'), label: t('label.project'),
type: 'select', type: 'select',
@ -551,6 +526,36 @@ class AdvancedSearchClassBase {
}; };
} }
// Since the column field key 'columns.name.keyword` is common in table and data model,
// Following function is used to get the column field config based on the search index
// or if it is an explore page
public getColumnConfig = (entitySearchIndex: SearchIndex[]) => {
const searchIndexWithColumns = entitySearchIndex.filter(
(index) =>
index === SearchIndex.TABLE ||
index === SearchIndex.DASHBOARD_DATA_MODEL ||
index === SearchIndex.DATA_ASSET ||
index === SearchIndex.ALL
);
return !isEmpty(searchIndexWithColumns)
? {
'columns.name.keyword': {
label: t('label.column'),
type: 'select',
mainWidgetProps: this.mainWidgetProps,
fieldSettings: {
asyncFetch: this.autocomplete({
searchIndex: searchIndexWithColumns,
entityField: EntityFields.COLUMN,
}),
useAsyncSearch: true,
},
},
}
: {};
};
/** /**
* Get entity specific fields for the query builder * Get entity specific fields for the query builder
*/ */
@ -631,6 +636,7 @@ class AdvancedSearchClassBase {
...this.getCommonConfig({ entitySearchIndex, tierOptions }), ...this.getCommonConfig({ entitySearchIndex, tierOptions }),
...(shouldAddServiceField ? serviceQueryBuilderFields : {}), ...(shouldAddServiceField ? serviceQueryBuilderFields : {}),
...this.getEntitySpecificQueryBuilderFields(entitySearchIndex), ...this.getEntitySpecificQueryBuilderFields(entitySearchIndex),
...this.getColumnConfig(entitySearchIndex),
}; };
// Sort the fields according to the label // Sort the fields according to the label
@ -648,7 +654,7 @@ class AdvancedSearchClassBase {
isExplorePage?: boolean isExplorePage?: boolean
) => BasicConfig = (tierOptions, entitySearchIndex, isExplorePage) => { ) => BasicConfig = (tierOptions, entitySearchIndex, isExplorePage) => {
const searchIndexWithServices = [ const searchIndexWithServices = [
SearchIndex.ALL, SearchIndex.DATA_ASSET,
SearchIndex.TABLE, SearchIndex.TABLE,
SearchIndex.DASHBOARD, SearchIndex.DASHBOARD,
SearchIndex.PIPELINE, SearchIndex.PIPELINE,

View File

@ -21,6 +21,7 @@ import {
fetchOptions, fetchOptions,
getEntityTableName, getEntityTableName,
getTaskAssignee, getTaskAssignee,
getTaskEntityFQN,
getTaskMessage, getTaskMessage,
} from './TasksUtils'; } from './TasksUtils';
@ -298,3 +299,28 @@ describe('Tests for getTaskAssignee', () => {
]); ]);
}); });
}); });
describe('Tests for getTaskEntityFQN', () => {
it('should return fqn for table entity', async () => {
const fqn = 'sample_data.ecommerce_db.shopify."dim.product"';
const response = getTaskEntityFQN(EntityType.TABLE, fqn);
expect(response).toEqual(fqn);
});
it('should return table fqn only when column name present in fqn', async () => {
const response = getTaskEntityFQN(
EntityType.TABLE,
'sample_data.ecommerce_db.shopify."dim.product".address_id'
);
expect(response).toEqual('sample_data.ecommerce_db.shopify."dim.product"');
});
it('should return fqn as it is if entity type is not table', async () => {
const fqn = 'sample_looker.customers';
const response = getTaskEntityFQN(EntityType.DASHBOARD, fqn);
expect(response).toEqual(fqn);
});
});

View File

@ -19,6 +19,7 @@ import { ReactComponent as CancelColored } from '../assets/svg/cancel-colored.sv
import { ReactComponent as EditColored } from '../assets/svg/edit-colored.svg'; import { ReactComponent as EditColored } from '../assets/svg/edit-colored.svg';
import { ReactComponent as SuccessColored } from '../assets/svg/success-colored.svg'; import { ReactComponent as SuccessColored } from '../assets/svg/success-colored.svg';
import { ActivityFeedTabs } from '../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface'; import { ActivityFeedTabs } from '../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import { import {
getEntityDetailsPath, getEntityDetailsPath,
getGlossaryTermDetailsPath, getGlossaryTermDetailsPath,
@ -901,3 +902,15 @@ export const getTaskAssignee = (entityData: Glossary): Option[] => {
return defaultAssignee; return defaultAssignee;
}; };
export const getTaskEntityFQN = (entityType: EntityType, fqn: string) => {
if (entityType === EntityType.TABLE) {
return getPartialNameFromTableFQN(
fqn,
[FqnPart.Service, FqnPart.Database, FqnPart.Schema, FqnPart.Table],
FQN_SEPARATOR_CHAR
);
}
return fqn;
};