From 608c30f9b74142e102fbb7ded877fbdf7d23f7f8 Mon Sep 17 00:00:00 2001 From: Gabe Lyons Date: Fri, 25 Jul 2025 16:51:35 -0700 Subject: [PATCH 01/10] stylistic fixes for ingestion modal --- .../components/ExecutionDetailsModal.tsx | 5 +- .../executions/components/LogsTab.tsx | 40 +++- .../executions/components/RecipeTab.tsx | 41 +++- .../executions/components/SummaryTab.tsx | 66 +++--- .../components/reporting/StructuredReport.tsx | 7 +- .../app/ingestV2/source/IngestedAssets.tsx | 195 +++++++++--------- .../src/app/ingestV2/source/utils.ts | 32 +-- 7 files changed, 221 insertions(+), 165 deletions(-) diff --git a/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx b/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx index 8fd90f1490eb2c..1c3255a36ad614 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx @@ -23,6 +23,7 @@ import { ExecutionRequestResult } from '@types'; const modalBodyStyle = { padding: 0, + height: '80vh', }; type Props = { @@ -82,7 +83,7 @@ export const ExecutionDetailsModal = ({ urn, open, onClose }: Props) => { name: TabType.Logs, }, { - component: , + component: , key: TabType.Recipe, name: TabType.Recipe, }, @@ -96,7 +97,7 @@ export const ExecutionDetailsModal = ({ urn, open, onClose }: Props) => { titlePill={titlePill} open={open} onCancel={onClose} - buttons={[{ text: 'Close', variant: 'outline', onClick: onClose }]} + buttons={[]} > {!data && loading && } {error && message.error('Failed to load execution run details :(')} diff --git a/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx index 009f42840433ad..bc1a48f20052a0 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx @@ -1,10 +1,11 @@ import { DownloadOutlined } from '@ant-design/icons'; -import { Button, Typography } from 'antd'; import React from 'react'; import styled from 'styled-components'; import { SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; import { downloadFile } from '@app/search/utils/csvUtils'; +import { Button, Text, Tooltip } from '@src/alchemy-components'; +import colors from '@src/alchemy-components/theme/foundations/colors'; import { GetIngestionExecutionRequestQuery } from '@graphql/ingestion.generated'; @@ -14,7 +15,7 @@ const SectionSubHeader = styled.div` align-items: center; `; -const SubHeaderParagraph = styled(Typography.Paragraph)` +const SubHeaderParagraph = styled(Text)` margin-bottom: 0px; `; @@ -24,6 +25,20 @@ const LogsSection = styled.div` padding-right: 30px; `; +const DetailsContainer = styled.div` + margin-top: 12px; + + pre { + background-color: ${colors.gray[1500]}; + border: 1px solid ${colors.gray[1400]}; + border-radius: 8px; + padding: 16px; + margin: 0; + color: ${colors.gray[1700]}; + overflow-y: auto; + } +`; + export const LogsTab = ({ urn, data }: { urn: string; data: GetIngestionExecutionRequestQuery | undefined }) => { const output = data?.executionRequest?.result?.report || 'No output found.'; @@ -35,15 +50,20 @@ export const LogsTab = ({ urn, data }: { urn: string; data: GetIngestionExecutio Logs - View logs that were collected during the sync. - + + View logs that were collected during the sync. + + + + - -
{output}
-
+ + +
{output}
+
+
); }; diff --git a/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx index e5b44f04105c83..bd63bf1e620b1c 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx @@ -1,9 +1,11 @@ -import { Typography } from 'antd'; +import { DownloadOutlined } from '@ant-design/icons'; import React from 'react'; import styled from 'styled-components'; import YAML from 'yamljs'; import { SectionBase, SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; +import { downloadFile } from '@app/search/utils/csvUtils'; +import { Button, Text, Tooltip } from '@src/alchemy-components'; import colors from '@src/alchemy-components/theme/foundations/colors'; import { GetIngestionExecutionRequestQuery } from '@graphql/ingestion.generated'; @@ -14,7 +16,7 @@ const SectionSubHeader = styled.div` align-items: center; `; -const SubHeaderParagraph = styled(Typography.Paragraph)` +const SubHeaderParagraph = styled(Text)` margin-bottom: 0px; `; @@ -22,7 +24,21 @@ const RecipeSection = styled(SectionBase)` border-top: 1px solid ${colors.gray[1400]}; `; -export const RecipeTab = ({ data }: { data: GetIngestionExecutionRequestQuery | undefined }) => { +const DetailsContainer = styled.div` + margin-top: 12px; + + pre { + background-color: ${colors.gray[1500]}; + border: 1px solid ${colors.gray[1400]}; + border-radius: 8px; + padding: 16px; + margin: 0; + color: ${colors.gray[1700]}; + overflow-y: auto; + } +`; + +export const RecipeTab = ({ urn, data }: { urn: string; data: GetIngestionExecutionRequestQuery | undefined }) => { const recipeJson = data?.executionRequest?.input?.arguments?.find((arg) => arg.key === 'recipe')?.value; let recipeYaml: string; try { @@ -31,17 +47,28 @@ export const RecipeTab = ({ data }: { data: GetIngestionExecutionRequestQuery | recipeYaml = ''; } + const downloadRecipe = () => { + downloadFile(recipeYaml, `recipe-${urn}.yaml`); + }; + return ( Recipe - + The configurations used for this sync with the data source. + + + - -
{recipeYaml || 'No recipe found.'}
-
+ + +
{recipeYaml || 'No recipe found.'}
+
+
); }; diff --git a/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx index b3929a799c2597..a4a5b28afade98 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx @@ -1,5 +1,5 @@ import { DownloadOutlined } from '@ant-design/icons'; -import { Button, Typography } from 'antd'; +import { Typography } from 'antd'; import React, { useState } from 'react'; import styled from 'styled-components'; import YAML from 'yamljs'; @@ -12,6 +12,7 @@ import { getExecutionRequestSummaryText } from '@app/ingestV2/executions/utils'; import IngestedAssets from '@app/ingestV2/source/IngestedAssets'; import { getStructuredReport } from '@app/ingestV2/source/utils'; import { downloadFile } from '@app/search/utils/csvUtils'; +import { Button, Text, Tooltip } from '@src/alchemy-components'; import colors from '@src/alchemy-components/theme/foundations/colors'; import { GetIngestionExecutionRequestQuery } from '@graphql/ingestion.generated'; @@ -35,29 +36,22 @@ const ButtonGroup = styled.div` align-items: center; `; -const SubHeaderParagraph = styled(Typography.Paragraph)` +const SubHeaderParagraph = styled(Text)` margin-bottom: 0px; `; const StatusSection = styled.div` - border-bottom: 1px solid ${colors.gray[1400]}; padding: 16px; padding-left: 30px; padding-right: 30px; `; -const IngestedAssetsSection = styled.div<{ isFirstSection?: boolean }>` - border-bottom: 1px solid ${colors.gray[1400]}; - ${({ isFirstSection }) => !isFirstSection && `border-top: 1px solid ${colors.gray[1400]};`} +const IngestedAssetsSection = styled.div` padding: 16px; padding-left: 30px; padding-right: 30px; `; -const ShowMoreButton = styled(Button)` - padding: 0px; -`; - const DetailsContainer = styled.div` margin-top: 12px; @@ -68,6 +62,8 @@ const DetailsContainer = styled.div` padding: 16px; margin: 0; color: ${colors.gray[1700]}; + max-height: 300px; + overflow-y: auto; } `; @@ -84,8 +80,8 @@ export const SummaryTab = ({ data: GetIngestionExecutionRequestQuery | undefined; onTabChange: (tab: TabType) => void; }) => { - const [showExpandedLogs] = useState(false); - const [showExpandedRecipe] = useState(false); + const [showExpandedLogs] = useState(true); + const [showExpandedRecipe] = useState(true); const output = data?.executionRequest?.result?.report || 'No output found.'; @@ -123,9 +119,7 @@ export const SummaryTab = ({ {structuredReport && structuredReport ? : null} )} - + {data?.executionRequest?.id && ( )} @@ -133,48 +127,52 @@ export const SummaryTab = ({ Logs - + View logs that were collected during the sync. {areLogsExpandable && ( - onTabChange(TabType.Logs)}> - View More - + )} - + + + - -
{`${logs}${areLogsExpandable ? '...' : ''}`}
-
+ +
{logs}
+
{recipe && ( Recipe - + The configurations used for this sync with the data source. {isRecipeExpandable && ( - onTabChange(TabType.Recipe)}> + )} - + + + - -
{`${recipe}${!showExpandedRecipe && isRecipeExpandable ? '...' : ''}`}
-
+ +
{recipe}
+
)} diff --git a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx index 66f4978512da6b..7b1a26080756e6 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx @@ -41,12 +41,7 @@ export function StructuredReport({ report }: Props) { return ( {errors.length ? ( - + ) : null} {warnings.length ? ( diff --git a/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx b/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx index 65b56947ac5233..37a93223c373ec 100644 --- a/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx +++ b/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx @@ -1,11 +1,10 @@ -import { Button } from 'antd'; import React, { useMemo, useState } from 'react'; import styled from 'styled-components'; import { EmbeddedListSearchModal } from '@app/entity/shared/components/styled/search/EmbeddedListSearchModal'; import { extractEntityTypeCountsFromFacets, - getEntitiesIngestedByType, + getEntitiesIngestedByTypeOrSubtype, getIngestionContents, getOtherIngestionContents, getTotalEntitiesIngested, @@ -15,7 +14,7 @@ import { Message } from '@app/shared/Message'; import { formatNumber } from '@app/shared/formatNumber'; import { capitalizeFirstLetterOnly } from '@app/shared/textUtil'; import { useEntityRegistry } from '@app/useEntityRegistry'; -import { Heading, Pill, Text } from '@src/alchemy-components'; +import { Button, Card, Heading, Pill, Text } from '@src/alchemy-components'; import colors from '@src/alchemy-components/theme/foundations/colors'; import { ExecutionRequestResult, Maybe } from '@src/types.generated'; @@ -27,15 +26,13 @@ const FlexContainer = styled.div` gap: 16px; `; -// Base card styling -const BaseCard = styled.div` - display: flex; - padding: 12px; - background-color: white; - border: 1px solid ${colors.gray[1400]}; - border-radius: 12px; - box-shadow: 0px 4px 8px 0px rgba(33, 23, 95, 0.04); - min-height: 60px; +const SectionSmallTitle = styled.div` + padding-top: 16px; + padding-bottom: 4px; +`; + +const SubTitleContainer = styled.div` + padding-top: 8px; `; const MainContainer = styled(FlexContainer)` @@ -43,28 +40,10 @@ const MainContainer = styled(FlexContainer)` margin-top: 16px; `; -const CardContainer = styled(BaseCard)` - flex-direction: column; - justify-content: center; - align-items: flex-start; - flex: 1 0 0; - min-height: 80px; - max-height: 80px; -`; - -const TotalContainer = styled(BaseCard)` - flex-direction: row; - align-items: center; - justify-content: space-between; - flex: 1 0 0; - min-height: 80px; - max-height: 80px; -`; - -const TotalInfo = styled.div` +const TotalSection = styled.div` display: flex; flex-direction: column; - align-items: flex-start; + align-items: stretch; `; const TypesSection = styled.div` @@ -75,8 +54,17 @@ const TypesSection = styled.div` width: 100%; `; +// the contents of this div are a bunch of cards which should take up the full width of the container const IngestionBoxesContainer = styled(FlexContainer)` + flex-direction: row; + flex-wrap: wrap; width: 100%; + + /* Make cards expand to fill available space */ + & > * { + flex: 1; + min-width: 200px; /* Ensure cards don't get too narrow */ + } `; const EntityCountsContainer = styled(FlexContainer)` @@ -85,6 +73,12 @@ const EntityCountsContainer = styled(FlexContainer)` align-items: stretch; justify-content: flex-start; flex-wrap: wrap; + + /* Make cards expand to fill available space */ + & > * { + flex: 1; + min-width: 130px; /* Ensure cards don't get too narrow */ + } `; const TypesHeaderContainer = styled.div` @@ -95,13 +89,7 @@ const TypesHeaderContainer = styled.div` position: relative; `; -const TypesHeader = styled(Text)` - position: absolute; - top: 0; - left: calc(50% + 33px); - margin-bottom: 0; - z-index: 1; -`; +const TypesHeader = styled(Text)``; const VerticalDivider = styled.div` width: 2px; @@ -150,22 +138,27 @@ type RenderIngestionContentsProps = { const IngestionContents: React.FC = ({ items, getKey, getLabel }) => ( {items.map((item) => ( - - - - {formatNumber(item.count)} - - - - - {getLabel(item)} - - + + + {formatNumber(item.count)} + + + + } + subTitle={ + + {getLabel(item)} + + } + key={getKey(item)} + /> ))} ); @@ -178,7 +171,7 @@ export default function IngestedAssets({ id, executionResult }: Props) { // Try getting the counts via the ingestion report. const totalEntitiesIngested = executionResult && getTotalEntitiesIngested(executionResult); - const entitiesIngestedByTypeFromReport = executionResult && getEntitiesIngestedByType(executionResult); + const entitiesIngestedByTypeFromReport = executionResult && getEntitiesIngestedByTypeOrSubtype(executionResult); // Fallback to the search across entities. // First thing to do is to search for all assets with the id as the run id! @@ -202,6 +195,7 @@ export default function IngestedAssets({ id, executionResult }: Props) { }); // Parse filter values to get results. + console.log('entitiesIngestedByTypeFromReport', entitiesIngestedByTypeFromReport); const facets = data?.searchAcrossEntities?.facets; // Extract facets to construct the per-entity type breakdown stats @@ -216,6 +210,11 @@ export default function IngestedAssets({ id, executionResult }: Props) { entitiesIngestedByTypeFromReport ?? (entityTypeFacets ? extractEntityTypeCountsFromFacets(entityRegistry, entityTypeFacets, subTypeFacets) : []); + console.log('entityTypeFacets', entityTypeFacets); + console.log('subTypeFacets', subTypeFacets); + console.log('countsByEntityType', countsByEntityType); + console.log('facets', facets); + // The total number of assets ingested const total = totalEntitiesIngested ?? data?.searchAcrossEntities?.total ?? 0; @@ -245,6 +244,9 @@ export default function IngestedAssets({ id, executionResult }: Props) { Assets + + Types and counts for this ingestion run. + {loading && ( Loading... @@ -253,40 +255,39 @@ export default function IngestedAssets({ id, executionResult }: Props) { {!loading && total === 0 && No assets were ingested.} {!loading && total > 0 && ( <> - - - Types and counts for this ingestion run. - - - Types - - - - - - {formatNumber(total)} - - - Total Assets Ingested - - - - + + setShowAssetSearch(true)} + > + View All + + } + subTitle={ + + Total Assets Ingested + + } + /> + {countsByEntityType.map((entityCount) => ( - - - {formatNumber(entityCount.count)} - - - {capitalizeFirstLetterOnly(entityCount.displayName)} - - + + {capitalizeFirstLetterOnly(entityCount.displayName)} + + } + key={entityCount.displayName} + > ))} @@ -296,12 +297,16 @@ export default function IngestedAssets({ id, executionResult }: Props) { Coverage - - Additional metadata collected during this ingestion run. - - - Lineage - + + + Additional metadata collected during this ingestion run. + + + + + Lineage + + item.title || ''} @@ -309,9 +314,11 @@ export default function IngestedAssets({ id, executionResult }: Props) { /> {otherIngestionContents && ( <> - - Statistics - + + + Statistics + + item.type || ''} diff --git a/datahub-web-react/src/app/ingestV2/source/utils.ts b/datahub-web-react/src/app/ingestV2/source/utils.ts index 9ad21f0dad3e09..b9562c646b0a9d 100644 --- a/datahub-web-react/src/app/ingestV2/source/utils.ts +++ b/datahub-web-react/src/app/ingestV2/source/utils.ts @@ -235,7 +235,7 @@ export const getStructuredReport = (result: Partial): St * @param result - The result of the execution request. * @returns {EntityTypeCount[] | null} */ -export const getEntitiesIngestedByType = (result: Partial): EntityTypeCount[] | null => { +export const getEntitiesIngestedByTypeOrSubtype = (result: Partial): EntityTypeCount[] | null => { const structuredReportObject = extractStructuredReportPOJO(result); if (!structuredReportObject) { return null; @@ -262,17 +262,19 @@ export const getEntitiesIngestedByType = (result: Partial { + Object.entries(entities).forEach(([entityName, aspects_by_subtypes]) => { // Use the status aspect count instead of max count - const statusCount = (aspects as any)?.status; - if (statusCount !== undefined) { - entitiesIngestedByType[entityName] = statusCount; - } else { - // Get the max count of all the sub-aspects for this entity type if status is not present. - entitiesIngestedByType[entityName] = Math.max(...(Object.values(aspects as object) as number[])); - } + Object.entries(aspects_by_subtypes as any)?.forEach(([subtype, aspects]) => { + const statusCount = (aspects as any)?.status; + if (statusCount !== undefined) { + entitiesIngestedByType[subtype !== 'unknown' ? subtype : entityName] = statusCount; + } else { + // Get the max count of all the sub-aspects for this entity type if status is not present. + entitiesIngestedByType[subtype !== 'unknown' ? subtype : entityName] = Math.max(...(Object.values(aspects as object) as number[])); + } + }); }); if (Object.keys(entitiesIngestedByType).length === 0) { @@ -295,7 +297,7 @@ export const getEntitiesIngestedByType = (result: Partial) => { - const entityTypeCounts = getEntitiesIngestedByType(result); + const entityTypeCounts = getEntitiesIngestedByTypeOrSubtype(result); if (!entityTypeCounts) { return null; } @@ -401,9 +403,15 @@ export const getIngestionContents = (executionResult: Partial Date: Fri, 25 Jul 2025 17:11:42 -0700 Subject: [PATCH 02/10] fixing lint issues --- .../src/app/ingestV2/source/IngestedAssets.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx b/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx index 37a93223c373ec..46b2cefe861d41 100644 --- a/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx +++ b/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx @@ -81,16 +81,6 @@ const EntityCountsContainer = styled(FlexContainer)` } `; -const TypesHeaderContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - margin-top: 16px; - position: relative; -`; - -const TypesHeader = styled(Text)``; - const VerticalDivider = styled.div` width: 2px; background-color: ${colors.gray[1400]}; @@ -287,7 +277,7 @@ export default function IngestedAssets({ id, executionResult }: Props) { } key={entityCount.displayName} - > + /> ))} From 12c49f4bf295af6153d64208c94460bd2b74bebb Mon Sep 17 00:00:00 2001 From: Gabe Lyons Date: Sun, 27 Jul 2025 12:23:35 -0700 Subject: [PATCH 03/10] additional UX fixes for errors/warnings section --- .../components/ExecutionDetailsModal.tsx | 58 +-- .../executions/components/SummaryTab.tsx | 6 +- .../components/reporting/StructuredReport.tsx | 220 ++++++++++- .../reporting/StructuredReportItem.tsx | 108 +++--- .../reporting/StructuredReportItemList.tsx | 44 +-- .../__tests__/StructuredReport.test.tsx | 345 ++++++++++++++++++ .../src/app/ingestV2/source/utils.ts | 10 +- 7 files changed, 657 insertions(+), 134 deletions(-) create mode 100644 datahub-web-react/src/app/ingestV2/executions/components/reporting/__tests__/StructuredReport.test.tsx diff --git a/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx b/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx index 1c3255a36ad614..14a85f2fa96fdb 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/ExecutionDetailsModal.tsx @@ -1,7 +1,7 @@ import { LoadingOutlined } from '@ant-design/icons'; import { Icon, Modal, Pill } from '@components'; import { message } from 'antd'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Tab, Tabs } from '@components/components/Tabs/Tabs'; @@ -59,35 +59,37 @@ export const ExecutionDetailsModal = ({ urn, open, onClose }: Props) => { const interval = setInterval(() => { if (status === EXECUTION_REQUEST_STATUS_RUNNING) refetch(); }, 2000); - return () => clearInterval(interval); - }); + }, [status, refetch]); - const tabs: Tab[] = [ - { - component: ( - setSelectedTab(tab)} - /> - ), - key: TabType.Summary, - name: TabType.Summary, - }, - { - component: , - key: TabType.Logs, - name: TabType.Logs, - }, - { - component: , - key: TabType.Recipe, - name: TabType.Recipe, - }, - ]; + const tabs: Tab[] = useMemo( + () => [ + { + component: ( + setSelectedTab(tab)} + /> + ), + key: TabType.Summary, + name: TabType.Summary, + }, + { + component: , + key: TabType.Logs, + name: TabType.Logs, + }, + { + component: , + key: TabType.Recipe, + name: TabType.Recipe, + }, + ], + [data, urn, result, status], + ); return ( {(resultSummaryText || (structuredReport && hasSomethingToShow(structuredReport))) && ( - {resultSummaryText} - {structuredReport && structuredReport ? : null} + {!structuredReport && resultSummaryText && ( + {resultSummaryText} + )} + {structuredReport && } )} diff --git a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx index 7b1a26080756e6..a4912870416978 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReport.tsx @@ -1,23 +1,59 @@ -import { CloseCircleOutlined, ExclamationCircleOutlined, InfoCircleOutlined } from '@ant-design/icons'; -import React from 'react'; +import { Card, Icon, colors } from '@components'; +import React, { useState } from 'react'; import styled from 'styled-components'; -import { REDESIGN_COLORS } from '@app/entity/shared/constants'; import { StructuredReportItemList } from '@app/ingestV2/executions/components/reporting/StructuredReportItemList'; import { StructuredReportItemLevel, + StructuredReportLogEntry, StructuredReport as StructuredReportType, } from '@app/ingestV2/executions/components/reporting/types'; +import { ShowMoreSection } from '@app/shared/ShowMoreSection'; -const Container = styled.div` +const Container = styled(Card)` display: flex; flex-direction: column; gap: 12px; `; -const ERROR_COLOR = '#F5222D'; -const WARNING_COLOR = '#FA8C16'; -const INFO_COLOR = REDESIGN_COLORS.BLUE; +const TitleContainer = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const StyledPill = styled.div` + display: flex; + height: 18px; + padding: 0 6px; + justify-content: center; + align-items: center; + gap: 4px; + border-radius: 200px; + background: ${colors.gray[1500]}; + font-size: 12px; + font-weight: 500; + color: ${colors.gray[1700]}; +`; + +const ChevronButton = styled.div` + display: flex; + align-items: center; + cursor: pointer; + gap: 4px; +`; + +const ChevronIcon = styled(Icon)` + color: ${colors.gray[1700]}; + font-size: 12px; +`; + +const ERROR_COLOR = colors.red[0]; +const ERROR_TEXT_COLOR = colors.red[1000]; +const WARNING_COLOR = colors.yellow[0]; +const WARNING_TEXT_COLOR = colors.yellow[1000]; +const INFO_COLOR = colors.gray[1500]; +const INFO_TEXT_COLOR = colors.gray[1700]; interface Props { report: StructuredReportType; @@ -30,7 +66,80 @@ export function hasSomethingToShow(report: StructuredReportType): boolean { return warnings.length > 0 || errors.length > 0 || infos.length > 0; } +export function generateReportTitle(hasErrors: boolean, hasWarnings: boolean, hasInfos: boolean): string { + if (hasErrors && hasWarnings && hasInfos) { + return 'Errors & Warnings'; + } + if (hasErrors && hasWarnings) { + return 'Errors & Warnings'; + } + if (hasErrors && hasInfos) { + return 'Errors & Infos'; + } + if (hasWarnings && hasInfos) { + return 'Warnings & Infos'; + } + if (hasErrors) { + return 'Errors'; + } + if (hasWarnings) { + return 'Warnings'; + } + if (hasInfos) { + return 'Infos'; + } + return ''; +} + +export function generateReportSubtitle(hasErrors: boolean, hasWarnings: boolean, hasInfos: boolean): string { + const parts: string[] = []; + if (hasErrors) parts.push('errors'); + if (hasWarnings) parts.push('warnings'); + if (hasInfos) parts.push('information'); + + let subtitle = 'Ingestion ran with '; + if (parts.length === 1) { + subtitle += parts[0]; + } else if (parts.length === 2) { + subtitle += parts.join(' and '); + } else { + subtitle += `${parts.slice(0, -1).join(', ')}, and ${parts[parts.length - 1]}`; + } + subtitle += '.'; + return subtitle; +} + +export function distributeVisibleItems( + errors: StructuredReportLogEntry[], + warnings: StructuredReportLogEntry[], + infos: StructuredReportLogEntry[], + visibleCount: number, +) { + let remainingCount = visibleCount; + + // Priority 1: Errors (show as many as possible up to total errors or remaining count) + const visibleErrors = Math.min(errors.length, remainingCount); + remainingCount -= visibleErrors; + + // Priority 2: Warnings (show as many as possible up to total warnings or remaining count) + const visibleWarnings = Math.min(warnings.length, remainingCount); + remainingCount -= visibleWarnings; + + // Priority 3: Infos (show remaining count) + const visibleInfos = Math.min(infos.length, remainingCount); + + return { + visibleErrors: errors.slice(0, visibleErrors), + visibleWarnings: warnings.slice(0, visibleWarnings), + visibleInfos: infos.slice(0, visibleInfos), + totalVisible: visibleErrors + visibleWarnings + visibleInfos, + }; +} + export function StructuredReport({ report }: Props) { + const [visibleCount, setVisibleCount] = useState(3); + const [isExpanded, setIsExpanded] = useState(false); + if (!report.items.length) { return null; } @@ -38,17 +147,94 @@ export function StructuredReport({ report }: Props) { const warnings = report.items.filter((item) => item.level === StructuredReportItemLevel.WARN); const errors = report.items.filter((item) => item.level === StructuredReportItemLevel.ERROR); const infos = report.items.filter((item) => item.level === StructuredReportItemLevel.INFO); + + const hasErrors = errors.length > 0; + const hasWarnings = warnings.length > 0; + const hasInfos = infos.length > 0; + + const title = generateReportTitle(hasErrors, hasWarnings, hasInfos); + const subtitle = generateReportSubtitle(hasErrors, hasWarnings, hasInfos); + const totalItems = errors.length + warnings.length + infos.length; + + const titleWithPill = ( + + {title} + {totalItems} + + ); + + // Determine what items to show based on expand state or visible count + const itemsToShow = isExpanded ? totalItems : visibleCount; + + // Distribute items based on priority and items to show + const { visibleErrors, visibleWarnings, visibleInfos, totalVisible } = distributeVisibleItems( + errors, + warnings, + infos, + itemsToShow, + ); + + const toggleExpanded = () => { + setIsExpanded(!isExpanded); + if (!isExpanded) { + // When expanding, set visible count to total so "Show more" disappears + setVisibleCount(totalItems); + } else { + // When collapsing, reset to initial page size + setVisibleCount(3); + } + }; + + // Auto-switch chevron to expanded when "Show more" reaches full expansion + const handleSetVisibleCount = (newCount: number) => { + setVisibleCount(newCount); + if (newCount >= totalItems && !isExpanded) { + setIsExpanded(true); + } + }; + + const chevronButton = ( + + + + ); + return ( - - {errors.length ? ( - - ) : null} - {warnings.length ? ( - - ) : null} - {infos.length ? ( - + <> + + {visibleErrors.length ? ( + + ) : null} + {visibleWarnings.length ? ( + + ) : null} + {visibleInfos.length ? ( + + ) : null} + + {totalItems > totalVisible && !isExpanded ? ( + ) : null} - + ); } diff --git a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItem.tsx b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItem.tsx index afde1cee6ecd9d..6208c4f473864a 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItem.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItem.tsx @@ -1,83 +1,81 @@ -import { Collapse } from 'antd'; -import React from 'react'; +import { Card, Icon, Text, colors } from '@components'; +import React, { useState } from 'react'; import styled from 'styled-components'; -import { ANTD_GRAY } from '@app/entity/shared/constants'; import { StructuredReportItemContext } from '@app/ingestV2/executions/components/reporting/StructuredReportItemContext'; import { StructuredReportLogEntry } from '@app/ingestV2/executions/components/reporting/types'; -import { applyOpacity } from '@app/shared/styleUtils'; -const StyledCollapse = styled(Collapse)<{ color: string }>` - background-color: ${(props) => applyOpacity(props.color, 8)}; - border: 1px solid ${(props) => applyOpacity(props.color, 20)}; - display: flex; - - && { - .ant-collapse-header { - display: flex; - align-items: center; - overflow: auto; - } - - .ant-collapse-item { - border: none; - width: 100%; - } - } -`; - -const Item = styled.div` - display: flex; - align-items: center; - justify-content: start; - gap: 4px; +const StyledCard = styled(Card)` + padding: 8px; + width: 100%; `; const Content = styled.div` border-radius: 8px; + margin-top: 8px; + background-color: white; + padding: 8px; `; -const Text = styled.div` +const HeaderContainer = styled.div` display: flex; - flex-direction: column; -`; - -const Type = styled.div` - font-weight: bold; - font-size: 14px; + align-items: center; + cursor: pointer; + gap: 8px; `; -const Message = styled.div` - color: ${ANTD_GRAY[8]}; +const ChevronIcon = styled(Icon)` + color: ${colors.gray[400]}; + font-size: 12px; `; interface Props { item: StructuredReportLogEntry; color: string; - icon?: React.ComponentType; + textColor?: string; + icon?: string; defaultActiveKey?: string; } -export function StructuredReportItem({ item, color, icon, defaultActiveKey }: Props) { - const Icon = icon; +export function StructuredReportItem({ item, color, textColor, icon, defaultActiveKey }: Props) { + const [isExpanded, setIsExpanded] = useState(defaultActiveKey === '0'); + + const toggleExpanded = () => { + setIsExpanded(!isExpanded); + }; + return ( - - - {Icon ? : null} - - {item.title} - {item.message} - - - } - key="0" - > + + {icon && } + + + } + title={ + + {item.title} + + } + subTitle={ + + {item.message} + + } + width="100%" + isCardClickable={false} + > + {isExpanded && ( - - + )} + ); } diff --git a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItemList.tsx b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItemList.tsx index be97b36f00ac2a..894c72568c7b21 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItemList.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/reporting/StructuredReportItemList.tsx @@ -1,9 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { StructuredReportItem } from '@app/ingestV2/executions/components/reporting/StructuredReportItem'; import { StructuredReportLogEntry } from '@app/ingestV2/executions/components/reporting/types'; -import { ShowMoreSection } from '@app/shared/ShowMoreSection'; const ItemList = styled.div` display: flex; @@ -14,37 +13,24 @@ const ItemList = styled.div` interface Props { items: StructuredReportLogEntry[]; color: string; - icon?: React.ComponentType; - pageSize?: number; + textColor?: string; + icon?: string; defaultActiveKey?: string; } -export function StructuredReportItemList({ items, color, icon, pageSize = 3, defaultActiveKey }: Props) { - const [visibleCount, setVisibleCount] = useState(pageSize); - const visibleItems = items.slice(0, visibleCount); - const totalCount = items.length; - +export function StructuredReportItemList({ items, color, textColor, icon, defaultActiveKey }: Props) { return ( - <> - - {visibleItems.map((item) => ( - - ))} - - {totalCount > visibleCount ? ( - + {items.map((item) => ( + - ) : null} - + ))} + ); } diff --git a/datahub-web-react/src/app/ingestV2/executions/components/reporting/__tests__/StructuredReport.test.tsx b/datahub-web-react/src/app/ingestV2/executions/components/reporting/__tests__/StructuredReport.test.tsx new file mode 100644 index 00000000000000..b475c77b5c08c7 --- /dev/null +++ b/datahub-web-react/src/app/ingestV2/executions/components/reporting/__tests__/StructuredReport.test.tsx @@ -0,0 +1,345 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { vi } from 'vitest'; + +import { + StructuredReport, + distributeVisibleItems, + generateReportSubtitle, + generateReportTitle, + hasSomethingToShow, +} from '@app/ingestV2/executions/components/reporting/StructuredReport'; +import { + StructuredReportItemLevel, + StructuredReportLogEntry, +} from '@app/ingestV2/executions/components/reporting/types'; + +// Mock the ShowMoreSection component +vi.mock('@app/shared/ShowMoreSection', () => ({ + ShowMoreSection: ({ totalCount, visibleCount, setVisibleCount }: any) => ( + + ), +})); + +// Mock the StructuredReportItemList component +vi.mock('../StructuredReportItemList', () => ({ + StructuredReportItemList: ({ items, color, textColor, icon }: any) => ( +
+ {items.map((item: any, _index: number) => ( +
+ {item.title}: {item.message} +
+ ))} +
+ ), +})); + +// Mock the components module +vi.mock('@components', () => ({ + Card: ({ title, subTitle, button, children }: any) => ( +
+
{title}
+
{subTitle}
+ {button &&
{button}
} +
{children}
+
+ ), + colors: { + red: { 0: '#FBF3EF', 1000: '#C4360B' }, + yellow: { 0: '#FFFAEB', 1000: '#C77100' }, + gray: { 1500: '#F9FAFC', 1700: '#5F6685' }, + }, + Icon: ({ icon, ...props }: any) => ( + + {icon} + + ), +})); + +const createMockItem = ( + level: StructuredReportItemLevel, + title: string, + message: string, +): StructuredReportLogEntry => ({ + level, + title, + message, + context: [], +}); + +describe('StructuredReport Utility Functions', () => { + describe('generateReportTitle', () => { + it('should return "Errors & Warnings" when all three types are present', () => { + expect(generateReportTitle(true, true, true)).toBe('Errors & Warnings'); + }); + + it('should return "Errors & Warnings" when errors and warnings are present', () => { + expect(generateReportTitle(true, true, false)).toBe('Errors & Warnings'); + }); + + it('should return "Errors & Infos" when errors and infos are present', () => { + expect(generateReportTitle(true, false, true)).toBe('Errors & Infos'); + }); + + it('should return "Warnings & Infos" when warnings and infos are present', () => { + expect(generateReportTitle(false, true, true)).toBe('Warnings & Infos'); + }); + + it('should return single type names', () => { + expect(generateReportTitle(true, false, false)).toBe('Errors'); + expect(generateReportTitle(false, true, false)).toBe('Warnings'); + expect(generateReportTitle(false, false, true)).toBe('Infos'); + }); + + it('should return empty string when nothing is present', () => { + expect(generateReportTitle(false, false, false)).toBe(''); + }); + }); + + describe('generateReportSubtitle', () => { + it('should generate subtitle for single type', () => { + expect(generateReportSubtitle(true, false, false)).toBe('Ingestion ran with errors.'); + expect(generateReportSubtitle(false, true, false)).toBe('Ingestion ran with warnings.'); + expect(generateReportSubtitle(false, false, true)).toBe('Ingestion ran with information.'); + }); + + it('should generate subtitle for two types', () => { + expect(generateReportSubtitle(true, true, false)).toBe('Ingestion ran with errors and warnings.'); + expect(generateReportSubtitle(true, false, true)).toBe('Ingestion ran with errors and information.'); + expect(generateReportSubtitle(false, true, true)).toBe('Ingestion ran with warnings and information.'); + }); + + it('should generate subtitle for all three types', () => { + expect(generateReportSubtitle(true, true, true)).toBe( + 'Ingestion ran with errors, warnings, and information.', + ); + }); + }); + + describe('distributeVisibleItems', () => { + const errors = [ + createMockItem(StructuredReportItemLevel.ERROR, 'Error 1', 'First error'), + createMockItem(StructuredReportItemLevel.ERROR, 'Error 2', 'Second error'), + ]; + const warnings = [ + createMockItem(StructuredReportItemLevel.WARN, 'Warning 1', 'First warning'), + createMockItem(StructuredReportItemLevel.WARN, 'Warning 2', 'Second warning'), + ]; + const infos = [createMockItem(StructuredReportItemLevel.INFO, 'Info 1', 'First info')]; + + it('should prioritize errors first', () => { + const result = distributeVisibleItems(errors, warnings, infos, 2); + expect(result.visibleErrors).toHaveLength(2); + expect(result.visibleWarnings).toHaveLength(0); + expect(result.visibleInfos).toHaveLength(0); + expect(result.totalVisible).toBe(2); + }); + + it('should show warnings after errors are exhausted', () => { + const result = distributeVisibleItems(errors, warnings, infos, 4); + expect(result.visibleErrors).toHaveLength(2); + expect(result.visibleWarnings).toHaveLength(2); + expect(result.visibleInfos).toHaveLength(0); + expect(result.totalVisible).toBe(4); + }); + + it('should show infos after errors and warnings are exhausted', () => { + const result = distributeVisibleItems(errors, warnings, infos, 5); + expect(result.visibleErrors).toHaveLength(2); + expect(result.visibleWarnings).toHaveLength(2); + expect(result.visibleInfos).toHaveLength(1); + expect(result.totalVisible).toBe(5); + }); + + it('should handle visibleCount larger than total items', () => { + const result = distributeVisibleItems(errors, warnings, infos, 10); + expect(result.visibleErrors).toHaveLength(2); + expect(result.visibleWarnings).toHaveLength(2); + expect(result.visibleInfos).toHaveLength(1); + expect(result.totalVisible).toBe(5); + }); + + it('should handle empty arrays', () => { + const result = distributeVisibleItems([], [], [], 3); + expect(result.visibleErrors).toHaveLength(0); + expect(result.visibleWarnings).toHaveLength(0); + expect(result.visibleInfos).toHaveLength(0); + expect(result.totalVisible).toBe(0); + }); + }); + + describe('hasSomethingToShow', () => { + it('should return true when there are items to show', () => { + const report = { + items: [createMockItem(StructuredReportItemLevel.ERROR, 'Error', 'Test error')], + infoCount: 0, + errorCount: 1, + warnCount: 0, + }; + expect(hasSomethingToShow(report)).toBe(true); + }); + + it('should return false when there are no items', () => { + const report = { + items: [], + infoCount: 0, + errorCount: 0, + warnCount: 0, + }; + expect(hasSomethingToShow(report)).toBe(false); + }); + }); +}); + +describe('StructuredReport Component', () => { + const mockReport = { + items: [ + createMockItem(StructuredReportItemLevel.ERROR, 'Error 1', 'First error'), + createMockItem(StructuredReportItemLevel.ERROR, 'Error 2', 'Second error'), + createMockItem(StructuredReportItemLevel.WARN, 'Warning 1', 'First warning'), + createMockItem(StructuredReportItemLevel.WARN, 'Warning 2', 'Second warning'), + createMockItem(StructuredReportItemLevel.INFO, 'Info 1', 'First info'), + ], + infoCount: 1, + errorCount: 2, + warnCount: 2, + }; + + it('should render nothing when report has no items', () => { + const { container } = render( + , + ); + expect(container.firstChild).toBeNull(); + }); + + it('should render title with pill showing total count', () => { + render(); + expect(screen.getByText('Errors & Warnings')).toBeInTheDocument(); + expect(screen.getByText('5')).toBeInTheDocument(); + }); + + it('should render subtitle describing the issues', () => { + render(); + expect(screen.getByText('Ingestion ran with errors, warnings, and information.')).toBeInTheDocument(); + }); + + it('should initially show only first 3 items (errors prioritized)', () => { + render(); + + // Should show error list with 2 items + const errorList = screen.getByTestId('item-list-WarningDiamond'); + expect(errorList).toBeInTheDocument(); + expect(errorList.children).toHaveLength(2); + + // Should show warning list with 1 item (remaining from initial 3) + const warningList = screen.getByTestId('item-list-WarningCircle'); + expect(warningList).toBeInTheDocument(); + expect(warningList.children).toHaveLength(1); + + // Should not show info list initially + expect(screen.queryByTestId('item-list-Info')).not.toBeInTheDocument(); + }); + + it('should show "Show X more" button when there are more items', () => { + render(); + expect(screen.getByTestId('show-more-button')).toBeInTheDocument(); + expect(screen.getByText('Show 2 more')).toBeInTheDocument(); + }); + + it('should expand to show more items when "Show more" is clicked', async () => { + const user = userEvent.setup(); + render(); + + const showMoreButton = screen.getByTestId('show-more-button'); + await user.click(showMoreButton); + + // Should now show all items + expect(screen.getByTestId('item-list-WarningDiamond').children).toHaveLength(2); + expect(screen.getByTestId('item-list-WarningCircle').children).toHaveLength(2); + expect(screen.getByTestId('item-list-Info')).toBeInTheDocument(); + expect(screen.getByTestId('item-list-Info').children).toHaveLength(1); + + // Show more button should be gone + expect(screen.queryByTestId('show-more-button')).not.toBeInTheDocument(); + }); + + it('should use correct colors for each item type', () => { + render(); + + const errorList = screen.getByTestId('item-list-WarningDiamond'); + expect(errorList).toHaveAttribute('data-color', '#FBF3EF'); // ERROR_COLOR + expect(errorList).toHaveAttribute('data-text-color', '#C4360B'); // ERROR_TEXT_COLOR + + const warningList = screen.getByTestId('item-list-WarningCircle'); + expect(warningList).toHaveAttribute('data-color', '#FFFAEB'); // WARNING_COLOR + expect(warningList).toHaveAttribute('data-text-color', '#C77100'); // WARNING_TEXT_COLOR + }); + + describe('Chevron Expand/Collapse Functionality', () => { + it('should render chevron button with correct initial state', () => { + render(); + + // Should have chevron down (collapsed) initially + const cardButton = screen.getByTestId('card-button'); + expect(cardButton).toBeInTheDocument(); + + // Check for caret down icon (collapsed state) + const caretIcon = screen.getByTestId('icon-CaretDown'); + expect(caretIcon).toBeInTheDocument(); + }); + + it('should auto-switch chevron to expanded when "Show more" reaches full expansion', async () => { + const user = userEvent.setup(); + render(); + + // Initially chevron should be down (collapsed) + expect(screen.getByTestId('icon-CaretDown')).toBeInTheDocument(); + + // Click "Show more" to reach full expansion + const showMoreButton = screen.getByTestId('show-more-button'); + await user.click(showMoreButton); + + // Chevron should auto-switch to expanded (up) + expect(screen.getByTestId('icon-CaretUp')).toBeInTheDocument(); + + // Show more button should be gone + expect(screen.queryByTestId('show-more-button')).not.toBeInTheDocument(); + + // All items should be visible + expect(screen.getByTestId('item-list-WarningDiamond').children).toHaveLength(2); + expect(screen.getByTestId('item-list-WarningCircle').children).toHaveLength(2); + expect(screen.getByTestId('item-list-Info')).toBeInTheDocument(); + }); + + it('should maintain pill count as total regardless of expansion state', async () => { + const user = userEvent.setup(); + render(); + + // Pill should always show total (5) + expect(screen.getByText('5')).toBeInTheDocument(); + + // Expand via chevron + const cardButton = screen.getByTestId('card-button'); + await user.click(cardButton); + + // Pill should still show total + expect(screen.getByText('5')).toBeInTheDocument(); + + // Collapse again + await user.click(cardButton); + + // Pill should still show total + expect(screen.getByText('5')).toBeInTheDocument(); + }); + }); +}); diff --git a/datahub-web-react/src/app/ingestV2/source/utils.ts b/datahub-web-react/src/app/ingestV2/source/utils.ts index b9562c646b0a9d..eefd3a4c6aaf60 100644 --- a/datahub-web-react/src/app/ingestV2/source/utils.ts +++ b/datahub-web-react/src/app/ingestV2/source/utils.ts @@ -235,7 +235,9 @@ export const getStructuredReport = (result: Partial): St * @param result - The result of the execution request. * @returns {EntityTypeCount[] | null} */ -export const getEntitiesIngestedByTypeOrSubtype = (result: Partial): EntityTypeCount[] | null => { +export const getEntitiesIngestedByTypeOrSubtype = ( + result: Partial, +): EntityTypeCount[] | null => { const structuredReportObject = extractStructuredReportPOJO(result); if (!structuredReportObject) { return null; @@ -272,7 +274,9 @@ export const getEntitiesIngestedByTypeOrSubtype = (result: Partial Date: Sun, 27 Jul 2025 12:28:33 -0700 Subject: [PATCH 04/10] title text underline on hover' --- .../src/app/ingestV2/source/IngestionSourceTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datahub-web-react/src/app/ingestV2/source/IngestionSourceTable.tsx b/datahub-web-react/src/app/ingestV2/source/IngestionSourceTable.tsx index be923bcaab9cc6..ff11b82e7d1393 100644 --- a/datahub-web-react/src/app/ingestV2/source/IngestionSourceTable.tsx +++ b/datahub-web-react/src/app/ingestV2/source/IngestionSourceTable.tsx @@ -103,7 +103,7 @@ function IngestionSourceTable({ title: 'Name', key: 'name', render: (record) => { - return ; + return onEdit(record.urn)} />; }, width: '25%', sorter: true, From c3001806eba2f6d4c25af8e2842b28046b6abaca Mon Sep 17 00:00:00 2001 From: Aseem Bansal Date: Mon, 28 Jul 2025 20:04:05 +0530 Subject: [PATCH 05/10] code review comments --- .../ingestV2/executions/components/BaseTab.tsx | 16 ++++++++++++++++ .../ingestV2/executions/components/LogsTab.tsx | 17 +---------------- .../executions/components/RecipeTab.tsx | 16 +--------------- .../executions/components/SummaryTab.tsx | 18 +----------------- .../src/app/ingestV2/source/IngestedAssets.tsx | 6 ------ 5 files changed, 19 insertions(+), 54 deletions(-) diff --git a/datahub-web-react/src/app/ingestV2/executions/components/BaseTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/BaseTab.tsx index 849df66cb3b289..31bcf659a1693f 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/BaseTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/BaseTab.tsx @@ -1,6 +1,8 @@ import { Typography } from 'antd'; import styled from 'styled-components'; +import colors from '@src/alchemy-components/theme/foundations/colors'; + export const SectionBase = styled.div` padding: 16px 30px 0; `; @@ -12,3 +14,17 @@ export const SectionHeader = styled(Typography.Title)` margin-bottom: 12px; } `; + +export const DetailsContainer = styled.div` + margin-top: 12px; + + pre { + background-color: ${colors.gray[1500]}; + border: 1px solid ${colors.gray[1400]}; + border-radius: 8px; + padding: 16px; + margin: 0; + color: ${colors.gray[1700]}; + overflow-y: auto; + } +`; diff --git a/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx index bc1a48f20052a0..cd4e7982b9f7de 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/LogsTab.tsx @@ -2,10 +2,9 @@ import { DownloadOutlined } from '@ant-design/icons'; import React from 'react'; import styled from 'styled-components'; -import { SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; +import { DetailsContainer, SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; import { downloadFile } from '@app/search/utils/csvUtils'; import { Button, Text, Tooltip } from '@src/alchemy-components'; -import colors from '@src/alchemy-components/theme/foundations/colors'; import { GetIngestionExecutionRequestQuery } from '@graphql/ingestion.generated'; @@ -25,20 +24,6 @@ const LogsSection = styled.div` padding-right: 30px; `; -const DetailsContainer = styled.div` - margin-top: 12px; - - pre { - background-color: ${colors.gray[1500]}; - border: 1px solid ${colors.gray[1400]}; - border-radius: 8px; - padding: 16px; - margin: 0; - color: ${colors.gray[1700]}; - overflow-y: auto; - } -`; - export const LogsTab = ({ urn, data }: { urn: string; data: GetIngestionExecutionRequestQuery | undefined }) => { const output = data?.executionRequest?.result?.report || 'No output found.'; diff --git a/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx index bd63bf1e620b1c..2878a108e50c4d 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/RecipeTab.tsx @@ -3,7 +3,7 @@ import React from 'react'; import styled from 'styled-components'; import YAML from 'yamljs'; -import { SectionBase, SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; +import { DetailsContainer, SectionBase, SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; import { downloadFile } from '@app/search/utils/csvUtils'; import { Button, Text, Tooltip } from '@src/alchemy-components'; import colors from '@src/alchemy-components/theme/foundations/colors'; @@ -24,20 +24,6 @@ const RecipeSection = styled(SectionBase)` border-top: 1px solid ${colors.gray[1400]}; `; -const DetailsContainer = styled.div` - margin-top: 12px; - - pre { - background-color: ${colors.gray[1500]}; - border: 1px solid ${colors.gray[1400]}; - border-radius: 8px; - padding: 16px; - margin: 0; - color: ${colors.gray[1700]}; - overflow-y: auto; - } -`; - export const RecipeTab = ({ urn, data }: { urn: string; data: GetIngestionExecutionRequestQuery | undefined }) => { const recipeJson = data?.executionRequest?.input?.arguments?.find((arg) => arg.key === 'recipe')?.value; let recipeYaml: string; diff --git a/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx index 62f7239c7c5e27..4055d01a146b4f 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx @@ -4,7 +4,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import YAML from 'yamljs'; -import { SectionBase, SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; +import { DetailsContainer, SectionBase, SectionHeader } from '@app/ingestV2/executions/components/BaseTab'; import { StructuredReport, hasSomethingToShow } from '@app/ingestV2/executions/components/reporting/StructuredReport'; import { EXECUTION_REQUEST_STATUS_SUCCESS } from '@app/ingestV2/executions/constants'; import { TabType } from '@app/ingestV2/executions/types'; @@ -13,7 +13,6 @@ import IngestedAssets from '@app/ingestV2/source/IngestedAssets'; import { getStructuredReport } from '@app/ingestV2/source/utils'; import { downloadFile } from '@app/search/utils/csvUtils'; import { Button, Text, Tooltip } from '@src/alchemy-components'; -import colors from '@src/alchemy-components/theme/foundations/colors'; import { GetIngestionExecutionRequestQuery } from '@graphql/ingestion.generated'; import { ExecutionRequestResult } from '@types'; @@ -52,21 +51,6 @@ const IngestedAssetsSection = styled.div` padding-right: 30px; `; -const DetailsContainer = styled.div` - margin-top: 12px; - - pre { - background-color: ${colors.gray[1500]}; - border: 1px solid ${colors.gray[1400]}; - border-radius: 8px; - padding: 16px; - margin: 0; - color: ${colors.gray[1700]}; - max-height: 300px; - overflow-y: auto; - } -`; - export const SummaryTab = ({ urn, status, diff --git a/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx b/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx index 46b2cefe861d41..95c4542be15350 100644 --- a/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx +++ b/datahub-web-react/src/app/ingestV2/source/IngestedAssets.tsx @@ -185,7 +185,6 @@ export default function IngestedAssets({ id, executionResult }: Props) { }); // Parse filter values to get results. - console.log('entitiesIngestedByTypeFromReport', entitiesIngestedByTypeFromReport); const facets = data?.searchAcrossEntities?.facets; // Extract facets to construct the per-entity type breakdown stats @@ -200,11 +199,6 @@ export default function IngestedAssets({ id, executionResult }: Props) { entitiesIngestedByTypeFromReport ?? (entityTypeFacets ? extractEntityTypeCountsFromFacets(entityRegistry, entityTypeFacets, subTypeFacets) : []); - console.log('entityTypeFacets', entityTypeFacets); - console.log('subTypeFacets', subTypeFacets); - console.log('countsByEntityType', countsByEntityType); - console.log('facets', facets); - // The total number of assets ingested const total = totalEntitiesIngested ?? data?.searchAcrossEntities?.total ?? 0; From f283b21436ab120800c845870ca115b0ae581ae9 Mon Sep 17 00:00:00 2001 From: Aseem Bansal Date: Mon, 28 Jul 2025 20:04:47 +0530 Subject: [PATCH 06/10] remove console log --- datahub-web-react/src/app/ingestV2/source/utils.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/datahub-web-react/src/app/ingestV2/source/utils.ts b/datahub-web-react/src/app/ingestV2/source/utils.ts index eefd3a4c6aaf60..700153af0a7654 100644 --- a/datahub-web-react/src/app/ingestV2/source/utils.ts +++ b/datahub-web-react/src/app/ingestV2/source/utils.ts @@ -407,12 +407,6 @@ export const getIngestionContents = (executionResult: Partial Date: Mon, 28 Jul 2025 20:10:28 +0530 Subject: [PATCH 07/10] cleanup --- .../executions/components/SummaryTab.tsx | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx b/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx index 4055d01a146b4f..3405d93119e05e 100644 --- a/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx +++ b/datahub-web-react/src/app/ingestV2/executions/components/SummaryTab.tsx @@ -1,6 +1,6 @@ import { DownloadOutlined } from '@ant-design/icons'; import { Typography } from 'antd'; -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; import YAML from 'yamljs'; @@ -64,16 +64,12 @@ export const SummaryTab = ({ data: GetIngestionExecutionRequestQuery | undefined; onTabChange: (tab: TabType) => void; }) => { - const [showExpandedLogs] = useState(true); - const [showExpandedRecipe] = useState(true); - - const output = data?.executionRequest?.result?.report || 'No output found.'; + const logs = data?.executionRequest?.result?.report || 'No output found.'; const downloadLogs = () => { - downloadFile(output, `exec-${urn}.log`); + downloadFile(logs, `exec-${urn}.log`); }; - const logs = (showExpandedLogs && output) || output?.split('\n')?.slice(0, 14)?.join('\n'); const structuredReport = result && getStructuredReport(result); const resultSummaryText = (status && status !== EXECUTION_REQUEST_STATUS_SUCCESS && ( @@ -81,18 +77,15 @@ export const SummaryTab = ({ )) || undefined; const recipeJson = data?.executionRequest?.input?.arguments?.find((arg) => arg.key === 'recipe')?.value; - let recipeYaml: string; + let recipe: string; try { - recipeYaml = recipeJson && YAML.stringify(JSON.parse(recipeJson), 8, 2).trim(); + recipe = recipeJson && YAML.stringify(JSON.parse(recipeJson), 8, 2).trim(); } catch (e) { - recipeYaml = ''; + recipe = ''; } - const recipe = showExpandedRecipe ? recipeYaml : recipeYaml?.split('\n')?.slice(0, 14)?.join('\n'); - const areLogsExpandable = output?.split(/\r\n|\r|\n/)?.length > 14; - const isRecipeExpandable = recipeYaml?.split(/\r\n|\r|\n/)?.length > 14; const downloadRecipe = () => { - downloadFile(recipeYaml, `recipe-${urn}.yaml`); + downloadFile(recipe, `recipe-${urn}.yaml`); }; return ( @@ -117,11 +110,9 @@ export const SummaryTab = ({ View logs that were collected during the sync. - {areLogsExpandable && ( - - )} + - )} +