Assets improvements part 2 (#14255)

* fix: make filters consistent

* disable if no selection

* fix: hide notification

* fix: failed status data

* localization

* fix notification loading

* remove close icon here

* fix: cypress

* update error alert design

* fix alert styling

* fix: minor issues

* fix: redirect link of data product

* fix: minor suggestion prompt

* complete user flow

* fix: glossary cypress

* update alert view

* fix tags manipulation

* fix: domain reload fixes

Co-Authored-By: Ashish Gupta <ashish@getcollate.io>

* fix: minor loading issue on delete

* fix: add admin check for activity feed

Co-Authored-By: Ashish Gupta <ashish@getcollate.io>

* fix: minor css fixes

* fix: localisation

* fix: minor issue

* miner cypress fix

* add mlmodelservice as mlmodel parent

* fix: added null checks

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
Co-authored-by: Ashish Gupta <ashish@getcollate.io>
Co-authored-by: Shailesh Parmar <shailesh.parmar.webdev@gmail.com>
Co-authored-by: 07Himank <himank07mehta@gmail.com>
This commit is contained in:
karanh37 2023-12-06 23:25:40 +05:30 committed by GitHub
parent a76308bf77
commit a215f03a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 554 additions and 431 deletions

View File

@ -273,7 +273,8 @@ public class SearchRepository {
if (changeDescription != null) { if (changeDescription != null) {
Pair<String, Map<String, Object>> updates = getInheritedFieldChanges(changeDescription); Pair<String, Map<String, Object>> updates = getInheritedFieldChanges(changeDescription);
Pair<String, String> parentMatch; Pair<String, String> parentMatch;
if (updates.getValue().get("type").toString().equalsIgnoreCase("domain") if (!updates.getValue().isEmpty()
&& updates.getValue().get("type").toString().equalsIgnoreCase("domain")
&& (entityType.equalsIgnoreCase(Entity.DATABASE_SERVICE) && (entityType.equalsIgnoreCase(Entity.DATABASE_SERVICE)
|| entityType.equalsIgnoreCase(Entity.DASHBOARD_SERVICE) || entityType.equalsIgnoreCase(Entity.DASHBOARD_SERVICE)
|| entityType.equalsIgnoreCase(Entity.MESSAGING_SERVICE) || entityType.equalsIgnoreCase(Entity.MESSAGING_SERVICE)

View File

@ -207,7 +207,7 @@
"indexName": "mlmodel_service_search_index", "indexName": "mlmodel_service_search_index",
"indexMappingFile": "/elasticsearch/%s/mlmodel_service_index_mapping.json", "indexMappingFile": "/elasticsearch/%s/mlmodel_service_index_mapping.json",
"alias": "mlModelService", "alias": "mlModelService",
"parentAliases": ["all"] "parentAliases": ["all","mlModelService"]
}, },
"testCaseResolutionStatus": { "testCaseResolutionStatus": {
"indexName": "test_case_resolution_status_search_index", "indexName": "test_case_resolution_status_search_index",

View File

@ -307,6 +307,7 @@ export const NEW_GLOSSARY = {
reviewer: 'Aaron Johnson', reviewer: 'Aaron Johnson',
addReviewer: true, addReviewer: true,
tag: 'PersonalData.Personal', tag: 'PersonalData.Personal',
isMutually: true,
}; };
export const NEW_GLOSSARY_1 = { export const NEW_GLOSSARY_1 = {
name: 'Cypress Product%Glossary', name: 'Cypress Product%Glossary',

View File

@ -116,7 +116,11 @@ const createGlossary = (glossaryData) => {
.should('be.visible') .should('be.visible')
.type(glossaryData.description); .type(glossaryData.description);
cy.get('[data-testid="mutually-exclusive-button"]').scrollIntoView().click(); if (glossaryData.isMutually) {
cy.get('[data-testid="mutually-exclusive-button"]')
.scrollIntoView()
.click();
}
if (glossaryData.tag) { if (glossaryData.tag) {
// Add tag // Add tag
@ -311,10 +315,12 @@ const removeAssetsFromGlossaryTerm = (glossaryTerm, glossary) => {
cy.get('[data-testid="delete-button"]').click(); cy.get('[data-testid="delete-button"]').click();
cy.get("[data-testid='save-button']").click(); cy.get("[data-testid='save-button']").click();
interceptURL('GET', '/api/v1/search/query*', 'assetTab'); cy.get('[data-testid="overview"]').click();
cy.get('[data-testid="assets"]').click();
// go assets tab // go assets tab
goToAssetsTab(glossaryTerm.name, glossaryTerm.fullyQualifiedName, true); verifyResponseStatusCode('@searchAssets', 200);
verifyResponseStatusCode('@assetTab', 200);
checkAssetsCount(glossaryTerm.assets.length - (index + 1)); checkAssetsCount(glossaryTerm.assets.length - (index + 1));
}); });
}; };
@ -719,7 +725,7 @@ describe('Glossary page should work properly', () => {
const ProductTerms = Object.values(NEW_GLOSSARY_1_TERMS); const ProductTerms = Object.values(NEW_GLOSSARY_1_TERMS);
ProductTerms.forEach((term) => ProductTerms.forEach((term) =>
createGlossaryTerm(term, NEW_GLOSSARY_1, 'Approved') createGlossaryTerm(term, NEW_GLOSSARY_1, 'Approved', false)
); );
}); });

View File

@ -131,7 +131,7 @@ describe('Postgres Ingestion', () => {
cy.get('#root\\/filterCondition') cy.get('#root\\/filterCondition')
.scrollIntoView() .scrollIntoView()
.type(`s.query like '%${tableName}%'`); .type(`s.query like '%%${tableName}%%'`);
cy.get('[data-testid="submit-btn"]') cy.get('[data-testid="submit-btn"]')
.scrollIntoView() .scrollIntoView()
.should('be.visible') .should('be.visible')

View File

@ -40,6 +40,7 @@ import {
ThreadTaskStatus, ThreadTaskStatus,
ThreadType, ThreadType,
} from '../../../generated/entity/feed/thread'; } from '../../../generated/entity/feed/thread';
import { useAuth } from '../../../hooks/authHooks';
import { useElementInView } from '../../../hooks/useElementInView'; import { useElementInView } from '../../../hooks/useElementInView';
import { getAllFeeds, getFeedCount } from '../../../rest/feedsAPI'; import { getAllFeeds, getFeedCount } from '../../../rest/feedsAPI';
import { getCountBadge, getEntityDetailLink } from '../../../utils/CommonUtils'; import { getCountBadge, getEntityDetailLink } from '../../../utils/CommonUtils';
@ -81,6 +82,7 @@ export const ActivityFeedTab = ({
}); });
const { subTab: activeTab = ActivityFeedTabs.ALL } = const { subTab: activeTab = ActivityFeedTabs.ALL } =
useParams<{ subTab: ActivityFeedTabs }>(); useParams<{ subTab: ActivityFeedTabs }>();
const { isAdminUser } = useAuth();
const [taskFilter, setTaskFilter] = useState<TaskFilter>('open'); const [taskFilter, setTaskFilter] = useState<TaskFilter>('open');
const [allCount, setAllCount] = useState(0); const [allCount, setAllCount] = useState(0);
const [tasksCount, setTasksCount] = useState(0); const [tasksCount, setTasksCount] = useState(0);
@ -162,7 +164,7 @@ export const ActivityFeedTab = ({
undefined, undefined,
undefined, undefined,
ThreadType.Task, ThreadType.Task,
FeedFilter.OWNER, isAdminUser ? undefined : FeedFilter.OWNER,
undefined, undefined,
userId userId
).then((res) => { ).then((res) => {
@ -178,7 +180,7 @@ export const ActivityFeedTab = ({
undefined, undefined,
undefined, undefined,
ThreadType.Conversation, ThreadType.Conversation,
FeedFilter.OWNER, isAdminUser ? undefined : FeedFilter.OWNER,
undefined, undefined,
userId userId
).then((res) => { ).then((res) => {

View File

@ -13,6 +13,7 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Loader from '../../components/Loader/Loader'; import Loader from '../../components/Loader/Loader';
@ -239,7 +240,7 @@ const Suggestions = ({
return <Loader />; return <Loader />;
} }
if (options.length === 0 && !isTourOpen) { if (options.length === 0 && !isTourOpen && !isEmpty(searchText)) {
return ( return (
<Typography.Text> <Typography.Text>
<Transi18next <Transi18next

View File

@ -1,188 +0,0 @@
/*
* Copyright 2023 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 { PlusOutlined } from '@ant-design/icons';
import { Button, Divider, Dropdown, Menu, Typography } from 'antd';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SearchIndex } from '../../enums/search.enum';
import {
QueryFieldInterface,
QueryFieldValueInterface,
} from '../../pages/ExplorePage/ExplorePage.interface';
import { getAssetsPageQuickFilters } from '../../utils/AdvancedSearchUtils';
import { getSelectedValuesFromQuickFilter } from '../../utils/Explore.utils';
import { ExploreQuickFilterField } from '../Explore/ExplorePage.interface';
import ExploreQuickFilters from '../Explore/ExploreQuickFilters';
import { AssetFiltersProps } from './AssetFilters.interface';
const AssetFilters = ({
filterData,
type,
aggregations,
onQuickFilterChange,
quickFilterQuery,
}: AssetFiltersProps) => {
const { t } = useTranslation();
const [filters, setFilters] = useState<ExploreQuickFilterField[]>([]);
const [selectedFilter, setSelectedFilter] = useState<string[]>([]);
const [selectedQuickFilters, setSelectedQuickFilters] = useState<
ExploreQuickFilterField[]
>([]);
const handleMenuClick = ({ key }: { key: string }) => {
setSelectedFilter((prevSelected) => [...prevSelected, key]);
};
const menu = useMemo(
() => (
<Menu selectedKeys={selectedFilter} onClick={handleMenuClick}>
{filters.map((filter) => (
<Menu.Item key={filter.key}>{filter.label}</Menu.Item>
))}
</Menu>
),
[selectedFilter, filters]
);
const handleQuickFiltersChange = (data: ExploreQuickFilterField[]) => {
const must: QueryFieldInterface[] = [];
data.forEach((filter) => {
if (!isEmpty(filter.value)) {
const should: QueryFieldValueInterface[] = [];
if (filter.value) {
filter.value.forEach((filterValue) => {
const term: Record<string, string> = {};
term[filter.key] = filterValue.key;
should.push({ term });
});
}
must.push({
bool: { should },
});
}
});
const quickFilterQuery = isEmpty(must)
? undefined
: {
query: { bool: { must } },
};
onQuickFilterChange?.(quickFilterQuery);
};
const handleQuickFiltersValueSelect = useCallback(
(field: ExploreQuickFilterField) => {
setSelectedQuickFilters((pre) => {
const data = pre.map((preField) => {
if (preField.key === field.key) {
return field;
} else {
return preField;
}
});
handleQuickFiltersChange(data);
return data;
});
},
[setSelectedQuickFilters]
);
const clearFilters = useCallback(() => {
setSelectedQuickFilters((pre) => {
const data = pre.map((preField) => {
return { ...preField, value: [] };
});
handleQuickFiltersChange(data);
return data;
});
}, [handleQuickFiltersChange, setSelectedQuickFilters]);
useEffect(() => {
if (filterData?.length === 0) {
const dropdownItems = getAssetsPageQuickFilters(type);
setFilters(
dropdownItems.map((item) => ({
...item,
value: getSelectedValuesFromQuickFilter(item, dropdownItems),
}))
);
} else {
setFilters(filterData ?? []);
}
}, [filterData, type]);
useEffect(() => {
const updatedQuickFilters = filters
.filter((filter) => selectedFilter.includes(filter.key))
.map((selectedFilterItem) => {
const originalFilterItem = selectedQuickFilters?.find(
(filter) => filter.key === selectedFilterItem.key
);
return originalFilterItem || selectedFilterItem;
});
const newItems = updatedQuickFilters.filter(
(item) =>
!selectedQuickFilters.some(
(existingItem) => item.key === existingItem.key
)
);
if (newItems.length > 0) {
setSelectedQuickFilters((prevSelected) => [...prevSelected, ...newItems]);
}
}, [selectedFilter, selectedQuickFilters, filters]);
return (
<>
<Dropdown overlay={menu} trigger={['click']}>
<Button icon={<PlusOutlined />} size="small" type="primary" />
</Dropdown>
{selectedQuickFilters.length > 0 && (
<>
<Divider className="m-x-md h-6" type="vertical" />
<div className="d-flex justify-between flex-1">
<ExploreQuickFilters
aggregations={aggregations}
fields={selectedQuickFilters}
index={SearchIndex.ALL}
onFieldValueSelect={(data) =>
handleQuickFiltersValueSelect?.(data)
}
/>
{quickFilterQuery && (
<Typography.Text
className="text-primary self-center cursor-pointer"
onClick={clearFilters}>
{t('label.clear-entity', {
entity: '',
})}
</Typography.Text>
)}
</div>
</>
)}
</>
);
};
export default AssetFilters;

View File

@ -1,25 +0,0 @@
/*
* Copyright 2023 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 { Aggregations } from '../../interface/search.interface';
import { QueryFilterInterface } from '../../pages/ExplorePage/ExplorePage.interface';
import { ExploreQuickFilterField } from '../Explore/ExplorePage.interface';
import { AssetsOfEntity } from '../Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
export interface AssetFiltersProps {
filterData?: ExploreQuickFilterField[];
defaultFilter?: string[];
aggregations?: Aggregations;
onQuickFilterChange?: (query?: QueryFilterInterface) => void;
type: AssetsOfEntity;
quickFilterQuery?: QueryFilterInterface;
}

View File

@ -10,9 +10,27 @@
* 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, Checkbox, List, Modal, Space, Typography } from 'antd'; import {
CheckOutlined,
CloseOutlined,
ExclamationCircleOutlined,
PlusOutlined,
} from '@ant-design/icons';
import {
Alert,
Button,
Checkbox,
Divider,
Dropdown,
List,
Modal,
Space,
Typography,
} from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { isEmpty } from 'lodash';
import { EntityDetailUnion } from 'Models'; import { EntityDetailUnion } from 'Models';
import VirtualList from 'rc-virtual-list'; import VirtualList from 'rc-virtual-list';
import { import {
@ -20,10 +38,11 @@ import {
UIEventHandler, UIEventHandler,
useCallback, useCallback,
useEffect, useEffect,
useMemo,
useState, useState,
} from 'react'; } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PAGE_SIZE_MEDIUM } from '../../../constants/constants'; import { ERROR_COLOR, PAGE_SIZE_MEDIUM } from '../../../constants/constants';
import { SearchIndex } from '../../../enums/search.enum'; import { SearchIndex } from '../../../enums/search.enum';
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm'; import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
import { DataProduct } from '../../../generated/entity/domains/dataProduct'; import { DataProduct } from '../../../generated/entity/domains/dataProduct';
@ -33,7 +52,11 @@ import {
Status, Status,
} from '../../../generated/type/bulkOperationResult'; } from '../../../generated/type/bulkOperationResult';
import { Aggregations } from '../../../interface/search.interface'; import { Aggregations } from '../../../interface/search.interface';
import { QueryFilterInterface } from '../../../pages/ExplorePage/ExplorePage.interface'; import {
QueryFieldInterface,
QueryFieldValueInterface,
QueryFilterInterface,
} from '../../../pages/ExplorePage/ExplorePage.interface';
import { import {
addAssetsToDataProduct, addAssetsToDataProduct,
getDataProductByName, getDataProductByName,
@ -53,11 +76,11 @@ import {
import { getCombinedQueryFilterObject } from '../../../utils/ExplorePage/ExplorePageUtils'; import { getCombinedQueryFilterObject } from '../../../utils/ExplorePage/ExplorePageUtils';
import { getEncodedFqn } from '../../../utils/StringsUtils'; import { getEncodedFqn } from '../../../utils/StringsUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import AssetFilters from '../../AssetFilters/AssetFilters.component';
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import Searchbar from '../../common/SearchBarComponent/SearchBar.component'; import Searchbar from '../../common/SearchBarComponent/SearchBar.component';
import TableDataCardV2 from '../../common/TableDataCardV2/TableDataCardV2'; import TableDataCardV2 from '../../common/TableDataCardV2/TableDataCardV2';
import { ExploreQuickFilterField } from '../../Explore/ExplorePage.interface'; import { ExploreQuickFilterField } from '../../Explore/ExplorePage.interface';
import ExploreQuickFilters from '../../Explore/ExploreQuickFilters';
import { AssetsOfEntity } from '../../Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; import { AssetsOfEntity } from '../../Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
import Loader from '../../Loader/Loader'; import Loader from '../../Loader/Loader';
import { SearchedDataProps } from '../../SearchedData/SearchedData.interface'; import { SearchedDataProps } from '../../SearchedData/SearchedData.interface';
@ -103,6 +126,20 @@ export const AssetSelectionModal = ({
}, },
}) })
); );
const [selectedFilter, setSelectedFilter] = useState<string[]>([]);
const [filters, setFilters] = useState<ExploreQuickFilterField[]>([]);
const handleMenuClick = ({ key }: { key: string }) => {
setSelectedFilter((prevSelected) => [...prevSelected, key]);
};
const filterMenu: ItemType[] = useMemo(() => {
return filters.map((filter) => ({
key: filter.key,
label: filter.label,
onClick: handleMenuClick,
}));
}, [filters]);
const fetchEntities = useCallback( const fetchEntities = useCallback(
async ({ async ({
@ -153,10 +190,14 @@ export const AssetSelectionModal = ({
useEffect(() => { useEffect(() => {
const dropdownItems = getAssetsPageQuickFilters(type); const dropdownItems = getAssetsPageQuickFilters(type);
setSelectedQuickFilters( setFilters(
dropdownItems.map((item) => ({ dropdownItems.map((item) => ({
...item, ...item,
value: getSelectedValuesFromQuickFilter(item, dropdownItems), value: getSelectedValuesFromQuickFilter(
item,
dropdownItems,
undefined // pass in state variable
),
})) }))
); );
}, [type]); }, [type]);
@ -213,6 +254,7 @@ export const AssetSelectionModal = ({
const handleSave = async () => { const handleSave = async () => {
try { try {
setIsSaveLoading(true); setIsSaveLoading(true);
setFailedStatus(undefined);
if (!activeEntity) { if (!activeEntity) {
return; return;
} }
@ -283,6 +325,29 @@ export const AssetSelectionModal = ({
mergeFilters(); mergeFilters();
}, [quickFilterQuery, queryFilter]); }, [quickFilterQuery, queryFilter]);
useEffect(() => {
const updatedQuickFilters = filters
.filter((filter) => selectedFilter.includes(filter.key))
.map((selectedFilterItem) => {
const originalFilterItem = selectedQuickFilters?.find(
(filter) => filter.key === selectedFilterItem.key
);
return originalFilterItem || selectedFilterItem;
});
const newItems = updatedQuickFilters.filter(
(item) =>
!selectedQuickFilters.some(
(existingItem) => item.key === existingItem.key
)
);
if (newItems.length > 0) {
setSelectedQuickFilters((prevSelected) => [...prevSelected, ...newItems]);
}
}, [selectedFilter, selectedQuickFilters, filters]);
const onScroll: UIEventHandler<HTMLElement> = useCallback( const onScroll: UIEventHandler<HTMLElement> = useCallback(
(e) => { (e) => {
const scrollHeight = const scrollHeight =
@ -353,6 +418,66 @@ export const AssetSelectionModal = ({
[failedStatus] [failedStatus]
); );
const handleQuickFiltersChange = (data: ExploreQuickFilterField[]) => {
const must: QueryFieldInterface[] = [];
data.forEach((filter) => {
if (!isEmpty(filter.value)) {
const should: QueryFieldValueInterface[] = [];
if (filter.value) {
filter.value.forEach((filterValue) => {
const term: Record<string, string> = {};
term[filter.key] = filterValue.key;
should.push({ term });
});
}
must.push({
bool: { should },
});
}
});
const quickFilterQuery = isEmpty(must)
? undefined
: {
query: { bool: { must } },
};
setQuickFilterQuery(quickFilterQuery);
};
const handleQuickFiltersValueSelect = useCallback(
(field: ExploreQuickFilterField) => {
setSelectedQuickFilters((pre) => {
const data = pre.map((preField) => {
if (preField.key === field.key) {
return field;
} else {
return preField;
}
});
handleQuickFiltersChange(data);
return data;
});
},
[setSelectedQuickFilters]
);
const clearFilters = useCallback(() => {
setQuickFilterQuery(undefined);
setSelectedQuickFilters((pre) => {
const data = pre.map((preField) => {
return { ...preField, value: [] };
});
handleQuickFiltersChange(data);
return data;
});
}, [setQuickFilterQuery, handleQuickFiltersChange, setSelectedQuickFilters]);
return ( return (
<Modal <Modal
destroyOnClose destroyOnClose
@ -362,12 +487,23 @@ export const AssetSelectionModal = ({
data-testid="asset-selection-modal" data-testid="asset-selection-modal"
footer={ footer={
<div className="d-flex justify-between"> <div className="d-flex justify-between">
<div> <div className="d-flex items-center gap-2">
{selectedItems && selectedItems.size > 1 && ( {selectedItems && selectedItems.size >= 1 && (
<Typography.Text> <Typography.Text className="gap-2">
<CheckOutlined className="text-success m-r-xs" />
{selectedItems.size} {t('label.selected-lowercase')} {selectedItems.size} {t('label.selected-lowercase')}
</Typography.Text> </Typography.Text>
)} )}
{failedStatus?.failedRequest &&
failedStatus.failedRequest.length > 0 && (
<>
<Divider className="m-x-xss" type="vertical" />
<Typography.Text type="danger">
<CloseOutlined className="m-r-xs" />
{failedStatus.failedRequest.length} {t('label.error')}
</Typography.Text>
</>
)}
</div> </div>
<div> <div>
@ -376,7 +512,7 @@ export const AssetSelectionModal = ({
</Button> </Button>
<Button <Button
data-testid="save-btn" data-testid="save-btn"
disabled={isLoading} disabled={!selectedItems?.size || isLoading}
loading={isSaveLoading} loading={isSaveLoading}
type="primary" type="primary"
onClick={onSaveAction}> onClick={onSaveAction}>
@ -391,26 +527,74 @@ export const AssetSelectionModal = ({
width={675} width={675}
onCancel={onCancel}> onCancel={onCancel}>
<Space className="w-full h-full" direction="vertical" size={16}> <Space className="w-full h-full" direction="vertical" size={16}>
<Searchbar <div className="d-flex items-center gap-3">
removeMargin <Dropdown
showClearSearch menu={{
placeholder={t('label.search-entity', { items: filterMenu,
entity: t('label.asset-plural'), selectedKeys: selectedFilter,
})} }}
searchValue={search} trigger={['click']}>
onSearch={setSearch} <Button icon={<PlusOutlined />} size="small" type="primary" />
/> </Dropdown>
<div className="flex-1">
<div className="d-flex items-center"> <Searchbar
<AssetFilters removeMargin
aggregations={aggregations} showClearSearch
filterData={selectedQuickFilters} placeholder={t('label.search-entity', {
quickFilterQuery={quickFilterQuery} entity: t('label.asset-plural'),
type={type} })}
onQuickFilterChange={(data) => setQuickFilterQuery(data)} searchValue={search}
/> onSearch={setSearch}
/>
</div>
</div> </div>
{selectedQuickFilters && selectedQuickFilters.length > 0 && (
<div className="d-flex items-center">
<div className="d-flex justify-between flex-1">
<ExploreQuickFilters
aggregations={aggregations}
fields={selectedQuickFilters}
index={SearchIndex.ALL}
showDeleted={false}
onFieldValueSelect={handleQuickFiltersValueSelect}
/>
{quickFilterQuery && (
<Typography.Text
className="p-r-xss text-primary self-center cursor-pointer"
onClick={clearFilters}>
{t('label.clear-entity', {
entity: '',
})}
</Typography.Text>
)}
</div>
</div>
)}
{failedStatus?.failedRequest && failedStatus.failedRequest.length > 0 && (
<Alert
closable
className="w-full"
description={
<Typography.Text className="text-grey-muted">
{t('message.validation-error-assets')}
</Typography.Text>
}
message={
<div className="d-flex items-center gap-3">
<ExclamationCircleOutlined
style={{ color: ERROR_COLOR, fontSize: '24px' }}
/>
<Typography.Text className="font-semibold text-sm">
{t('label.validation-error-plural')}
</Typography.Text>
</div>
}
type="error"
/>
)}
{items.length > 0 && ( {items.length > 0 && (
<div className="border p-xs"> <div className="border p-xs">
<Checkbox <Checkbox
@ -435,7 +619,8 @@ export const AssetSelectionModal = ({
<div <div
className={classNames({ className={classNames({
'm-y-sm border-danger rounded-4': isError, 'm-y-sm border-danger rounded-4': isError,
})}> })}
key={item.id}>
<TableDataCardV2 <TableDataCardV2
openEntityInNewPage openEntityInNewPage
showCheckboxes showCheckboxes
@ -449,11 +634,19 @@ export const AssetSelectionModal = ({
source={{ ...item, tags: [] }} source={{ ...item, tags: [] }}
/> />
{isError && ( {isError && (
<div className="p-x-sm p-b-sm"> <>
<Typography.Text type="danger"> <div className="p-x-sm">
{errorMessage} <Divider className="m-t-0 m-y-sm " />
</Typography.Text> </div>
</div> <div className="d-flex gap-3 p-x-sm p-b-sm">
<ExclamationCircleOutlined
style={{ color: ERROR_COLOR, fontSize: '24px' }}
/>
<Typography.Text className="break-all">
{errorMessage}
</Typography.Text>
</div>
</>
)} )}
</div> </div>
); );

View File

@ -14,7 +14,7 @@
@import url('../../../styles/variables.less'); @import url('../../../styles/variables.less');
.asset-selection-model-card.table-data-card-container { .asset-selection-model-card.table-data-card-container {
box-shadow: none; box-shadow: none !important;
} }
.asset-selection-model-card.table-data-card-container:hover { .asset-selection-model-card.table-data-card-container:hover {
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.13); box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.13);

View File

@ -14,6 +14,7 @@ import { Col, Row, Space, Tag, Typography } from 'antd';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { ReactComponent as EditIcon } from '../../assets/svg/edit-new.svg'; import { ReactComponent as EditIcon } from '../../assets/svg/edit-new.svg';
import { ReactComponent as DataProductIcon } from '../../assets/svg/ic-data-product.svg'; import { ReactComponent as DataProductIcon } from '../../assets/svg/ic-data-product.svg';
import DataProductSelectForm from '../../components/DataProductSelectForm/DataProductsSelectForm'; import DataProductSelectForm from '../../components/DataProductSelectForm/DataProductsSelectForm';
@ -27,6 +28,8 @@ import { DataProduct } from '../../generated/entity/domains/dataProduct';
import { EntityReference } from '../../generated/entity/type'; import { EntityReference } from '../../generated/entity/type';
import { fetchDataProductsElasticSearch } from '../../rest/dataProductAPI'; import { fetchDataProductsElasticSearch } from '../../rest/dataProductAPI';
import { getEntityName } from '../../utils/EntityUtils'; import { getEntityName } from '../../utils/EntityUtils';
import { getDataProductsDetailsPath } from '../../utils/RouterUtils';
import { getEncodedFqn } from '../../utils/StringsUtils';
interface DataProductsContainerProps { interface DataProductsContainerProps {
showHeader?: boolean; showHeader?: boolean;
@ -44,6 +47,7 @@ const DataProductsContainer = ({
onSave, onSave,
}: DataProductsContainerProps) => { }: DataProductsContainerProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory();
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const handleAddClick = () => { const handleAddClick = () => {
@ -70,6 +74,10 @@ const DataProductsContainer = ({
[activeDomain] [activeDomain]
); );
const redirectLink = useCallback((fqn) => {
history.push(getDataProductsDetailsPath(getEncodedFqn(fqn)));
}, []);
const handleSave = async (dataProducts: DataProduct[]) => { const handleSave = async (dataProducts: DataProduct[]) => {
await onSave?.(dataProducts); await onSave?.(dataProducts);
setIsEditMode(false); setIsEditMode(false);
@ -107,7 +115,8 @@ const DataProductsContainer = ({
return ( return (
<Tag <Tag
className="tag-chip tag-chip-content" className="tag-chip tag-chip-content"
key={`dp-tags-${product.fullyQualifiedName}`}> key={`dp-tags-${product.fullyQualifiedName}`}
onClick={() => redirectLink(product.fullyQualifiedName)}>
<div className="d-flex w-full"> <div className="d-flex w-full">
<div className="d-flex items-center p-x-xs w-full gap-1"> <div className="d-flex items-center p-x-xs w-full gap-1">
<DataProductIcon <DataProductIcon

View File

@ -83,6 +83,7 @@ import {
} from '../../../utils/RouterUtils'; } from '../../../utils/RouterUtils';
import { import {
escapeESReservedCharacters, escapeESReservedCharacters,
getDecodedFqn,
getEncodedFqn, getEncodedFqn,
} from '../../../utils/StringsUtils'; } from '../../../utils/StringsUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
@ -111,7 +112,7 @@ const DomainDetailsPage = ({
tab: activeTab, tab: activeTab,
version, version,
} = useParams<{ fqn: string; tab: string; version: string }>(); } = useParams<{ fqn: string; tab: string; version: string }>();
const domainFqn = fqn ? decodeURIComponent(fqn) : ''; const domainFqn = fqn ? getDecodedFqn(fqn) : '';
const assetTabRef = useRef<AssetsTabRef>(null); const assetTabRef = useRef<AssetsTabRef>(null);
const dataProductsTabRef = useRef<DataProductsTabRef>(null); const dataProductsTabRef = useRef<DataProductsTabRef>(null);
const [domainPermission, setDomainPermission] = useState<OperationPermission>( const [domainPermission, setDomainPermission] = useState<OperationPermission>(
@ -512,7 +513,7 @@ const DomainDetailsPage = ({
fetchDomainPermission(); fetchDomainPermission();
fetchDomainAssets(); fetchDomainAssets();
fetchDataProducts(); fetchDataProducts();
}, [fqn]); }, [domain.fullyQualifiedName]);
return ( return (
<> <>

View File

@ -173,11 +173,15 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
{showCheckboxes && ( {showCheckboxes && (
<Col flex="25px"> <Col flex="25px">
<Checkbox <div onClick={(e) => e.stopPropagation()}>
checked={checked} <Checkbox
className="assets-checkbox" checked={checked}
onChange={(e) => onCheckboxChange?.(e.target.checked)} className="assets-checkbox"
/> onChange={(e) => {
onCheckboxChange?.(e.target.checked);
}}
/>
</div>
</Col> </Col>
)} )}
{!hideBreadcrumbs && ( {!hideBreadcrumbs && (

View File

@ -36,7 +36,10 @@ import { getCountBadge, getFeedCounts } from '../../../utils/CommonUtils';
import { getEntityVersionByField } from '../../../utils/EntityVersionUtils'; import { getEntityVersionByField } from '../../../utils/EntityVersionUtils';
import { getQueryFilterToExcludeTerm } from '../../../utils/GlossaryUtils'; import { getQueryFilterToExcludeTerm } from '../../../utils/GlossaryUtils';
import { getGlossaryTermsVersionsPath } from '../../../utils/RouterUtils'; import { getGlossaryTermsVersionsPath } from '../../../utils/RouterUtils';
import { escapeESReservedCharacters } from '../../../utils/StringsUtils'; import {
escapeESReservedCharacters,
getEncodedFqn,
} from '../../../utils/StringsUtils';
import { ActivityFeedTab } from '../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.component'; import { ActivityFeedTab } from '../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
import { AssetSelectionModal } from '../../Assets/AssetsSelectionModal/AssetSelectionModal'; import { AssetSelectionModal } from '../../Assets/AssetsSelectionModal/AssetSelectionModal';
import { CustomPropertyTable } from '../../common/CustomPropertyTable/CustomPropertyTable'; import { CustomPropertyTable } from '../../common/CustomPropertyTable/CustomPropertyTable';
@ -258,13 +261,14 @@ const GlossaryTermsV1 = ({
const fetchGlossaryTermAssets = async () => { const fetchGlossaryTermAssets = async () => {
if (glossaryTerm) { if (glossaryTerm) {
try { try {
const encodedFqn = getEncodedFqn(
escapeESReservedCharacters(glossaryTerm.fullyQualifiedName)
);
const res = await searchData( const res = await searchData(
'', '',
1, 1,
0, 0,
`(tags.tagFQN:"${escapeESReservedCharacters( `(tags.tagFQN:"${encodedFqn}")`,
glossaryTerm.fullyQualifiedName
)}")`,
'', '',
'', '',
SearchIndex.ALL SearchIndex.ALL

View File

@ -14,11 +14,11 @@
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { import {
Affix,
Button, Button,
Checkbox, Checkbox,
Col, Col,
Dropdown, Dropdown,
Menu,
MenuProps, MenuProps,
notification, notification,
Row, Row,
@ -136,6 +136,7 @@ const AssetsTabs = forwardRef(
const [itemCount, setItemCount] = useState<Record<EntityType, number>>( const [itemCount, setItemCount] = useState<Record<EntityType, number>>(
{} as Record<EntityType, number> {} as Record<EntityType, number>
); );
const [assetRemoving, setAssetRemoving] = useState(false);
const [activeFilter, _] = useState<SearchIndex[]>([]); const [activeFilter, _] = useState<SearchIndex[]>([]);
const { fqn } = useParams<{ fqn: string }>(); const { fqn } = useParams<{ fqn: string }>();
@ -173,8 +174,9 @@ const AssetsTabs = forwardRef(
Domain | DataProduct | GlossaryTerm Domain | DataProduct | GlossaryTerm
>(); >();
const [selectedItems, setSelectedItems] = const [selectedItems, setSelectedItems] = useState<
useState<Map<string, EntityDetailUnion>>(); Map<string, EntityDetailUnion>
>(new Map());
const [aggregations, setAggregations] = useState<Aggregations>(); const [aggregations, setAggregations] = useState<Aggregations>();
const [selectedFilter, setSelectedFilter] = useState<string[]>([]); // Contains menu selection const [selectedFilter, setSelectedFilter] = useState<string[]>([]); // Contains menu selection
const [selectedQuickFilters, setSelectedQuickFilters] = useState< const [selectedQuickFilters, setSelectedQuickFilters] = useState<
@ -193,16 +195,13 @@ const AssetsTabs = forwardRef(
setSelectedFilter((prevSelected) => [...prevSelected, key]); setSelectedFilter((prevSelected) => [...prevSelected, key]);
}; };
const filterMenu = useMemo( const filterMenu: ItemType[] = useMemo(() => {
() => ( return filters.map((filter) => ({
<Menu selectedKeys={selectedFilter} onClick={handleMenuClick}> key: filter.key,
{filters.map((filter) => ( label: filter.label,
<Menu.Item key={filter.key}>{filter.label}</Menu.Item> onClick: handleMenuClick,
))} }));
</Menu> }, [filters]);
),
[selectedFilter, filters]
);
const queryParam = useMemo(() => { const queryParam = useMemo(() => {
const encodedFqn = getEncodedFqn(escapeESReservedCharacters(entityFqn)); const encodedFqn = getEncodedFqn(escapeESReservedCharacters(entityFqn));
@ -425,6 +424,7 @@ const AssetsTabs = forwardRef(
return () => { return () => {
onAssetClick?.(undefined); onAssetClick?.(undefined);
hideNotification();
}; };
}, []); }, []);
@ -434,15 +434,6 @@ const AssetsTabs = forwardRef(
} }
}, [entityFqn]); }, [entityFqn]);
useEffect(() => {
if (selectedItems) {
hideNotification();
if (selectedItems.size > 1) {
openNotification();
}
}
}, [selectedItems]);
const assetErrorPlaceHolder = useMemo(() => { const assetErrorPlaceHolder = useMemo(() => {
if (!isEmpty(activeFilter)) { if (!isEmpty(activeFilter)) {
return ( return (
@ -713,6 +704,8 @@ const AssetsTabs = forwardRef(
return; return;
} }
setAssetRemoving(true);
try { try {
const entities = [...(assetsData?.values() ?? [])].map((item) => { const entities = [...(assetsData?.values() ?? [])].map((item) => {
return getEntityReferenceFromEntity( return getEntityReferenceFromEntity(
@ -760,6 +753,7 @@ const AssetsTabs = forwardRef(
} finally { } finally {
setShowDeleteModal(false); setShowDeleteModal(false);
onRemoveAsset?.(); onRemoveAsset?.();
setAssetRemoving(false);
hideNotification(); hideNotification();
setSelectedItems(new Map()); // Reset selected items setSelectedItems(new Map()); // Reset selected items
} }
@ -784,31 +778,6 @@ const AssetsTabs = forwardRef(
setSelectedQuickFilters, setSelectedQuickFilters,
]); ]);
const openNotification = () => {
notification.warning({
key: 'asset-tab-notification-key',
message: (
<div className="d-flex items-center justify-between">
{selectedItems && selectedItems.size > 1 && (
<Typography.Text className="text-white">
{selectedItems.size} {t('label.items-selected-lowercase')}
</Typography.Text>
)}
<Button
danger
data-testid="delete-all-button"
type="primary"
onClick={deleteSelectedItems}>
{t('label.delete')}
</Button>
</div>
),
placement: 'bottom',
className: 'asset-tab-delete-notification',
duration: 0,
});
};
useEffect(() => { useEffect(() => {
fetchAssets({ fetchAssets({
index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter, index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter,
@ -861,7 +830,7 @@ const AssetsTabs = forwardRef(
refreshAssets() { refreshAssets() {
fetchAssets({ fetchAssets({
index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter, index: isEmpty(activeFilter) ? [SearchIndex.ALL] : activeFilter,
page: currentPage, page: 1,
}); });
fetchCountsByEntity(); fetchCountsByEntity();
}, },
@ -885,12 +854,18 @@ const AssetsTabs = forwardRef(
return ( return (
<div <div
className={classNames('assets-tab-container p-md')} className={classNames('assets-tab-container p-md')}
data-testid="table-container"> data-testid="table-container"
id="asset-tab">
{assetCount > 0 && ( {assetCount > 0 && (
<Row className="filters-row gap-2 p-l-lg"> <Row className="filters-row gap-2 p-l-lg">
<Col span={18}> <Col span={18}>
<div className="d-flex items-center gap-3"> <div className="d-flex items-center gap-3">
<Dropdown overlay={filterMenu} trigger={['click']}> <Dropdown
menu={{
items: filterMenu,
selectedKeys: selectedFilter,
}}
trigger={['click']}>
<Button icon={<PlusOutlined />} size="small" type="primary" /> <Button icon={<PlusOutlined />} size="small" type="primary" />
</Dropdown> </Dropdown>
<div className="flex-1"> <div className="flex-1">
@ -952,10 +927,36 @@ const AssetsTabs = forwardRef(
header={t('label.remove-entity', { header={t('label.remove-entity', {
entity: getEntityName(assetToDelete) + '?', entity: getEntityName(assetToDelete) + '?',
})} })}
isLoading={assetRemoving}
visible={showDeleteModal} visible={showDeleteModal}
onCancel={() => setShowDeleteModal(false)} onCancel={() => setShowDeleteModal(false)}
onConfirm={() => onAssetRemove(assetToDelete ? [assetToDelete] : [])} onConfirm={() => onAssetRemove(assetToDelete ? [assetToDelete] : [])}
/> />
{selectedItems.size > 1 && (
<Affix
className={classNames('asset-tab-delete-notification', {
visible: selectedItems.size > 1,
})}
offsetBottom={20}
target={() =>
document.getElementById('asset-tab') || document.body
}>
<div className="d-flex items-center justify-between">
<Typography.Text className="text-white">
{selectedItems.size} {t('label.items-selected-lowercase')}
</Typography.Text>
<Button
danger
data-testid="delete-all-button"
loading={assetRemoving}
type="primary"
onClick={deleteSelectedItems}>
{t('label.delete')}
</Button>
</div>
</Affix>
)}
</div> </div>
); );
} }

View File

@ -83,10 +83,6 @@ const GlossaryOverviewTab = ({
} }
}, [selectedData, isVersionView]); }, [selectedData, isVersionView]);
const handleTagsUpdate = async (updatedTags: TagLabel[]) => {
setTagsUpdating(updatedTags);
};
const tags = useMemo( const tags = useMemo(
() => () =>
isVersionView isVersionView
@ -98,14 +94,16 @@ const GlossaryOverviewTab = ({
[isVersionView, selectedData] [isVersionView, selectedData]
); );
const handleTagsUpdate = async (updatedTags: TagLabel[]) => {
setTagsUpdating(updatedTags);
};
const handleGlossaryTagUpdateValidationConfirm = async () => { const handleGlossaryTagUpdateValidationConfirm = async () => {
if (selectedData) { if (selectedData) {
await onUpdate({ await onUpdate({
...selectedData, ...selectedData,
tags: tagsUpdatating, tags: tagsUpdatating,
}); });
setTagsUpdating(undefined);
} }
}; };

View File

@ -81,20 +81,24 @@
} }
.asset-tab-delete-notification { .asset-tab-delete-notification {
background-color: @text-color !important; &.visible {
box-shadow: none !important; .ant-affix {
.ant-notification-notice-icon { bottom: 10px !important;
display: none; }
} }
.ant-notification-notice-close {
color: @white !important; .ant-affix {
left: 24px; border-radius: 10px;
top: 22px; background: #292929;
width: 20px; height: 64px !important;
} width: 350px !important;
.ant-notification-notice-message { bottom: -64px !important;
padding-right: 0 !important; left: 40%;
margin-left: 20px !important; transform: translateX(-60%);
font-size: 14px !important; transition: bottom ease-in 0.5s;
& > div {
padding: 15px 20px;
}
} }
} }

View File

@ -19,3 +19,11 @@ export interface GlossaryUpdateConfirmationModalProps {
onCancel: () => void; onCancel: () => void;
updatedTags: TagLabel[]; updatedTags: TagLabel[];
} }
export enum UpdateState {
INITIAL,
VALIDATING,
FAILED,
UPDATATING,
SUCESS,
}

View File

@ -11,8 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import Icon from '@ant-design/icons'; import Icon from '@ant-design/icons';
import { Button, Modal, Space, Typography } from 'antd'; import { Alert, Button, Modal, Progress, Space, Typography } from 'antd';
import { isEmpty } from 'lodash';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -31,7 +30,10 @@ import {
getEntityName, getEntityName,
} from '../../../utils/EntityUtils'; } from '../../../utils/EntityUtils';
import Table from '../../common/Table/Table'; import Table from '../../common/Table/Table';
import { GlossaryUpdateConfirmationModalProps } from './GlossaryUpdateConfirmationModal.interface'; import {
GlossaryUpdateConfirmationModalProps,
UpdateState,
} from './GlossaryUpdateConfirmationModal.interface';
export const GlossaryUpdateConfirmationModal = ({ export const GlossaryUpdateConfirmationModal = ({
glossaryTerm, glossaryTerm,
@ -41,13 +43,12 @@ export const GlossaryUpdateConfirmationModal = ({
}: GlossaryUpdateConfirmationModalProps) => { }: GlossaryUpdateConfirmationModalProps) => {
const [failedStatus, setFailedStatus] = useState<BulkOperationResult>(); const [failedStatus, setFailedStatus] = useState<BulkOperationResult>();
const [tagError, setTagError] = useState<{ code: number; message: string }>(); const [tagError, setTagError] = useState<{ code: number; message: string }>();
const [tagAdditionConfirmation, setTagAdditionConfirmation] = useState(false); const [updateState, setUpdateState] = useState(UpdateState.INITIAL);
const [validating, setValidating] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const handleUpdateConfirmation = async () => { const handleUpdateConfirmation = async () => {
setTagAdditionConfirmation(true); setUpdateState(UpdateState.VALIDATING);
setValidating(true);
try { try {
// dryRun validations so that we can list failures if any // dryRun validations so that we can list failures if any
const res = await validateTagAddtionToGlossary( const res = await validateTagAddtionToGlossary(
@ -55,16 +56,24 @@ export const GlossaryUpdateConfirmationModal = ({
true true
); );
if (res.status && res.status === Status.Success) { if (res.status === Status.Success) {
await onValidationSuccess(); setUpdateState(UpdateState.UPDATATING);
try {
await onValidationSuccess();
setUpdateState(UpdateState.SUCESS);
} catch (err) {
// Error
} finally {
setTimeout(onCancel, 500);
}
} else { } else {
setUpdateState(UpdateState.FAILED);
setFailedStatus(res); setFailedStatus(res);
} }
} catch (err) { } catch (err) {
// error // error
setTagError(err.response?.data); setTagError(err.response?.data);
} finally { setUpdateState(UpdateState.FAILED);
setValidating(false);
} }
}; };
@ -96,81 +105,139 @@ export const GlossaryUpdateConfirmationModal = ({
]; ];
}, []); }, []);
const progress =
updateState === UpdateState.VALIDATING
? 10
: updateState === UpdateState.UPDATATING
? 60
: 100;
const data = useMemo(() => {
const footer = (
<div className="d-flex justify-between">
<Typography.Text type="secondary">
{failedStatus?.numberOfRowsFailed &&
`${failedStatus.numberOfRowsFailed} ${t('label.failed')}`}
</Typography.Text>
<Button onClick={onCancel}>{t('label.cancel')}</Button>
</div>
);
const progressBar = (
<div className="text-center">
<Progress percent={progress} status="normal" type="circle" />
</div>
);
switch (updateState) {
case UpdateState.INITIAL:
return {
footer: null,
content: (
<div className="d-flex items-center flex-column gap-2">
<Icon
className="m-b-lg"
component={ExclamationIcon}
style={{ fontSize: '60px' }}
/>
<Typography.Title level={5}>
{t('message.tag-update-confirmation')}
</Typography.Title>
<Typography.Text className="text-center">
{t('message.glossary-tag-update-description')}{' '}
<span className="font-medium">
{getEntityName(glossaryTerm)}
</span>
</Typography.Text>
<div className="m-t-lg">
<Space size={8}>
<Button onClick={onCancel}>
{t('label.no-comma-cancel')}
</Button>
<Button type="primary" onClick={handleUpdateConfirmation}>
{t('label.yes-comma-confirm')}
</Button>
</Space>
</div>
</div>
),
};
case UpdateState.VALIDATING:
return {
content: progressBar,
footer: footer,
};
case UpdateState.FAILED:
return {
content: (
<div className="d-flex flex-column gap-2">
{failedStatus && (
<>
<Table
bordered
columns={tagsColumn}
dataSource={failedStatus?.failedRequest ?? []}
pagination={{
pageSize: 5,
showSizeChanger: true,
}}
rowKey={(record) => record.request?.id}
/>
<Alert
className="m-t-sm"
message={t('message.glossary-tag-assignement-help-message')}
type="warning"
/>
</>
)}
{tagError?.code === ClientErrors.BAD_REQUEST && (
<Alert message={tagError.message} type="warning" />
)}
</div>
),
footer: (
<div className="d-flex justify-between">
<Typography.Text type="secondary">
{failedStatus?.numberOfRowsFailed &&
`${failedStatus.numberOfRowsFailed} ${t('label.failed')}`}
</Typography.Text>
<Button onClick={onCancel}>{t('label.cancel')}</Button>
</div>
),
};
case UpdateState.UPDATATING:
return {
content: progressBar,
footer: <Button onClick={onCancel}>{t('label.cancel')}</Button>,
};
case UpdateState.SUCESS:
return {
content: progressBar,
footer: <Button onClick={onCancel}>{t('label.cancel')}</Button>,
};
}
}, [updateState, failedStatus]);
return ( return (
<Modal <Modal
centered centered
open open
closable={false} closable={false}
closeIcon={null} closeIcon={null}
footer={ footer={data.footer}
tagAdditionConfirmation && (
<div className="d-flex justify-between">
<Typography.Text type="secondary">
{failedStatus?.numberOfRowsFailed &&
`${failedStatus.numberOfRowsFailed} ${t('label.failed')}`}
</Typography.Text>
<Button onClick={onCancel}>{t('label.cancel')}</Button>
</div>
)
}
title={ title={
tagAdditionConfirmation [
UpdateState.VALIDATING,
UpdateState.FAILED,
UpdateState.UPDATATING,
UpdateState.SUCESS,
].includes(updateState)
? t('message.glossary-tag-update-modal-title') ? t('message.glossary-tag-update-modal-title')
: undefined : undefined
} }
width={tagAdditionConfirmation ? 750 : undefined} width={updateState === UpdateState.FAILED ? 750 : undefined}
onCancel={onCancel}> onCancel={onCancel}>
{tagAdditionConfirmation || validating ? ( {data.content}
<div className="d-flex flex-column gap-2">
{!isEmpty(failedStatus?.failedRequest) && !validating && (
<>
<Table
bordered
columns={tagsColumn}
dataSource={failedStatus?.failedRequest}
loading={validating}
pagination={{
pageSize: 5,
showSizeChanger: true,
}}
rowKey={(record) => record.request.id}
/>
<Typography.Text italic className="m-t-sm" type="secondary">
{t('message.glossary-tag-assignement-help-message')}
</Typography.Text>
</>
)}
{tagError?.code === ClientErrors.BAD_REQUEST && (
<Typography.Text type="danger">{tagError.message}</Typography.Text>
)}
</div>
) : (
<div className="d-flex items-center flex-column gap-2">
<Icon
className="m-b-lg"
component={ExclamationIcon}
style={{ fontSize: '60px' }}
/>
<Typography.Title level={5}>
{t('message.tag-update-confirmation')}
</Typography.Title>
<Typography.Text className="text-center">
{t('message.glossary-tag-update-description')}{' '}
<span className="font-medium">{getEntityName(glossaryTerm)}</span>
</Typography.Text>
<div className="m-t-lg">
<Space size={8}>
<Button onClick={onCancel}>{t('label.no-comma-cancel')}</Button>
<Button
loading={validating}
type="primary"
onClick={handleUpdateConfirmation}>
{t('label.yes-comma-confirm')}
</Button>
</Space>
</div>
</div>
)}
</Modal> </Modal>
); );
}; };

View File

@ -13,7 +13,7 @@
import { Col, Form, Row, Space, Tooltip, Typography } from 'antd'; import { Col, Form, Row, Space, Tooltip, Typography } from 'antd';
import { DefaultOptionType } from 'antd/lib/select'; import { DefaultOptionType } from 'antd/lib/select';
import { isEmpty } from 'lodash'; import { isEmpty, isEqual } from 'lodash';
import { EntityTags } from 'Models'; import { EntityTags } from 'Models';
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';
@ -104,7 +104,7 @@ const TagsContainerV2 = ({
const handleSave = async (data: DefaultOptionType | DefaultOptionType[]) => { const handleSave = async (data: DefaultOptionType | DefaultOptionType[]) => {
const updatedTags = (data as DefaultOptionType[]).map((tag) => { const updatedTags = (data as DefaultOptionType[]).map((tag) => {
let tagData: EntityTags = { let tagData: EntityTags = {
tagFQN: tag.value, tagFQN: typeof tag === 'string' ? tag : tag.value,
source: tagType, source: tagType,
}; };
@ -115,13 +115,16 @@ const TagsContainerV2 = ({
displayName: tag.data?.displayName, displayName: tag.data?.displayName,
description: tag.data?.description, description: tag.data?.description,
style: tag.data?.style, style: tag.data?.style,
labelType: tag.data?.labelType,
}; };
} }
return tagData; return tagData;
}); });
if (onSelectionChange) { const newTags = updatedTags.map((t) => t.tagFQN);
if (onSelectionChange && !isEqual(selectedTagsInternal, newTags)) {
await onSelectionChange([ await onSelectionChange([
...updatedTags, ...updatedTags,
...((isGlossaryType ...((isGlossaryType

View File

@ -30,6 +30,7 @@ import {
export const PRIMERY_COLOR = '#0968da'; export const PRIMERY_COLOR = '#0968da';
export const SECONDARY_COLOR = '#B02AAC'; export const SECONDARY_COLOR = '#B02AAC';
export const INFO_COLOR = '#2196f3'; export const INFO_COLOR = '#2196f3';
export const ERROR_COLOR = '#ff4c3b';
export const LITE_GRAY_COLOR = '#DBE0EB'; export const LITE_GRAY_COLOR = '#DBE0EB';
export const TEXT_BODY_COLOR = '#37352F'; export const TEXT_BODY_COLOR = '#37352F';
export const TEXT_GREY_MUTED = '#757575'; export const TEXT_GREY_MUTED = '#757575';

View File

@ -396,6 +396,7 @@
"entity-service": "{{entity}}-Dienst", "entity-service": "{{entity}}-Dienst",
"entity-type-plural": "{{entity}}-Typen", "entity-type-plural": "{{entity}}-Typen",
"entity-version-detail-plural": "Details zu {{entity}}-Versionen", "entity-version-detail-plural": "Details zu {{entity}}-Versionen",
"error": "Error",
"event-publisher-plural": "Ereignisveröffentlicher", "event-publisher-plural": "Ereignisveröffentlicher",
"event-type": "Ereignistyp", "event-type": "Ereignistyp",
"every": "Jede/r/s", "every": "Jede/r/s",
@ -1133,6 +1134,7 @@
"username-or-email": "Benutzername oder E-Mail", "username-or-email": "Benutzername oder E-Mail",
"valid-condition": "Gültige Bedingung", "valid-condition": "Gültige Bedingung",
"validating-condition": "Bedingung validieren...", "validating-condition": "Bedingung validieren...",
"validation-error-plural": "Validation Errors!",
"value": "Wert", "value": "Wert",
"value-count": "Wertanzahl", "value-count": "Wertanzahl",
"value-plural": "Werte", "value-plural": "Werte",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}} hat Sie in einem Kommentar erwähnt.", "user-mentioned-in-comment": "{{user}} hat Sie in einem Kommentar erwähnt.",
"user-verified-successfully": "Benutzer erfolgreich verifiziert", "user-verified-successfully": "Benutzer erfolgreich verifiziert",
"valid-url-endpoint": "Endpunkte sollten gültige URLs sein", "valid-url-endpoint": "Endpunkte sollten gültige URLs sein",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "{{version}} Veröffentlicht <0>Sehen Sie, was es Neues gibt!</0>", "version-released-try-now": "{{version}} Veröffentlicht <0>Sehen Sie, was es Neues gibt!</0>",
"view-deleted-entity": "Alle gelöschten {{entity}}, die zu diesem {{parent}} gehören, anzeigen.", "view-deleted-entity": "Alle gelöschten {{entity}}, die zu diesem {{parent}} gehören, anzeigen.",
"view-sample-data-entity": "Um Musterdaten anzuzeigen, führen Sie {{entity}} aus. Bitte beachten Sie dieses Dokument, um die <0>{{entity}}</0> zu planen.", "view-sample-data-entity": "Um Musterdaten anzuzeigen, führen Sie {{entity}} aus. Bitte beachten Sie dieses Dokument, um die <0>{{entity}}</0> zu planen.",

View File

@ -396,6 +396,7 @@
"entity-service": "{{entity}} Service", "entity-service": "{{entity}} Service",
"entity-type-plural": "{{entity}} Type", "entity-type-plural": "{{entity}} Type",
"entity-version-detail-plural": "{{entity}} Version Details", "entity-version-detail-plural": "{{entity}} Version Details",
"error": "Error",
"event-publisher-plural": "Event Publishers", "event-publisher-plural": "Event Publishers",
"event-type": "Event Type", "event-type": "Event Type",
"every": "Every", "every": "Every",
@ -1133,6 +1134,7 @@
"username-or-email": "Username or Email", "username-or-email": "Username or Email",
"valid-condition": "Valid condition", "valid-condition": "Valid condition",
"validating-condition": "Validating the condition...", "validating-condition": "Validating the condition...",
"validation-error-plural": "Validation Errors!",
"value": "Value", "value": "Value",
"value-count": "Value Count", "value-count": "Value Count",
"value-plural": "Values", "value-plural": "Values",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}} mentioned you in a comment.", "user-mentioned-in-comment": "{{user}} mentioned you in a comment.",
"user-verified-successfully": "User Verified Successfully", "user-verified-successfully": "User Verified Successfully",
"valid-url-endpoint": "Endpoints should be valid URL", "valid-url-endpoint": "Endpoints should be valid URL",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "{{version}} Released <0>See What's New!</0>", "version-released-try-now": "{{version}} Released <0>See What's New!</0>",
"view-deleted-entity": "View All the Deleted {{entity}}, which come under this {{parent}}.", "view-deleted-entity": "View All the Deleted {{entity}}, which come under this {{parent}}.",
"view-sample-data-entity": "To view Sample Data, run the {{entity}}. Please refer to this doc to schedule the <0>{{entity}}</0>", "view-sample-data-entity": "To view Sample Data, run the {{entity}}. Please refer to this doc to schedule the <0>{{entity}}</0>",

View File

@ -396,6 +396,7 @@
"entity-service": "Servicio de {{entity}}", "entity-service": "Servicio de {{entity}}",
"entity-type-plural": "{{entity}} Type", "entity-type-plural": "{{entity}} Type",
"entity-version-detail-plural": "{{entity}} Version Details", "entity-version-detail-plural": "{{entity}} Version Details",
"error": "Error",
"event-publisher-plural": "Publicadores de eventos", "event-publisher-plural": "Publicadores de eventos",
"event-type": "Tipo de evento", "event-type": "Tipo de evento",
"every": "Cada", "every": "Cada",
@ -1133,6 +1134,7 @@
"username-or-email": "Nombre de usuario o correo electrónico", "username-or-email": "Nombre de usuario o correo electrónico",
"valid-condition": "Condición válida", "valid-condition": "Condición válida",
"validating-condition": "Validando la condición...", "validating-condition": "Validando la condición...",
"validation-error-plural": "Validation Errors!",
"value": "Valor", "value": "Valor",
"value-count": "Recuento de valor", "value-count": "Recuento de valor",
"value-plural": "Valores", "value-plural": "Valores",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}} te mencionó en un comentario.", "user-mentioned-in-comment": "{{user}} te mencionó en un comentario.",
"user-verified-successfully": "Usuario verificado con éxito", "user-verified-successfully": "Usuario verificado con éxito",
"valid-url-endpoint": "Los terminales deben ser URL válidas", "valid-url-endpoint": "Los terminales deben ser URL válidas",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "{{version}} Released <0>See What's New!</0>", "version-released-try-now": "{{version}} Released <0>See What's New!</0>",
"view-deleted-entity": "Ver todas las {{entity}} eliminadas que se encuentran bajo este {{parent}}.", "view-deleted-entity": "Ver todas las {{entity}} eliminadas que se encuentran bajo este {{parent}}.",
"view-sample-data-entity": "To view Sample Data, run the {{entity}}. Please refer to this doc to schedule the <0>{{entity}}</0>", "view-sample-data-entity": "To view Sample Data, run the {{entity}}. Please refer to this doc to schedule the <0>{{entity}}</0>",

View File

@ -396,6 +396,7 @@
"entity-service": "Service de {{entity}}", "entity-service": "Service de {{entity}}",
"entity-type-plural": "{{entity}} Types", "entity-type-plural": "{{entity}} Types",
"entity-version-detail-plural": "Détails des Versions de {{entity}}", "entity-version-detail-plural": "Détails des Versions de {{entity}}",
"error": "Error",
"event-publisher-plural": "Publicateurs d'Événements", "event-publisher-plural": "Publicateurs d'Événements",
"event-type": "Type d'Événement", "event-type": "Type d'Événement",
"every": "Chaque", "every": "Chaque",
@ -1133,6 +1134,7 @@
"username-or-email": "Nom d'Utilisateur ou Email", "username-or-email": "Nom d'Utilisateur ou Email",
"valid-condition": "Condition Valide", "valid-condition": "Condition Valide",
"validating-condition": "Validation de la Condition...", "validating-condition": "Validation de la Condition...",
"validation-error-plural": "Validation Errors!",
"value": "Valeur", "value": "Valeur",
"value-count": "Décompte de la Valeur", "value-count": "Décompte de la Valeur",
"value-plural": "Valeurs", "value-plural": "Valeurs",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}} vous a mentionné dans un commentaire.", "user-mentioned-in-comment": "{{user}} vous a mentionné dans un commentaire.",
"user-verified-successfully": "L'utilisateur a été vérifié avec succès", "user-verified-successfully": "L'utilisateur a été vérifié avec succès",
"valid-url-endpoint": "Les points de terminaison doivent être une URL valide", "valid-url-endpoint": "Les points de terminaison doivent être une URL valide",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "{{version}} publié essayez maintenant !", "version-released-try-now": "{{version}} publié essayez maintenant !",
"view-deleted-entity": "Afficher toutes les {{entity}} supprimées qui relèvent de ce {{parent}}.", "view-deleted-entity": "Afficher toutes les {{entity}} supprimées qui relèvent de ce {{parent}}.",
"view-sample-data-entity": "Pour afficher des données d'exemple, exécutez {{entity}}. Veuillez vous référer à ce document pour planifier {{entity}}.", "view-sample-data-entity": "Pour afficher des données d'exemple, exécutez {{entity}}. Veuillez vous référer à ce document pour planifier {{entity}}.",

View File

@ -396,6 +396,7 @@
"entity-service": "{{entity}}サービス", "entity-service": "{{entity}}サービス",
"entity-type-plural": "{{entity}} Type", "entity-type-plural": "{{entity}} Type",
"entity-version-detail-plural": "{{entity}} Version Details", "entity-version-detail-plural": "{{entity}} Version Details",
"error": "Error",
"event-publisher-plural": "イベントの作成者", "event-publisher-plural": "イベントの作成者",
"event-type": "イベントの種類", "event-type": "イベントの種類",
"every": "Every", "every": "Every",
@ -1133,6 +1134,7 @@
"username-or-email": "ユーザ名あるいはEmailアドレス", "username-or-email": "ユーザ名あるいはEmailアドレス",
"valid-condition": "正しい条件", "valid-condition": "正しい条件",
"validating-condition": "条件を検証中...", "validating-condition": "条件を検証中...",
"validation-error-plural": "Validation Errors!",
"value": "値", "value": "値",
"value-count": "値の数", "value-count": "値の数",
"value-plural": "値", "value-plural": "値",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}}がコメントであなたにメンションしました。", "user-mentioned-in-comment": "{{user}}がコメントであなたにメンションしました。",
"user-verified-successfully": "ユーザ認証に成功しました", "user-verified-successfully": "ユーザ認証に成功しました",
"valid-url-endpoint": "エンドポイントは有効なURLでなければなりません", "valid-url-endpoint": "エンドポイントは有効なURLでなければなりません",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "{{version}} Released <0>See What's New!</0>", "version-released-try-now": "{{version}} Released <0>See What's New!</0>",
"view-deleted-entity": "View All the Deleted {{entity}}, which come under this {{parent}}.", "view-deleted-entity": "View All the Deleted {{entity}}, which come under this {{parent}}.",
"view-sample-data-entity": "To view Sample Data, run the {{entity}}. Please refer to this doc to schedule the <0>{{entity}}</0>", "view-sample-data-entity": "To view Sample Data, run the {{entity}}. Please refer to this doc to schedule the <0>{{entity}}</0>",

View File

@ -396,6 +396,7 @@
"entity-service": "Serviço de {{entity}}", "entity-service": "Serviço de {{entity}}",
"entity-type-plural": "Tipo de {{entity}}", "entity-type-plural": "Tipo de {{entity}}",
"entity-version-detail-plural": "Detalhes da Versão de {{entity}}", "entity-version-detail-plural": "Detalhes da Versão de {{entity}}",
"error": "Error",
"event-publisher-plural": "Publicadores de Eventos", "event-publisher-plural": "Publicadores de Eventos",
"event-type": "Tipo de Evento", "event-type": "Tipo de Evento",
"every": "Cada", "every": "Cada",
@ -1133,6 +1134,7 @@
"username-or-email": "Nome de Usuário ou Email", "username-or-email": "Nome de Usuário ou Email",
"valid-condition": "Condição Válida", "valid-condition": "Condição Válida",
"validating-condition": "Validando a condição...", "validating-condition": "Validando a condição...",
"validation-error-plural": "Validation Errors!",
"value": "Valor", "value": "Valor",
"value-count": "Contagem de Valor", "value-count": "Contagem de Valor",
"value-plural": "Valores", "value-plural": "Valores",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}} mencionou você em um comentário.", "user-mentioned-in-comment": "{{user}} mencionou você em um comentário.",
"user-verified-successfully": "Usuário verificado com sucesso", "user-verified-successfully": "Usuário verificado com sucesso",
"valid-url-endpoint": "Os endpoints devem ser URLs válidas", "valid-url-endpoint": "Os endpoints devem ser URLs válidas",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "{{version}} Lançada <0>Veja o que há de novo!</0>", "version-released-try-now": "{{version}} Lançada <0>Veja o que há de novo!</0>",
"view-deleted-entity": "Visualize todos os {{entity}} excluídos, que fazem parte deste {{parent}}.", "view-deleted-entity": "Visualize todos os {{entity}} excluídos, que fazem parte deste {{parent}}.",
"view-sample-data-entity": "Para visualizar os Dados de Amostra, execute o {{entity}}. Consulte este documento para agendar o <0>{{entity}}</0>", "view-sample-data-entity": "Para visualizar os Dados de Amostra, execute o {{entity}}. Consulte este documento para agendar o <0>{{entity}}</0>",

View File

@ -396,6 +396,7 @@
"entity-service": "Сервис {{entity}}", "entity-service": "Сервис {{entity}}",
"entity-type-plural": "Тип {{entity}}", "entity-type-plural": "Тип {{entity}}",
"entity-version-detail-plural": "{{entity}} Version Details", "entity-version-detail-plural": "{{entity}} Version Details",
"error": "Error",
"event-publisher-plural": "Издатели события", "event-publisher-plural": "Издатели события",
"event-type": "Тип события", "event-type": "Тип события",
"every": "Каждый", "every": "Каждый",
@ -1133,6 +1134,7 @@
"username-or-email": "Имя пользователя или Email", "username-or-email": "Имя пользователя или Email",
"valid-condition": "Действительное условие", "valid-condition": "Действительное условие",
"validating-condition": "Проверка условия...", "validating-condition": "Проверка условия...",
"validation-error-plural": "Validation Errors!",
"value": "Значение", "value": "Значение",
"value-count": "Количество значений", "value-count": "Количество значений",
"value-plural": "Значения", "value-plural": "Значения",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}} упомянул вас в комментарии.", "user-mentioned-in-comment": "{{user}} упомянул вас в комментарии.",
"user-verified-successfully": "Пользователь успешно проверен", "user-verified-successfully": "Пользователь успешно проверен",
"valid-url-endpoint": "Конечные точки должны быть действительным URL", "valid-url-endpoint": "Конечные точки должны быть действительным URL",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "Выпущена {{версия}} <0>Посмотрите, что нового!</0>", "version-released-try-now": "Выпущена {{версия}} <0>Посмотрите, что нового!</0>",
"view-deleted-entity": "Просмотреть все удаленные {{entity}}, относящиеся к этому {{parent}}.", "view-deleted-entity": "Просмотреть все удаленные {{entity}}, относящиеся к этому {{parent}}.",
"view-sample-data-entity": "Чтобы просмотреть образцы данных, запустите {{entity}}. Пожалуйста, обратитесь к этому документу, чтобы запланировать <0>{{entity}}</0>", "view-sample-data-entity": "Чтобы просмотреть образцы данных, запустите {{entity}}. Пожалуйста, обратитесь к этому документу, чтобы запланировать <0>{{entity}}</0>",

View File

@ -396,6 +396,7 @@
"entity-service": "{{entity}}服务", "entity-service": "{{entity}}服务",
"entity-type-plural": "{{entity}}类型", "entity-type-plural": "{{entity}}类型",
"entity-version-detail-plural": "{{entity}}版本详情", "entity-version-detail-plural": "{{entity}}版本详情",
"error": "Error",
"event-publisher-plural": "事件发布者", "event-publisher-plural": "事件发布者",
"event-type": "事件类型", "event-type": "事件类型",
"every": "每个", "every": "每个",
@ -1133,6 +1134,7 @@
"username-or-email": "用户名或电子邮箱", "username-or-email": "用户名或电子邮箱",
"valid-condition": "有效条件", "valid-condition": "有效条件",
"validating-condition": "正在验证条件...", "validating-condition": "正在验证条件...",
"validation-error-plural": "Validation Errors!",
"value": "值", "value": "值",
"value-count": "值数量", "value-count": "值数量",
"value-plural": "值", "value-plural": "值",
@ -1628,6 +1630,7 @@
"user-mentioned-in-comment": "{{user}}在评论中提到了您", "user-mentioned-in-comment": "{{user}}在评论中提到了您",
"user-verified-successfully": "用户验证成功", "user-verified-successfully": "用户验证成功",
"valid-url-endpoint": "端点应该是有效的 URL", "valid-url-endpoint": "端点应该是有效的 URL",
"validation-error-assets": "Please examine all your assets that are being added",
"version-released-try-now": "查看新版本 {{version}} 的新特性!", "version-released-try-now": "查看新版本 {{version}} 的新特性!",
"view-deleted-entity": "查看所有被删除的{{entity}},隶属于该{{parent}}", "view-deleted-entity": "查看所有被删除的{{entity}},隶属于该{{parent}}",
"view-sample-data-entity": "要查看样本数据,请运行{{entity}}。您还可以前往查看任务调度相关的信息<0>{{entity}}</0>。", "view-sample-data-entity": "要查看样本数据,请运行{{entity}}。您还可以前往查看任务调度相关的信息<0>{{entity}}</0>。",

View File

@ -305,9 +305,12 @@ const GlossaryPage = () => {
.finally(() => setDeleteStatus(LOADING_STATE.INITIAL)); .finally(() => setDeleteStatus(LOADING_STATE.INITIAL));
}; };
const handleAssetClick = (asset?: EntityDetailsObjectInterface) => { const handleAssetClick = useCallback(
setPreviewAsset(asset); (asset?: EntityDetailsObjectInterface) => {
}; setPreviewAsset(asset);
},
[]
);
if (isLoading) { if (isLoading) {
return <Loader />; return <Loader />;

View File

@ -103,4 +103,4 @@ jest.mock('react-i18next', () => ({
jest.mock('./utils/ToastUtils', () => ({ jest.mock('./utils/ToastUtils', () => ({
showErrorToast: jest.fn(), showErrorToast: jest.fn(),
})); }));

View File

@ -15,15 +15,20 @@ import { Status } from '../generated/entity/applications/appRunRecord';
import { PipelineState } from '../generated/entity/services/ingestionPipelines/ingestionPipeline'; import { PipelineState } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
export const getStatusTypeForApplication = (status: Status) => { export const getStatusTypeForApplication = (status: Status) => {
if (status === Status.Failed) { switch (status) {
return StatusType.Failure; case Status.Failed:
} else if (status === Status.Success) { return StatusType.Failure;
return StatusType.Success;
} else if (status === Status.Running) {
return StatusType.Warning;
}
return StatusType.Failure; case Status.Success:
case Status.Completed:
return StatusType.Success;
case Status.Running:
return StatusType.Warning;
default:
return StatusType.Failure;
}
}; };
export const getStatusFromPipelineState = (status: PipelineState) => { export const getStatusFromPipelineState = (status: PipelineState) => {