@openshift-migration-advisor/shared-components 0.0.0

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 (105) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +76 -0
  3. package/dist/charts/MigrationDonutChart.d.ts +53 -0
  4. package/dist/charts/MigrationDonutChart.d.ts.map +1 -0
  5. package/dist/charts/MigrationDonutChart.js +186 -0
  6. package/dist/charts/index.d.ts +2 -0
  7. package/dist/charts/index.d.ts.map +1 -0
  8. package/dist/charts/index.js +1 -0
  9. package/dist/forms/CheckboxFormGroup.d.ts +5 -0
  10. package/dist/forms/CheckboxFormGroup.d.ts.map +1 -0
  11. package/dist/forms/CheckboxFormGroup.js +14 -0
  12. package/dist/forms/FormFieldHelperText.d.ts +7 -0
  13. package/dist/forms/FormFieldHelperText.d.ts.map +1 -0
  14. package/dist/forms/FormFieldHelperText.js +8 -0
  15. package/dist/forms/RadioButtonFormGroup.d.ts +12 -0
  16. package/dist/forms/RadioButtonFormGroup.d.ts.map +1 -0
  17. package/dist/forms/RadioButtonFormGroup.js +12 -0
  18. package/dist/forms/SelectFormGroup.d.ts +8 -0
  19. package/dist/forms/SelectFormGroup.d.ts.map +1 -0
  20. package/dist/forms/SelectFormGroup.js +12 -0
  21. package/dist/forms/TextAreaFormGroup.d.ts +3 -0
  22. package/dist/forms/TextAreaFormGroup.d.ts.map +1 -0
  23. package/dist/forms/TextAreaFormGroup.js +12 -0
  24. package/dist/forms/TextInputFormGroup.d.ts +11 -0
  25. package/dist/forms/TextInputFormGroup.d.ts.map +1 -0
  26. package/dist/forms/TextInputFormGroup.js +16 -0
  27. package/dist/forms/index.d.ts +8 -0
  28. package/dist/forms/index.d.ts.map +1 -0
  29. package/dist/forms/index.js +6 -0
  30. package/dist/forms/types.d.ts +11 -0
  31. package/dist/forms/types.d.ts.map +1 -0
  32. package/dist/forms/types.js +1 -0
  33. package/dist/index.d.ts +4 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +3 -0
  36. package/dist/report/CardEmptyState.d.ts +8 -0
  37. package/dist/report/CardEmptyState.d.ts.map +1 -0
  38. package/dist/report/CardEmptyState.js +13 -0
  39. package/dist/report/DashboardEmptyStateBase.d.ts +8 -0
  40. package/dist/report/DashboardEmptyStateBase.d.ts.map +1 -0
  41. package/dist/report/DashboardEmptyStateBase.js +5 -0
  42. package/dist/report/EmptySearchResults.d.ts +7 -0
  43. package/dist/report/EmptySearchResults.d.ts.map +1 -0
  44. package/dist/report/EmptySearchResults.js +4 -0
  45. package/dist/report/OSDistribution.d.ts +14 -0
  46. package/dist/report/OSDistribution.d.ts.map +1 -0
  47. package/dist/report/OSDistribution.js +37 -0
  48. package/dist/report/OsSupportTiersHelpPopover.d.ts +3 -0
  49. package/dist/report/OsSupportTiersHelpPopover.d.ts.map +1 -0
  50. package/dist/report/OsSupportTiersHelpPopover.js +37 -0
  51. package/dist/report/OsUpgradeNotice.d.ts +3 -0
  52. package/dist/report/OsUpgradeNotice.d.ts.map +1 -0
  53. package/dist/report/OsUpgradeNotice.js +9 -0
  54. package/dist/report/OsUpgradeRecommendationPopover.d.ts +13 -0
  55. package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -0
  56. package/dist/report/OsUpgradeRecommendationPopover.js +24 -0
  57. package/dist/report/PopoverIcon.d.ts +11 -0
  58. package/dist/report/PopoverIcon.d.ts.map +1 -0
  59. package/dist/report/PopoverIcon.js +8 -0
  60. package/dist/report/SupportTierBadge.d.ts +9 -0
  61. package/dist/report/SupportTierBadge.d.ts.map +1 -0
  62. package/dist/report/SupportTierBadge.js +23 -0
  63. package/dist/report/constants.d.ts +11 -0
  64. package/dist/report/constants.d.ts.map +1 -0
  65. package/dist/report/constants.js +12 -0
  66. package/dist/report/dashboardStyles.d.ts +20 -0
  67. package/dist/report/dashboardStyles.d.ts.map +1 -0
  68. package/dist/report/dashboardStyles.js +99 -0
  69. package/dist/report/index.d.ts +15 -0
  70. package/dist/report/index.d.ts.map +1 -0
  71. package/dist/report/index.js +13 -0
  72. package/dist/report/osSupportTier.d.ts +42 -0
  73. package/dist/report/osSupportTier.d.ts.map +1 -0
  74. package/dist/report/osSupportTier.js +106 -0
  75. package/dist/report/useOsBarChartViewModel.d.ts +26 -0
  76. package/dist/report/useOsBarChartViewModel.d.ts.map +1 -0
  77. package/dist/report/useOsBarChartViewModel.js +59 -0
  78. package/package.json +63 -0
  79. package/src/charts/MigrationDonutChart.tsx +398 -0
  80. package/src/charts/index.ts +5 -0
  81. package/src/forms/CheckboxFormGroup.tsx +53 -0
  82. package/src/forms/FormFieldHelperText.tsx +32 -0
  83. package/src/forms/RadioButtonFormGroup.tsx +62 -0
  84. package/src/forms/SelectFormGroup.tsx +57 -0
  85. package/src/forms/TextAreaFormGroup.tsx +43 -0
  86. package/src/forms/TextInputFormGroup.tsx +63 -0
  87. package/src/forms/index.ts +7 -0
  88. package/src/forms/types.ts +11 -0
  89. package/src/index.ts +3 -0
  90. package/src/report/CardEmptyState.tsx +30 -0
  91. package/src/report/DashboardEmptyStateBase.tsx +30 -0
  92. package/src/report/EmptySearchResults.tsx +14 -0
  93. package/src/report/OSDistribution.tsx +206 -0
  94. package/src/report/OsSupportTiersHelpPopover.tsx +87 -0
  95. package/src/report/OsUpgradeNotice.tsx +29 -0
  96. package/src/report/OsUpgradeRecommendationPopover.tsx +78 -0
  97. package/src/report/PopoverIcon.tsx +46 -0
  98. package/src/report/SupportTierBadge.tsx +55 -0
  99. package/src/report/__tests__/OSDistribution.test.tsx +236 -0
  100. package/src/report/__tests__/osSupportTier.test.ts +101 -0
  101. package/src/report/constants.ts +14 -0
  102. package/src/report/dashboardStyles.ts +116 -0
  103. package/src/report/index.ts +54 -0
  104. package/src/report/osSupportTier.ts +161 -0
  105. package/src/report/useOsBarChartViewModel.ts +115 -0
