@openshift-migration-advisor/shared-components 0.25.0 → 0.27.0-5d313f88dc52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +25 -0
  2. package/dist/report/ChartDownloadButton.d.ts +11 -0
  3. package/dist/report/ChartDownloadButton.d.ts.map +1 -0
  4. package/dist/report/ChartDownloadButton.js +24 -0
  5. package/dist/report/ChartExportProvider.d.ts +23 -0
  6. package/dist/report/ChartExportProvider.d.ts.map +1 -0
  7. package/dist/report/ChartExportProvider.js +222 -0
  8. package/dist/report/ChartExportSurface.d.ts +12 -0
  9. package/dist/report/ChartExportSurface.d.ts.map +1 -0
  10. package/dist/report/ChartExportSurface.js +32 -0
  11. package/dist/report/ErrorTable.d.ts +18 -0
  12. package/dist/report/ErrorTable.d.ts.map +1 -0
  13. package/dist/report/ErrorTable.js +20 -0
  14. package/dist/report/HostPowerStates.d.ts +0 -1
  15. package/dist/report/HostPowerStates.d.ts.map +1 -1
  16. package/dist/report/HostPowerStates.js +2 -2
  17. package/dist/report/HostsOverview.d.ts +13 -0
  18. package/dist/report/HostsOverview.d.ts.map +1 -0
  19. package/dist/report/HostsOverview.js +84 -0
  20. package/dist/report/InfrastructureSummary.d.ts.map +1 -1
  21. package/dist/report/InfrastructureSummary.js +6 -2
  22. package/dist/report/NetworkOverview.d.ts +31 -0
  23. package/dist/report/NetworkOverview.d.ts.map +1 -0
  24. package/dist/report/NetworkOverview.js +201 -0
  25. package/dist/report/OSDistribution.d.ts +0 -2
  26. package/dist/report/OSDistribution.d.ts.map +1 -1
  27. package/dist/report/OSDistribution.js +8 -5
  28. package/dist/report/OsUpgradeRecommendationPopover.d.ts +0 -1
  29. package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -1
  30. package/dist/report/OsUpgradeRecommendationPopover.js +4 -3
  31. package/dist/report/PowerStateCard.d.ts +0 -1
  32. package/dist/report/PowerStateCard.d.ts.map +1 -1
  33. package/dist/report/PowerStateCard.js +4 -2
  34. package/dist/report/ReportExportMenu.d.ts +19 -0
  35. package/dist/report/ReportExportMenu.d.ts.map +1 -0
  36. package/dist/report/ReportExportMenu.js +31 -0
  37. package/dist/report/ReportTable.d.ts +14 -0
  38. package/dist/report/ReportTable.d.ts.map +1 -0
  39. package/dist/report/ReportTable.js +23 -0
  40. package/dist/report/SupportTierBadge.d.ts +0 -1
  41. package/dist/report/SupportTierBadge.d.ts.map +1 -1
  42. package/dist/report/SupportTierBadge.js +1 -21
  43. package/dist/report/VCenterClusterDetails.d.ts +0 -1
  44. package/dist/report/VCenterClusterDetails.d.ts.map +1 -1
  45. package/dist/report/VCenterClusterDetails.js +9 -4
  46. package/dist/report/VmPowerStates.d.ts +0 -1
  47. package/dist/report/VmPowerStates.d.ts.map +1 -1
  48. package/dist/report/VmPowerStates.js +2 -2
  49. package/dist/report/WarningsTable.d.ts +9 -0
  50. package/dist/report/WarningsTable.d.ts.map +1 -0
  51. package/dist/report/WarningsTable.js +20 -0
  52. package/dist/report/captureChartElement.d.ts +8 -0
  53. package/dist/report/captureChartElement.d.ts.map +1 -0
  54. package/dist/report/captureChartElement.js +304 -0
  55. package/dist/report/chartExport.d.ts +80 -0
  56. package/dist/report/chartExport.d.ts.map +1 -0
  57. package/dist/report/chartExport.js +182 -0
  58. package/dist/report/chartExportContext.d.ts +32 -0
  59. package/dist/report/chartExportContext.d.ts.map +1 -0
  60. package/dist/report/chartExportContext.js +36 -0
  61. package/dist/report/chartExportFilenames.d.ts +4 -0
  62. package/dist/report/chartExportFilenames.d.ts.map +1 -0
  63. package/dist/report/chartExportFilenames.js +16 -0
  64. package/dist/report/constants.d.ts +2 -0
  65. package/dist/report/constants.d.ts.map +1 -1
  66. package/dist/report/constants.js +2 -0
  67. package/dist/report/dashboardStyles.d.ts +0 -4
  68. package/dist/report/dashboardStyles.d.ts.map +1 -1
  69. package/dist/report/dashboardStyles.js +0 -19
  70. package/dist/report/htmlExport.d.ts +9 -0
  71. package/dist/report/htmlExport.d.ts.map +1 -0
  72. package/dist/report/htmlExport.js +65 -0
  73. package/dist/report/index.d.ts +18 -0
  74. package/dist/report/index.d.ts.map +1 -1
  75. package/dist/report/index.js +18 -0
  76. package/dist/report/pdfExport.d.ts +3 -0
  77. package/dist/report/pdfExport.d.ts.map +1 -0
  78. package/dist/report/pdfExport.js +131 -0
  79. package/dist/report/pdfPage.d.ts +15 -0
  80. package/dist/report/pdfPage.d.ts.map +1 -0
  81. package/dist/report/pdfPage.js +44 -0
  82. package/dist/report/reportExportOptions.d.ts +17 -0
  83. package/dist/report/reportExportOptions.d.ts.map +1 -0
  84. package/dist/report/reportExportOptions.js +31 -0
  85. package/dist/report/zipChartPngs.d.ts +3 -0
  86. package/dist/report/zipChartPngs.d.ts.map +1 -0
  87. package/dist/report/zipChartPngs.js +8 -0
  88. package/package.json +7 -2
  89. package/src/report/ChartDownloadButton.tsx +59 -0
  90. package/src/report/ChartExportProvider.tsx +392 -0
  91. package/src/report/ChartExportSurface.tsx +60 -0
  92. package/src/report/ErrorTable.tsx +85 -0
  93. package/src/report/HostPowerStates.tsx +0 -3
  94. package/src/report/HostsOverview.tsx +185 -0
  95. package/src/report/InfrastructureSummary.tsx +48 -31
  96. package/src/report/NetworkOverview.tsx +405 -0
  97. package/src/report/OSDistribution.tsx +46 -42
  98. package/src/report/OsUpgradeRecommendationPopover.tsx +3 -4
  99. package/src/report/PowerStateCard.tsx +49 -36
  100. package/src/report/ReportExportMenu.tsx +119 -0
  101. package/src/report/ReportTable.tsx +81 -0
  102. package/src/report/SupportTierBadge.tsx +12 -40
  103. package/src/report/VCenterClusterDetails.tsx +38 -16
  104. package/src/report/VmPowerStates.tsx +0 -3
  105. package/src/report/WarningsTable.tsx +75 -0
  106. package/src/report/__tests__/ChartExportProvider.test.tsx +582 -0
  107. package/src/report/__tests__/HostsOverview.test.tsx +145 -0
  108. package/src/report/__tests__/InfrastructureSummary.test.tsx +0 -22
  109. package/src/report/__tests__/NetworkOverview.test.tsx +269 -0
  110. package/src/report/__tests__/OSDistribution.test.tsx +10 -11
  111. package/src/report/__tests__/captureChartElement.test.ts +205 -0
  112. package/src/report/__tests__/chartExportPackagers.test.ts +299 -0
  113. package/src/report/captureChartElement.ts +398 -0
  114. package/src/report/chartExport.ts +277 -0
  115. package/src/report/chartExportContext.tsx +92 -0
  116. package/src/report/chartExportFilenames.ts +21 -0
  117. package/src/report/constants.ts +2 -0
  118. package/src/report/dashboardStyles.ts +0 -23
  119. package/src/report/htmlExport.ts +83 -0
  120. package/src/report/index.ts +98 -0
  121. package/src/report/pdfExport.ts +233 -0
  122. package/src/report/pdfPage.ts +83 -0
  123. package/src/report/reportExportOptions.ts +53 -0
  124. package/src/report/zipChartPngs.ts +10 -0
