diff --git a/datahub-web-react/.eslintrc.js b/datahub-web-react/.eslintrc.js index 598a08dc96..9a43074164 100644 --- a/datahub-web-react/.eslintrc.js +++ b/datahub-web-react/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { jsx: true, // Allows for the parsing of JSX }, project: './tsconfig.json', + tsconfigRootDir: __dirname, }, rules: { '@typescript-eslint/no-explicit-any': 'off', diff --git a/datahub-web-react/src/app/entityV2/shared/components/search/InlineListSearch.tsx b/datahub-web-react/src/app/entityV2/shared/components/search/InlineListSearch.tsx index 270108ffc5..5a8e718a63 100644 --- a/datahub-web-react/src/app/entityV2/shared/components/search/InlineListSearch.tsx +++ b/datahub-web-react/src/app/entityV2/shared/components/search/InlineListSearch.tsx @@ -26,20 +26,22 @@ export const InlineListSearch: React.FC = ({ entityTypeName, options, }) => { - const [debouncedSearchText, setDebouncedSearchText] = useState(searchText); + const [localSearchText, setLocalSearchText] = useState(searchText); + useDebounce( () => { - debouncedSetFilterText(debouncedSearchText); + debouncedSetFilterText(localSearchText); }, 500, - [debouncedSearchText], + [localSearchText], ); + return ( setDebouncedSearchText(e.target.value)} + onChange={(e) => setLocalSearchText(e.target.value)} icon={options?.hidePrefix ? undefined : { icon: 'MagnifyingGlass', source: 'phosphor' }} label="" /> diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTable.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTable.tsx index a3cad831c8..ba1348a154 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTable.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTable.tsx @@ -18,7 +18,7 @@ type StyledTableProps = { showSelect?: boolean; } & TableProps; -export const StyledTable = styled(Table)` +const BaseStyledTable = styled(Table)` ${(props) => !props.showSelect && `margin-left: -50px;`} max-width: none; overflow: inherit; @@ -27,6 +27,9 @@ export const StyledTable = styled(Table)` font-weight: 600; font-size: 12px; color: ${ANTD_GRAY[8]}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } && .ant-table-thead @@ -40,6 +43,9 @@ export const StyledTable = styled(Table)` .ant-table-tbody > tr > td { border: none; ${(props) => props.showSelect && `padding: 16px 20px;`} + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } &&& .ant-table-cell { @@ -55,8 +61,19 @@ export const StyledTable = styled(Table)` &&& .acryl-selected-assertions-table-row { background-color: ${ANTD_GRAY[4]}; } + &&& .ant-table-fixed-right { + background-color: inherit; + } + &&& .ant-table-tbody > tr > td.ant-table-cell-fix-right { + background-color: inherit; + } + &&& .ant-table-thead > tr > th.ant-table-cell-fix-right { + background-color: inherit; + } `; +export const StyledTable = BaseStyledTable as (props: StyledTableProps & TableProps) => JSX.Element; + const DetailsColumnWrapper = styled.div` display: flex; align-items: center; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTableColumns.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTableColumns.tsx index e73c954ce5..763eaa89db 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTableColumns.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTableColumns.tsx @@ -1,20 +1,10 @@ -import { AuditOutlined } from '@ant-design/icons'; -import { Tooltip } from '@components'; import React from 'react'; -import { Link } from 'react-router-dom'; import styled from 'styled-components'; -import { useEntityData } from '@app/entity/shared/EntityContext'; -import { REDESIGN_COLORS } from '@app/entityV2/shared/constants'; +import { AssertionName } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionName'; import { AssertionListItemActions } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/actions/AssertionListItemActions'; -import { AssertionResultDot } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/shared/AssertionResultDot'; -import { AssertionResultPopover } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/shared/result/AssertionResultPopover'; -import { AssertionDescription } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/AssertionDescription'; -import { ResultStatusType } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/shared/resultMessageUtils'; -import { isAssertionPartOfContract } from '@app/entityV2/shared/tabs/Dataset/Validations/contract/utils'; -import { useEntityRegistry } from '@app/useEntityRegistry'; -import { Assertion, AssertionRunEvent, DataContract, EntityType } from '@types'; +import { Assertion, AssertionRunEvent, DataContract } from '@types'; const DetailsContainer = styled.div` display: flex; @@ -26,24 +16,12 @@ const DetailsContainer = styled.div` font-size: 14px; `; -const Result = styled.div` - margin: 0px 40px 0px 48px; - display: flex; - align-items: center; -`; - const ActionButtonContainer = styled.div<{ removeRightPadding?: boolean }>` display: flex; align-items: center; margin-left: ${(props) => (props.removeRightPadding ? 'auto' : undefined)}; `; -const DataContractLogo = styled(AuditOutlined)` - margin-left: 8px; - font-size: 16px; - color: ${REDESIGN_COLORS.BLUE}; -`; - interface DetailsColumnProps { assertion: Assertion; contract?: DataContract; @@ -52,53 +30,20 @@ interface DetailsColumnProps { } export function DetailsColumn({ assertion, contract, lastEvaluation, onViewAssertionDetails }: DetailsColumnProps) { - const entityRegistry = useEntityRegistry(); - const entityData = useEntityData(); - if (!assertion.info) { return <>No details found; } - const disabled = false; - const isPartOfContract = contract && isAssertionPartOfContract(assertion, contract); + return ( - - - - - - - {(isPartOfContract && entityData?.urn && ( - - Part of Data Contract{' '} - - View - - - } - > - - - - - )) || - undefined} + /> ); } diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylValidationsTab.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylValidationsTab.tsx index 15e3284c96..4965be707b 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylValidationsTab.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AcrylValidationsTab.tsx @@ -38,13 +38,6 @@ const TabToolbar = styled.div` flex: 0 0 auto; `; -const TabContentWrapper = styled.div` - @media screen and (max-height: 800px) { - display: contents; - overflow: auto; - } -`; - enum TabPaths { ASSERTIONS = 'List', DATA_CONTRACT = 'Data Contract', @@ -148,9 +141,7 @@ export const AcrylValidationsTab = () => { ))} - - {tabs.filter((tab) => tab.path === selectedTab).map((tab) => tab.content)} - + {tabs.filter((tab) => tab.path === selectedTab).map((tab) => tab.content)} ); }; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionList.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionList.tsx index 4bb0691a1e..7afb6ef111 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionList.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionList.tsx @@ -1,5 +1,6 @@ import { Empty } from 'antd'; import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; import { useEntityData } from '@app/entity/shared/EntityContext'; import { combineEntityDataWithSiblings } from '@app/entity/shared/siblingUtils'; @@ -19,6 +20,15 @@ import { useGetDatasetContractQuery } from '@src/graphql/contract.generated'; import { useGetDatasetAssertionsWithRunEventsQuery } from '@src/graphql/dataset.generated'; import { Assertion, DataContract } from '@src/types.generated'; +const AssertionListContainer = styled.div` + display: flex; + height: 100%; + flex-direction: column; + margin: 0px 20px; + flex: 1; + overflow: hidden; +`; + /** * Component used for rendering the Assertions Sub Tab on the Validations Tab */ @@ -81,7 +91,6 @@ export const AcrylAssertionList = () => { { refetch(); contractRefetch(); @@ -93,7 +102,7 @@ export const AcrylAssertionList = () => { }; return ( - <> + {assertionMonitorData?.length > 0 && ( { /> )} {renderListTable()} - + ); }; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListFilters.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListFilters.tsx index dc5ff41a8c..33861ceb5d 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListFilters.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListFilters.tsx @@ -5,12 +5,10 @@ import { AcrylAssertionRecommendedFilters } from '@app/entityV2/shared/tabs/Data import { ASSERTION_DEFAULT_FILTERS, ASSERTION_FILTER_TYPES, - ASSERTION_GROUP_BY_FILTER_OPTIONS, } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/constant'; import { useSetFilterFromURLParams } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/hooks'; import { AssertionListFilter, AssertionTable } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/types'; import { FilterSelect } from '@src/app/entityV2/shared/FilterSelect'; -import { GroupBySelect } from '@src/app/entityV2/shared/GroupBySelect'; import { InlineListSearch } from '@src/app/entityV2/shared/components/search/InlineListSearch'; interface FilterItem { @@ -31,9 +29,6 @@ interface AcrylAssertionListFiltersProps { const SearchFilterContainer = styled.div` display: flex; - padding: 0px 10px; - margin-bottom: 8px; - margin-top: 8px; gap: 12px; justify-content: space-between; `; @@ -70,10 +65,6 @@ export const AcrylAssertionListFilters: React.FC }); }; - const handleAssertionTypeChange = (value: string) => { - handleFilterChange({ ...selectedFilters, groupBy: value }); - }; - const handleFilterOptionChange = (updatedFilters: FilterItem[]) => { /** Set Recommended Filters when there is value in type,status or source if not then set it as empty to clear the filter */ const selectedRecommendedFilters = updatedFilters.reduce>( @@ -140,15 +131,6 @@ export const AcrylAssertionListFilters: React.FC initialSelectedOptions={initialSelectedOptions} /> - {/* ************Render Group By Component ************************* */} -
- -
diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListTable.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListTable.tsx index f827905a73..a2711e966c 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListTable.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListTable.tsx @@ -1,43 +1,39 @@ -import React, { useEffect, useState } from 'react'; +import ResizeObserver from 'rc-resize-observer'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import styled from 'styled-components'; -import { StyledTableContainer } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/StyledComponents'; +import { StyledTable } from '@app/entityV2/shared/tabs/Dataset/Validations/AcrylAssertionsTable'; import { useAssertionsTableColumns } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/hooks'; -import { AssertionListFilter, AssertionTable } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/types'; +import { + AssertionListTableRow, + AssertionTable, +} from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/types'; import { getEntityUrnForAssertion, getSiblingWithUrn } from '@app/entityV2/shared/tabs/Dataset/Validations/acrylUtils'; import { useOpenAssertionDetailModal } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/builder/hooks'; import { AssertionProfileDrawer } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/AssertionProfileDrawer'; -import { Table } from '@src/alchemy-components'; -import { SortingState } from '@src/alchemy-components/components/Table/types'; import { useEntityData } from '@src/app/entity/shared/EntityContext'; -import { useGetExpandedTableGroupsFromEntityUrnInUrl } from '@src/app/entityV2/shared/hooks'; import { DataContract } from '@src/types.generated'; type Props = { assertionData: AssertionTable; - filter: AssertionListFilter; refetch: () => void; contract: DataContract; }; -export const AcrylAssertionListTable = ({ assertionData, filter, refetch, contract }: Props) => { +const HEADER_AND_PAGINATION_HEIGHT_PX = 130; + +const TableContainer = styled.div` + overflow: hidden; + height: 100%; + max-height: 100%; +`; + +export const AcrylAssertionListTable = ({ assertionData, refetch, contract }: Props) => { const { entityData } = useEntityData(); - const { groupBy } = filter; - - const [sortedOptions, setSortedOptions] = useState<{ sortColumn: string; sortOrder: SortingState }>({ - sortColumn: '', - sortOrder: SortingState.ORIGINAL, - }); - - const { expandedGroupIds, setExpandedGroupIds } = useGetExpandedTableGroupsFromEntityUrnInUrl( - assertionData?.groupBy ? assertionData?.groupBy[groupBy] : [], - { isGroupBy: !!groupBy }, - 'assertion_urn', - (group) => group.assertions, - ); + const [tableHeight, setTableHeight] = useState(0); // get columns data from the custom hooks const assertionsTableCols = useAssertionsTableColumns({ - groupBy, contract, refetch, }); @@ -57,15 +53,6 @@ export const AcrylAssertionListTable = ({ assertionData, filter, refetch, contra useOpenAssertionDetailModal(setFocusAssertionUrn); - const onAssertionExpand = (record) => { - const key = record.name; - setExpandedGroupIds((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key])); - }; - - const getGroupData = () => { - return (assertionData?.groupBy && assertionData?.groupBy[groupBy]) || []; - }; - const rowClassName = (record): string => { if (record.groupName) { return 'group-header'; @@ -76,72 +63,47 @@ export const AcrylAssertionListTable = ({ assertionData, filter, refetch, contra return 'acryl-assertions-table-row'; }; - const onRowClick = (record) => { - setFocusAssertionUrn(record.urn); - }; + const memoizedData = useMemo( + () => assertionData.assertions.map((assertion) => ({ ...assertion, key: assertion.urn })), + [assertionData.assertions], + ); - const getSortedAssertions = (record) => { - const { sortOrder, sortColumn } = sortedOptions; - if (sortOrder === SortingState.ORIGINAL) { - return record.assertions; - } - - const sortFunctions = { - lastEvaluation: { - [SortingState.DESCENDING]: (a, b) => a.lastEvaluationTimeMs - b.lastEvaluationTimeMs, - [SortingState.ASCENDING]: (a, b) => b.lastEvaluationTimeMs - a.lastEvaluationTimeMs, - }, - name: { - [SortingState.ASCENDING]: (a, b) => a.description.localeCompare(b.description), - [SortingState.DESCENDING]: (a, b) => b.description.localeCompare(a.description), - }, - }; - - const sortFunction = sortFunctions[sortColumn]?.[sortOrder]; - return sortFunction ? [...record.assertions].sort(sortFunction) : record.assertions; - }; + const handleRowClick = useCallback( + (record) => { + return { + onClick: () => { + setFocusAssertionUrn(record.urn); + }, + }; + }, + [setFocusAssertionUrn], + ); return ( - <> - - + setTableHeight(dimensions.height - HEADER_AND_PAGINATION_HEIGHT_PX)} + > + columns={assertionsTableCols} - data={groupBy ? getGroupData() : assertionData.assertions || []} + showSelect + dataSource={memoizedData} showHeader - isScrollable - rowClassName={rowClassName} - handleSortColumnChange={({ - sortColumn, - sortOrder, - }: { - sortColumn: string; - sortOrder: SortingState; - }) => setSortedOptions({ sortColumn, sortOrder })} - expandable={{ - expandedRowRender: (record) => { - let sortedAssertions = record.assertions; - if (sortedOptions.sortColumn && sortedOptions.sortOrder) { - sortedAssertions = getSortedAssertions(record); - } - return ( -
- ); - }, - rowExpandable: () => !!groupBy, - expandIconPosition: 'end', - expandedGroupIds, + scroll={{ + y: tableHeight, + x: 'max-content', }} - onExpand={onAssertionExpand} + pagination={{ + pageSize: 50, + position: ['bottomCenter'], + showSizeChanger: false, + }} + rowClassName={rowClassName} + bordered={false} + onRow={handleRowClick} + tableLayout="fixed" /> - + {focusAssertionUrn && focusedAssertionEntity && ( )} - + ); }; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionListTitleContainer.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionListTitleContainer.tsx index 05d1790163..c067e4116b 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionListTitleContainer.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionListTitleContainer.tsx @@ -6,7 +6,6 @@ const AssertionTitleContainer = styled.div` display: flex; justify-content: space-between; align-items: center; - margin: 20px; div { border-bottom: 0px; } diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionName.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionName.tsx index 54bf5ded2b..c738161940 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionName.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AssertionName.tsx @@ -4,16 +4,15 @@ import styled from 'styled-components'; import AcrylAssertionListStatusDot from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionListStatusDot'; import { DataContractBadge } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/DataContractBadge'; -import { AssertionListTableRow } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/types'; import { AssertionPlatformAvatar } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionPlatformAvatar'; import { AssertionResultPopover } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/shared/result/AssertionResultPopover'; import { ResultStatusType } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/shared/resultMessageUtils'; -import { useBuildAssertionDescriptionLabels } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/utils'; +import { useBuildAssertionPrimaryLabel } from '@app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/utils'; import { isAssertionPartOfContract } from '@app/entityV2/shared/tabs/Dataset/Validations/contract/utils'; import { useEntityData } from '@src/app/entity/shared/EntityContext'; import { UNKNOWN_DATA_PLATFORM } from '@src/app/entityV2/shared/constants'; import { useEntityRegistry } from '@src/app/useEntityRegistry'; -import { DataContract, EntityType } from '@src/types.generated'; +import { Assertion, AssertionRunEvent, DataContract, DataPlatform, EntityType, Maybe } from '@src/types.generated'; const StyledAssertionNameContainer = styled.div` display: flex; @@ -50,27 +49,29 @@ const StyledAssertionName = styled(Typography.Paragraph)` `; type Props = { - record: AssertionListTableRow; - groupBy: string; - contract: DataContract; + assertion: Assertion; + lastEvaluation?: AssertionRunEvent; + lastEvaluationUrl?: Maybe; + platform?: DataPlatform; + contract?: DataContract; + onClickProfileButton?: () => void; }; -export const AssertionName = ({ record, groupBy, contract }: Props) => { +export const AssertionName = ({ + assertion, + contract, + lastEvaluation, + lastEvaluationUrl, + platform, + onClickProfileButton, +}: Props) => { const entityRegistry = useEntityRegistry(); const entityData = useEntityData(); - const { platform, assertion, lastEvaluation, lastEvaluationUrl } = record; const monitorSchedule = null; - const { primaryLabel } = useBuildAssertionDescriptionLabels(record?.assertion?.info, monitorSchedule, { + const name = useBuildAssertionPrimaryLabel(assertion.info, monitorSchedule, { showColumnTag: true, }); - let name = primaryLabel; - - // if it is group header then just display group name instead of other fields - if (groupBy && record.name) { - name = <>{record.groupName}; - return {name}; - } const disabled = false; const isPartOfContract = contract && isAssertionPartOfContract(assertion, contract); @@ -83,6 +84,7 @@ export const AssertionName = ({ record, groupBy, contract }: Props) => { run={lastEvaluation} showProfileButton placement="right" + onClickProfileButton={onClickProfileButton} resultStatusType={ResultStatusType.LATEST} > diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/Tags/AcrylAssertionTagColumn.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/Tags/AcrylAssertionTagColumn.tsx index 97a157941c..892001cfae 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/Tags/AcrylAssertionTagColumn.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/Tags/AcrylAssertionTagColumn.tsx @@ -87,6 +87,7 @@ export const AcrylAssertionTagColumn: React.FC = ( {displayTags?.map((tag) => ( = ( ?.slice(1, MAX_TAGS_FOR_HOVER) .map((tag) => ( { +export const useAssertionsTableColumns = ({ contract, refetch }) => { + const renderAssertionName = useCallback( + (_, record) => ( + + ), + [contract], + ); + + const renderCategory = useCallback( + (_, record) => + !record.groupName && + record?.type && {getAssertionGroupName(record.type)}, + [], + ); + + const renderLastRun = useCallback( + (_, record) => + !record.groupName && {getTimeFromNow(record.lastEvaluationTimeMs)}, + [], + ); + + const renderTags = useCallback( + (_, record) => + !record.groupName && , + [refetch], + ); + + const renderActions = useCallback( + (_, record) => { + return ( + !record.groupName && ( + + ) + ); + }, + [contract, refetch], + ); + return useMemo(() => { - const columns = [ + const columns: ColumnsType = [ { title: 'Name', dataIndex: 'name', key: 'name', - render: (record) => , - width: '42%', + render: renderAssertionName, + width: '45%', sorter: (a, b) => { - return a - b; + return a.description.localeCompare(b.description); + }, + ellipsis: { + showTitle: false, }, }, { title: 'Category', dataIndex: 'type', key: 'type', - render: (record) => - !record.groupName && {getAssertionGroupName(record?.type)}, - width: '10%', + render: renderCategory, + width: '12%', + sorter: (a, b) => { + if (a.type && b.type) { + return getAssertionGroupName(a.type).localeCompare(getAssertionGroupName(b.type)); + } + return 0; + }, + ellipsis: { + showTitle: false, + }, }, { title: 'Last Run', dataIndex: 'lastEvaluation', key: 'lastEvaluation', - render: (record) => { - return !record.groupName && {getTimeFromNow(record.lastEvaluationTimeMs)}; - }, - width: '10%', + render: renderLastRun, + width: '15%', sorter: (sourceA, sourceB) => { + if (!sourceA.lastEvaluationTimeMs || !sourceB.lastEvaluationTimeMs) { + return 0; + } return sourceA.lastEvaluationTimeMs - sourceB.lastEvaluationTimeMs; }, + defaultSortOrder: 'descend', + ellipsis: { + showTitle: false, + }, }, { title: 'Tags', dataIndex: 'tags', key: 'tags', - width: '20%', - render: (record) => !record.groupName && , + width: '18%', + render: renderTags, + ellipsis: { + showTitle: false, + }, }, { title: '', dataIndex: '', key: 'actions', - width: '15%', - render: (record) => { - return ( - !record.groupName && ( - - ) - ); - }, + width: '10%', + render: renderActions, + fixed: 'right', }, ]; return columns; - }, [groupBy, contract, refetch]); + }, [renderAssertionName, renderCategory, renderLastRun, renderTags, renderActions]); }; export const usePinnedAssertionTableHeaderProps = () => { diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/utils.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/utils.tsx index 7cf79f93e0..a7f7a15ec0 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/utils.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/AssertionList/utils.tsx @@ -58,6 +58,7 @@ const ASSERTION_STATUS_NAME_MAP = { FAILURE: 'Failing', SUCCESS: 'Passing', ERROR: 'Error', + INIT: 'Initializing', [NO_STATUS]: 'No Status', }; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/acrylUtils.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/acrylUtils.tsx index 2e5e2c72e5..047bfd5f15 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/acrylUtils.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/acrylUtils.tsx @@ -10,7 +10,6 @@ import { FAILURE_COLOR_HEX, SUCCESS_COLOR_HEX, WARNING_COLOR_HEX } from '@compon import { GenericEntityProperties } from '@app/entity/shared/types'; import { AssertionGroup, AssertionStatusSummary } from '@app/entityV2/shared/tabs/Dataset/Validations/acrylTypes'; import { sortAssertions } from '@app/entityV2/shared/tabs/Dataset/Validations/assertionUtils'; -import { toProperTitleCase } from '@app/entityV2/shared/utils'; import { lowerFirstLetter } from '@app/shared/textUtil'; import { ASSERTION_TYPE_TO_ICON_MAP } from '@src/app/entityV2/shared/tabs/Dataset/Validations/shared/constant'; import { GetDatasetAssertionsWithRunEventsQuery } from '@src/graphql/dataset.generated'; @@ -135,7 +134,7 @@ ASSERTION_INFO.forEach((info) => { }); export const getAssertionGroupName = (type: string): string => { - return ASSERTION_TYPE_TO_INFO.has(type) ? ASSERTION_TYPE_TO_INFO.get(type).name : toProperTitleCase(type); + return ASSERTION_TYPE_TO_INFO.has(type) ? ASSERTION_TYPE_TO_INFO.get(type).name : type; }; export const getAssertionGroupTypeIcon = (type: string) => { diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/utils.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/utils.tsx index 2267d4f765..4b32087f80 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/utils.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/assertion/profile/summary/utils.tsx @@ -348,7 +348,7 @@ export const getFreshnessAssertionPlainTextDescription = ( * @param monitorSchedule * @returns {JSX.Element} */ -const useBuildPrimaryLabel = ( +export const useBuildAssertionPrimaryLabel = ( assertionInfo?: Maybe, monitorSchedule?: Maybe, options?: { showColumnTag?: boolean }, @@ -511,7 +511,7 @@ export const useBuildAssertionDescriptionLabels = ( } => { // ------- Primary label with assertion description ------ // // IMPORTANT: if you modify this, also modify {@link #getPlainTextDescriptionFromAssertion} below - const primaryLabel = useBuildPrimaryLabel(assertionInfo, monitorSchedule, options); + const primaryLabel = useBuildAssertionPrimaryLabel(assertionInfo, monitorSchedule, options); // ----------- Try displaying secondary label showing creator/updater context ------------ // const secondaryLabel = useBuildSecondaryLabel(assertionInfo); @@ -523,16 +523,45 @@ export const useBuildAssertionDescriptionLabels = ( }; /** - * Similar to {@link #useBuildPrimaryLabel}, but returns plaintext instead of jsx. + * Similar to {@link #useBuildAssertionPrimaryLabel}, but returns plaintext instead of jsx. * Primarily used for building the search index! */ -export const getPlainTextDescriptionFromAssertion = (assertionInfo?: AssertionInfo): string => { +export const getPlainTextDescriptionFromAssertion = ( + assertionInfo?: AssertionInfo, + monitorSchedule?: CronSchedule, +): string => { // if description is present don't generate dynamic description if (assertionInfo?.description) { return assertionInfo.description; } - return assertionInfo - ? getDatasetAssertionPlainTextDescription(assertionInfo.datasetAssertion as DatasetAssertionInfo) - : ''; + let primaryLabel = ''; + switch (assertionInfo?.type) { + case AssertionType.Dataset: + primaryLabel = getDatasetAssertionPlainTextDescription( + assertionInfo.datasetAssertion as DatasetAssertionInfo, + ); + break; + case AssertionType.Freshness: + primaryLabel = getFreshnessAssertionPlainTextDescription( + assertionInfo.freshnessAssertion as FreshnessAssertionInfo, + monitorSchedule as CronSchedule, + ); + break; + case AssertionType.Volume: + primaryLabel = getVolumeAssertionPlainTextDescription(assertionInfo.volumeAssertion as VolumeAssertionInfo); + break; + case AssertionType.Sql: + primaryLabel = assertionInfo.description || ''; + break; + case AssertionType.Field: + primaryLabel = getFieldAssertionPlainTextDescription(assertionInfo.fieldAssertion as FieldAssertionInfo); + break; + case AssertionType.DataSchema: + primaryLabel = getSchemaAssertionPlainTextDescription(assertionInfo.schemaAssertion as SchemaAssertionInfo); + break; + default: + break; + } + return primaryLabel; }; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilder.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilder.tsx index e2dc810bbb..d5f44004e3 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilder.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilder.tsx @@ -21,8 +21,19 @@ import { useGetDatasetAssertionsWithRunEventsQuery } from '@src/graphql/dataset. import { useUpsertDataContractMutation } from '@graphql/contract.generated'; import { Assertion, AssertionType, DataContract } from '@types'; +const BuilderContainer = styled.div` + display: flex; + flex-direction: column; + max-height: 70vh; + height: 70vh; + overflow: hidden; +`; + const AssertionsSection = styled.div` border: 0.5px solid ${ANTD_GRAY[4]}; + flex: 1; + overflow: auto; + min-height: 0; `; const HeaderText = styled.div` @@ -34,7 +45,10 @@ const HeaderText = styled.div` const ActionContainer = styled.div` display: flex; justify-content: space-between; - margin-top: 16px; + flex-shrink: 0; + padding: 16px 20px; + border-top: 1px solid ${ANTD_GRAY[4]}; + margin-top: 0; `; const CancelButton = styled(Button)` @@ -42,7 +56,7 @@ const CancelButton = styled(Button)` `; const SaveButton = styled(Button)` - margin-right: 20px; + margin-right: 0; `; type Props = { @@ -150,7 +164,7 @@ export const DataContractBuilder = ({ entityUrn, initialState, onSubmit, onCance const hasAssertions = freshnessAssertions.length || schemaAssertions.length || dataQualityAssertions.length; return ( - <> + {(hasAssertions && Select the assertions that will make up your contract.) || ( Add a few assertions on this entity to create a data contract out of them. )} @@ -195,6 +209,6 @@ export const DataContractBuilder = ({ entityUrn, initialState, onSubmit, onCance - + ); }; diff --git a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilderModal.tsx b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilderModal.tsx index 94c6e1aea7..1fae2285a8 100644 --- a/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilderModal.tsx +++ b/datahub-web-react/src/app/entityV2/shared/tabs/Dataset/Validations/contract/builder/DataContractBuilderModal.tsx @@ -11,9 +11,9 @@ const modalStyle = {}; const modalBodyStyle = { paddingRight: 0, paddingLeft: 0, - paddingBottom: 20, + paddingBottom: 0, paddingTop: 0, - maxHeight: '70vh', + height: '70vh', 'overflow-x': 'auto', };