mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-17 03:38:18 +00:00
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:
parent
75f62a7872
commit
fc07324254
@ -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
|
||||||
);
|
);
|
||||||
|
@ -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 |
@ -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,
|
||||||
|
@ -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}
|
||||||
|
@ -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',
|
||||||
}),
|
}),
|
||||||
|
@ -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')
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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>)
|
||||||
|
@ -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);
|
||||||
|
@ -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>)
|
||||||
|
@ -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({
|
||||||
|
@ -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>)
|
||||||
|
@ -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]
|
||||||
|
@ -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', () =>
|
||||||
|
@ -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]
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user