From b68076d18dcaa347c8a081b6724a7c825d6ccf32 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Tue, 19 Mar 2024 12:07:40 -0400 Subject: [PATCH 01/20] Add overall status label to breakdown header https://issues.redhat.com/browse/COST-4761 --- .../breakdown/breakdownHeader.styles.ts | 2 + .../components/breakdown/breakdownHeader.tsx | 4 +- .../clusterInfo/clusterInfo.styles.ts | 2 +- .../clusterInfo/clusterInfo.tsx | 2 +- .../dataDetails/components/cloudIData.tsx | 4 +- .../dataDetails/components/clusterData.tsx | 4 +- .../dataDetails/components/overallStatus.tsx | 73 +++++++++++++------ .../dataDetails/dataDetails.styles.ts | 5 +- .../dataDetails/dataDetails.tsx | 10 +-- .../dataDetails/utils/status.ts | 50 +++++++++++-- 10 files changed, 112 insertions(+), 44 deletions(-) diff --git a/src/routes/details/components/breakdown/breakdownHeader.styles.ts b/src/routes/details/components/breakdown/breakdownHeader.styles.ts index 20a98dc0a..9c872185d 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.styles.ts +++ b/src/routes/details/components/breakdown/breakdownHeader.styles.ts @@ -31,6 +31,8 @@ export const styles = { description: { color: global_disabled_color_100.value, fontSize: global_FontSize_xs.value, + }, + descriptionContainer: { paddingLeft: '1px', }, header: { diff --git a/src/routes/details/components/breakdown/breakdownHeader.tsx b/src/routes/details/components/breakdown/breakdownHeader.tsx index 9a1666ed4..c4d403157 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.tsx +++ b/src/routes/details/components/breakdown/breakdownHeader.tsx @@ -161,8 +161,8 @@ class BreakdownHeader extends React.Component { {intl.formatMessage(messages.breakdownTitle, { value: title })} {description && ( -
- {description} +
+ {description} {clusterInfoComponent && isClusterInfoToggleEnabled ? clusterInfoComponent : null} {dataDetailsComponent && isClusterInfoToggleEnabled ?
{dataDetailsComponent}
: null}
diff --git a/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts index a82b201a2..c20bdbf4e 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts @@ -5,7 +5,7 @@ import global_warning_color_100 from '@patternfly/react-tokens/dist/js/global_wa import type React from 'react'; export const styles = { - clusterInfo: { + clusterInfoButton: { fontSize: global_FontSize_xs.value, }, loading: { diff --git a/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx index fb74fcac8..6c210ff74 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx @@ -30,7 +30,7 @@ const ClusterInfo: React.FC = ({ clusterId }: ClusterInfoProps return ( <> - diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx index d3ed48b1c..9ca896c53 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx @@ -31,10 +31,10 @@ const CloudIData: React.FC = ({ provider }: CloudDataProps) => { {intl.formatMessage(messages.dataDetailsCloudIntegrationStatus)}
diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx index a4fa4a099..17a1b61b6 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx @@ -35,10 +35,10 @@ const ClusterData: React.FC = ({ provider }: ClusterDataProps) > {intl.formatMessage(messages.dataDetailsIntegrationStatus)}
diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx index 69a2a45e4..af5eb5dae 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx @@ -3,6 +3,8 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { AxiosError } from 'axios/index'; import React from 'react'; +import type { MessageDescriptor } from 'react-intl'; +import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { styles } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles'; import { getOverallStatusIcon } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon'; @@ -31,6 +33,7 @@ type OverallStatusProps = OverallStatusOwnProps; const OverallStatus: React.FC = ({ clusterId }: OverallStatusProps) => { const { providers, providersError } = useMapToProps(); + const intl = useIntl(); if (!providers || providersError) { return null; @@ -41,7 +44,8 @@ const OverallStatus: React.FC = ({ clusterId }: OverallStatu const clusterProvider = ocpProviders?.data?.find(val => val.authentication?.credentials?.cluster_id === clusterId); const cloudProvider = providers?.data?.find(val => val.uuid === clusterProvider?.infrastructure?.uuid); - const getOverallStatus = () => { + const getOverallStatus = (): { msg: MessageDescriptor; status: StatusType } => { + let msg; let status; const cloudAvailability = getProviderAvailability(cloudProvider); @@ -49,28 +53,55 @@ const OverallStatus: React.FC = ({ clusterId }: OverallStatu const cloudStatus = getProviderStatus(cloudProvider); const clusterStatus = getProviderStatus(clusterProvider); - if (cloudAvailability === StatusType.failed || clusterAvailability === StatusType.failed) { - status = StatusType.failed; - } else if (cloudStatus === StatusType.failed || clusterStatus === StatusType.failed) { - status = 'failed'; - } else if (cloudAvailability === StatusType.paused || clusterAvailability === StatusType.paused) { - status = 'paused'; - } else if (cloudStatus === StatusType.inProgress || clusterStatus === StatusType.inProgress) { - status = 'in_progress'; - } else if (cloudStatus === StatusType.pending || clusterStatus === StatusType.pending) { - status = 'pending'; - } else if ( - cloudStatus === StatusType.complete && - clusterStatus === StatusType.complete && - cloudAvailability === StatusType.complete && - clusterAvailability === StatusType.complete - ) { - status = 'complete'; - } - return status; + const initializeState = (statusType: StatusType, state1, state2, state3, state4) => { + if (msg && status) { + return; + } + if (statusType === StatusType.complete) { + if ( + state1.status === statusType && + state2.status === statusType && + state3.status === statusType && + state4.status === statusType + ) { + msg = state1.msg; + status = statusType; + } + } else { + if (state1.status === statusType) { + msg = state1.msg; + status = statusType; + } else if (state2.status === statusType) { + msg = state2.msg; + status = statusType; + } else if (state3.status === statusType) { + msg = state3.msg; + status = statusType; + } else if (state4.status === statusType) { + msg = state4.msg; + status = statusType; + } + } + }; + + // Note: status is not synchronous; however, status shall be applied in order provided below (e.g., failed takes precedence over any other state). + // Cloud availability takes precedence over cloud status, while cluster availability takes precedence over cluster status, and so on... + initializeState(StatusType.failed, cloudAvailability, clusterAvailability, cloudStatus, clusterStatus); + initializeState(StatusType.paused, cloudAvailability, clusterAvailability, cloudStatus, clusterStatus); + initializeState(StatusType.inProgress, cloudAvailability, clusterAvailability, cloudStatus, clusterStatus); // Availability won't likely have in-progress and pending states + initializeState(StatusType.pending, cloudAvailability, clusterAvailability, cloudStatus, clusterStatus); + initializeState(StatusType.complete, clusterStatus, cloudStatus, clusterAvailability, cloudAvailability); // Must display the cluster status msg here + + return { msg, status }; }; - return {getOverallStatusIcon(getOverallStatus())}; + const overallStatus = getOverallStatus(); + return ( + <> + {getOverallStatusIcon(overallStatus.status)} + {intl.formatMessage(overallStatus.msg)} + + ); }; // eslint-disable-next-line no-empty-pattern diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts index 4f7833cec..33f39d5aa 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts @@ -9,7 +9,6 @@ import type React from 'react'; export const styles = { dataDetailsButton: { fontSize: global_FontSize_xs.value, - paddingLeft: global_spacer_sm.value, }, description: { color: global_disabled_color_100.value, @@ -25,8 +24,8 @@ export const styles = { marginRight: global_spacer_md.value, }, statusIcon: { - paddingLeft: '1px', - paddingRight: '5px', + fontSize: global_FontSize_xs.value, + paddingRight: global_spacer_sm.value, }, stepper: { margin: global_spacer_lg.value, diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx index 448fae66a..d9dbd8090 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx @@ -31,12 +31,10 @@ const DataDetails: React.FC = ({ clusterId }: DataDetailsProps return ( <> -
- - -
+ + diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts index e82b3f938..69dc6eb37 100644 --- a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts @@ -1,4 +1,7 @@ import type { Provider } from 'api/providers'; +import { ProviderType } from 'api/providers'; +import messages from 'locales/messages'; +import type { MessageDescriptor } from 'react-intl'; import { normalize } from 'routes/details/ocpBreakdown/providerDetails/utils/normailize'; // eslint-disable-next-line no-shadow @@ -27,12 +30,17 @@ export const lookupKey = (value: string) => { } }; -export const getProviderAvailability = (provider: Provider) => { +export const getProviderAvailability = (provider: Provider): { msg: MessageDescriptor; status: StatusType } => { let status; if (!provider) { return status; } + const msg = + provider.source_type === ProviderType.ocp + ? messages.dataDetailsIntegrationStatus + : messages.dataDetailsCloudIntegrationStatus; + if (provider.active === false && provider.paused === false) { status = StatusType.failed; // Inactive sources } else if (provider.paused === true) { @@ -40,11 +48,37 @@ export const getProviderAvailability = (provider: Provider) => { } else { status = StatusType.complete; } - return status; + return { msg, status }; +}; + +const getProviderStatusMsg = ( + { + downloadState, + processingState, + summaryState, + }: { + downloadState: StatusType; + processingState: StatusType; + summaryState: StatusType; + }, + status: StatusType +): MessageDescriptor => { + let msg; + + // We don't have a separate messages for cloud and on-prem + if (downloadState === status) { + msg = messages.dataDetailsRetrieval; + } else if (processingState === status) { + msg = messages.dataDetailsProcessing; + } else if (summaryState === status) { + msg = messages.dataDetailsIntegrationAndFinalization; + } + return msg; }; -export const getProviderStatus = (provider: Provider) => { +export const getProviderStatus = (provider: Provider): { msg: MessageDescriptor; status: StatusType } => { let status; + let msg; if (!provider) { return status; } @@ -59,24 +93,28 @@ export const getProviderStatus = (provider: Provider) => { summaryState === StatusType.failed ) { status = StatusType.failed; + msg = getProviderStatusMsg({ downloadState, processingState, summaryState }, StatusType.failed); } else if ( downloadState === StatusType.inProgress || processingState === StatusType.inProgress || summaryState === StatusType.inProgress ) { status = StatusType.inProgress; + msg = getProviderStatusMsg({ downloadState, processingState, summaryState }, StatusType.inProgress); } else if ( downloadState === StatusType.pending || processingState === StatusType.pending || summaryState === StatusType.pending ) { status = StatusType.pending; + msg = getProviderStatusMsg({ downloadState, processingState, summaryState }, StatusType.pending); } else if ( - downloadState === StatusType.complete || - processingState === StatusType.complete || + downloadState === StatusType.complete && + processingState === StatusType.complete && summaryState === StatusType.complete ) { status = StatusType.complete; + msg = messages.dataDetailsIntegrationAndFinalization; // only one final step } - return status; + return { msg, status }; }; From 4676d3b8f373032b07ec387e4ebf7ce5fe22d4ea Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Tue, 19 Mar 2024 18:43:35 -0400 Subject: [PATCH 02/20] Updated text to delete child tags --- locales/data.json | 26 ++++++++++++++++--- locales/translations.json | 6 +++-- src/locales/messages.ts | 18 ++++++++++--- .../deleteTagMapping/deleteTagMapping.tsx | 13 +++++++--- .../deleteTagMappingAction.tsx | 1 + 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/locales/data.json b/locales/data.json index 74600a0ac..aaedaa779 100644 --- a/locales/data.json +++ b/locales/data.json @@ -12209,13 +12209,13 @@ "tagMappingDelete": [ { "type": 0, - "value": "Delete tag mapping" + "value": "Delete tag mapping?" } ], "tagMappingDeleteDesc": [ { "type": 0, - "value": "This action will remove the " + "value": "Deleting " }, { "type": 1, @@ -12223,7 +12223,7 @@ }, { "type": 0, - "value": " tag mapping. Changes will be reflected within 24 hours." + "value": " will queue a resummarization. Changes will be reflected within 24 hours." } ], "tagMappingDesc": [ @@ -12244,6 +12244,26 @@ "value": "learnMore" } ], + "tagMappingRemove": [ + { + "type": 0, + "value": "Remove child tag?" + } + ], + "tagMappingRemoveDesc": [ + { + "type": 0, + "value": "Removing " + }, + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": " will queue a resummarization. Changes will be reflected within 24 hours." + } + ], "tagMappingSelectChildTags": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index c1e813d7a..dcda8ced9 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -560,9 +560,11 @@ "tagLabelsMap": "Map tags and labels", "tagMappingAddChildTags": "Add child tags", "tagMappingAddChildTagsDesc": "Select additional tag key(s) that will be mapped to the {value} tag map. Tags that have been already mapped will not be available for selection.", - "tagMappingDelete": "Delete tag mapping", - "tagMappingDeleteDesc": "This action will remove the {value} tag mapping. Changes will be reflected within 24 hours.", + "tagMappingDelete": "Delete tag mapping?", + "tagMappingDeleteDesc": "Deleting {value} will queue a resummarization. Changes will be reflected within 24 hours.", "tagMappingDesc": "Combine multiple tags across your cloud integrations to group and filter similar tags with one tag key. {warning} Changes will be reflected within 24 hours. {learnMore}", + "tagMappingRemove": "Remove child tag?", + "tagMappingRemoveDesc": "Removing {value} will queue a resummarization. Changes will be reflected within 24 hours.", "tagMappingSelectChildTags": "Select child tags", "tagMappingSelectChildTagsDesc": "Select the child tags that you want to map to a parent key. Tags that have been already mapped will not be available for selection. {learnMore}", "tagMappingSelectParentTags": "Select parent tag", diff --git a/src/locales/messages.ts b/src/locales/messages.ts index a36591085..3fa3212d4 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -3441,13 +3441,13 @@ export default defineMessages({ id: 'tagMappingAddChildTagsDesc', }, tagMappingDelete: { - defaultMessage: 'Delete tag mapping', - description: 'Delete tag mapping', + defaultMessage: 'Delete tag mapping?', + description: 'Delete tag mapping?', id: 'tagMappingDelete', }, tagMappingDeleteDesc: { - defaultMessage: 'This action will remove the {value} tag mapping. Changes will be reflected within 24 hours.', - description: 'This action will remove the {value} tag mapping. Changes will be reflected within 24 hours.', + defaultMessage: 'Deleting {value} will queue a resummarization. Changes will be reflected within 24 hours.', + description: 'Deleting {value} will queue a resummarization. Changes will be reflected within 24 hours.', id: 'tagMappingDeleteDesc', }, tagMappingDesc: { @@ -3457,6 +3457,16 @@ export default defineMessages({ 'Combine multiple tags across your cloud integrations to group and filter similar tags with one tag key. {warning} Changes will be reflected within 24 hours. {learnMore}', id: 'tagMappingDesc', }, + tagMappingRemove: { + defaultMessage: 'Remove child tag?', + description: 'Remove child tag?', + id: 'tagMappingRemove', + }, + tagMappingRemoveDesc: { + defaultMessage: 'Removing {value} will queue a resummarization. Changes will be reflected within 24 hours.', + description: 'Removing {value} will queue a resummarization. Changes will be reflected within 24 hours.', + id: 'tagMappingRemoveDesc', + }, tagMappingSelectChildTags: { defaultMessage: 'Select child tags', description: 'Select child tags', diff --git a/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx index d6585bbdf..c6a3b849d 100644 --- a/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx +++ b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx @@ -17,6 +17,7 @@ import { settingsActions, settingsSelectors } from 'store/settings'; interface DeleteTagMappingOwnProps { isOpen?: boolean; + isChild?: boolean; item: SettingsData; onClose?: () => void; settingsType: SettingsType; @@ -33,7 +34,7 @@ interface DeleteTagMappingStateProps { type DeleteTagMappingProps = DeleteTagMappingOwnProps; -const DeleteTagMapping: React.FC = ({ isOpen, item, onClose, settingsType }) => { +const DeleteTagMapping: React.FC = ({ isOpen, isChild, item, onClose, settingsType }) => { const [isFinish, setIsFinish] = useState(false); const { settingsUpdateError, settingsUpdateStatus } = useMapToProps({ settingsType }); @@ -64,13 +65,17 @@ const DeleteTagMapping: React.FC = ({ isOpen, item, onClo - {intl.formatMessage(messages.tagMappingDeleteDesc, { value: {item.key} })} + + {intl.formatMessage(isChild ? messages.tagMappingRemoveDesc : messages.tagMappingDeleteDesc, { + value: {item.key}, + })} +
}> - + + + diff --git a/src/routes/settings/costModels/components/readOnlyTooltip.tsx b/src/routes/settings/costModels/components/readOnlyTooltip.tsx index e6cd88531..dc6f05faa 100644 --- a/src/routes/settings/costModels/components/readOnlyTooltip.tsx +++ b/src/routes/settings/costModels/components/readOnlyTooltip.tsx @@ -1,25 +1,34 @@ import { Tooltip } from '@patternfly/react-core'; import messages from 'locales/messages'; import React from 'react'; +import type { MessageDescriptor } from 'react-intl'; import { useIntl } from 'react-intl'; interface ReadOnlyTooltipBase { - tooltip?: string; + defaultMsg?: MessageDescriptor; children: JSX.Element; - isDisabled: boolean; + isDisabled?: boolean; } -const ReadOnlyTooltip: React.FC = ({ children, tooltip, isDisabled }) => { +const ReadOnlyTooltip: React.FC = ({ children, defaultMsg, isDisabled }) => { const intl = useIntl(); - const content = tooltip ? tooltip : intl.formatMessage(messages.readOnlyPermissions); - return isDisabled ? ( - {content}
}> -
{children}
- - ) : ( - children - ); + const getChildren = () => { + if (isDisabled) { + return
{children}
; + } + return children; + }; + + if (defaultMsg || isDisabled) { + const msg = intl.formatMessage(isDisabled ? messages.readOnly : defaultMsg); + return ( + + {getChildren()} + + ); + } + return children; }; export { ReadOnlyTooltip }; diff --git a/src/routes/settings/costModels/costModel/sourcesTable.tsx b/src/routes/settings/costModels/costModel/sourcesTable.tsx index 47378bbc8..03b9ee65c 100644 --- a/src/routes/settings/costModels/costModel/sourcesTable.tsx +++ b/src/routes/settings/costModels/costModel/sourcesTable.tsx @@ -56,7 +56,7 @@ const SourcesTable: React.FC = ({ canWrite, costModels, intl, })} - +