From dfc8de70667b522c9a1862c2bfae0027d87bacca Mon Sep 17 00:00:00 2001 From: Nikita Dementev Date: Wed, 25 Jan 2023 15:16:10 +0300 Subject: [PATCH] Fix LinkedUrlList for DataQualityTests (#1224) --- .../dto/DataEntityDimensionsDto.java | 3 +- .../attributes/DataQualityTestAttributes.java | 2 +- .../dto/attributes/LinkedUrlAttribute.java | 4 ++ .../mapper/DataEntityMapperImpl.java | 23 +++++++- .../api/ingestion/AlertIngestionTest.java | 3 + odd-platform-specification/components.yaml | 13 ++++- .../OverviewExpectations.tsx | 13 +++-- .../TestReportDetailsOverview.tsx | 22 +++---- .../components/shared/AppButton/AppButton.tsx | 57 +++++++++--------- .../shared/AppButton/AppButtonStyles.ts | 6 +- .../shared/TruncatedCell/TruncatedCell.tsx | 58 ++++++++++++++++--- .../TruncatedCellMenu/TruncatedCellMenu.tsx | 40 +++++++------ 12 files changed, 159 insertions(+), 85 deletions(-) create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/LinkedUrlAttribute.java diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/DataEntityDimensionsDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/DataEntityDimensionsDto.java index 4738667a1..0efcb5e06 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/DataEntityDimensionsDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/DataEntityDimensionsDto.java @@ -9,6 +9,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.opendatadiscovery.oddplatform.dto.attributes.DataEntityAttributes; +import org.opendatadiscovery.oddplatform.dto.attributes.LinkedUrlAttribute; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityTaskRunPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataSourcePojo; @@ -64,7 +65,7 @@ public record DataTransformerDetailsDto(Collection sourceList, public record DataQualityTestDetailsDto(String suiteName, String suiteUrl, Collection datasetList, - List linkedUrlList, + List linkedUrlList, String expectationType, DataEntityTaskRunPojo latestTaskRun, Map expectationParameters) { diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/DataQualityTestAttributes.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/DataQualityTestAttributes.java index e3a181e00..49a9ff611 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/DataQualityTestAttributes.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/DataQualityTestAttributes.java @@ -25,7 +25,7 @@ public class DataQualityTestAttributes extends DataEntityAttributes { private Set datasetOddrnList; @JsonProperty("linked_url_list") - private List linkedUrlList; + private List linkedUrlList; @JsonProperty("expectation") private DataQualityTestExpectationAttributes expectation; diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/LinkedUrlAttribute.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/LinkedUrlAttribute.java new file mode 100644 index 000000000..a9b344471 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/attributes/LinkedUrlAttribute.java @@ -0,0 +1,4 @@ +package org.opendatadiscovery.oddplatform.dto.attributes; + +public record LinkedUrlAttribute(String name, String url) { +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/DataEntityMapperImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/DataEntityMapperImpl.java index 1e5c34738..a68a9ae31 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/DataEntityMapperImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/DataEntityMapperImpl.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntity; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityClass; @@ -28,6 +29,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.DataQualityTestExpectation; import org.opendatadiscovery.oddplatform.api.contract.model.DataQualityTestSeverity; import org.opendatadiscovery.oddplatform.api.contract.model.DataSetStats; +import org.opendatadiscovery.oddplatform.api.contract.model.LinkedUrl; import org.opendatadiscovery.oddplatform.api.contract.model.PageInfo; import org.opendatadiscovery.oddplatform.dto.DataEntityClassDto; import org.opendatadiscovery.oddplatform.dto.DataEntityDetailsDto; @@ -35,6 +37,7 @@ import org.opendatadiscovery.oddplatform.dto.DataEntityDto; import org.opendatadiscovery.oddplatform.dto.DataEntityTypeDto; import org.opendatadiscovery.oddplatform.dto.DataSourceDto; +import org.opendatadiscovery.oddplatform.dto.attributes.LinkedUrlAttribute; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityStatisticsPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataQualityTestSeverityPojo; @@ -96,7 +99,9 @@ public DataEntity mapPojo(final DataEntityDimensionsDto dto) { } if (entityClasses.contains(DataEntityClassDto.DATA_QUALITY_TEST)) { - entity.datasetsList(dto.getDataQualityTestDetailsDto() + entity.setLinkedUrlList(mapLinkedUrlList(dto.getDataQualityTestDetailsDto().linkedUrlList())); + + entity.setDatasetsList(dto.getDataQualityTestDetailsDto() .datasetList() .stream() .distinct() @@ -251,7 +256,7 @@ public DataEntityDetails mapDtoDetails(final DataEntityDetailsDto dto) { .distinct() .map(this::mapReference) .collect(Collectors.toList())) - .linkedUrlList(dto.getDataQualityTestDetailsDto().linkedUrlList()) + .linkedUrlList(mapLinkedUrlList(dto.getDataQualityTestDetailsDto().linkedUrlList())) .latestRun(dataEntityRunMapper.mapDataEntityRun( dto.getDataEntity().getId(), dto.getDataQualityTestDetailsDto().latestTaskRun()) @@ -302,7 +307,7 @@ public DataEntity mapDataQualityTest(final DataEntityDimensionsDto dto, .suiteUrl(dqDto.suiteUrl()) .expectation(mapDataQualityTestExpectation(dqDto)) .latestRun(latestRun) - .linkedUrlList(dqDto.linkedUrlList()) + .linkedUrlList(mapLinkedUrlList(dqDto.linkedUrlList())) .severity(severity != null ? DataQualityTestSeverity.valueOf(severity) : null) .datasetsList(dqDto .datasetList() @@ -404,6 +409,18 @@ public DataEntityUsageInfo mapUsageInfo(final DataEntityStatisticsPojo pojo, ); } + private LinkedUrl mapLinkedUrl(final LinkedUrlAttribute linkedUrlAttribute) { + return new LinkedUrl() + .url(linkedUrlAttribute.url()) + .name(linkedUrlAttribute.name()); + } + + private List mapLinkedUrlList(final Collection linkedUrlAttributes) { + return CollectionUtils.emptyIfNull(linkedUrlAttributes) + .stream() + .map(this::mapLinkedUrl).toList(); + } + private DataEntityRef mapReference(final DataEntityDto dto) { return mapReference(dto.getDataEntity()).hasAlerts(dto.isHasAlerts()); } diff --git a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/api/ingestion/AlertIngestionTest.java b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/api/ingestion/AlertIngestionTest.java index 2c6e98431..9bc7adf57 100644 --- a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/api/ingestion/AlertIngestionTest.java +++ b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/api/ingestion/AlertIngestionTest.java @@ -5,6 +5,7 @@ import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -236,6 +237,8 @@ public void failedDQTestAlertIngestionTest() { expectedDetails.setLatestRun( buildExpectedDataEntityRun(ingestionMap.get(dataQualityTest.getOddrn()), dataQualityTestRun)); + expectedDetails.setLinkedUrlList(Collections.emptyList()); + assertDataEntityDetailsEqual(expectedDetails, (expected, actual) -> assertThat(actual.getDatasetsList()) .usingRecursiveFieldByFieldElementComparatorIgnoringFields("entityClasses") diff --git a/odd-platform-specification/components.yaml b/odd-platform-specification/components.yaml index 913782c8a..60a9349b5 100644 --- a/odd-platform-specification/components.yaml +++ b/odd-platform-specification/components.yaml @@ -762,6 +762,17 @@ components: - MAJOR - CRITICAL + LinkedUrl: + type: object + properties: + url: + type: string + name: + type: string + required: + - url + - name + DataQualityTest: allOf: - $ref: '#/components/schemas/DataEntityBaseObject' @@ -780,7 +791,7 @@ components: linked_url_list: type: array items: - type: string + $ref: '#/components/schemas/LinkedUrl' latest_run: $ref: '#/components/schemas/DataEntityRun' diff --git a/odd-platform-ui/src/components/DataEntityDetails/Overview/OverviewExpectations/OverviewExpectations.tsx b/odd-platform-ui/src/components/DataEntityDetails/Overview/OverviewExpectations/OverviewExpectations.tsx index 4a5a5730c..ea348b15b 100644 --- a/odd-platform-ui/src/components/DataEntityDetails/Overview/OverviewExpectations/OverviewExpectations.tsx +++ b/odd-platform-ui/src/components/DataEntityDetails/Overview/OverviewExpectations/OverviewExpectations.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DataQualityTestExpectation } from 'generated-sources'; +import type { DataQualityTestExpectation, LinkedUrl } from 'generated-sources'; import { Grid, Typography } from '@mui/material'; import { AppButton } from 'components/shared'; import { DropdownIcon } from 'components/shared/Icons'; @@ -7,7 +7,7 @@ import * as S from './OverviewExpectationsStyles'; interface OverviewExpectationsProps { parameters: DataQualityTestExpectation | undefined; - linkedUrlList: string[] | undefined; + linkedUrlList: LinkedUrl[] | undefined; } const OverviewExpectations: React.FC = ({ @@ -40,16 +40,17 @@ const OverviewExpectations: React.FC = ({ Links - {linkedUrlList?.map(link => ( + {linkedUrlList?.map(({ name, url }) => ( - {link} + {name} ))} diff --git a/odd-platform-ui/src/components/DataEntityDetails/TestReport/TestReportDetails/TestReportDetailsOverview/TestReportDetailsOverview.tsx b/odd-platform-ui/src/components/DataEntityDetails/TestReport/TestReportDetails/TestReportDetailsOverview/TestReportDetailsOverview.tsx index b29106905..23e1349c9 100644 --- a/odd-platform-ui/src/components/DataEntityDetails/TestReport/TestReportDetails/TestReportDetailsOverview/TestReportDetailsOverview.tsx +++ b/odd-platform-ui/src/components/DataEntityDetails/TestReport/TestReportDetails/TestReportDetailsOverview/TestReportDetailsOverview.tsx @@ -1,11 +1,9 @@ import React from 'react'; -import { Box, Grid, SelectChangeEvent, Typography } from '@mui/material'; +import type { SelectChangeEvent } from '@mui/material'; +import { Box, Grid, Typography } from '@mui/material'; import { AppButton, AppMenuItem, AppSelect, LabeledInfoItem } from 'components/shared'; -import { - DataQualityTestExpectation, - DataQualityTestSeverity, - Permission, -} from 'generated-sources'; +import type { DataQualityTestExpectation } from 'generated-sources'; +import { DataQualityTestSeverity, Permission } from 'generated-sources'; import { getDatasetTestListFetchingStatuses, getQualityTestByTestId, @@ -128,19 +126,17 @@ const TestReportDetailsOverview: React.FC = () => { Links - {qualityTest.linkedUrlList.map(link => ( + {qualityTest.linkedUrlList.map(({ name, url }) => ( - {link} + {name} ))} diff --git a/odd-platform-ui/src/components/shared/AppButton/AppButton.tsx b/odd-platform-ui/src/components/shared/AppButton/AppButton.tsx index e948f4870..05b09c670 100644 --- a/odd-platform-ui/src/components/shared/AppButton/AppButton.tsx +++ b/odd-platform-ui/src/components/shared/AppButton/AppButton.tsx @@ -1,8 +1,9 @@ -import React, { HTMLAttributeAnchorTarget } from 'react'; -import { Box, ButtonProps, Theme } from '@mui/material'; +import type { HTMLAttributeAnchorTarget } from 'react'; +import React from 'react'; +import type { ButtonProps } from '@mui/material'; import { Link } from 'react-router-dom'; -import { SxProps } from '@mui/system'; -import { ButtonColors, Loader, StyledAppButton } from './AppButtonStyles'; +import type { ButtonColors } from './AppButtonStyles'; +import { Loader, StyledAppButton } from './AppButtonStyles'; interface AppButtonProps extends Pick< @@ -27,48 +28,42 @@ interface AppButtonProps truncate?: boolean; linkTarget?: HTMLAttributeAnchorTarget; isLoading?: boolean; - containerSx?: SxProps; } const AppButton: React.FC = React.forwardRef( - ( - { color, children, truncate, to, linkTarget, isLoading, containerSx, ...props }, - ref - ) => { + ({ color, children, truncate, to, linkTarget, isLoading, ...props }, ref) => { const [isOverflowed, setIsOverflowed] = React.useState(truncate); const buttonRef = React.useRef(null); React.useEffect(() => { const element = buttonRef.current; - if (element) { + if (element && !truncate) { const { scrollWidth, clientWidth } = element; setIsOverflowed(scrollWidth > clientWidth); } - }, [buttonRef.current]); + }, [buttonRef]); if (to) { return ( - - + - - {isLoading ? : children} - - - + {isLoading ? : children} + + ); } diff --git a/odd-platform-ui/src/components/shared/AppButton/AppButtonStyles.ts b/odd-platform-ui/src/components/shared/AppButton/AppButtonStyles.ts index c79ed61c8..502e160ee 100644 --- a/odd-platform-ui/src/components/shared/AppButton/AppButtonStyles.ts +++ b/odd-platform-ui/src/components/shared/AppButton/AppButtonStyles.ts @@ -37,7 +37,11 @@ export const StyledAppButton = styled(Button)( textOverflow: 'ellipsis', overflow: 'hidden', ...breakpointDownLgBody2, - ...($isOverflowed && { width: 'inherit', display: 'block' }), + ...($isOverflowed && { + width: 'inherit', + maxWidth: 'fit-content', + display: 'block', + }), }, [`&.${buttonClasses.text}`]: { padding: isTertiary($color) ? theme.spacing(0, 0.5) : theme.spacing(0.25, 1.5), diff --git a/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCell.tsx b/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCell.tsx index 5266427e8..163bc3c75 100644 --- a/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCell.tsx +++ b/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCell.tsx @@ -1,6 +1,6 @@ import React from 'react'; import TruncateMarkup from 'react-truncate-markup'; -import { type DataEntityRef } from 'generated-sources'; +import type { DataEntityRef, LinkedUrl } from 'generated-sources'; import AppButton from 'components/shared/AppButton/AppButton'; import { useAppPaths } from 'lib/hooks'; import TruncatedCellMenu from './TruncatedCellMenu/TruncatedCellMenu'; @@ -8,24 +8,58 @@ import * as S from './TruncatedCellStyles'; interface TruncatedCellProps { externalEntityId: number; - dataList: DataEntityRef[] | string[] | undefined; + dataList: DataEntityRef[] | string[] | LinkedUrl[] | undefined; } +const isLinkedUrl = (val: LinkedUrl | DataEntityRef): val is LinkedUrl => + !('id' in val) && 'url' in val; + +export type Values = { + key: string; + linkTo: string; + linkContent: string | undefined; +}; + const TruncatedCell: React.FC = ({ dataList, externalEntityId }) => { const { dataEntityDetailsPath } = useAppPaths(); - const getTruncateMarkupAtom = (item: DataEntityRef | string) => { - const key = typeof item === 'string' ? item : item.id; - const linkTo = typeof item === 'string' ? item : dataEntityDetailsPath(item.id); - const linkContent = - typeof item === 'string' ? item : item.internalName || item.externalName; + const getValues = React.useCallback( + (item: DataEntityRef | LinkedUrl | string): Values => { + let key = ''; + let linkTo = ''; + let linkContent: string | undefined = ''; + + if (typeof item === 'string') { + key = item; + linkTo = item; + linkContent = item; + } else if (isLinkedUrl(item)) { + key = item.url; + linkTo = item.url; + linkContent = item.name; + } else { + key = `${item.id}`; + linkTo = dataEntityDetailsPath(item.id); + linkContent = item.internalName || item.externalName; + } + + return { key, linkTo, linkContent }; + }, + [] + ); + + const getTruncateMarkupAtom = (item: DataEntityRef | LinkedUrl | string) => { + const { key, linkTo, linkContent } = getValues(item); + const updatedLink = + typeof item !== 'string' && 'id' in item ? linkTo : { pathname: linkTo }; + return ( { e.stopPropagation(); }} - to={linkTo} + to={updatedLink} linkTarget='_blank' color='primaryLight' size='small' @@ -40,7 +74,13 @@ const TruncatedCell: React.FC = ({ dataList, externalEntityI return ( } + ellipsis={ + + } > {dataList?.map(getTruncateMarkupAtom)} diff --git a/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCellMenu/TruncatedCellMenu.tsx b/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCellMenu/TruncatedCellMenu.tsx index 89d30330e..c30204be7 100644 --- a/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCellMenu/TruncatedCellMenu.tsx +++ b/odd-platform-ui/src/components/shared/TruncatedCell/TruncatedCellMenu/TruncatedCellMenu.tsx @@ -1,21 +1,24 @@ import React from 'react'; import { Typography } from '@mui/material'; import { Link } from 'react-router-dom'; -import { type DataEntityRef } from 'generated-sources'; +import type { DataEntityRef, LinkedUrl } from 'generated-sources'; import MoreIcon from 'components/shared/Icons/MoreIcon'; import AppIconButton from 'components/shared/AppIconButton/AppIconButton'; import AppMenuItem from 'components/shared/AppMenuItem/AppMenuItem'; import AppMenu from 'components/shared/AppMenu/AppMenu'; -import { useAppPaths } from 'lib/hooks'; +import { type Values } from '../TruncatedCell'; interface TruncatedCellMenuProps { - dataList: DataEntityRef[] | string[] | undefined; + dataList: DataEntityRef[] | string[] | LinkedUrl[] | undefined; menuId: number; + getValues: (item: DataEntityRef | LinkedUrl | string) => Values; } -const TruncatedCellMenu: React.FC = ({ dataList, menuId }) => { - const { dataEntityDetailsPath } = useAppPaths(); - +const TruncatedCellMenu: React.FC = ({ + dataList, + menuId, + getValues, +}) => { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); const handleMenuOpen = (e: React.MouseEvent) => { @@ -53,29 +56,28 @@ const TruncatedCellMenu: React.FC = ({ dataList, menuId maxHeight={300} maxWidth={240} > - {dataList?.map((item: DataEntityRef | string) => - typeof item === 'string' ? ( - {item} + {dataList?.map((item: DataEntityRef | LinkedUrl | string) => { + const { key, linkTo, linkContent } = getValues(item); + const updatedLink = + typeof item !== 'string' && 'id' in item ? linkTo : { pathname: linkTo }; + + return typeof item === 'string' ? ( + {linkContent} ) : ( - + - {item.internalName || item.externalName} + {linkContent} - ) - )} + ); + })} );