@@ -0,0 +1,57 @@
1
+ import {
2
+ FormGroup,
3
+ FormSelect,
4
+ FormSelectOption,
5
+ } from "@patternfly/react-core";
6
+ import { Controller, type FieldError, useFormContext } from "react-hook-form";
7
+
8
+ import FormFieldHelperText from "./FormFieldHelperText.js";
9
+ import type { FormGroupProps } from "./types.js";
10
+
11
+ export default function SelectFormGroup({
12
+ id,
13
+ label,
14
+ name,
15
+ isRequired = false,
16
+ options,
17
+ helpText,
18
+ isDisabled = false,
19
+ ...props
20
+ }: {
21
+ options: {
22
+ label: string;
23
+ value: string;
24
+ }[];
25
+ } & FormGroupProps) {
26
+ const methods = useFormContext();
27
+ const error = methods.formState.errors[name] as FieldError | undefined;
28
+ const isTouched = methods.formState.touchedFields[name] as
29
+ | boolean
30
+ | undefined;
31
+ const isSubmitted = methods.formState.isSubmitted;
32
+ const showError = error && (isTouched || isSubmitted);
33
+
34
+ return (
35
+ <FormGroup label={label} isRequired={isRequired} fieldId={id} {...props}>
36
+ <Controller
37
+ name={name}
38
+ control={methods.control}
39
+ render={({ field }) => (
40
+ <FormSelect id={id} isDisabled={isDisabled} {...field}>
41
+ {options.map((option) => (
42
+ <FormSelectOption
43
+ key={option.value}
44
+ value={option.value}
45
+ label={option.label}
46
+ />
47
+ ))}
48
+ </FormSelect>
49
+ )}
50
+ />
51
+ <FormFieldHelperText
52
+ helpText={helpText}
53
+ errorMessage={showError ? error?.message : undefined}
54
+ />
55
+ </FormGroup>
56
+ );
57
+ }
@@ -0,0 +1,43 @@
1
+ import { FormGroup, TextArea, ValidatedOptions } from "@patternfly/react-core";
2
+ import { type FieldError, useFormContext } from "react-hook-form";
3
+
4
+ import FormFieldHelperText from "./FormFieldHelperText.js";
5
+ import type { FormGroupProps } from "./types.js";
6
+
7
+ export default function TextAreaFormGroup({
8
+ id,
9
+ label,
10
+ name,
11
+ isRequired = false,
12
+ isDisabled = false,
13
+ placeholder = "",
14
+ helpText,
15
+ ...props
16
+ }: FormGroupProps) {
17
+ const methods = useFormContext();
18
+ const error = methods.formState.errors[name] as FieldError | undefined;
19
+ const isTouched = methods.formState.touchedFields[name] as
20
+ | boolean
21
+ | undefined;
22
+ const isSubmitted = methods.formState.isSubmitted;
23
+ const showError = error && (isTouched || isSubmitted);
24
+
25
+ return (
26
+ <FormGroup label={label} isRequired={isRequired} fieldId={id} {...props}>
27
+ <TextArea
28
+ id={id}
29
+ placeholder={placeholder}
30
+ isRequired={isRequired}
31
+ isDisabled={isDisabled}
32
+ validated={
33
+ showError ? ValidatedOptions.error : ValidatedOptions.default
34
+ }
35
+ {...methods.register(name)}
36
+ />
37
+ <FormFieldHelperText
38
+ helpText={helpText}
39
+ errorMessage={showError ? error?.message : undefined}
40
+ />
41
+ </FormGroup>
42
+ );
43
+ }
@@ -0,0 +1,63 @@
1
+ import {
2
+ FormGroup,
3
+ TextInput,
4
+ type TextInputProps,
5
+ ValidatedOptions,
6
+ } from "@patternfly/react-core";
7
+ import type { FocusEvent } from "react";
8
+ import { type FieldError, useFormContext } from "react-hook-form";
9
+
10
+ import FormFieldHelperText from "./FormFieldHelperText.js";
11
+ import type { FormGroupProps } from "./types.js";
12
+
13
+ interface TextInputFormGroupProps extends FormGroupProps {
14
+ type?: TextInputProps["type"];
15
+ onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
16
+ isDisabled?: boolean;
17
+ }
18
+
19
+ export default function TextInputFormGroup({
20
+ id,
21
+ label,
22
+ name,
23
+ isRequired = false,
24
+ placeholder = "",
25
+ type = "text",
26
+ onBlur,
27
+ isDisabled = false,
28
+ helpText,
29
+ ...props
30
+ }: TextInputFormGroupProps) {
31
+ const methods = useFormContext();
32
+ const error = methods.formState.errors[name] as FieldError | undefined;
33
+ const isTouched = methods.formState.touchedFields[name] as
34
+ | boolean
35
+ | undefined;
36
+ const isSubmitted = methods.formState.isSubmitted;
37
+ const registration = methods.register(name);
38
+ const showError = error && (isTouched || isSubmitted);
39
+
40
+ return (
41
+ <FormGroup label={label} isRequired={isRequired} fieldId={id} {...props}>
42
+ <TextInput
43
+ id={id}
44
+ placeholder={placeholder}
45
+ isRequired={isRequired}
46
+ validated={
47
+ showError ? ValidatedOptions.error : ValidatedOptions.default
48
+ }
49
+ type={type}
50
+ isDisabled={isDisabled}
51
+ {...registration}
52
+ onBlur={(e: FocusEvent<HTMLInputElement>) => {
53
+ void registration.onBlur(e);
54
+ onBlur?.(e);
55
+ }}
56
+ />
57
+ <FormFieldHelperText
58
+ helpText={helpText}
59
+ errorMessage={showError ? error?.message : undefined}
60
+ />
61
+ </FormGroup>
62
+ );
63
+ }
@@ -0,0 +1,7 @@
1
+ export { default as CheckboxFormGroup } from "./CheckboxFormGroup.js";
2
+ export { default as FormFieldHelperText } from "./FormFieldHelperText.js";
3
+ export { default as RadioButtonFormGroup } from "./RadioButtonFormGroup.js";
4
+ export { default as SelectFormGroup } from "./SelectFormGroup.js";
5
+ export { default as TextAreaFormGroup } from "./TextAreaFormGroup.js";
6
+ export { default as TextInputFormGroup } from "./TextInputFormGroup.js";
7
+ export type { FormGroupProps } from "./types.js";
@@ -0,0 +1,11 @@
1
+ import type { FormGroupProps as PFFormGroupProps } from "@patternfly/react-core";
2
+
3
+ export interface FormGroupProps extends Omit<PFFormGroupProps, "fieldId"> {
4
+ id: string;
5
+ name: string;
6
+ label?: string;
7
+ isRequired?: boolean;
8
+ placeholder?: string;
9
+ helpText?: string;
10
+ isDisabled?: boolean;
11
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./charts/index.js";
2
+ export * from "./forms/index.js";
3
+ export * from "./report/index.js";
@@ -0,0 +1,30 @@
1
+ import { css } from "@emotion/css";
2
+ import type { FC } from "react";
3
+ import { DashboardEmptyStateBase } from "./DashboardEmptyStateBase.js";
4
+
5
+ export const CARD_EMPTY_STATE_DESCRIPTION =
6
+ "This data is not available for older inventories or certain imported reports.";
7
+
8
+ const cardEmptyStateContainer = css`
9
+ display: flex;
10
+ flex-direction: column;
11
+ justify-content: center;
12
+ flex: 1;
13
+ min-height: 250px;
14
+ `;
15
+
16
+ export interface CardEmptyStateProps {
17
+ title: string;
18
+ description?: string;
19
+ }
20
+
21
+ export const CardEmptyState: FC<CardEmptyStateProps> = ({
22
+ title,
23
+ description = CARD_EMPTY_STATE_DESCRIPTION,
24
+ }) => (
25
+ <div className={cardEmptyStateContainer}>
26
+ <DashboardEmptyStateBase title={title} body={description} />
27
+ </div>
28
+ );
29
+
30
+ CardEmptyState.displayName = "CardEmptyState";
@@ -0,0 +1,30 @@
1
+ import {
2
+ EmptyState,
3
+ EmptyStateBody,
4
+ EmptyStateVariant,
5
+ } from "@patternfly/react-core";
6
+ import { SearchIcon } from "@patternfly/react-icons";
7
+ import type { ComponentType, FC, SVGProps } from "react";
8
+
9
+ export interface DashboardEmptyStateBaseProps {
10
+ title: string;
11
+ body: string;
12
+ icon?: ComponentType<SVGProps<SVGSVGElement>>;
13
+ }
14
+
15
+ export const DashboardEmptyStateBase: FC<DashboardEmptyStateBaseProps> = ({
16
+ title,
17
+ body,
18
+ icon: IconComponent = SearchIcon,
19
+ }) => (
20
+ <EmptyState
21
+ headingLevel="h4"
22
+ icon={IconComponent}
23
+ titleText={title}
24
+ variant={EmptyStateVariant.sm}
25
+ >
26
+ <EmptyStateBody>{body}</EmptyStateBody>
27
+ </EmptyState>
28
+ );
29
+
30
+ DashboardEmptyStateBase.displayName = "DashboardEmptyStateBase";
@@ -0,0 +1,14 @@
1
+ import type { FC } from "react";
2
+ import { DashboardEmptyStateBase } from "./DashboardEmptyStateBase.js";
3
+
4
+ export interface EmptySearchResultsProps {
5
+ title?: string;
6
+ body?: string;
7
+ }
8
+
9
+ export const EmptySearchResults: FC<EmptySearchResultsProps> = ({
10
+ title = "No results found",
11
+ body = "To continue, adjust your search or filters and try again",
12
+ }) => <DashboardEmptyStateBase title={title} body={body} />;
13
+
14
+ EmptySearchResults.displayName = "EmptySearchResults";
@@ -0,0 +1,206 @@
1
+ import { css } from "@emotion/css";
2
+ import {
3
+ Card,
4
+ CardBody,
5
+ CardTitle,
6
+ Flex,
7
+ FlexItem,
8
+ MenuToggle,
9
+ type MenuToggleElement,
10
+ SearchInput,
11
+ Select,
12
+ SelectList,
13
+ SelectOption,
14
+ Toolbar,
15
+ ToolbarContent,
16
+ ToolbarItem,
17
+ } from "@patternfly/react-core";
18
+ import { DesktopIcon } from "@patternfly/react-icons";
19
+ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
20
+ import type { FC, Ref } from "react";
21
+ import { CardEmptyState } from "./CardEmptyState.js";
22
+ import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
23
+ import { dashboardStyles, tableFullWidthStyle } from "./dashboardStyles.js";
24
+ import { EmptySearchResults } from "./EmptySearchResults.js";
25
+ import { OsSupportTiersHelpPopover } from "./OsSupportTiersHelpPopover.js";
26
+ import { OsUpgradeNotice } from "./OsUpgradeNotice.js";
27
+ import { OsNameCell } from "./OsUpgradeRecommendationPopover.js";
28
+ import {
29
+ getSupportTierLegendLabel,
30
+ ORDERED_SUPPORT_TIERS,
31
+ type OSDistributionEntry,
32
+ } from "./osSupportTier.js";
33
+ import { SupportTierBadge } from "./SupportTierBadge.js";
34
+ import {
35
+ ALL_TIERS_FILTER,
36
+ useOsBarChartViewModel,
37
+ } from "./useOsBarChartViewModel.js";
38
+
39
+ const filterToolbarStyle = css`
40
+ padding-left: 0;
41
+ padding-right: 0;
42
+ `;
43
+
44
+ const filterItemStyle = css`
45
+ flex: 1 1 0;
46
+ min-width: 0;
47
+ `;
48
+
49
+ const vmsColumnStyle = css`
50
+ text-align: right;
51
+ `;
52
+
53
+ const tableScrollStyle = css`
54
+ overflow: auto;
55
+ `;
56
+
57
+ interface OSDistributionProps {
58
+ osData: Record<string, OSDistributionEntry>;
59
+ isExportMode?: boolean;
60
+ }
61
+
62
+ export const OSDistribution: FC<OSDistributionProps> = ({
63
+ osData,
64
+ isExportMode = false,
65
+ }) => (
66
+ <Card
67
+ className={isExportMode ? dashboardStyles.cardPrint : dashboardStyles.card}
68
+ id="os-distribution"
69
+ >
70
+ <CardTitle>
71
+ <Flex
72
+ alignItems={{ default: "alignItemsCenter" }}
73
+ spaceItems={{ default: "spaceItemsSm" }}
74
+ >
75
+ <FlexItem>
76
+ <DesktopIcon /> Operating Systems
77
+ </FlexItem>
78
+ <FlexItem>
79
+ <OsSupportTiersHelpPopover />
80
+ </FlexItem>
81
+ </Flex>
82
+ </CardTitle>
83
+ <CardBody>
84
+ <OSBarChart osData={osData} isExportMode={isExportMode} />
85
+ </CardBody>
86
+ </Card>
87
+ );
88
+
89
+ interface OSBarChartProps {
90
+ osData: Record<string, OSDistributionEntry>;
91
+ isExportMode?: boolean;
92
+ }
93
+
94
+ export const OSBarChart: FC<OSBarChartProps> = ({
95
+ osData,
96
+ isExportMode = false,
97
+ }) => {
98
+ const vm = useOsBarChartViewModel(osData);
99
+
100
+ if (vm.tableRows.length === 0) {
101
+ return (
102
+ <CardEmptyState title={REPORT_CARD_EMPTY_STATE_TITLES.operatingSystems} />
103
+ );
104
+ }
105
+
106
+ return (
107
+ <>
108
+ {!isExportMode ? (
109
+ <Toolbar className={filterToolbarStyle}>
110
+ <ToolbarContent>
111
+ <ToolbarItem className={filterItemStyle}>
112
+ <SearchInput
113
+ placeholder="Filter by OS"
114
+ value={vm.osFilter}
115
+ onChange={(_event, value) => vm.setOsFilter(value)}
116
+ onClear={vm.clearOsFilter}
117
+ aria-label="Filter operating systems by name"
118
+ />
119
+ </ToolbarItem>
120
+ <ToolbarItem className={filterItemStyle}>
121
+ <Select
122
+ isOpen={vm.isTierSelectOpen}
123
+ selected={vm.tierFilter}
124
+ onSelect={vm.handleTierSelect}
125
+ onOpenChange={vm.setIsTierSelectOpen}
126
+ toggle={(toggleRef: Ref<MenuToggleElement>) => (
127
+ <MenuToggle
128
+ ref={toggleRef}
129
+ isExpanded={vm.isTierSelectOpen}
130
+ onClick={vm.toggleTierSelectOpen}
131
+ style={{ width: "100%" }}
132
+ >
133
+ {vm.tierFilterLabel}
134
+ </MenuToggle>
135
+ )}
136
+ aria-label="Filter operating systems by support tier"
137
+ >
138
+ <SelectList>
139
+ <SelectOption value={ALL_TIERS_FILTER}>
140
+ All tiers
141
+ </SelectOption>
142
+ {ORDERED_SUPPORT_TIERS.map((tier) => (
143
+ <SelectOption key={tier} value={tier}>
144
+ {getSupportTierLegendLabel(tier)}
145
+ </SelectOption>
146
+ ))}
147
+ </SelectList>
148
+ </Select>
149
+ </ToolbarItem>
150
+ </ToolbarContent>
151
+ </Toolbar>
152
+ ) : null}
153
+
154
+ {vm.showUpgradeNotice ? <OsUpgradeNotice /> : null}
155
+
156
+ <div
157
+ className={isExportMode ? undefined : tableScrollStyle}
158
+ style={isExportMode ? undefined : { maxHeight: "350px" }}
159
+ >
160
+ <Table
161
+ aria-label="Operating systems"
162
+ variant="compact"
163
+ className={tableFullWidthStyle}
164
+ >
165
+ <Thead>
166
+ <Tr>
167
+ <Th>OS</Th>
168
+ <Th>Tier</Th>
169
+ <Th className={vmsColumnStyle}>VMs</Th>
170
+ </Tr>
171
+ </Thead>
172
+ <Tbody>
173
+ {vm.showNoResults ? (
174
+ <Tr>
175
+ <Td colSpan={3}>
176
+ <EmptySearchResults title="No matching operating system found" />
177
+ </Td>
178
+ </Tr>
179
+ ) : (
180
+ vm.filteredRows.map((row) => (
181
+ <Tr key={row.osName}>
182
+ <Td dataLabel="OS">
183
+ <OsNameCell
184
+ osName={row.osName}
185
+ upgradeRecommendation={row.upgradeRecommendation}
186
+ isExportMode={isExportMode}
187
+ />
188
+ </Td>
189
+ <Td dataLabel="Tier">
190
+ <SupportTierBadge
191
+ tier={row.tier}
192
+ isExportMode={isExportMode}
193
+ />
194
+ </Td>
195
+ <Td dataLabel="VMs" className={vmsColumnStyle}>
196
+ {row.count}
197
+ </Td>
198
+ </Tr>
199
+ ))
200
+ )}
201
+ </Tbody>
202
+ </Table>
203
+ </div>
204
+ </>
205
+ );
206
+ };
@@ -0,0 +1,87 @@
1
+ import { css } from "@emotion/css";
2
+ import { Label } from "@patternfly/react-core";
3
+ import { ExternalLinkAltIcon } from "@patternfly/react-icons";
4
+ import type { FC } from "react";
5
+ import {
6
+ getSupportTierBadgeColor,
7
+ getSupportTierDefinition,
8
+ getSupportTierLegendLabel,
9
+ ORDERED_SUPPORT_TIERS,
10
+ SUPPORT_TIER_LEARN_MORE_URL,
11
+ } from "./osSupportTier.js";
12
+ import PopoverIcon from "./PopoverIcon.js";
13
+
14
+ const tierListStyle = css`
15
+ list-style: none;
16
+ padding: 0;
17
+ margin: 0;
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: var(--pf-t--global--spacer--400);
21
+ `;
22
+
23
+ const tierItemStyle = css`
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--pf-t--global--spacer--100);
27
+ `;
28
+
29
+ const tierBadgeStyle = css`
30
+ width: fit-content;
31
+ max-width: 100%;
32
+ align-self: flex-start;
33
+ `;
34
+
35
+ const tierDescriptionStyle = css`
36
+ margin: 0;
37
+ color: var(--pf-t--global--text--color--regular);
38
+ `;
39
+
40
+ const learnMoreLinkStyle = css`
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: var(--pf-t--global--spacer--xs);
44
+ margin-top: var(--pf-t--global--spacer--400);
45
+ `;
46
+
47
+ const OsSupportTiersHelpBody: FC = () => (
48
+ <div>
49
+ <ul className={tierListStyle}>
50
+ {ORDERED_SUPPORT_TIERS.map((tier) => (
51
+ <li key={tier} className={tierItemStyle}>
52
+ <Label
53
+ className={tierBadgeStyle}
54
+ color={getSupportTierBadgeColor(tier)}
55
+ isCompact
56
+ >
57
+ {getSupportTierLegendLabel(tier)}
58
+ </Label>
59
+ <p className={tierDescriptionStyle}>
60
+ {getSupportTierDefinition(tier)}
61
+ </p>
62
+ </li>
63
+ ))}
64
+ </ul>
65
+ <a
66
+ className={learnMoreLinkStyle}
67
+ href={SUPPORT_TIER_LEARN_MORE_URL}
68
+ target="_blank"
69
+ rel="noopener noreferrer"
70
+ >
71
+ Learn more <ExternalLinkAltIcon />
72
+ </a>
73
+ </div>
74
+ );
75
+
76
+ export const OsSupportTiersHelpPopover: FC = () => (
77
+ <PopoverIcon
78
+ noVerticalAlign
79
+ maxWidth="40rem"
80
+ headerContent="Guest OS support tiers"
81
+ bodyContent={<OsSupportTiersHelpBody />}
82
+ buttonOuiaId="guest-os-support-tiers-help"
83
+ aria-label="Guest OS support tiers help"
84
+ />
85
+ );
86
+
87
+ OsSupportTiersHelpPopover.displayName = "OsSupportTiersHelpPopover";
@@ -0,0 +1,29 @@
1
+ import { css } from "@emotion/css";
2
+ import { Content, Flex, FlexItem, Icon } from "@patternfly/react-core";
3
+ import { InfoCircleIcon } from "@patternfly/react-icons";
4
+ import type { FC } from "react";
5
+
6
+ const noticeStyle = css`
7
+ margin-bottom: var(--pf-t--global--spacer--200);
8
+ `;
9
+
10
+ export const OsUpgradeNotice: FC = () => (
11
+ <Flex
12
+ alignItems={{ default: "alignItemsCenter" }}
13
+ spaceItems={{ default: "spaceItemsSm" }}
14
+ className={noticeStyle}
15
+ >
16
+ <FlexItem>
17
+ <Icon status="info">
18
+ <InfoCircleIcon />
19
+ </Icon>
20
+ </FlexItem>
21
+ <FlexItem>
22
+ <Content component="p" style={{ fontWeight: 500 }}>
23
+ Some operating systems may need upgrades before migration
24
+ </Content>
25
+ </FlexItem>
26
+ </Flex>
27
+ );
28
+
29
+ OsUpgradeNotice.displayName = "OsUpgradeNotice";
@@ -0,0 +1,78 @@
1
+ import { css } from "@emotion/css";
2
+ import { Button, Flex, FlexItem, Popover } from "@patternfly/react-core";
3
+ import { InfoCircleIcon } from "@patternfly/react-icons";
4
+ import type { FC } from "react";
5
+
6
+ const upgradeRecommendationPopoverCloseButton = css`
7
+ .pf-v6-c-popover__close .pf-v6-c-button.pf-m-plain,
8
+ .pf-v6-c-popover__close .pf-v6-c-button.pf-m-plain:hover {
9
+ color: var(--pf-t--global--text--color--regular);
10
+ }
11
+ `;
12
+
13
+ const infoButtonStyle = css`
14
+ padding: 0;
15
+ vertical-align: middle;
16
+ `;
17
+
18
+ interface OsUpgradeRecommendationPopoverProps {
19
+ upgradeRecommendation: string;
20
+ }
21
+
22
+ export const OsUpgradeRecommendationPopover: FC<
23
+ OsUpgradeRecommendationPopoverProps
24
+ > = ({ upgradeRecommendation }) => (
25
+ <Popover
26
+ className={upgradeRecommendationPopoverCloseButton}
27
+ position="bottom"
28
+ headerContent="Upgrade to get support"
29
+ bodyContent={<div>{upgradeRecommendation}</div>}
30
+ >
31
+ <Button
32
+ type="button"
33
+ aria-label="Open operating system upgrade information"
34
+ variant="plain"
35
+ className={infoButtonStyle}
36
+ >
37
+ <InfoCircleIcon color="var(--pf-t--global--icon--color--status--info--default)" />
38
+ </Button>
39
+ </Popover>
40
+ );
41
+
42
+ OsUpgradeRecommendationPopover.displayName = "OsUpgradeRecommendationPopover";
43
+
44
+ interface OsNameCellProps {
45
+ osName: string;
46
+ upgradeRecommendation?: string;
47
+ isExportMode?: boolean;
48
+ }
49
+
50
+ export const OsNameCell: FC<OsNameCellProps> = ({
51
+ osName,
52
+ upgradeRecommendation,
53
+ isExportMode = false,
54
+ }) => {
55
+ const hasUpgradeRecommendation =
56
+ upgradeRecommendation && upgradeRecommendation.trim() !== "";
57
+
58
+ if (!hasUpgradeRecommendation || isExportMode) {
59
+ return <>{osName}</>;
60
+ }
61
+
62
+ return (
63
+ <Flex
64
+ alignItems={{ default: "alignItemsCenter" }}
65
+ spaceItems={{ default: "spaceItemsXs" }}
66
+ flexWrap={{ default: "nowrap" }}
67
+ >
68
+ <FlexItem>{osName}</FlexItem>
69
+ <FlexItem shrink={{ default: "shrink" }}>
70
+ <OsUpgradeRecommendationPopover
71
+ upgradeRecommendation={upgradeRecommendation}
72
+ />
73
+ </FlexItem>
74
+ </Flex>
75
+ );
76
+ };
77
+
78
+ OsNameCell.displayName = "OsNameCell";