@@ -21,6 +21,9 @@ import { DesktopIcon } from "@patternfly/react-icons";
21
21
  import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
22
22
  import type { FC, Ref } from "react";
23
23
  import { CardEmptyState } from "./CardEmptyState.js";
24
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
25
+ import { ChartExportSurface } from "./ChartExportSurface.js";
26
+ import { chartExportHideProps, chartExportScrollProps } from "./chartExport.js";
24
27
  import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
25
28
  import { dashboardStyles, tableFullWidthStyle } from "./dashboardStyles.js";
26
29
  import { EmptySearchResults } from "./EmptySearchResults.js";
@@ -43,47 +46,55 @@ const tableScrollStyle = css`
43
46
  max-height: 350px;
44
47
  `;
45
48
 
49
+ const OS_DISTRIBUTION_CHART_ID = "os-distribution";
50
+ const OS_DISTRIBUTION_TITLE = "Operating system distribution";
51
+
46
52
  interface OSDistributionProps {
47
53
  osData: Record<string, OSDistributionEntry>;
48
- isExportMode?: boolean;
49
54
  }
50
55
 
51
- export const OSDistribution: FC<OSDistributionProps> = ({
52
- osData,
53
- isExportMode = false,
54
- }) => (
55
- <Card
56
- className={isExportMode ? dashboardStyles.cardPrint : dashboardStyles.card}
57
- id="os-distribution"
56
+ export const OSDistribution: FC<OSDistributionProps> = ({ osData }) => (
57
+ <ChartExportSurface
58
+ id={OS_DISTRIBUTION_CHART_ID}
59
+ title={OS_DISTRIBUTION_TITLE}
58
60
  >
59
- <CardTitle>
60
- <Flex
61
- alignItems={{ default: "alignItemsCenter" }}
62
- spaceItems={{ default: "spaceItemsSm" }}
63
- >
64
- <FlexItem>
65
- <DesktopIcon /> Operating Systems
66
- </FlexItem>
67
- <FlexItem>
68
- <OsSupportTiersHelpPopover />
69
- </FlexItem>
70
- </Flex>
71
- </CardTitle>
72
- <CardBody>
73
- <OSBarChart osData={osData} isExportMode={isExportMode} />
74
- </CardBody>
75
- </Card>
61
+ <Card className={dashboardStyles.card}>
62
+ <CardTitle>
63
+ <Flex
64
+ justifyContent={{ default: "justifyContentSpaceBetween" }}
65
+ alignItems={{ default: "alignItemsCenter" }}
66
+ >
67
+ <FlexItem>
68
+ <Flex
69
+ alignItems={{ default: "alignItemsCenter" }}
70
+ spaceItems={{ default: "spaceItemsSm" }}
71
+ >
72
+ <FlexItem>
73
+ <DesktopIcon /> Operating Systems
74
+ </FlexItem>
75
+ <FlexItem {...chartExportHideProps}>
76
+ <OsSupportTiersHelpPopover />
77
+ </FlexItem>
78
+ </Flex>
79
+ </FlexItem>
80
+ <ChartHeaderActions
81
+ chartId={OS_DISTRIBUTION_CHART_ID}
82
+ title={OS_DISTRIBUTION_TITLE}
83
+ />
84
+ </Flex>
85
+ </CardTitle>
86
+ <CardBody>
87
+ <OSBarChart osData={osData} />
88
+ </CardBody>
89
+ </Card>
90
+ </ChartExportSurface>
76
91
  );
77
92
 
78
93
  interface OSBarChartProps {
79
94
  osData: Record<string, OSDistributionEntry>;
80
- isExportMode?: boolean;
81
95
  }
82
96
 
83
- export const OSBarChart: FC<OSBarChartProps> = ({
84
- osData,
85
- isExportMode = false,
86
- }) => {
97
+ export const OSBarChart: FC<OSBarChartProps> = ({ osData }) => {
87
98
  const vm = useOsBarChartViewModel(osData);
88
99
 
89
100
  if (vm.tableRows.length === 0) {
@@ -94,7 +105,7 @@ export const OSBarChart: FC<OSBarChartProps> = ({
94
105
 
95
106
  return (
96
107
  <>
97
- {!isExportMode ? (
108
+ <div {...chartExportHideProps}>
98
109
  <Toolbar hasNoPadding>
99
110
  <ToolbarContent>
100
111
  <ToolbarItem>
@@ -138,7 +149,7 @@ export const OSBarChart: FC<OSBarChartProps> = ({
138
149
  </ToolbarItem>
139
150
  </ToolbarContent>
140
151
  </Toolbar>
141
- ) : null}
152
+ </div>
142
153
 
143
154
  <Stack hasGutter>
144
155
  {vm.showUpgradeNotice ? (
@@ -148,11 +159,8 @@ export const OSBarChart: FC<OSBarChartProps> = ({
148
159
  ) : null}
149
160
  <StackItem>
150
161
  <div
151
- className={
152
- isExportMode
153
- ? tableFullWidthStyle
154
- : `${tableScrollStyle} ${tableFullWidthStyle}`
155
- }
162
+ className={`${tableScrollStyle} ${tableFullWidthStyle}`}
163
+ {...chartExportScrollProps}
156
164
  >
157
165
  <Table aria-label="Operating systems" variant="compact">
158
166
  <Thead>
@@ -176,14 +184,10 @@ export const OSBarChart: FC<OSBarChartProps> = ({
176
184
  <OsNameCell
177
185
  osName={row.osName}
178
186
  upgradeRecommendation={row.upgradeRecommendation}
179
- isExportMode={isExportMode}
180
187
  />
181
188
  </Td>
182
189
  <Td dataLabel="Tier">
183
- <SupportTierBadge
184
- tier={row.tier}
185
- isExportMode={isExportMode}
186
- />
190
+ <SupportTierBadge tier={row.tier} />
187
191
  </Td>
188
192
  <Td dataLabel="VMs">{row.count}</Td>
189
193
  </Tr>
@@ -2,6 +2,7 @@ import { css } from "@emotion/css";
2
2
  import { Button, Flex, FlexItem, Popover } from "@patternfly/react-core";
3
3
  import { InfoCircleIcon } from "@patternfly/react-icons";
4
4
  import type { FC } from "react";
5
+ import { chartExportHideProps } from "./chartExport.js";
5
6
 
6
7
  const upgradeRecommendationPopoverCloseButton = css`
7
8
  .pf-v6-c-popover__close .pf-v6-c-button.pf-m-plain,
@@ -44,18 +45,16 @@ OsUpgradeRecommendationPopover.displayName = "OsUpgradeRecommendationPopover";
44
45
  interface OsNameCellProps {
45
46
  osName: string;
46
47
  upgradeRecommendation?: string;
47
- isExportMode?: boolean;
48
48
  }
49
49
 
50
50
  export const OsNameCell: FC<OsNameCellProps> = ({
51
51
  osName,
52
52
  upgradeRecommendation,
53
- isExportMode = false,
54
53
  }) => {
55
54
  const hasUpgradeRecommendation =
56
55
  upgradeRecommendation && upgradeRecommendation.trim() !== "";
57
56
 
58
- if (!hasUpgradeRecommendation || isExportMode) {
57
+ if (!hasUpgradeRecommendation) {
59
58
  return <>{osName}</>;
60
59
  }
61
60
 
@@ -66,7 +65,7 @@ export const OsNameCell: FC<OsNameCellProps> = ({
66
65
  flexWrap={{ default: "nowrap" }}
67
66
  >
68
67
  <FlexItem>{osName}</FlexItem>
69
- <FlexItem shrink={{ default: "shrink" }}>
68
+ <FlexItem shrink={{ default: "shrink" }} {...chartExportHideProps}>
70
69
  <OsUpgradeRecommendationPopover
71
70
  upgradeRecommendation={upgradeRecommendation}
72
71
  />
@@ -1,4 +1,10 @@
1
- import { Card, CardBody, CardTitle } from "@patternfly/react-core";
1
+ import {
2
+ Card,
3
+ CardBody,
4
+ CardTitle,
5
+ Flex,
6
+ FlexItem,
7
+ } from "@patternfly/react-core";
2
8
  import type { FC, ReactNode } from "react";
3
9
  import {
4
10
  MigrationDonutChart,
@@ -6,6 +12,8 @@ import {
6
12
  type MigrationDonutChartLegendVariant,
7
13
  } from "../charts/MigrationDonutChart.js";
8
14
  import { CardEmptyState } from "./CardEmptyState.js";
15
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
16
+ import { ChartExportSurface } from "./ChartExportSurface.js";
9
17
  import { dashboardStyles } from "./dashboardStyles.js";
10
18
 
11
19
  export interface PowerStateCardProps {
@@ -17,7 +25,6 @@ export interface PowerStateCardProps {
17
25
  legend: Record<string, string>;
18
26
  total: number;
19
27
  subTitle: string;
20
- isExportMode?: boolean;
21
28
  itemsPerRow?: number;
22
29
  legendVariant?: MigrationDonutChartLegendVariant;
23
30
  }
@@ -31,43 +38,49 @@ export const PowerStateCard: FC<PowerStateCardProps> = ({
31
38
  legend,
32
39
  total,
33
40
  subTitle,
34
- isExportMode = false,
35
41
  itemsPerRow = 2,
36
42
  legendVariant = "html",
37
43
  }) => (
38
- <Card
39
- className={isExportMode ? dashboardStyles.cardPrint : dashboardStyles.card}
40
- id={id}
41
- >
42
- <CardTitle>
43
- {icon} {title}
44
- </CardTitle>
45
- <CardBody>
46
- {total === 0 ? (
47
- <CardEmptyState title={emptyTitle} />
48
- ) : (
49
- <MigrationDonutChart
50
- legendVariant={legendVariant}
51
- data={slices}
52
- legend={legend}
53
- height={300}
54
- width={420}
55
- donutThickness={18}
56
- padAngle={1}
57
- title={`${total}`}
58
- subTitle={subTitle}
59
- subTitleColor="var(--pf-t--global--text--color--subtle)"
60
- titleFontSize={34}
61
- labelFontSize={16}
62
- itemsPerRow={itemsPerRow}
63
- marginLeft="0%"
64
- tooltipLabelFormatter={({ datum, percent }) =>
65
- `${datum.countDisplay}\n${percent.toFixed(1)}%`
66
- }
67
- />
68
- )}
69
- </CardBody>
70
- </Card>
44
+ <ChartExportSurface id={id} title={title}>
45
+ <Card className={dashboardStyles.card}>
46
+ <CardTitle>
47
+ <Flex
48
+ justifyContent={{ default: "justifyContentSpaceBetween" }}
49
+ alignItems={{ default: "alignItemsCenter" }}
50
+ >
51
+ <FlexItem>
52
+ {icon} {title}
53
+ </FlexItem>
54
+ <ChartHeaderActions chartId={id} title={title} />
55
+ </Flex>
56
+ </CardTitle>
57
+ <CardBody>
58
+ {total === 0 ? (
59
+ <CardEmptyState title={emptyTitle} />
60
+ ) : (
61
+ <MigrationDonutChart
62
+ legendVariant={legendVariant}
63
+ data={slices}
64
+ legend={legend}
65
+ height={300}
66
+ width={420}
67
+ donutThickness={18}
68
+ padAngle={1}
69
+ title={`${total}`}
70
+ subTitle={subTitle}
71
+ subTitleColor="var(--pf-t--global--text--color--subtle)"
72
+ titleFontSize={34}
73
+ labelFontSize={16}
74
+ itemsPerRow={itemsPerRow}
75
+ marginLeft="0%"
76
+ tooltipLabelFormatter={({ datum, percent }) =>
77
+ `${datum.countDisplay}\n${percent.toFixed(1)}%`
78
+ }
79
+ />
80
+ )}
81
+ </CardBody>
82
+ </Card>
83
+ </ChartExportSurface>
71
84
  );
72
85
 
73
86
  PowerStateCard.displayName = "PowerStateCard";
@@ -0,0 +1,119 @@
1
+ import {
2
+ Dropdown,
3
+ DropdownItem,
4
+ DropdownList,
5
+ MenuToggle,
6
+ type MenuToggleElement,
7
+ Spinner,
8
+ } from "@patternfly/react-core";
9
+ import { DownloadIcon } from "@patternfly/react-icons";
10
+ import { type FC, type Ref, useState } from "react";
11
+ import {
12
+ type ReportExportOption,
13
+ type StandardReportExportHandlers,
14
+ standardReportExportOptions,
15
+ } from "./reportExportOptions.js";
16
+
17
+ export interface ReportExportMenuProps {
18
+ options: ReportExportOption[];
19
+ isLoading?: boolean;
20
+ loadingLabel?: string | null;
21
+ isDisabled?: boolean;
22
+ toggleLabel?: string;
23
+ }
24
+
25
+ export const ReportExportMenu: FC<ReportExportMenuProps> = ({
26
+ options,
27
+ isLoading = false,
28
+ loadingLabel = null,
29
+ isDisabled = false,
30
+ toggleLabel = "Export",
31
+ }) => {
32
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
33
+
34
+ if (options.length === 0) {
35
+ return null;
36
+ }
37
+
38
+ return (
39
+ <Dropdown
40
+ isOpen={isDropdownOpen}
41
+ popperProps={{
42
+ placement: "bottom-end",
43
+ preventOverflow: true,
44
+ }}
45
+ onSelect={() => {
46
+ setIsDropdownOpen(false);
47
+ }}
48
+ onOpenChange={(isOpen: boolean) => {
49
+ setIsDropdownOpen(isOpen);
50
+ }}
51
+ toggle={(toggleRef: Ref<MenuToggleElement>) => (
52
+ <MenuToggle
53
+ ref={toggleRef}
54
+ onClick={() => {
55
+ setIsDropdownOpen((open) => !open);
56
+ }}
57
+ isExpanded={isDropdownOpen}
58
+ variant="secondary"
59
+ isDisabled={isLoading || isDisabled}
60
+ aria-label="Export report options"
61
+ icon={
62
+ isLoading ? (
63
+ <Spinner size="sm" aria-hidden="true" />
64
+ ) : (
65
+ <DownloadIcon aria-hidden="true" />
66
+ )
67
+ }
68
+ >
69
+ {isLoading ? (loadingLabel ?? "Generating...") : toggleLabel}
70
+ </MenuToggle>
71
+ )}
72
+ >
73
+ <DropdownList>
74
+ {options.map((option) => (
75
+ <DropdownItem
76
+ key={option.key}
77
+ isDisabled={option.isDisabled}
78
+ description={option.description}
79
+ onClick={option.onSelect}
80
+ >
81
+ {option.label}
82
+ </DropdownItem>
83
+ ))}
84
+ </DropdownList>
85
+ </Dropdown>
86
+ );
87
+ };
88
+
89
+ ReportExportMenu.displayName = "ReportExportMenu";
90
+
91
+ export interface ExportReportButtonProps extends StandardReportExportHandlers {
92
+ extraOptions?: ReportExportOption[];
93
+ isLoading?: boolean;
94
+ loadingLabel?: string | null;
95
+ isDisabled?: boolean;
96
+ toggleLabel?: string;
97
+ }
98
+
99
+ export const ExportReportButton: FC<ExportReportButtonProps> = ({
100
+ onExportPdf,
101
+ onExportPng,
102
+ onExportHtml,
103
+ extraOptions = [],
104
+ ...menuProps
105
+ }) => (
106
+ <ReportExportMenu
107
+ options={[
108
+ ...standardReportExportOptions({
109
+ onExportPdf,
110
+ onExportPng,
111
+ onExportHtml,
112
+ }),
113
+ ...extraOptions,
114
+ ]}
115
+ {...menuProps}
116
+ />
117
+ );
118
+
119
+ ExportReportButton.displayName = "ExportReportButton";
@@ -0,0 +1,81 @@
1
+ import { Button } from "@patternfly/react-core";
2
+ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
3
+ import type React from "react";
4
+ import { tableFullWidthStyle } from "./dashboardStyles.js";
5
+
6
+ export interface ReportTableProps<DataItem> {
7
+ columns: string[];
8
+ data: DataItem[];
9
+ fields: Array<keyof DataItem>;
10
+ caption?: string;
11
+ onRowClick?: (item: DataItem) => void;
12
+ clickableFields?: Array<keyof DataItem>;
13
+ }
14
+
15
+ export function ReportTable<DataItem>(
16
+ props: ReportTableProps<DataItem>,
17
+ ): React.ReactNode {
18
+ const {
19
+ columns,
20
+ data,
21
+ fields,
22
+ caption,
23
+ onRowClick,
24
+ clickableFields = [],
25
+ } = props;
26
+
27
+ const renderCell = (
28
+ item: DataItem,
29
+ field: keyof DataItem,
30
+ ): React.ReactNode => {
31
+ const value = item[field];
32
+
33
+ if (value === "" || value == null) {
34
+ return "-";
35
+ }
36
+
37
+ if (typeof value === "boolean") {
38
+ return value ? "True" : "False";
39
+ }
40
+
41
+ // Make field clickable if specified
42
+ if (clickableFields.includes(field) && onRowClick) {
43
+ return (
44
+ <Button
45
+ variant="link"
46
+ isInline
47
+ onClick={() => onRowClick(item)}
48
+ style={{ padding: 0, fontSize: "inherit" }}
49
+ >
50
+ {value as React.ReactNode}
51
+ </Button>
52
+ );
53
+ }
54
+
55
+ return value as React.ReactNode;
56
+ };
57
+
58
+ return (
59
+ <Table variant="compact" borders={false} className={tableFullWidthStyle}>
60
+ {caption && <caption>{caption}</caption>}
61
+ <Thead>
62
+ <Tr>
63
+ {columns.map((name) => (
64
+ <Th key={name}>{name}</Th>
65
+ ))}
66
+ </Tr>
67
+ </Thead>
68
+ <Tbody>
69
+ {data.map((item) => (
70
+ <Tr key={`row-${String(item[fields[0]])}`}>
71
+ {fields.map((f) => (
72
+ <Td key={String(f)}>{renderCell(item, f)}</Td>
73
+ ))}
74
+ </Tr>
75
+ ))}
76
+ </Tbody>
77
+ </Table>
78
+ );
79
+ }
80
+
81
+ ReportTable.displayName = "ReportTable";
@@ -1,4 +1,3 @@
1
- import { css } from "@emotion/css";
2
1
  import { Label, Tooltip } from "@patternfly/react-core";
3
2
  import type { FC } from "react";
4
3
  import {
@@ -9,49 +8,22 @@ import {
9
8
  type SupportTier,
10
9
  } from "./osSupportTier.js";
11
10
 
12
- const exportBadgeStyle = (backgroundColor: string, color: string) => css`
13
- display: inline-block;
14
- padding: var(--pf-t--global--spacer--xs) var(--pf-t--global--spacer--sm);
15
- border-radius: var(--pf-t--global--border--radius--pill);
16
- font-size: var(--pf-t--global--font--size--body--sm);
17
- line-height: var(--pf-t--global--font--line-height--body);
18
- white-space: nowrap;
19
- background-color: ${backgroundColor};
20
- color: ${color};
21
- `;
22
-
23
11
  interface SupportTierBadgeProps {
24
12
  tier: SupportTier;
25
- isExportMode?: boolean;
26
13
  }
27
14
 
28
- const ExportSupportTierBadge: FC<{ tier: SupportTier }> = ({ tier }) => {
29
- const { backgroundColor, color } = getSupportTierBadgeInlineStyle(tier);
30
-
31
- return (
32
- <span className={exportBadgeStyle(backgroundColor, color)}>
33
- {getSupportTierLegendLabel(tier)}
15
+ export const SupportTierBadge: FC<SupportTierBadgeProps> = ({ tier }) => (
16
+ <Tooltip content={getSupportTierDefinition(tier)}>
17
+ <span>
18
+ <Label
19
+ color={getSupportTierBadgeColor(tier)}
20
+ isCompact
21
+ style={getSupportTierBadgeInlineStyle(tier)}
22
+ >
23
+ {getSupportTierLegendLabel(tier)}
24
+ </Label>
34
25
  </span>
35
- );
36
- };
37
-
38
- export const SupportTierBadge: FC<SupportTierBadgeProps> = ({
39
- tier,
40
- isExportMode = false,
41
- }) => {
42
- if (isExportMode) {
43
- return <ExportSupportTierBadge tier={tier} />;
44
- }
45
-
46
- return (
47
- <Tooltip content={getSupportTierDefinition(tier)}>
48
- <span>
49
- <Label color={getSupportTierBadgeColor(tier)} isCompact>
50
- {getSupportTierLegendLabel(tier)}
51
- </Label>
52
- </span>
53
- </Tooltip>
54
- );
55
- };
26
+ </Tooltip>
27
+ );
56
28
 
57
29
  SupportTierBadge.displayName = "SupportTierBadge";
@@ -8,6 +8,8 @@ import {
8
8
  DescriptionListDescription,
9
9
  DescriptionListGroup,
10
10
  DescriptionListTerm,
11
+ Flex,
12
+ FlexItem,
11
13
  Grid,
12
14
  GridItem,
13
15
  Label,
@@ -15,6 +17,9 @@ import {
15
17
  } from "@patternfly/react-core";
16
18
  import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
17
19
  import type { FC } from "react";
20
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
21
+ import { ChartExportSurface } from "./ChartExportSurface.js";
22
+ import { chartExportScrollProps } from "./chartExport.js";
18
23
  import { FeatureStatusBadge } from "./FeatureStatusBadge.js";
19
24
  import type {
20
25
  ClusterDetailRow,
@@ -26,7 +31,6 @@ export interface VCenterClusterDetailsProps {
26
31
  isAggregateView: boolean;
27
32
  rows: ClusterDetailRow[];
28
33
  details?: ClusterDetailsModel;
29
- isExportMode?: boolean;
30
34
  }
31
35
 
32
36
  const clusterDetailsTableScrollStyle = css`
@@ -91,16 +95,16 @@ const AggregateClusterTable: FC<{
91
95
 
92
96
  const AggregateCluster: FC<{
93
97
  rows: ClusterDetailRow[];
94
- isExportMode: boolean;
95
- }> = ({ rows, isExportMode }) => {
98
+ }> = ({ rows }) => {
96
99
  if (rows.length === 0) {
97
100
  return <Content component="p">No vSphere clusters detected</Content>;
98
101
  }
99
102
 
100
103
  return (
101
104
  <div
102
- className={isExportMode ? undefined : clusterDetailsTableScrollStyle}
103
- data-scroll-constrained={isExportMode ? undefined : "true"}
105
+ className={clusterDetailsTableScrollStyle}
106
+ data-scroll-constrained="true"
107
+ {...chartExportScrollProps}
104
108
  >
105
109
  <AggregateClusterTable rows={rows} />
106
110
  </div>
@@ -190,22 +194,40 @@ const DetailedClusterView: FC<{ details: ClusterDetailsModel }> = ({
190
194
  </Grid>
191
195
  );
192
196
 
197
+ const CLUSTER_DETAILS_CHART_ID = "vcenter-cluster-details";
198
+ const CLUSTER_DETAILS_TITLE = "vCenter cluster details";
199
+
193
200
  export const VCenterClusterDetails: FC<VCenterClusterDetailsProps> = ({
194
201
  isAggregateView,
195
202
  rows,
196
203
  details,
197
- isExportMode = false,
198
204
  }) => (
199
- <Card isFullHeight id="vcenter-cluster-details">
200
- <CardTitle>vCenter cluster details</CardTitle>
201
- <CardBody>
202
- {isAggregateView || !details ? (
203
- <AggregateCluster rows={rows} isExportMode={isExportMode} />
204
- ) : (
205
- <DetailedClusterView details={details} />
206
- )}
207
- </CardBody>
208
- </Card>
205
+ <ChartExportSurface
206
+ id={CLUSTER_DETAILS_CHART_ID}
207
+ title={CLUSTER_DETAILS_TITLE}
208
+ >
209
+ <Card isFullHeight>
210
+ <CardTitle>
211
+ <Flex
212
+ justifyContent={{ default: "justifyContentSpaceBetween" }}
213
+ alignItems={{ default: "alignItemsCenter" }}
214
+ >
215
+ <FlexItem>{CLUSTER_DETAILS_TITLE}</FlexItem>
216
+ <ChartHeaderActions
217
+ chartId={CLUSTER_DETAILS_CHART_ID}
218
+ title={CLUSTER_DETAILS_TITLE}
219
+ />
220
+ </Flex>
221
+ </CardTitle>
222
+ <CardBody>
223
+ {isAggregateView || !details ? (
224
+ <AggregateCluster rows={rows} />
225
+ ) : (
226
+ <DetailedClusterView details={details} />
227
+ )}
228
+ </CardBody>
229
+ </Card>
230
+ </ChartExportSurface>
209
231
  );
210
232
 
211
233
  VCenterClusterDetails.displayName = "VCenterClusterDetails";
@@ -8,13 +8,11 @@ import { buildVmPowerStateChart } from "./powerStates.js";
8
8
 
9
9
  export interface VmPowerStatesProps {
10
10
  powerStates?: Record<string, number>;
11
- isExportMode?: boolean;
12
11
  legendVariant?: MigrationDonutChartLegendVariant;
13
12
  }
14
13
 
15
14
  export const VmPowerStates: FC<VmPowerStatesProps> = ({
16
15
  powerStates,
17
- isExportMode = false,
18
16
  legendVariant,
19
17
  }) => {
20
18
  const chart = useMemo(
@@ -32,7 +30,6 @@ export const VmPowerStates: FC<VmPowerStatesProps> = ({
32
30
  legend={chart.legend}
33
31
  total={chart.total}
34
32
  subTitle="VMs"
35
- isExportMode={isExportMode}
36
33
  legendVariant={legendVariant}
37
34
  />
38
35
  );