@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.
- package/LICENSE +13 -0
- package/README.md +76 -0
- package/dist/charts/MigrationDonutChart.d.ts +53 -0
- package/dist/charts/MigrationDonutChart.d.ts.map +1 -0
- package/dist/charts/MigrationDonutChart.js +186 -0
- package/dist/charts/index.d.ts +2 -0
- package/dist/charts/index.d.ts.map +1 -0
- package/dist/charts/index.js +1 -0
- package/dist/forms/CheckboxFormGroup.d.ts +5 -0
- package/dist/forms/CheckboxFormGroup.d.ts.map +1 -0
- package/dist/forms/CheckboxFormGroup.js +14 -0
- package/dist/forms/FormFieldHelperText.d.ts +7 -0
- package/dist/forms/FormFieldHelperText.d.ts.map +1 -0
- package/dist/forms/FormFieldHelperText.js +8 -0
- package/dist/forms/RadioButtonFormGroup.d.ts +12 -0
- package/dist/forms/RadioButtonFormGroup.d.ts.map +1 -0
- package/dist/forms/RadioButtonFormGroup.js +12 -0
- package/dist/forms/SelectFormGroup.d.ts +8 -0
- package/dist/forms/SelectFormGroup.d.ts.map +1 -0
- package/dist/forms/SelectFormGroup.js +12 -0
- package/dist/forms/TextAreaFormGroup.d.ts +3 -0
- package/dist/forms/TextAreaFormGroup.d.ts.map +1 -0
- package/dist/forms/TextAreaFormGroup.js +12 -0
- package/dist/forms/TextInputFormGroup.d.ts +11 -0
- package/dist/forms/TextInputFormGroup.d.ts.map +1 -0
- package/dist/forms/TextInputFormGroup.js +16 -0
- package/dist/forms/index.d.ts +8 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms/index.js +6 -0
- package/dist/forms/types.d.ts +11 -0
- package/dist/forms/types.d.ts.map +1 -0
- package/dist/forms/types.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/report/CardEmptyState.d.ts +8 -0
- package/dist/report/CardEmptyState.d.ts.map +1 -0
- package/dist/report/CardEmptyState.js +13 -0
- package/dist/report/DashboardEmptyStateBase.d.ts +8 -0
- package/dist/report/DashboardEmptyStateBase.d.ts.map +1 -0
- package/dist/report/DashboardEmptyStateBase.js +5 -0
- package/dist/report/EmptySearchResults.d.ts +7 -0
- package/dist/report/EmptySearchResults.d.ts.map +1 -0
- package/dist/report/EmptySearchResults.js +4 -0
- package/dist/report/OSDistribution.d.ts +14 -0
- package/dist/report/OSDistribution.d.ts.map +1 -0
- package/dist/report/OSDistribution.js +37 -0
- package/dist/report/OsSupportTiersHelpPopover.d.ts +3 -0
- package/dist/report/OsSupportTiersHelpPopover.d.ts.map +1 -0
- package/dist/report/OsSupportTiersHelpPopover.js +37 -0
- package/dist/report/OsUpgradeNotice.d.ts +3 -0
- package/dist/report/OsUpgradeNotice.d.ts.map +1 -0
- package/dist/report/OsUpgradeNotice.js +9 -0
- package/dist/report/OsUpgradeRecommendationPopover.d.ts +13 -0
- package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -0
- package/dist/report/OsUpgradeRecommendationPopover.js +24 -0
- package/dist/report/PopoverIcon.d.ts +11 -0
- package/dist/report/PopoverIcon.d.ts.map +1 -0
- package/dist/report/PopoverIcon.js +8 -0
- package/dist/report/SupportTierBadge.d.ts +9 -0
- package/dist/report/SupportTierBadge.d.ts.map +1 -0
- package/dist/report/SupportTierBadge.js +23 -0
- package/dist/report/constants.d.ts +11 -0
- package/dist/report/constants.d.ts.map +1 -0
- package/dist/report/constants.js +12 -0
- package/dist/report/dashboardStyles.d.ts +20 -0
- package/dist/report/dashboardStyles.d.ts.map +1 -0
- package/dist/report/dashboardStyles.js +99 -0
- package/dist/report/index.d.ts +15 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +13 -0
- package/dist/report/osSupportTier.d.ts +42 -0
- package/dist/report/osSupportTier.d.ts.map +1 -0
- package/dist/report/osSupportTier.js +106 -0
- package/dist/report/useOsBarChartViewModel.d.ts +26 -0
- package/dist/report/useOsBarChartViewModel.d.ts.map +1 -0
- package/dist/report/useOsBarChartViewModel.js +59 -0
- package/package.json +63 -0
- package/src/charts/MigrationDonutChart.tsx +398 -0
- package/src/charts/index.ts +5 -0
- package/src/forms/CheckboxFormGroup.tsx +53 -0
- package/src/forms/FormFieldHelperText.tsx +32 -0
- package/src/forms/RadioButtonFormGroup.tsx +62 -0
- package/src/forms/SelectFormGroup.tsx +57 -0
- package/src/forms/TextAreaFormGroup.tsx +43 -0
- package/src/forms/TextInputFormGroup.tsx +63 -0
- package/src/forms/index.ts +7 -0
- package/src/forms/types.ts +11 -0
- package/src/index.ts +3 -0
- package/src/report/CardEmptyState.tsx +30 -0
- package/src/report/DashboardEmptyStateBase.tsx +30 -0
- package/src/report/EmptySearchResults.tsx +14 -0
- package/src/report/OSDistribution.tsx +206 -0
- package/src/report/OsSupportTiersHelpPopover.tsx +87 -0
- package/src/report/OsUpgradeNotice.tsx +29 -0
- package/src/report/OsUpgradeRecommendationPopover.tsx +78 -0
- package/src/report/PopoverIcon.tsx +46 -0
- package/src/report/SupportTierBadge.tsx +55 -0
- package/src/report/__tests__/OSDistribution.test.tsx +236 -0
- package/src/report/__tests__/osSupportTier.test.ts +101 -0
- package/src/report/constants.ts +14 -0
- package/src/report/dashboardStyles.ts +116 -0
- package/src/report/index.ts +54 -0
- package/src/report/osSupportTier.ts +161 -0
- 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,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";
|