From e36dd8d401c7c9be88053e6dfa4d4f4171fd8546 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 3 Oct 2024 13:40:23 -0400 Subject: [PATCH 01/22] Details pages: Show previous data when current month is unavailable https://issues.redhat.com/browse/COST-5563 --- locales/data.json | 14 +++ locales/translations.json | 1 + .../featureToggle/featureToggle.tsx | 8 ++ src/locales/messages.ts | 5 + .../historicalTrendChart.tsx | 7 +- src/routes/components/export/exportModal.tsx | 21 ++-- src/routes/components/groupBy/groupBy.tsx | 4 +- .../details/awsBreakdown/awsBreakdown.tsx | 7 +- .../details/awsBreakdown/historicalData.tsx | 2 +- .../awsBreakdown/instances/instances.tsx | 4 +- .../instances/instancesToolbar.tsx | 7 +- src/routes/details/awsDetails/awsDetails.tsx | 95 +++++++++++----- .../details/awsDetails/detailsHeader.tsx | 66 ++++++++++- .../details/awsDetails/detailsTable.tsx | 24 +++- .../details/awsDetails/detailsToolbar.tsx | 97 ++++++++-------- .../details/azureBreakdown/azureBreakdown.tsx | 8 +- .../details/azureBreakdown/historicalData.tsx | 2 +- .../details/azureDetails/azureDetails.tsx | 92 +++++++++++----- .../details/azureDetails/detailsHeader.tsx | 66 ++++++++++- .../details/azureDetails/detailsTable.tsx | 24 +++- .../details/azureDetails/detailsToolbar.tsx | 44 ++++---- .../components/breakdown/breakdownBase.tsx | 13 ++- .../components/breakdown/breakdownHeader.tsx | 7 +- .../components/dateRange/dateRange.tsx | 83 ++++++++++++++ .../details/components/dateRange/index.ts | 1 + .../historicalData/historicalDataBase.tsx | 16 ++- .../historicalDataCostChart.tsx | 7 +- .../historicalDataNetworkChart.tsx | 7 +- .../historicalDataTrendChart.tsx | 7 +- .../historicalDataUsageChart.tsx | 7 +- .../historicalDataVolumeChart.tsx | 7 +- .../components/pvcChart/modal/pvcContent.tsx | 11 +- .../details/components/pvcChart/pvcChart.tsx | 8 +- .../summary/modal/summaryContent.tsx | 6 +- .../components/summary/summaryCard.tsx | 4 +- .../details/components/tag/modal/tagModal.tsx | 4 +- src/routes/details/components/tag/tagLink.tsx | 4 +- .../components/usageChart/usageChart.tsx | 6 +- .../details/gcpBreakdown/gcpBreakdown.tsx | 10 +- .../details/gcpBreakdown/historicalData.tsx | 2 +- .../details/gcpDetails/detailsHeader.tsx | 66 ++++++++++- .../details/gcpDetails/detailsTable.tsx | 24 +++- .../details/gcpDetails/detailsToolbar.tsx | 46 ++++---- src/routes/details/gcpDetails/gcpDetails.tsx | 95 +++++++++++----- .../details/ibmBreakdown/historicalData.tsx | 2 +- .../details/ibmBreakdown/ibmBreakdown.tsx | 8 +- .../details/ibmDetails/detailsHeader.tsx | 66 ++++++++++- .../details/ibmDetails/detailsTable.tsx | 24 +++- .../details/ibmDetails/detailsToolbar.tsx | 46 ++++---- src/routes/details/ibmDetails/ibmDetails.tsx | 94 +++++++++++----- .../details/ociBreakdown/historicalData.tsx | 2 +- .../details/ociBreakdown/ociBreakdown.tsx | 8 +- .../details/ociDetails/detailsHeader.tsx | 64 ++++++++++- .../details/ociDetails/detailsTable.tsx | 24 +++- .../details/ociDetails/detailsToolbar.tsx | 44 ++++---- src/routes/details/ociDetails/ociDetails.tsx | 96 +++++++++++----- .../details/ocpBreakdown/historicalData.tsx | 2 +- .../details/ocpBreakdown/ocpBreakdown.tsx | 12 +- .../details/ocpDetails/detailsHeader.tsx | 63 ++++++++++- .../details/ocpDetails/detailsTable.tsx | 21 +++- .../details/ocpDetails/detailsToolbar.tsx | 44 ++++---- src/routes/details/ocpDetails/ocpDetails.tsx | 104 ++++++++++++------ .../details/rhelBreakdown/historicalData.tsx | 2 +- .../details/rhelBreakdown/rhelBreakdown.tsx | 8 +- .../details/rhelDetails/detailsHeader.tsx | 64 ++++++++++- .../details/rhelDetails/detailsTable.tsx | 21 +++- .../details/rhelDetails/detailsToolbar.tsx | 44 ++++---- .../details/rhelDetails/rhelDetails.tsx | 102 +++++++++++------ src/routes/utils/timeScope.ts | 11 ++ .../__snapshots__/featureToggle.test.ts.snap | 1 + .../featureToggle/featureToggleActions.ts | 1 + .../featureToggle/featureToggleReducer.ts | 3 + .../featureToggle/featureToggleSelectors.ts | 2 + src/utils/dates.ts | 46 +++++++- 74 files changed, 1540 insertions(+), 528 deletions(-) create mode 100644 src/routes/details/components/dateRange/dateRange.tsx create mode 100644 src/routes/details/components/dateRange/index.ts create mode 100644 src/routes/utils/timeScope.ts diff --git a/locales/data.json b/locales/data.json index fbf9ac6ee..6a3a556cd 100644 --- a/locales/data.json +++ b/locales/data.json @@ -10880,6 +10880,20 @@ "value": "No" } ], + "noCurrentData": [ + { + "type": 0, + "value": "No data available for " + }, + { + "type": 1, + "value": "dateRange" + }, + { + "type": 0, + "value": ". You are viewing data for the previous month." + } + ], "noDataForDate": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index c6cb32396..bc07615a3 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -403,6 +403,7 @@ "networkUnattributedDistributedDesc": "Costs associated with ingress and egress network traffic for individual nodes.", "next": "next", "no": "No", + "noCurrentData": "No data available for {dateRange}. You are viewing data for the previous month.", "noDataForDate": "No data available for {dateRange}", "noDataStateDesc": "We have detected an integration, but we are not done processing the incoming data. {status}The time to process could take up to 24 hours. Try refreshing the page at a later time.", "noDataStateRefresh": "Refresh this page", diff --git a/src/components/featureToggle/featureToggle.tsx b/src/components/featureToggle/featureToggle.tsx index a9b74de2f..6d5c7c48d 100644 --- a/src/components/featureToggle/featureToggle.tsx +++ b/src/components/featureToggle/featureToggle.tsx @@ -8,6 +8,7 @@ export const enum FeatureToggle { accountInfoEmptyState = 'cost-management.ui.account-info-empty-state', // https://issues.redhat.com/browse/COST-5335 awsEc2Instances = 'cost-management.ui.aws-ec2-instances', // https://issues.redhat.com/browse/COST-4855 debug = 'cost-management.ui.debug', + detailsDateRange = 'cost-management.ui.details-date-range', // https://issues.redhat.com/browse/COST-5563 exports = 'cost-management.ui.exports', // Async exports https://issues.redhat.com/browse/COST-2223 finsights = 'cost-management.ui.finsights', // RHEL support for FINsights https://issues.redhat.com/browse/COST-3306 ibm = 'cost-management.ui.ibm', // IBM https://issues.redhat.com/browse/COST-935 @@ -31,6 +32,10 @@ export const useIsDebugToggleEnabled = () => { return useIsToggleEnabled(FeatureToggle.debug); }; +export const useIsDetailsDateRangeToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.detailsDateRange); +}; + export const useIsExportsToggleEnabled = () => { return useIsToggleEnabled(FeatureToggle.exports); }; @@ -55,6 +60,7 @@ export const useFeatureToggle = () => { const isAccountInfoEmptyStateToggleEnabled = useIsAccountInfoEmptyStateToggleEnabled(); const isAwsEc2InstancesToggleEnabled = useIsAwsEc2InstancesToggleEnabled(); const isDebugToggleEnabled = useIsDebugToggleEnabled(); + const isDetailsDateRangeToggleEnabled = useIsDetailsDateRangeToggleEnabled(); const isExportsToggleEnabled = useIsExportsToggleEnabled(); const isFinsightsToggleEnabled = useIsFinsightsToggleEnabled(); const isIbmToggleEnabled = useIsIbmToggleEnabled(); @@ -73,6 +79,7 @@ export const useFeatureToggle = () => { isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled, isDebugToggleEnabled, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, isFinsightsToggleEnabled, isIbmToggleEnabled, @@ -87,6 +94,7 @@ export const useFeatureToggle = () => { isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled, isDebugToggleEnabled, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, isFinsightsToggleEnabled, isIbmToggleEnabled, diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 7322e8d9f..2b8e29f99 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -2586,6 +2586,11 @@ export default defineMessages({ description: 'No', id: 'no', }, + noCurrentData: { + defaultMessage: 'No data available for {dateRange}. You are viewing data for the previous month.', + description: 'No data available for Jan 1-31. You are viewing data for the previous month.', + id: 'noCurrentData', + }, noDataForDate: { defaultMessage: 'No data available for {dateRange}', description: 'No data available for Jan 1-31', diff --git a/src/routes/components/charts/historicalTrendChart/historicalTrendChart.tsx b/src/routes/components/charts/historicalTrendChart/historicalTrendChart.tsx index ed3710ebe..a1c7d80ae 100644 --- a/src/routes/components/charts/historicalTrendChart/historicalTrendChart.tsx +++ b/src/routes/components/charts/historicalTrendChart/historicalTrendChart.tsx @@ -291,7 +291,12 @@ class HistoricalTrendChartBase extends React.Component { return this.getChart(s, index); })} - + diff --git a/src/routes/components/export/exportModal.tsx b/src/routes/components/export/exportModal.tsx index 3faa1c1ef..d6f65484b 100644 --- a/src/routes/components/export/exportModal.tsx +++ b/src/routes/components/export/exportModal.tsx @@ -45,9 +45,11 @@ export interface ExportModalOwnProps { showAggregateType?: boolean; // Monthly resolution filters are not valid with date range showFormatType?: boolean; // Format type; CVS / JSON showTimeScope?: boolean; // timeScope filters are not valid with date range + timeScopeValue?: number; } interface ExportModalStateProps { + isDetailsDateRangeToggleEnabled?: boolean; isExportsToggleEnabled?: boolean; } @@ -89,16 +91,17 @@ export class ExportModalBase extends React.Component(state => { return { + isDetailsDateRangeToggleEnabled: FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state), isExportsToggleEnabled: FeatureToggleSelectors.selectIsExportsToggleEnabled(state), }; }); diff --git a/src/routes/components/groupBy/groupBy.tsx b/src/routes/components/groupBy/groupBy.tsx index 62ac07b80..7bb0378a6 100644 --- a/src/routes/components/groupBy/groupBy.tsx +++ b/src/routes/components/groupBy/groupBy.tsx @@ -16,6 +16,7 @@ import type { SelectWrapperOption } from 'routes/components/selectWrapper'; import { SelectWrapper } from 'routes/components/selectWrapper'; import type { PerspectiveType } from 'routes/explorer/explorerUtils'; import { getDateRangeFromQuery } from 'routes/utils/dateRange'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; import { orgActions, orgSelectors } from 'store/orgs'; @@ -326,13 +327,14 @@ class GroupByBase extends React.Component { const mapStateToProps = createMapStateToProps( (state, { orgPathsType, router, resourcePathsType, tagPathsType }) => { const queryFromRoute = parseQuery(router.location.search); + const timeScopeValue = getTimeScopeValue(queryFromRoute); // Default to current month filter for details pages let tagFilter: any = { filter: { resolution: 'monthly', time_scope_units: 'month', - time_scope_value: -1, + time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, }, }; diff --git a/src/routes/details/awsBreakdown/awsBreakdown.tsx b/src/routes/details/awsBreakdown/awsBreakdown.tsx index ebb70982e..983283e4f 100644 --- a/src/routes/details/awsBreakdown/awsBreakdown.tsx +++ b/src/routes/details/awsBreakdown/awsBreakdown.tsx @@ -15,6 +15,7 @@ import { BreakdownBase } from 'routes/details/components/breakdown'; import { getGroupById, getGroupByOrgValue, getGroupByValue } from 'routes/utils/groupBy'; import { filterProviders } from 'routes/utils/providers'; import { getQueryState } from 'routes/utils/queryState'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; @@ -56,6 +57,7 @@ const mapStateToProps = createMapStateToProps, + historicalDataComponent: , instancesComponent: groupBy === serviceKey && groupByValue === 'AmazonEC2' ? ( @@ -139,6 +141,7 @@ const mapStateToProps = createMapStateToProps { const dispatch: ThunkDispatch = useDispatch(); const queryFromRoute = useQueryFromRoute(); const queryState = useQueryState('details'); + const timeScopeValue = getTimeScopeValue(queryState); const reportQuery = { cost_type: costType, @@ -341,7 +343,7 @@ const useMapToProps = ({ costType, currency, query }): InstancesStateProps => { ...(query.filter || baseQuery.filter), resolution: 'monthly', time_scope_units: 'month', - time_scope_value: -1, + time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, }, filter_by: { // Add filters here to apply logical OR/AND diff --git a/src/routes/details/awsBreakdown/instances/instancesToolbar.tsx b/src/routes/details/awsBreakdown/instances/instancesToolbar.tsx index fd3d60f96..2903c3e3b 100644 --- a/src/routes/details/awsBreakdown/instances/instancesToolbar.tsx +++ b/src/routes/details/awsBreakdown/instances/instancesToolbar.tsx @@ -13,9 +13,11 @@ import { DataToolbar } from 'routes/components/dataToolbar'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { isEqual } from 'routes/utils/equal'; import type { Filter } from 'routes/utils/filter'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; import { tagActions, tagSelectors } from 'store/tags'; +import { useQueryState } from 'utils/hooks'; import { accountKey, regionKey, tagKey } from 'utils/props'; interface InstancesToolbarOwnProps { @@ -187,13 +189,16 @@ export class InstancesToolbarBase extends React.Component((state, props) => { + const queryState = useQueryState('details'); + const timeScopeValue = getTimeScopeValue(queryState); + // Note: Omitting key_only would help to share a single, cached request. Only the toolbar requires key values; // however, for better server-side performance, we chose to use key_only here. const baseQuery = { filter: { resolution: 'monthly', time_scope_units: 'month', - time_scope_value: -1, + time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, }, key_only: true, limit: 1000, diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index 581496b2a..f217c51c6 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -1,6 +1,6 @@ import 'routes/components/dataTable/dataTable.scss'; -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import type { AwsQuery } from 'api/queries/awsQuery'; @@ -25,7 +25,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAwsRe import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getGroupByCostCategory, getGroupByOrgValue, getGroupByTagKey } from 'routes/utils/groupBy'; -import { filterProviders, hasCurrentMonthData } from 'routes/utils/providers'; +import { filterProviders, hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; import { handleOnCostTypeSelect, @@ -36,10 +36,12 @@ import { handleOnSetPage, handleOnSort, } from 'routes/utils/queryNavigate'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; +import { getSinceDateRangeString } from 'utils/dates'; import { formatPath } from 'utils/paths'; import { awsCategoryPrefix, logicalOrPrefix, noPrefix, orgUnitIdKey, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -55,6 +57,9 @@ interface AwsDetailsStateProps { costType: string; currency?: string; isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; + isPreviousMonthData?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -63,6 +68,7 @@ interface AwsDetailsStateProps { reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; + timeScopeValue?: number; } interface AwsDetailsDispatchProps { @@ -109,14 +115,6 @@ class AwsDetails extends React.Component { }; public state: AwsDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleonSelect = this.handleonSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -150,7 +148,7 @@ class AwsDetails extends React.Component { }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -182,6 +180,7 @@ class AwsDetails extends React.Component { reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -216,7 +215,7 @@ class AwsDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -248,12 +247,13 @@ class AwsDetails extends React.Component { report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; private getToolbar = (computedItems: ComputedReportItem[]) => { - const { query, router, report } = this.props; + const { query, router, report, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -283,6 +283,7 @@ class AwsDetails extends React.Component { pagination={this.getPagination(isDisabled)} query={query} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -308,11 +309,11 @@ class AwsDetails extends React.Component { } }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -369,6 +370,9 @@ class AwsDetails extends React.Component { currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, @@ -376,6 +380,7 @@ class AwsDetails extends React.Component { reportError, reportFetchStatus, router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -395,7 +400,7 @@ class AwsDetails extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( { costType={costType} currency={currency} groupBy={groupById} + isCurrentMonthData={isCurrentMonthData} + isPreviousMonthData={isPreviousMonthData} onCostTypeSelect={() => handleOnCostTypeSelect(query, router)} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -439,12 +459,37 @@ class AwsDetails extends React.Component { const mapStateToProps = createMapStateToProps((state, { router }) => { const queryFromRoute = parseQuery(router.location.search); + const costType = getCostType(); const currency = getCurrency(); + // Check for current and previous data first + const providersQueryString = getProvidersQuery(providersQuery); + const providers = providersSelectors.selectProviders(state, ProviderType.all, providersQueryString); + const providersError = providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString); + const providersFetchStatus = providersSelectors.selectProvidersFetchStatus( + state, + ProviderType.all, + providersQueryString + ); + + // Fetch based on time scope value + const filteredProviders = filterProviders(providers, ProviderType.aws); + const isCurrentMonthData = hasCurrentMonthData(filteredProviders); + const isDetailsDateRangeToggleEnabled = FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state); + + let timeScopeValue = getTimeScopeValue(queryFromRoute); + timeScopeValue = Number( + !isCurrentMonthData && isDetailsDateRangeToggleEnabled ? -2 : timeScopeValue !== undefined ? timeScopeValue : -1 + ); + const query: any = { ...baseQuery, ...queryFromRoute, + filter: { + ...queryFromRoute.filter, + time_scope_value: timeScopeValue, + }, }; const reportQuery = { cost_type: costType, @@ -455,7 +500,6 @@ const mapStateToProps = createMapStateToProps { + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; + public state: DetailsHeaderState = { ...this.defaultState }; + private handleOnCostTypeSelect = (value: string) => { const { onCostTypeSelect } = this.props; @@ -70,21 +94,39 @@ class DetailsHeaderBase extends React.Component { } }; + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { costType, currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; - const showContent = report && !providersError && providers?.meta?.count > 0; + const { currentDateRangeType } = this.state; + const showContent = report && !providersError && providers?.meta?.count > 0; const hasCost = report?.meta?.total?.cost?.total; return ( @@ -121,6 +163,17 @@ class DetailsHeaderBase extends React.Component { + {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -132,7 +185,9 @@ class DetailsHeaderBase extends React.Component { hasCost ? report.meta.total.cost.total.units : 'USD' )} -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -154,6 +209,7 @@ const mapStateToProps = createMapStateToProps { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; const value = formatCurrency(Math.abs(item.cost.total.value - item.delta_value), item.cost.total.units); const percentage = item.delta_percent !== null ? formatPercentage(Math.abs(item.delta_percent)) : 0; @@ -247,7 +252,11 @@ class DetailsTableBase extends React.Component @@ -268,7 +277,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/awsDetails/detailsToolbar.tsx b/src/routes/details/awsDetails/detailsToolbar.tsx index d532cad4a..d51d96132 100644 --- a/src/routes/details/awsDetails/detailsToolbar.tsx +++ b/src/routes/details/awsDetails/detailsToolbar.tsx @@ -39,6 +39,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: AwsQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -209,55 +210,61 @@ export class DetailsToolbarBase extends React.Component((state, props) => { - // Note: Omitting key_only would help to share a single, cached request. Only the toolbar requires key values; - // however, for better server-side performance, we chose to use key_only here. - const baseQuery = { - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }; +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue = -1 }) => { + // Note: Omitting key_only would help to share a single, cached request. Only the toolbar requires key values; + // however, for better server-side performance, we chose to use key_only here. + const baseQuery = { + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }; - const resourceQueryString = getQuery({ - key_only: true, - }); - const resourceReport = resourceSelectors.selectResource(state, resourcePathsType, resourceType, resourceQueryString); - const resourceReportFetchStatus = resourceSelectors.selectResourceFetchStatus( - state, - resourcePathsType, - resourceType, - resourceQueryString - ); + const resourceQueryString = getQuery({ + key_only: true, + }); + const resourceReport = resourceSelectors.selectResource( + state, + resourcePathsType, + resourceType, + resourceQueryString + ); + const resourceReportFetchStatus = resourceSelectors.selectResourceFetchStatus( + state, + resourcePathsType, + resourceType, + resourceQueryString + ); - const tagQueryString = getQuery({ - ...baseQuery, - }); - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + const tagQueryString = getQuery({ + ...baseQuery, + }); + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - const orgQueryString = getQuery({ - ...baseQuery, - }); - const orgReport = orgSelectors.selectOrg(state, orgPathsType, orgType, orgQueryString); - const orgReportFetchStatus = orgSelectors.selectOrgFetchStatus(state, orgPathsType, orgType, orgQueryString); + const orgQueryString = getQuery({ + ...baseQuery, + }); + const orgReport = orgSelectors.selectOrg(state, orgPathsType, orgType, orgQueryString); + const orgReportFetchStatus = orgSelectors.selectOrgFetchStatus(state, orgPathsType, orgType, orgQueryString); - return { - orgReport, - orgReportFetchStatus, - orgQueryString, - resourceReport, - resourceReportFetchStatus, - resourceQueryString, - tagReport, - tagReportFetchStatus, - tagQueryString, - }; -}); + return { + orgReport, + orgReportFetchStatus, + orgQueryString, + resourceReport, + resourceReportFetchStatus, + resourceQueryString, + tagReport, + tagReportFetchStatus, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchOrg: orgActions.fetchOrg, diff --git a/src/routes/details/azureBreakdown/azureBreakdown.tsx b/src/routes/details/azureBreakdown/azureBreakdown.tsx index 455fe3c20..3ad1ad55c 100644 --- a/src/routes/details/azureBreakdown/azureBreakdown.tsx +++ b/src/routes/details/azureBreakdown/azureBreakdown.tsx @@ -15,6 +15,7 @@ import { BreakdownBase } from 'routes/details/components/breakdown'; import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; import { filterProviders } from 'routes/utils/providers'; import { getQueryState } from 'routes/utils/queryState'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; @@ -43,7 +44,9 @@ const mapStateToProps = createMapStateToProps, + historicalDataComponent: , providers: filterProviders(providers, ProviderType.azure), providersError, providersFetchStatus, @@ -110,6 +113,7 @@ const mapStateToProps = createMapStateToProps }; public state: AzureDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleOnSelect = this.handleOnSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -144,7 +142,7 @@ class AzureDetails extends React.Component }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -169,6 +167,7 @@ class AzureDetails extends React.Component reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -203,7 +202,7 @@ class AzureDetails extends React.Component }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -225,6 +224,7 @@ class AzureDetails extends React.Component report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -278,11 +278,11 @@ class AzureDetails extends React.Component } }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -328,14 +328,17 @@ class AzureDetails extends React.Component currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, report, reportError, reportFetchStatus, - router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -355,7 +358,7 @@ class AzureDetails extends React.Component if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -399,9 +417,33 @@ const mapStateToProps = createMapStateToProps(router.location.search); const currency = getCurrency(); + // Check for current and previous data first + const providersQueryString = getProvidersQuery(providersQuery); + const providers = providersSelectors.selectProviders(state, ProviderType.all, providersQueryString); + const providersError = providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString); + const providersFetchStatus = providersSelectors.selectProvidersFetchStatus( + state, + ProviderType.all, + providersQueryString + ); + + // Fetch based on time scope value + const filteredProviders = filterProviders(providers, ProviderType.azure); + const isCurrentMonthData = hasCurrentMonthData(filteredProviders); + const isDetailsDateRangeToggleEnabled = FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state); + + let timeScopeValue = getTimeScopeValue(queryFromRoute); + timeScopeValue = Number( + !isCurrentMonthData && isDetailsDateRangeToggleEnabled ? -2 : timeScopeValue !== undefined ? timeScopeValue : -1 + ); + const query: any = { ...baseQuery, ...queryFromRoute, + filter: { + ...queryFromRoute.filter, + time_scope_value: timeScopeValue, + }, }; const reportQuery = { currency, @@ -411,7 +453,6 @@ const mapStateToProps = createMapStateToProps { + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; + public state: DetailsHeaderState = { ...this.defaultState }; + + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; - const showContent = report && !providersError && providers?.meta?.count > 0; + const { currentDateRangeType } = this.state; + const showContent = report && !providersError && providers?.meta?.count > 0; const hasCost = report?.meta?.total?.cost?.total; return ( @@ -100,6 +142,17 @@ class DetailsHeaderBase extends React.Component { />
+ {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -111,7 +164,9 @@ class DetailsHeaderBase extends React.Component { hasCost ? report.meta.total.cost.total.units : 'USD' )} -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -133,6 +188,7 @@ const mapStateToProps = createMapStateToProps { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; const value = formatCurrency(Math.abs(item.cost.total.value - item.delta_value), item.cost.total.units); const percentage = item.delta_percent !== null ? formatPercentage(Math.abs(item.delta_percent)) : 0; @@ -219,7 +224,11 @@ class DetailsTableBase extends React.Component @@ -240,7 +249,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/azureDetails/detailsToolbar.tsx b/src/routes/details/azureDetails/detailsToolbar.tsx index 71575e7ec..0b9559de6 100644 --- a/src/routes/details/azureDetails/detailsToolbar.tsx +++ b/src/routes/details/azureDetails/detailsToolbar.tsx @@ -33,6 +33,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: AzureQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -162,27 +163,28 @@ export class DetailsToolbarBase extends React.Component((state, props) => { - // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values - // However, for better server-side performance, we chose to use key_only here. - const tagQueryString = getQuery({ - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }); - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - return { - tagReportFetchStatus, - tagReport, - tagQueryString, - }; -}); +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue = -1 }) => { + // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values + // However, for better server-side performance, we chose to use key_only here. + const tagQueryString = getQuery({ + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }); + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + return { + tagReportFetchStatus, + tagReport, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchTag: tagActions.fetchTag, diff --git a/src/routes/details/components/breakdown/breakdownBase.tsx b/src/routes/details/components/breakdown/breakdownBase.tsx index 69d0dc8e3..7b488e1db 100644 --- a/src/routes/details/components/breakdown/breakdownBase.tsx +++ b/src/routes/details/components/breakdown/breakdownBase.tsx @@ -15,7 +15,7 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import { hasCurrentMonthData } from 'routes/utils/providers'; +import { hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { handleOnCostDistributionSelect, handleOnCostTypeSelect, @@ -70,6 +70,7 @@ export interface BreakdownStateProps { historicalDataComponent?: React.ReactNode; instancesComponent?: React.ReactNode; isAwsEc2InstancesToggleEnabled?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; isOptimizationsTab?: boolean; optimizationsBadgeComponent?: React.ReactNode; optimizationsComponent?: React.ReactNode; @@ -88,6 +89,7 @@ export interface BreakdownStateProps { showCostDistribution?: boolean; showCostType?: boolean; tagPathsType?: TagPathsType; + timeScopeValue?: number; title?: string; } @@ -289,6 +291,7 @@ class BreakdownBase extends React.Component { detailsURL, emptyStateTitle, groupBy, + isDetailsDateRangeToggleEnabled, optimizationsComponent, providers, providersFetchStatus, @@ -301,6 +304,7 @@ class BreakdownBase extends React.Component { showCostDistribution, showCostType, tagPathsType, + timeScopeValue, title, } = this.props; const { activeTabKey } = this.state; @@ -320,7 +324,11 @@ class BreakdownBase extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if ( + isDetailsDateRangeToggleEnabled + ? !hasCurrentMonthData(providers) && !hasPreviousMonthData(providers) + : !hasCurrentMonthData(providers) + ) { return ; } } @@ -352,6 +360,7 @@ class BreakdownBase extends React.Component { showCurrency={!(optimizationsComponent && activeTabKey === 2)} tabs={this.getTabs(availableTabs)} tagPathsType={tagPathsType} + timeScopeValue={timeScopeValue} title={title} />
{this.getTabContent(availableTabs)}
diff --git a/src/routes/details/components/breakdown/breakdownHeader.tsx b/src/routes/details/components/breakdown/breakdownHeader.tsx index 91b755a32..541505091 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.tsx +++ b/src/routes/details/components/breakdown/breakdownHeader.tsx @@ -59,6 +59,7 @@ interface BreakdownHeaderOwnProps extends RouterComponentProps { showCurrency?: boolean; tabs: React.ReactNode; tagPathsType: TagPathsType; + timeScopeValue?: number; title: string; } @@ -206,6 +207,7 @@ class BreakdownHeader extends React.Component { showCurrency, tabs, tagPathsType, + timeScopeValue, title, } = this.props; @@ -291,7 +293,10 @@ class BreakdownHeader extends React.Component {
{getTotalCostDateRangeString( - intl.formatMessage(messages.groupByValuesTitleCase, { value: groupByKey, count: 2 }) + intl.formatMessage(messages.groupByValuesTitleCase, { value: groupByKey, count: 2 }), + undefined, + timeScopeValue === -2 ? 1 : 0, + true )}
diff --git a/src/routes/details/components/dateRange/dateRange.tsx b/src/routes/details/components/dateRange/dateRange.tsx new file mode 100644 index 000000000..5017c684f --- /dev/null +++ b/src/routes/details/components/dateRange/dateRange.tsx @@ -0,0 +1,83 @@ +import type { MessageDescriptor } from '@formatjs/intl/src/types'; +import type { MenuToggleElement } from '@patternfly/react-core'; +import { Dropdown, DropdownItem, DropdownList, MenuToggle } from '@patternfly/react-core'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { getSinceDateRangeString } from 'utils/dates'; + +interface DateRangeOwnProps { + dateRangeType?: string; + isCurrentMonthData: boolean; + isDisabled?: boolean; + isPreviousMonthData: boolean; + onSelect(value: string); +} + +type DateRangeProps = DateRangeOwnProps; + +const DateRange: React.FC = ({ dateRangeType, isCurrentMonthData, isPreviousMonthData, onSelect }) => { + const [isOpen, setIsOpen] = React.useState(false); + const intl = useIntl(); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const getOptions = (): { + isDisabled?: boolean; + label: MessageDescriptor; + value: string; + }[] => { + return [ + { label: messages.explorerDateRange, value: 'current_month_to_date', isDisabled: isCurrentMonthData === false }, + { label: messages.explorerDateRange, value: 'previous_month', isDisabled: isPreviousMonthData === false }, + ]; + }; + + const handleOnSelect = (_evt, value) => { + if (onSelect) { + onSelect(value); + } + setIsOpen(false); + }; + + return ( + setIsOpen(val)} + toggle={(toggleRef: React.Ref) => ( + + {intl.formatMessage(messages.explorerDateRange, { value: dateRangeType })} + + )} + ouiaId="BasicDropdown" + shouldFocusToggleOnSelect + > + + {getOptions().map((option, index) => ( + + {intl.formatMessage(option.label, { value: option.value })} + + ))} + + + ); +}; + +export { DateRange }; diff --git a/src/routes/details/components/dateRange/index.ts b/src/routes/details/components/dateRange/index.ts new file mode 100644 index 000000000..0f4b8e353 --- /dev/null +++ b/src/routes/details/components/dateRange/index.ts @@ -0,0 +1 @@ +export * from './dateRange'; diff --git a/src/routes/details/components/historicalData/historicalDataBase.tsx b/src/routes/details/components/historicalData/historicalDataBase.tsx index b791d0b0e..b5512d72b 100644 --- a/src/routes/details/components/historicalData/historicalDataBase.tsx +++ b/src/routes/details/components/historicalData/historicalDataBase.tsx @@ -18,6 +18,7 @@ interface HistoricalDataOwnProps { costType?: string; currency?: string; groupBy?: string; + timeScopeValue?: number; } export interface HistoricalDataStateProps { @@ -39,7 +40,7 @@ class HistoricalDatasBase extends React.Component { // Returns cost chart private getCostChart = (widget: HistoricalDataWidget) => { - const { costDistribution, costType, currency, intl } = this.props; + const { costDistribution, costType, currency, intl, timeScopeValue } = this.props; return ( @@ -58,6 +59,7 @@ class HistoricalDatasBase extends React.Component { currency={currency} reportPathsType={widget.reportPathsType} reportType={widget.reportType} + timeScopeValue={timeScopeValue} /> @@ -66,7 +68,7 @@ class HistoricalDatasBase extends React.Component { // Returns network chart private getNetworkChart = (widget: HistoricalDataWidget) => { - const { groupBy, intl } = this.props; + const { groupBy, intl, timeScopeValue } = this.props; let showWidget = false; if (widget?.showWidgetOnGroupBy) { @@ -92,6 +94,7 @@ class HistoricalDatasBase extends React.Component { chartName={widget.chartName} reportPathsType={widget.reportPathsType} reportType={widget.reportType} + timeScopeValue={timeScopeValue} /> @@ -102,7 +105,7 @@ class HistoricalDatasBase extends React.Component { // Returns storage summary private getVolumeChart = (widget: HistoricalDataWidget) => { - const { groupBy, intl } = this.props; + const { groupBy, intl, timeScopeValue } = this.props; let showWidget = false; if (widget?.showWidgetOnGroupBy) { @@ -128,6 +131,7 @@ class HistoricalDatasBase extends React.Component { chartName={widget.chartName} reportPathsType={widget.reportPathsType} reportType={widget.reportType} + timeScopeValue={timeScopeValue} /> @@ -138,7 +142,7 @@ class HistoricalDatasBase extends React.Component { // Returns trend chart private getTrendChart = (widget: HistoricalDataWidget) => { - const { costType, currency, intl } = this.props; + const { costType, currency, intl, timeScopeValue } = this.props; return ( @@ -156,6 +160,7 @@ class HistoricalDatasBase extends React.Component { currency={currency} reportPathsType={widget.reportPathsType} reportType={widget.reportType} + timeScopeValue={timeScopeValue} /> @@ -164,7 +169,7 @@ class HistoricalDatasBase extends React.Component { // Returns usage chart private getUsageChart = (widget: HistoricalDataWidget) => { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; return ( @@ -180,6 +185,7 @@ class HistoricalDatasBase extends React.Component { chartName={widget.chartName} reportPathsType={widget.reportPathsType} reportType={widget.reportType} + timeScopeValue={timeScopeValue} /> diff --git a/src/routes/details/components/historicalData/historicalDataCostChart.tsx b/src/routes/details/components/historicalData/historicalDataCostChart.tsx index 11f9200ad..db66b5ee5 100644 --- a/src/routes/details/components/historicalData/historicalDataCostChart.tsx +++ b/src/routes/details/components/historicalData/historicalDataCostChart.tsx @@ -29,6 +29,7 @@ interface HistoricalDataCostChartOwnProps extends RouterComponentProps, WrappedC currency?: string; reportPathsType: ReportPathsType; reportType: ReportType; + timeScopeValue?: number; } interface HistoricalDataCostChartStateProps { @@ -147,7 +148,7 @@ class HistoricalDataCostChartBase extends React.Component( - (state, { costType, currency, reportPathsType, reportType, router }) => { + (state, { costType, currency, reportPathsType, reportType, router, timeScopeValue }) => { const queryFromRoute = parseQuery(router.location.search); const queryState = getQueryState(router.location, 'details'); @@ -182,7 +183,7 @@ const mapStateToProps = createMapStateToProps( - (state, { reportPathsType, reportType, router }) => { + (state, { reportPathsType, reportType, router, timeScopeValue }) => { const queryFromRoute = parseQuery(router.location.search); const queryState = getQueryState(router.location, 'details'); @@ -176,7 +177,7 @@ const mapStateToProps = createMapStateToProps( - (state, { costType, currency, reportPathsType, reportType, router }) => { + (state, { costType, currency, reportPathsType, reportType, router, timeScopeValue }) => { const queryFromRoute = parseQuery(router.location.search); const queryState = getQueryState(router.location, 'details'); @@ -189,7 +190,7 @@ const mapStateToProps = createMapStateToProps( - (state, { reportPathsType, reportType, router }) => { + (state, { reportPathsType, reportType, router, timeScopeValue }) => { const queryFromRoute = parseQuery(router.location.search); const queryState = getQueryState(router.location, 'details'); @@ -160,7 +161,7 @@ const mapStateToProps = createMapStateToProps( - (state, { reportPathsType, reportType, router }) => { + (state, { reportPathsType, reportType, router, timeScopeValue }) => { const queryFromRoute = parseQuery(router.location.search); const queryState = getQueryState(router.location, 'details'); @@ -157,7 +158,7 @@ const mapStateToProps = createMapStateToProps { const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); + const timeScopeValue = getTimeScopeValue(queryState); const reportQuery: Query = { filter: { ...query.filter, limit: query.limit, offset: query.offset, + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, }, filter_by: { // Add filters here to apply logical OR/AND diff --git a/src/routes/details/components/pvcChart/pvcChart.tsx b/src/routes/details/components/pvcChart/pvcChart.tsx index c7af55c84..2a26fe4eb 100644 --- a/src/routes/details/components/pvcChart/pvcChart.tsx +++ b/src/routes/details/components/pvcChart/pvcChart.tsx @@ -29,6 +29,7 @@ import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; import { noop } from 'routes/utils/noop'; import { getQueryState } from 'routes/utils/queryState'; import { skeletonWidth } from 'routes/utils/skeleton'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { reportActions, reportSelectors } from 'store/reports'; import { formatUsage, unitsLookupKey } from 'utils/format'; @@ -73,9 +74,6 @@ const baseQuery: OcpQuery = { filter: { limit: 2, // Render 2 items max offset: 0, - time_scope_units: 'month', - time_scope_value: -1, - resolution: 'monthly', }, order_by: { request: 'desc', @@ -358,11 +356,15 @@ const mapStateToProps = createMapStateToProps, + historicalDataComponent: , + isDetailsDateRangeToggleEnabled: FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state), providers: filterProviders(providers, ProviderType.gcp), providersError, providersFetchStatus, @@ -108,6 +113,7 @@ const mapStateToProps = createMapStateToProps { + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; + public state: DetailsHeaderState = { ...this.defaultState }; + + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; - const showContent = report && !providersError && providers?.meta?.count > 0; + const { currentDateRangeType } = this.state; + const showContent = report && !providersError && providers?.meta?.count > 0; const hasCost = report?.meta?.total?.cost?.total; return ( @@ -101,6 +143,17 @@ class DetailsHeaderBase extends React.Component { /> + {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -112,7 +165,9 @@ class DetailsHeaderBase extends React.Component { hasCost ? report.meta.total.cost.total.units : 'USD' )} -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -134,6 +189,7 @@ const mapStateToProps = createMapStateToProps { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; const value = formatCurrency(Math.abs(item.cost.total.value - item.delta_value), item.cost.total.units); const percentage = item.delta_percent !== null ? formatPercentage(Math.abs(item.delta_percent)) : 0; @@ -219,7 +224,11 @@ class DetailsTableBase extends React.Component @@ -240,7 +249,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/gcpDetails/detailsToolbar.tsx b/src/routes/details/gcpDetails/detailsToolbar.tsx index 883ae41af..196559e6d 100644 --- a/src/routes/details/gcpDetails/detailsToolbar.tsx +++ b/src/routes/details/gcpDetails/detailsToolbar.tsx @@ -34,6 +34,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: GcpQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -160,28 +161,29 @@ export class DetailsToolbarBase extends React.Component((state, props) => { - // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values - // However, for better server-side performance, we chose to use key_only here. - const tagQueryString = getQuery({ - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }); - - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - return { - tagReport, - tagReportFetchStatus, - tagQueryString, - }; -}); +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue = -1 }) => { + // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values + // However, for better server-side performance, we chose to use key_only here. + const tagQueryString = getQuery({ + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }); + + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + return { + tagReport, + tagReportFetchStatus, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchTag: tagActions.fetchTag, diff --git a/src/routes/details/gcpDetails/gcpDetails.tsx b/src/routes/details/gcpDetails/gcpDetails.tsx index f10ff25b8..7e173ef20 100644 --- a/src/routes/details/gcpDetails/gcpDetails.tsx +++ b/src/routes/details/gcpDetails/gcpDetails.tsx @@ -1,4 +1,4 @@ -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import type { GcpQuery } from 'api/queries/gcpQuery'; @@ -23,7 +23,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedGcpRe import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getGroupByTagKey } from 'routes/utils/groupBy'; -import { filterProviders, hasCurrentMonthData } from 'routes/utils/providers'; +import { filterProviders, hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; import { handleOnCurrencySelect, @@ -33,10 +33,12 @@ import { handleOnSetPage, handleOnSort, } from 'routes/utils/queryNavigate'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; +import { getSinceDateRangeString } from 'utils/dates'; import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -51,6 +53,9 @@ import { styles } from './gcpDetails.styles'; interface GcpDetailsStateProps { currency?: string; isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; + isPreviousMonthData?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -59,6 +64,7 @@ interface GcpDetailsStateProps { reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; + timeScopeValue?: number; } interface GcpDetailsDispatchProps { @@ -105,14 +111,6 @@ class GcpDetails extends React.Component { }; public state: GcpDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleonSelect = this.handleonSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -144,7 +142,7 @@ class GcpDetails extends React.Component { }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -169,6 +167,7 @@ class GcpDetails extends React.Component { reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -203,7 +202,7 @@ class GcpDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); const groupByTagKey = getGroupByTagKey(query); @@ -224,12 +223,13 @@ class GcpDetails extends React.Component { report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; private getToolbar = (computedItems: ComputedReportItem[]) => { - const { query, router, report } = this.props; + const { query, router, report, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -252,6 +252,7 @@ class GcpDetails extends React.Component { pagination={this.getPagination(isDisabled)} query={query} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -277,11 +278,11 @@ class GcpDetails extends React.Component { } }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -327,6 +328,9 @@ class GcpDetails extends React.Component { currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, @@ -334,6 +338,7 @@ class GcpDetails extends React.Component { reportError, reportFetchStatus, router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -353,7 +358,7 @@ class GcpDetails extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( { handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -395,11 +415,36 @@ class GcpDetails extends React.Component { const mapStateToProps = createMapStateToProps((state, { router }) => { const queryFromRoute = parseQuery(router.location.search); + const currency = getCurrency(); + // Check for current and previous data first + const providersQueryString = getProvidersQuery(providersQuery); + const providers = providersSelectors.selectProviders(state, ProviderType.all, providersQueryString); + const providersError = providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString); + const providersFetchStatus = providersSelectors.selectProvidersFetchStatus( + state, + ProviderType.all, + providersQueryString + ); + + // Fetch based on time scope value + const filteredProviders = filterProviders(providers, ProviderType.gcp); + const isCurrentMonthData = hasCurrentMonthData(filteredProviders); + const isDetailsDateRangeToggleEnabled = FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state); + + let timeScopeValue = getTimeScopeValue(queryFromRoute); + timeScopeValue = Number( + !isCurrentMonthData && isDetailsDateRangeToggleEnabled ? -2 : timeScopeValue !== undefined ? timeScopeValue : -1 + ); + const query: any = { ...baseQuery, ...queryFromRoute, + filter: { + ...queryFromRoute.filter, + time_scope_value: timeScopeValue, + }, }; const reportQuery = { currency, @@ -409,7 +454,6 @@ const mapStateToProps = createMapStateToProps, + historicalDataComponent: , providers: filterProviders(providers, ProviderType.ibm), providersError, providersFetchStatus, @@ -108,6 +111,7 @@ const mapStateToProps = createMapStateToProps { + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; + public state: DetailsHeaderState = { ...this.defaultState }; + + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; - const showContent = report && !providersError && providers?.meta?.count > 0; + const { currentDateRangeType } = this.state; + const showContent = report && !providersError && providers?.meta?.count > 0; const hasCost = report?.meta?.total?.cost?.total; return ( @@ -101,6 +143,17 @@ class DetailsHeaderBase extends React.Component { />
+ {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -112,7 +165,9 @@ class DetailsHeaderBase extends React.Component { hasCost ? report.meta.total.cost.total.units : 'USD' )} -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -134,6 +189,7 @@ const mapStateToProps = createMapStateToProps { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; const value = formatCurrency(Math.abs(item.cost.total.value - item.delta_value), item.cost.total.units); const percentage = item.delta_percent !== null ? formatPercentage(Math.abs(item.delta_percent)) : 0; @@ -219,7 +224,11 @@ class DetailsTableBase extends React.Component @@ -240,7 +249,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/ibmDetails/detailsToolbar.tsx b/src/routes/details/ibmDetails/detailsToolbar.tsx index af6f37f3f..3a4ca85da 100644 --- a/src/routes/details/ibmDetails/detailsToolbar.tsx +++ b/src/routes/details/ibmDetails/detailsToolbar.tsx @@ -34,6 +34,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: IbmQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -160,28 +161,29 @@ export class DetailsToolbarBase extends React.Component((state, props) => { - // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values - // However, for better server-side performance, we chose to use key_only here. - const tagQueryString = getQuery({ - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }); - - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - return { - tagReport, - tagReportFetchStatus, - tagQueryString, - }; -}); +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue = -1 }) => { + // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values + // However, for better server-side performance, we chose to use key_only here. + const tagQueryString = getQuery({ + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }); + + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + return { + tagReport, + tagReportFetchStatus, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchTag: tagActions.fetchTag, diff --git a/src/routes/details/ibmDetails/ibmDetails.tsx b/src/routes/details/ibmDetails/ibmDetails.tsx index 1af6f03db..2b0ed6810 100644 --- a/src/routes/details/ibmDetails/ibmDetails.tsx +++ b/src/routes/details/ibmDetails/ibmDetails.tsx @@ -1,4 +1,4 @@ -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import type { IbmQuery } from 'api/queries/ibmQuery'; @@ -23,7 +23,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedIbmRe import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getGroupByTagKey } from 'routes/utils/groupBy'; -import { hasCurrentMonthData } from 'routes/utils/providers'; +import { hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { filterProviders } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; import { @@ -34,10 +34,12 @@ import { handleOnSetPage, handleOnSort, } from 'routes/utils/queryNavigate'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; +import { getSinceDateRangeString } from 'utils/dates'; import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -52,6 +54,9 @@ import { styles } from './ibmDetails.styles'; interface IbmDetailsStateProps { currency?: string; isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; + isPreviousMonthData?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -60,6 +65,7 @@ interface IbmDetailsStateProps { reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; + timeScopeValue?: number; } interface IbmDetailsDispatchProps { @@ -106,14 +112,6 @@ class IbmDetails extends React.Component { }; public state: IbmDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleonSelect = this.handleonSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -145,7 +143,7 @@ class IbmDetails extends React.Component { }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -170,6 +168,7 @@ class IbmDetails extends React.Component { reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -204,7 +203,7 @@ class IbmDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -226,12 +225,13 @@ class IbmDetails extends React.Component { report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; private getToolbar = (computedItems: ComputedReportItem[]) => { - const { query, router, report } = this.props; + const { query, router, report, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -254,6 +254,7 @@ class IbmDetails extends React.Component { pagination={this.getPagination(isDisabled)} query={query} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -279,11 +280,11 @@ class IbmDetails extends React.Component { } }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -329,6 +330,9 @@ class IbmDetails extends React.Component { currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, @@ -336,6 +340,7 @@ class IbmDetails extends React.Component { reportError, reportFetchStatus, router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -355,7 +360,7 @@ class IbmDetails extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( { handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -397,11 +417,36 @@ class IbmDetails extends React.Component { const mapStateToProps = createMapStateToProps((state, { router }) => { const queryFromRoute = parseQuery(router.location.search); + const currency = getCurrency(); + // Check for current and previous data first + const providersQueryString = getProvidersQuery(providersQuery); + const providers = providersSelectors.selectProviders(state, ProviderType.all, providersQueryString); + const providersError = providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString); + const providersFetchStatus = providersSelectors.selectProvidersFetchStatus( + state, + ProviderType.all, + providersQueryString + ); + + // Fetch based on time scope value + const filteredProviders = filterProviders(providers, ProviderType.ibm); + const isCurrentMonthData = hasCurrentMonthData(filteredProviders); + const isDetailsDateRangeToggleEnabled = FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state); + + let timeScopeValue = getTimeScopeValue(queryFromRoute); + timeScopeValue = Number( + !isCurrentMonthData && isDetailsDateRangeToggleEnabled ? -2 : timeScopeValue !== undefined ? timeScopeValue : -1 + ); + const query: any = { ...baseQuery, ...queryFromRoute, + filter: { + ...queryFromRoute.filter, + time_scope_value: timeScopeValue, + }, }; const reportQuery = { currency, @@ -411,7 +456,6 @@ const mapStateToProps = createMapStateToProps( const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); + const currency = getCurrency(); + const timeScopeValue = getTimeScopeValue(queryState); const query = { ...queryFromRoute }; const reportQuery = { @@ -51,7 +54,7 @@ const mapStateToProps = createMapStateToProps( filter: { resolution: 'monthly', time_scope_units: 'month', - time_scope_value: -1, + time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, }, filter_by: { // Add filters here to apply logical OR/AND @@ -95,7 +98,7 @@ const mapStateToProps = createMapStateToProps( emptyStateTitle: intl.formatMessage(messages.ociDetailsTitle), groupBy, groupByValue, - historicalDataComponent: , + historicalDataComponent: , providers: filterProviders(providers, ProviderType.oci), providersError, providersFetchStatus, @@ -108,6 +111,7 @@ const mapStateToProps = createMapStateToProps( reportPathsType, reportQueryString, tagPathsType: TagPathsType.oci, + timeScopeValue, title: groupByValue, }; }); diff --git a/src/routes/details/ociDetails/detailsHeader.tsx b/src/routes/details/ociDetails/detailsHeader.tsx index 1397f156b..370e2766d 100644 --- a/src/routes/details/ociDetails/detailsHeader.tsx +++ b/src/routes/details/ociDetails/detailsHeader.tsx @@ -2,6 +2,7 @@ import { Flex, FlexItem, Title, TitleSizes } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; +import type { Query } from 'api/queries/query'; import type { OciReport } from 'api/reports/ociReports'; import { TagPathsType } from 'api/tags/tag'; import type { AxiosError } from 'axios'; @@ -13,27 +14,37 @@ import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; import { GroupBy } from 'routes/components/groupBy'; +import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedOciReportItemsParams } from 'routes/utils/computedReport/getComputedOciReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOciReportItems'; +import { DateRangeType } from 'routes/utils/dateRange'; import { filterProviders } from 'routes/utils/providers'; +import { getRouteForQuery } from 'routes/utils/query'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; +import type { RouterComponentProps } from 'utils/router'; +import { withRouter } from 'utils/router'; import { styles } from './detailsHeader.styles'; interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; + isCurrentMonthData?: boolean; + isPreviousMonthData?: boolean; onCurrencySelect(value: string); onGroupBySelect(value: string); + query?: Query; report: OciReport; + timeScopeValue?: number; } interface DetailsHeaderStateProps { + isDetailsDateRangeToggleEnabled: boolean; isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; @@ -41,7 +52,14 @@ interface DetailsHeaderStateProps { providersQueryString?: string; } -type DetailsHeaderProps = DetailsHeaderOwnProps & DetailsHeaderStateProps & WrappedComponentProps; +interface DetailsHeaderState { + currentDateRangeType?: string; +} + +type DetailsHeaderProps = DetailsHeaderOwnProps & + DetailsHeaderStateProps & + RouterComponentProps & + WrappedComponentProps; const groupByOptions: { label: string; @@ -55,18 +73,42 @@ const groupByOptions: { const tagPathsType = TagPathsType.oci; class DetailsHeaderBase extends React.Component { + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; + public state: DetailsHeaderState = { ...this.defaultState }; + + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; + const { currentDateRangeType } = this.state; const showContent = report && !providersError && providers?.meta?.count > 0; const hasCost = report?.meta?.total?.cost?.total; @@ -100,6 +142,17 @@ class DetailsHeaderBase extends React.Component { />
+ {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -111,7 +164,9 @@ class DetailsHeaderBase extends React.Component { hasCost ? report.meta.total.cost.total.units : 'USD' )} -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -133,6 +188,7 @@ const mapStateToProps = createMapStateToProps { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; const value = formatCurrency(Math.abs(item.cost.total.value - item.delta_value), item.cost.total.units); const percentage = item.delta_percent !== null ? formatPercentage(Math.abs(item.delta_percent)) : 0; @@ -219,7 +224,11 @@ class DetailsTableBase extends React.Component @@ -240,7 +249,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/ociDetails/detailsToolbar.tsx b/src/routes/details/ociDetails/detailsToolbar.tsx index 5bcb25b60..a7e5259cd 100644 --- a/src/routes/details/ociDetails/detailsToolbar.tsx +++ b/src/routes/details/ociDetails/detailsToolbar.tsx @@ -33,6 +33,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: OciQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -165,27 +166,28 @@ export class DetailsToolbarBase extends React.Component((state, props) => { - // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values - // However, for better server-side performance, we chose to use key_only here. - const tagQueryString = getQuery({ - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }); - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - return { - tagReportFetchStatus, - tagReport, - tagQueryString, - }; -}); +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue = -1 }) => { + // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values + // However, for better server-side performance, we chose to use key_only here. + const tagQueryString = getQuery({ + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }); + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + return { + tagReportFetchStatus, + tagReport, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchTag: tagActions.fetchTag, diff --git a/src/routes/details/ociDetails/ociDetails.tsx b/src/routes/details/ociDetails/ociDetails.tsx index 3301980ab..8ec21e892 100644 --- a/src/routes/details/ociDetails/ociDetails.tsx +++ b/src/routes/details/ociDetails/ociDetails.tsx @@ -1,4 +1,4 @@ -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import type { OciQuery } from 'api/queries/ociQuery'; @@ -23,7 +23,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOciRe import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getGroupByTagKey } from 'routes/utils/groupBy'; -import { filterProviders, hasCurrentMonthData } from 'routes/utils/providers'; +import { filterProviders, hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; import { handleOnCurrencySelect, @@ -33,10 +33,12 @@ import { handleOnSetPage, handleOnSort, } from 'routes/utils/queryNavigate'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; +import { getSinceDateRangeString } from 'utils/dates'; import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -51,6 +53,9 @@ import { styles } from './ociDetails.styles'; interface OciDetailsStateProps { currency?: string; isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; + isPreviousMonthData?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -59,6 +64,7 @@ interface OciDetailsStateProps { reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; + timeScopeValue?: number; } interface OciDetailsDispatchProps { @@ -105,14 +111,6 @@ class OciDetails extends React.Component { }; public state: OciDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleonSelect = this.handleonSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -144,7 +142,7 @@ class OciDetails extends React.Component { }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -169,6 +167,7 @@ class OciDetails extends React.Component { reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -203,7 +202,7 @@ class OciDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -225,12 +224,13 @@ class OciDetails extends React.Component { report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; private getToolbar = (computedItems: ComputedReportItem[]) => { - const { query, router, report } = this.props; + const { query, router, report, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -253,6 +253,7 @@ class OciDetails extends React.Component { pagination={this.getPagination(isDisabled)} query={query} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -278,11 +279,11 @@ class OciDetails extends React.Component { } }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -328,14 +329,17 @@ class OciDetails extends React.Component { currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, report, reportError, reportFetchStatus, - router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -355,7 +359,7 @@ class OciDetails extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( { handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -397,11 +416,36 @@ class OciDetails extends React.Component { const mapStateToProps = createMapStateToProps((state, { router }) => { const queryFromRoute = parseQuery(router.location.search); + const currency = getCurrency(); + // Check for current and previous data first + const providersQueryString = getProvidersQuery(providersQuery); + const providers = providersSelectors.selectProviders(state, ProviderType.all, providersQueryString); + const providersError = providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString); + const providersFetchStatus = providersSelectors.selectProvidersFetchStatus( + state, + ProviderType.all, + providersQueryString + ); + + // Fetch based on time scope value + const filteredProviders = filterProviders(providers, ProviderType.oci); + const isCurrentMonthData = hasCurrentMonthData(filteredProviders); + const isDetailsDateRangeToggleEnabled = FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state); + + let timeScopeValue = getTimeScopeValue(queryFromRoute); + timeScopeValue = Number( + !isCurrentMonthData && isDetailsDateRangeToggleEnabled ? -2 : timeScopeValue !== undefined ? timeScopeValue : -1 + ); + const query: any = { ...baseQuery, ...queryFromRoute, + filter: { + ...queryFromRoute.filter, + time_scope_value: timeScopeValue, + }, }; const reportQuery = { currency, @@ -411,7 +455,6 @@ const mapStateToProps = createMapStateToProps + ), isOptimizationsTab: queryFromRoute.optimizationsTab !== undefined, optimizationsComponent: groupBy === 'project' && groupByValue !== '*' ? : undefined, @@ -138,6 +145,7 @@ const mapStateToProps = createMapStateToProps { - protected defaultState: DetailsHeaderState = {}; + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; public state: DetailsHeaderState = { ...this.defaultState }; + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { costDistribution, currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCostDistributionSelect, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; + const { currentDateRangeType } = this.state; + const showContent = report && !providersError && providers?.meta?.count > 0; const showCostDistribution = groupBy === 'project' && report?.meta?.distributed_overhead === true; @@ -141,6 +178,17 @@ class DetailsHeaderBase extends React.Component )} + {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -157,7 +205,9 @@ class DetailsHeaderBase extends React.Component -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -179,6 +229,7 @@ const mapStateToProps = createMapStateToProps { - const { costDistribution, intl } = this.props; + const { costDistribution, intl, timeScopeValue } = this.props; const reportItemValue = costDistribution ? costDistribution : ComputedReportItemValueType.total; const value = formatCurrency( @@ -440,7 +442,11 @@ class DetailsTableBase extends React.Component @@ -458,7 +464,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/ocpDetails/detailsToolbar.tsx b/src/routes/details/ocpDetails/detailsToolbar.tsx index 383dae42b..0af210f88 100644 --- a/src/routes/details/ocpDetails/detailsToolbar.tsx +++ b/src/routes/details/ocpDetails/detailsToolbar.tsx @@ -34,6 +34,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: OcpQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -169,27 +170,28 @@ export class DetailsToolbarBase extends React.Component((state, props) => { - // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values - // However, for better server-side performance, we chose to use key_only here. - const tagQueryString = getQuery({ - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }); - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - return { - tagReport, - tagReportFetchStatus, - tagQueryString, - }; -}); +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue }) => { + // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values + // However, for better server-side performance, we chose to use key_only here. + const tagQueryString = getQuery({ + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }); + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + return { + tagReport, + tagReportFetchStatus, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchTag: tagActions.fetchTag, diff --git a/src/routes/details/ocpDetails/ocpDetails.tsx b/src/routes/details/ocpDetails/ocpDetails.tsx index 36879065f..ee8e9eb6c 100644 --- a/src/routes/details/ocpDetails/ocpDetails.tsx +++ b/src/routes/details/ocpDetails/ocpDetails.tsx @@ -1,4 +1,4 @@ -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import type { OcpQuery } from 'api/queries/ocpQuery'; @@ -27,7 +27,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOcpRe import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getGroupById, getGroupByTagKey } from 'routes/utils/groupBy'; -import { filterProviders, hasCurrentMonthData } from 'routes/utils/providers'; +import { filterProviders, hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; import { handleOnCostDistributionSelect, @@ -38,10 +38,12 @@ import { handleOnSetPage, handleOnSort, } from 'routes/utils/queryNavigate'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; +import { getSinceDateRangeString } from 'utils/dates'; import { formatPath } from 'utils/paths'; import { noPrefix, platformCategoryKey, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -56,7 +58,11 @@ import { styles } from './ocpDetails.styles'; export interface OcpDetailsStateProps { costDistribution?: string; currency?: string; + currentDateRangeType?: string; isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; + isPreviousMonthData?: boolean; providers: Providers; providersFetchStatus: FetchStatus; query: OcpQuery; @@ -64,6 +70,7 @@ export interface OcpDetailsStateProps { reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; + timeScopeValue?: number; } interface OcpDetailsDispatchProps { @@ -130,18 +137,6 @@ class OcpDetails extends React.Component { }; public state: OcpDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnColumnManagementModalClose = this.handleOnColumnManagementModalClose.bind(this); - this.handleOnColumnManagementModalOpen = this.handleOnColumnManagementModalOpen.bind(this); - this.handleOnColumnManagementModalSave = this.handleOnColumnManagementModalSave.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleOnPlatformCostsChanged = this.handleOnPlatformCostsChanged.bind(this); - this.handleOnSelect = this.handleOnSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -191,7 +186,7 @@ class OcpDetails extends React.Component { }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -216,6 +211,7 @@ class OcpDetails extends React.Component { reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -250,7 +246,8 @@ class OcpDetails extends React.Component { }; private getTable = () => { - const { costDistribution, query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { costDistribution, query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = + this.props; const { hiddenColumns, isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -274,12 +271,13 @@ class OcpDetails extends React.Component { report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; private getToolbar = (computedItems: ComputedReportItem[]) => { - const { query, report, router } = this.props; + const { query, report, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -304,6 +302,7 @@ class OcpDetails extends React.Component { pagination={this.getPagination(isDisabled)} query={query} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -329,23 +328,23 @@ class OcpDetails extends React.Component { } }; - public handleOnColumnManagementModalClose = (isOpen: boolean) => { + private handleOnColumnManagementModalClose = (isOpen: boolean) => { this.setState({ isColumnManagementModalOpen: isOpen }); }; - public handleOnColumnManagementModalOpen = () => { + private handleOnColumnManagementModalOpen = () => { this.setState({ isColumnManagementModalOpen: true }); }; - public handleOnColumnManagementModalSave = (hiddenColumns: Set) => { + private handleOnColumnManagementModalSave = (hiddenColumns: Set) => { this.setState({ hiddenColumns }); }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -405,6 +404,9 @@ class OcpDetails extends React.Component { currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, @@ -412,6 +414,7 @@ class OcpDetails extends React.Component { reportError, reportFetchStatus, router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -431,7 +434,7 @@ class OcpDetails extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( { costDistribution={costDistribution} currency={currency} groupBy={groupById} + isCurrentMonthData={isCurrentMonthData} + isPreviousMonthData={isPreviousMonthData} onCostDistributionSelect={() => handleOnCostDistributionSelect(query, router)} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {this.getColumnManagementModal()} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -481,6 +499,25 @@ const mapStateToProps = createMapStateToProps, + historicalDataComponent: , providers: filterProviders(providers, ProviderType.rhel), providersFetchStatus, providerType: ProviderType.rhel, @@ -109,6 +112,7 @@ const mapStateToProps = createMapStateToProps { - protected defaultState: DetailsHeaderState = {}; + protected defaultState: DetailsHeaderState = { + currentDateRangeType: + this.props.timeScopeValue === -2 ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate, + }; public state: DetailsHeaderState = { ...this.defaultState }; + private handleOnDateRangeSelected = (value: string) => { + const { query, router } = this.props; + + this.setState({ currentDateRangeType: value }, () => { + const newQuery = { + filter: {}, + ...JSON.parse(JSON.stringify(query)), + }; + newQuery.filter.time_scope_value = value === DateRangeType.previousMonth ? -2 : -1; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }); + }; + public render() { const { currency, groupBy, + intl, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, + isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, providersError, report, - intl, + timeScopeValue, } = this.props; + const { currentDateRangeType } = this.state; + const showContent = report && !providersError && providers?.meta?.count > 0; let cost: string | React.ReactNode = ; @@ -126,6 +166,17 @@ class DetailsHeaderBase extends React.Component { />
+ {isDetailsDateRangeToggleEnabled && ( + + + + )} @@ -142,7 +193,9 @@ class DetailsHeaderBase extends React.Component { {cost} -
{getSinceDateRangeString()}
+
+ {getSinceDateRangeString(undefined, timeScopeValue === -2 ? 1 : 0, true)} +
)}
@@ -164,6 +217,7 @@ const mapStateToProps = createMapStateToProps { - const { intl } = this.props; + const { intl, timeScopeValue } = this.props; const value = formatCurrency(Math.abs(item.cost.total.value - item.delta_value), item.cost.total.units); const percentage = item.delta_percent !== null ? formatPercentage(Math.abs(item.delta_percent)) : 0; @@ -341,7 +343,11 @@ class DetailsTableBase extends React.Component @@ -359,7 +365,12 @@ class DetailsTableBase extends React.Component
- {getForDateRangeString(value)} + {getForDateRangeString( + value, + undefined, + timeScopeValue === -2 ? 2 : 1, + timeScopeValue === -2 ? true : false + )}
); diff --git a/src/routes/details/rhelDetails/detailsToolbar.tsx b/src/routes/details/rhelDetails/detailsToolbar.tsx index d85af1b33..6f87a4a85 100644 --- a/src/routes/details/rhelDetails/detailsToolbar.tsx +++ b/src/routes/details/rhelDetails/detailsToolbar.tsx @@ -33,6 +33,7 @@ interface DetailsToolbarOwnProps { pagination?: React.ReactNode; query?: OcpQuery; selectedItems?: ComputedReportItem[]; + timeScopeValue?: number; } interface DetailsToolbarStateProps { @@ -161,27 +162,28 @@ export class DetailsToolbarBase extends React.Component { } } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps((state, props) => { - // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values - // However, for better server-side performance, we chose to use key_only here. - const tagQueryString = getQuery({ - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: -1, - }, - key_only: true, - limit: 1000, - }); - const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); - const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); - return { - tagReport, - tagReportFetchStatus, - tagQueryString, - }; -}); +const mapStateToProps = createMapStateToProps( + (state, { timeScopeValue = -1 }) => { + // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values + // However, for better server-side performance, we chose to use key_only here. + const tagQueryString = getQuery({ + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue, + }, + key_only: true, + limit: 1000, + }); + const tagReport = tagSelectors.selectTag(state, tagPathsType, tagType, tagQueryString); + const tagReportFetchStatus = tagSelectors.selectTagFetchStatus(state, tagPathsType, tagType, tagQueryString); + return { + tagReport, + tagReportFetchStatus, + tagQueryString, + }; + } +); const mapDispatchToProps: DetailsToolbarDispatchProps = { fetchTag: tagActions.fetchTag, diff --git a/src/routes/details/rhelDetails/rhelDetails.tsx b/src/routes/details/rhelDetails/rhelDetails.tsx index 18e5c232f..25f61854e 100644 --- a/src/routes/details/rhelDetails/rhelDetails.tsx +++ b/src/routes/details/rhelDetails/rhelDetails.tsx @@ -1,4 +1,4 @@ -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; @@ -26,7 +26,7 @@ import type { ComputedReportItem } from 'routes/utils/computedReport/getComputed import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedRhelReportItems'; import { getGroupByTagKey } from 'routes/utils/groupBy'; -import { filterProviders, hasCurrentMonthData } from 'routes/utils/providers'; +import { filterProviders, hasCurrentMonthData, hasPreviousMonthData } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; import { handleOnCurrencySelect, @@ -36,10 +36,12 @@ import { handleOnSetPage, handleOnSort, } from 'routes/utils/queryNavigate'; +import { getTimeScopeValue } from 'routes/utils/timeScope'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; +import { getSinceDateRangeString } from 'utils/dates'; import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -54,6 +56,9 @@ import { styles } from './rhelDetails.styles'; interface RhelDetailsStateProps { currency?: string; isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; + isPreviousMonthData?: boolean; providers: Providers; providersFetchStatus: FetchStatus; query: RhelQuery; @@ -61,6 +66,7 @@ interface RhelDetailsStateProps { reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; + timeScopeValue?: number; } interface RhelDetailsDispatchProps { @@ -127,17 +133,6 @@ class RhelDetails extends React.Component { }; public state: RhelDetailsState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnColumnManagementModalClose = this.handleOnColumnManagementModalClose.bind(this); - this.handleOnColumnManagementModalOpen = this.handleOnColumnManagementModalOpen.bind(this); - this.handleOnColumnManagementModalSave = this.handleOnColumnManagementModalSave.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleonSelect = this.handleonSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -187,7 +182,7 @@ class RhelDetails extends React.Component { }; private getExportModal = (computedItems: ComputedReportItem[]) => { - const { query, report, reportQueryString } = this.props; + const { query, report, reportQueryString, timeScopeValue } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -212,6 +207,7 @@ class RhelDetails extends React.Component { reportPathsType={reportPathsType} reportQueryString={reportQueryString} reportType={reportType} + timeScopeValue={timeScopeValue} /> ); }; @@ -246,7 +242,7 @@ class RhelDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { hiddenColumns, isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -268,12 +264,13 @@ class RhelDetails extends React.Component { report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; private getToolbar = (computedItems: ComputedReportItem[]) => { - const { query, report, router } = this.props; + const { query, report, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -297,6 +294,7 @@ class RhelDetails extends React.Component { pagination={this.getPagination()} query={query} selectedItems={selectedItems} + timeScopeValue={timeScopeValue} /> ); }; @@ -322,23 +320,23 @@ class RhelDetails extends React.Component { } }; - public handleOnColumnManagementModalClose = (isOpen: boolean) => { + private handleOnColumnManagementModalClose = (isOpen: boolean) => { this.setState({ isColumnManagementModalOpen: isOpen }); }; - public handleOnColumnManagementModalOpen = () => { + private handleOnColumnManagementModalOpen = () => { this.setState({ isColumnManagementModalOpen: true }); }; - public handleOnColumnManagementModalSave = (hiddenColumns: Set) => { + private handleOnColumnManagementModalSave = (hiddenColumns: Set) => { this.setState({ hiddenColumns }); }; - public handleOnExportModalClose = (isOpen: boolean) => { + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; - public handleOnExportModalOpen = () => { + private handleOnExportModalOpen = () => { this.setState({ isExportModalOpen: true }); }; @@ -385,6 +383,9 @@ class RhelDetails extends React.Component { currency, intl, isAccountInfoEmptyStateToggleEnabled, + isCurrentMonthData, + isDetailsDateRangeToggleEnabled, + isPreviousMonthData, providers, providersFetchStatus, query, @@ -392,6 +393,7 @@ class RhelDetails extends React.Component { reportError, reportFetchStatus, router, + timeScopeValue, } = this.props; const computedItems = this.getComputedItems(); @@ -411,7 +413,7 @@ class RhelDetails extends React.Component { if (noProviders) { return ; } - if (!hasCurrentMonthData(providers)) { + if (isDetailsDateRangeToggleEnabled ? !isCurrentMonthData && !isPreviousMonthData : !isCurrentMonthData) { return ( { handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} + query={query} report={report} + timeScopeValue={timeScopeValue} />
-
{this.getToolbar(computedItems)}
+
+ {!isCurrentMonthData && isDetailsDateRangeToggleEnabled && ( + + )} + {this.getToolbar(computedItems)} +
{this.getExportModal(computedItems)} {this.getColumnManagementModal()} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -454,11 +471,35 @@ class RhelDetails extends React.Component { const mapStateToProps = createMapStateToProps((state, { router }) => { const queryFromRoute = parseQuery(router.location.search); + const currency = getCurrency(); + // Check for current and previous data first + const providersQueryString = getProvidersQuery(providersQuery); + const providers = providersSelectors.selectProviders(state, ProviderType.all, providersQueryString); + const providersFetchStatus = providersSelectors.selectProvidersFetchStatus( + state, + ProviderType.all, + providersQueryString + ); + + // Fetch based on time scope value + const filteredProviders = filterProviders(providers, ProviderType.ocp); + const isCurrentMonthData = hasCurrentMonthData(filteredProviders); + const isDetailsDateRangeToggleEnabled = FeatureToggleSelectors.selectIsDetailsDateRangeToggleEnabled(state); + + let timeScopeValue = getTimeScopeValue(queryFromRoute); + timeScopeValue = Number( + !isCurrentMonthData && isDetailsDateRangeToggleEnabled ? -2 : timeScopeValue !== undefined ? timeScopeValue : -1 + ); + const query: any = { ...baseQuery, ...queryFromRoute, + filter: { + ...queryFromRoute.filter, + time_scope_value: timeScopeValue, + }, }; const reportQuery = { currency, @@ -468,7 +509,6 @@ const mapStateToProps = createMapStateToProps { + const filters = query?.filter ? Object.keys(query.filter) : []; + return filters.find(key => key === 'time_scope_value'); +}; + +export const getTimeScopeValue = (query: Query) => { + const timeScope = getTimeScope(query); + return timeScope ? query.filter[timeScope] : undefined; +}; diff --git a/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap b/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap index 45946a25e..dc8c364c8 100644 --- a/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap +++ b/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap @@ -6,6 +6,7 @@ exports[`default state 1`] = ` "isAccountInfoEmptyStateToggleEnabled": false, "isAwsEc2InstancesToggleEnabled": false, "isDebugToggleEnabled": false, + "isDetailsDateRangeToggleEnabled": false, "isExportsToggleEnabled": false, "isFinsightsToggleEnabled": false, "isIbmToggleEnabled": false, diff --git a/src/store/featureToggle/featureToggleActions.ts b/src/store/featureToggle/featureToggleActions.ts index ba0f03901..ccfc3591e 100644 --- a/src/store/featureToggle/featureToggleActions.ts +++ b/src/store/featureToggle/featureToggleActions.ts @@ -4,6 +4,7 @@ export interface FeatureToggleActionMeta { isAccountInfoEmptyStateToggleEnabled?: boolean; isAwsEc2InstancesToggleEnabled?: boolean; isDebugToggleEnabled?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; isExportsToggleEnabled?: boolean; isFinsightsToggleEnabled?: boolean; isIbmToggleEnabled?: boolean; diff --git a/src/store/featureToggle/featureToggleReducer.ts b/src/store/featureToggle/featureToggleReducer.ts index f31821667..6397851ea 100644 --- a/src/store/featureToggle/featureToggleReducer.ts +++ b/src/store/featureToggle/featureToggleReducer.ts @@ -11,6 +11,7 @@ export type FeatureToggleState = Readonly<{ isAccountInfoEmptyStateToggleEnabled: boolean; isAwsEc2InstancesToggleEnabled: boolean; isDebugToggleEnabled: boolean; + isDetailsDateRangeToggleEnabled: boolean; isExportsToggleEnabled: boolean; isFinsightsToggleEnabled: boolean; isIbmToggleEnabled: boolean; @@ -22,6 +23,7 @@ export const defaultState: FeatureToggleState = { isAccountInfoEmptyStateToggleEnabled: false, isAwsEc2InstancesToggleEnabled: false, isDebugToggleEnabled: false, + isDetailsDateRangeToggleEnabled: false, isExportsToggleEnabled: false, isFinsightsToggleEnabled: false, isIbmToggleEnabled: false, @@ -39,6 +41,7 @@ export function FeatureToggleReducer(state = defaultState, action: FeatureToggle isAccountInfoEmptyStateToggleEnabled: action.payload.isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled: action.payload.isAwsEc2InstancesToggleEnabled, isDebugToggleEnabled: action.payload.isDebugToggleEnabled, + isDetailsDateRangeToggleEnabled: action.payload.isDetailsDateRangeToggleEnabled, isExportsToggleEnabled: action.payload.isExportsToggleEnabled, isFinsightsToggleEnabled: action.payload.isFinsightsToggleEnabled, isIbmToggleEnabled: action.payload.isIbmToggleEnabled, diff --git a/src/store/featureToggle/featureToggleSelectors.ts b/src/store/featureToggle/featureToggleSelectors.ts index a3e1ec578..b1e09901b 100644 --- a/src/store/featureToggle/featureToggleSelectors.ts +++ b/src/store/featureToggle/featureToggleSelectors.ts @@ -11,6 +11,8 @@ export const selectIsAccountInfoEmptyStateToggleEnabled = (state: RootState) => export const selectIsAwsEc2InstancesToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isAwsEc2InstancesToggleEnabled; export const selectIsDebugToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isDebugToggleEnabled; +export const selectIsDetailsDateRangeToggleEnabled = (state: RootState) => + selectFeatureToggleState(state).isDetailsDateRangeToggleEnabled; export const selectIsExportsToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isExportsToggleEnabled; export const selectIsFinsightsToggleEnabled = (state: RootState) => diff --git a/src/utils/dates.ts b/src/utils/dates.ts index fb7c7e274..eebb4262d 100644 --- a/src/utils/dates.ts +++ b/src/utils/dates.ts @@ -27,7 +27,8 @@ export const getToday = (hrs: number = 0, min: number = 0, sec: number = 0, ms: export const getNoDataForDateRangeString = ( message: MessageDescriptor = messages.noDataForDate, - offset: number = 1 + offset: number = 0, + isEndOfMonth = false ) => { const endDate = getToday(); const startDate = getToday(); @@ -36,7 +37,11 @@ export const getNoDataForDateRangeString = ( if (offset) { startDate.setMonth(startDate.getMonth() - offset); - endDate.setMonth(endDate.getMonth() - offset); + if (isEndOfMonth) { + endDate.setMonth(endDate.getMonth() - offset + 1, 0); + } else { + endDate.setMonth(endDate.getMonth() - offset); + } } const dateRange = intl.formatDateTimeRange(startDate, endDate, { day: 'numeric', @@ -48,7 +53,8 @@ export const getNoDataForDateRangeString = ( export const getForDateRangeString = ( value: string | number, message: MessageDescriptor = messages.forDate, - offset = 1 + offset = 0, + isEndOfMonth = false ) => { const endDate = getToday(); const startDate = getToday(); @@ -57,7 +63,11 @@ export const getForDateRangeString = ( if (offset) { startDate.setMonth(startDate.getMonth() - offset); - endDate.setMonth(endDate.getMonth() - offset); + if (isEndOfMonth) { + endDate.setMonth(endDate.getMonth() - offset + 1, 0); + } else { + endDate.setMonth(endDate.getMonth() - offset); + } } const dateRange = intl.formatDateTimeRange(startDate, endDate, { day: 'numeric', @@ -66,11 +76,24 @@ export const getForDateRangeString = ( return intl.formatMessage(message, { dateRange, value }); }; -export const getSinceDateRangeString = (message: MessageDescriptor = messages.sinceDate) => { +export const getSinceDateRangeString = ( + message: MessageDescriptor = messages.sinceDate, + offset = 0, + isEndOfMonth = false +) => { const endDate = getToday(); const startDate = getToday(); startDate.setDate(1); + + if (offset) { + startDate.setMonth(startDate.getMonth() - offset); + if (isEndOfMonth) { + endDate.setMonth(endDate.getMonth() - offset + 1, 0); + } else { + endDate.setMonth(endDate.getMonth() - offset); + } + } const dateRange = intl.formatDateTimeRange(startDate, endDate, { day: 'numeric', month: 'long', @@ -80,12 +103,23 @@ export const getSinceDateRangeString = (message: MessageDescriptor = messages.si export const getTotalCostDateRangeString = ( value: string | number, - message: MessageDescriptor = messages.breakdownTotalCostDate + message: MessageDescriptor = messages.breakdownTotalCostDate, + offset = 0, + isEndOfMonth = false ) => { const endDate = getToday(); const startDate = getToday(); startDate.setDate(1); + + if (offset) { + startDate.setMonth(startDate.getMonth() - offset); + if (isEndOfMonth) { + endDate.setMonth(endDate.getMonth() - offset + 1, 0); + } else { + endDate.setMonth(endDate.getMonth() - offset); + } + } const dateRange = intl.formatDateTimeRange(startDate, endDate, { day: 'numeric', month: 'long', From f16a1e0884aa4194b6a89c0ddd9a52c3af53d62c Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Fri, 4 Oct 2024 15:33:56 -0400 Subject: [PATCH 02/22] Cost Explorer: Show previous data when current month is unavailable --- .../components/dateRange/dateRange.scss | 7 + src/routes/components/dateRange/dateRange.tsx | 95 ++++++++ .../components/dateRange/index.ts | 0 src/routes/details/awsDetails/awsDetails.tsx | 1 - .../details/awsDetails/detailsHeader.tsx | 5 +- .../details/azureDetails/azureDetails.tsx | 1 - .../details/azureDetails/detailsHeader.tsx | 5 +- .../components/dateRange/dateRange.tsx | 83 ------- .../details/gcpDetails/detailsHeader.tsx | 5 +- src/routes/details/gcpDetails/gcpDetails.tsx | 1 - .../details/ibmDetails/detailsHeader.tsx | 5 +- src/routes/details/ibmDetails/ibmDetails.tsx | 1 - .../details/ociDetails/detailsHeader.tsx | 5 +- src/routes/details/ociDetails/ociDetails.tsx | 1 - .../details/ocpDetails/detailsHeader.tsx | 5 +- src/routes/details/ocpDetails/ocpDetails.tsx | 1 - .../details/rhelDetails/detailsHeader.tsx | 5 +- .../details/rhelDetails/rhelDetails.tsx | 1 - src/routes/explorer/explorer.styles.ts | 4 + src/routes/explorer/explorer.tsx | 211 ++++++++++++++---- src/routes/explorer/explorerChart.tsx | 8 +- src/routes/explorer/explorerDatePicker.tsx | 11 +- src/routes/explorer/explorerDateRange.tsx | 76 ------- src/routes/explorer/explorerFilter.tsx | 89 +++++--- src/routes/explorer/explorerHeader.tsx | 12 +- src/routes/explorer/explorerTable.tsx | 21 +- src/routes/explorer/explorerUtils.ts | 90 ++++++-- src/routes/utils/dateRange.ts | 19 ++ 28 files changed, 454 insertions(+), 314 deletions(-) create mode 100644 src/routes/components/dateRange/dateRange.scss create mode 100644 src/routes/components/dateRange/dateRange.tsx rename src/routes/{details => }/components/dateRange/index.ts (100%) delete mode 100644 src/routes/details/components/dateRange/dateRange.tsx delete mode 100644 src/routes/explorer/explorerDateRange.tsx diff --git a/src/routes/components/dateRange/dateRange.scss b/src/routes/components/dateRange/dateRange.scss new file mode 100644 index 000000000..1ae639c0c --- /dev/null +++ b/src/routes/components/dateRange/dateRange.scss @@ -0,0 +1,7 @@ +@import url("~@patternfly/patternfly/base/patternfly-variables.css"); + +.dropdownOverride { + button.pf-v5-c-menu-toggle { + max-width: unset; + } +} diff --git a/src/routes/components/dateRange/dateRange.tsx b/src/routes/components/dateRange/dateRange.tsx new file mode 100644 index 000000000..0c47c5905 --- /dev/null +++ b/src/routes/components/dateRange/dateRange.tsx @@ -0,0 +1,95 @@ +import './dateRange.scss'; + +import type { MessageDescriptor } from '@formatjs/intl/src/types'; +import type { MenuToggleElement } from '@patternfly/react-core'; +import { Dropdown, DropdownItem, DropdownList, MenuToggle } from '@patternfly/react-core'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { getSinceDateRangeString } from 'utils/dates'; + +interface DateRangeOwnProps { + dateRangeType?: string; + isCurrentMonthData: boolean; + isDisabled?: boolean; + isExplorer?: boolean; + onSelect(value: string); +} + +type DateRangeProps = DateRangeOwnProps; + +const DateRange: React.FC = ({ dateRangeType, isCurrentMonthData, isExplorer, onSelect }) => { + const [isOpen, setIsOpen] = React.useState(false); + const intl = useIntl(); + + const onToggleClick = () => { + setIsOpen(!isOpen); + }; + + const getOptions = () => { + const options: { + isDisabled?: boolean; + label: MessageDescriptor; + value: string; + }[] = [ + { label: messages.explorerDateRange, value: 'current_month_to_date', isDisabled: isCurrentMonthData === false }, + { label: messages.explorerDateRange, value: 'previous_month' }, + ]; + if (isExplorer) { + options.push( + { label: messages.explorerDateRange, value: 'previous_month_to_date' }, + { label: messages.explorerDateRange, value: 'last_thirty_days' }, + { label: messages.explorerDateRange, value: 'last_sixty_days' }, + { label: messages.explorerDateRange, value: 'last_ninety_days' }, + { label: messages.explorerDateRange, value: 'custom' } + ); + } + return options; + }; + + const handleOnSelect = (_evt, value) => { + if (onSelect) { + onSelect(value); + } + setIsOpen(false); + }; + + return ( +
+ setIsOpen(val)} + toggle={(toggleRef: React.Ref) => ( + + {intl.formatMessage(messages.explorerDateRange, { value: dateRangeType })} + + )} + > + + {getOptions().map((option, index) => ( + + {intl.formatMessage(option.label, { value: option.value })} + + ))} + + +
+ ); +}; + +export { DateRange }; diff --git a/src/routes/details/components/dateRange/index.ts b/src/routes/components/dateRange/index.ts similarity index 100% rename from src/routes/details/components/dateRange/index.ts rename to src/routes/components/dateRange/index.ts diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index f217c51c6..e7f4fcd19 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -419,7 +419,6 @@ class AwsDetails extends React.Component { currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCostTypeSelect={() => handleOnCostTypeSelect(query, router)} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} diff --git a/src/routes/details/awsDetails/detailsHeader.tsx b/src/routes/details/awsDetails/detailsHeader.tsx index 4c63089fc..f29878aa1 100644 --- a/src/routes/details/awsDetails/detailsHeader.tsx +++ b/src/routes/details/awsDetails/detailsHeader.tsx @@ -16,8 +16,8 @@ import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { CostType } from 'routes/components/costType'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedAwsReportItemsParams } from 'routes/utils/computedReport/getComputedAwsReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAwsReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -39,7 +39,6 @@ interface DetailsHeaderOwnProps { costType?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCostTypeSelect(value: string); onCurrencySelect(value: string); onGroupBySelect(value: string); @@ -116,7 +115,6 @@ class DetailsHeaderBase extends React.Component { isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, - isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, @@ -169,7 +167,6 @@ class DetailsHeaderBase extends React.Component { dateRangeType={currentDateRangeType} isCurrentMonthData={isCurrentMonthData} isDisabled={!showContent} - isPreviousMonthData={isPreviousMonthData} onSelect={this.handleOnDateRangeSelected} /> diff --git a/src/routes/details/azureDetails/azureDetails.tsx b/src/routes/details/azureDetails/azureDetails.tsx index 16b14e4cb..c3d598cd6 100644 --- a/src/routes/details/azureDetails/azureDetails.tsx +++ b/src/routes/details/azureDetails/azureDetails.tsx @@ -376,7 +376,6 @@ class AzureDetails extends React.Component currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} query={query} diff --git a/src/routes/details/azureDetails/detailsHeader.tsx b/src/routes/details/azureDetails/detailsHeader.tsx index 5c48db3b7..b7d9166eb 100644 --- a/src/routes/details/azureDetails/detailsHeader.tsx +++ b/src/routes/details/azureDetails/detailsHeader.tsx @@ -13,8 +13,8 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedAzureReportItemsParams } from 'routes/utils/computedReport/getComputedAzureReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAzureReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -35,7 +35,6 @@ interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCurrencySelect(value: string); onGroupBySelect(value: string); query?: Query; @@ -100,7 +99,6 @@ class DetailsHeaderBase extends React.Component { isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, - isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, @@ -148,7 +146,6 @@ class DetailsHeaderBase extends React.Component { dateRangeType={currentDateRangeType} isCurrentMonthData={isCurrentMonthData} isDisabled={!showContent} - isPreviousMonthData={isPreviousMonthData} onSelect={this.handleOnDateRangeSelected} /> diff --git a/src/routes/details/components/dateRange/dateRange.tsx b/src/routes/details/components/dateRange/dateRange.tsx deleted file mode 100644 index 5017c684f..000000000 --- a/src/routes/details/components/dateRange/dateRange.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { MenuToggleElement } from '@patternfly/react-core'; -import { Dropdown, DropdownItem, DropdownList, MenuToggle } from '@patternfly/react-core'; -import messages from 'locales/messages'; -import React from 'react'; -import { useIntl } from 'react-intl'; -import { getSinceDateRangeString } from 'utils/dates'; - -interface DateRangeOwnProps { - dateRangeType?: string; - isCurrentMonthData: boolean; - isDisabled?: boolean; - isPreviousMonthData: boolean; - onSelect(value: string); -} - -type DateRangeProps = DateRangeOwnProps; - -const DateRange: React.FC = ({ dateRangeType, isCurrentMonthData, isPreviousMonthData, onSelect }) => { - const [isOpen, setIsOpen] = React.useState(false); - const intl = useIntl(); - - const onToggleClick = () => { - setIsOpen(!isOpen); - }; - - const getOptions = (): { - isDisabled?: boolean; - label: MessageDescriptor; - value: string; - }[] => { - return [ - { label: messages.explorerDateRange, value: 'current_month_to_date', isDisabled: isCurrentMonthData === false }, - { label: messages.explorerDateRange, value: 'previous_month', isDisabled: isPreviousMonthData === false }, - ]; - }; - - const handleOnSelect = (_evt, value) => { - if (onSelect) { - onSelect(value); - } - setIsOpen(false); - }; - - return ( - setIsOpen(val)} - toggle={(toggleRef: React.Ref) => ( - - {intl.formatMessage(messages.explorerDateRange, { value: dateRangeType })} - - )} - ouiaId="BasicDropdown" - shouldFocusToggleOnSelect - > - - {getOptions().map((option, index) => ( - - {intl.formatMessage(option.label, { value: option.value })} - - ))} - - - ); -}; - -export { DateRange }; diff --git a/src/routes/details/gcpDetails/detailsHeader.tsx b/src/routes/details/gcpDetails/detailsHeader.tsx index e6c184eed..dbfd4b42b 100644 --- a/src/routes/details/gcpDetails/detailsHeader.tsx +++ b/src/routes/details/gcpDetails/detailsHeader.tsx @@ -13,8 +13,8 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedGcpReportItemsParams } from 'routes/utils/computedReport/getComputedGcpReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedGcpReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -35,7 +35,6 @@ interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCurrencySelect(value: string); onGroupBySelect(value: string); query?: Query; @@ -101,7 +100,6 @@ class DetailsHeaderBase extends React.Component { isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, - isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, @@ -149,7 +147,6 @@ class DetailsHeaderBase extends React.Component { dateRangeType={currentDateRangeType} isCurrentMonthData={isCurrentMonthData} isDisabled={!showContent} - isPreviousMonthData={isPreviousMonthData} onSelect={this.handleOnDateRangeSelected} /> diff --git a/src/routes/details/gcpDetails/gcpDetails.tsx b/src/routes/details/gcpDetails/gcpDetails.tsx index 7e173ef20..1d3ad34a4 100644 --- a/src/routes/details/gcpDetails/gcpDetails.tsx +++ b/src/routes/details/gcpDetails/gcpDetails.tsx @@ -376,7 +376,6 @@ class GcpDetails extends React.Component { currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} query={query} diff --git a/src/routes/details/ibmDetails/detailsHeader.tsx b/src/routes/details/ibmDetails/detailsHeader.tsx index af9abf9c2..625b4fdb0 100644 --- a/src/routes/details/ibmDetails/detailsHeader.tsx +++ b/src/routes/details/ibmDetails/detailsHeader.tsx @@ -13,8 +13,8 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedIbmReportItemsParams } from 'routes/utils/computedReport/getComputedIbmReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedIbmReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -35,7 +35,6 @@ interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCurrencySelect(value: string); onGroupBySelect(value: string); query?: Query; @@ -101,7 +100,6 @@ class DetailsHeaderBase extends React.Component { isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, - isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, @@ -149,7 +147,6 @@ class DetailsHeaderBase extends React.Component { dateRangeType={currentDateRangeType} isCurrentMonthData={isCurrentMonthData} isDisabled={!showContent} - isPreviousMonthData={isPreviousMonthData} onSelect={this.handleOnDateRangeSelected} /> diff --git a/src/routes/details/ibmDetails/ibmDetails.tsx b/src/routes/details/ibmDetails/ibmDetails.tsx index 2b0ed6810..c7ad8fc26 100644 --- a/src/routes/details/ibmDetails/ibmDetails.tsx +++ b/src/routes/details/ibmDetails/ibmDetails.tsx @@ -378,7 +378,6 @@ class IbmDetails extends React.Component { currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} query={query} diff --git a/src/routes/details/ociDetails/detailsHeader.tsx b/src/routes/details/ociDetails/detailsHeader.tsx index 370e2766d..3d4324bfb 100644 --- a/src/routes/details/ociDetails/detailsHeader.tsx +++ b/src/routes/details/ociDetails/detailsHeader.tsx @@ -13,8 +13,8 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedOciReportItemsParams } from 'routes/utils/computedReport/getComputedOciReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOciReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -35,7 +35,6 @@ interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCurrencySelect(value: string); onGroupBySelect(value: string); query?: Query; @@ -100,7 +99,6 @@ class DetailsHeaderBase extends React.Component { isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, - isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, @@ -148,7 +146,6 @@ class DetailsHeaderBase extends React.Component { dateRangeType={currentDateRangeType} isCurrentMonthData={isCurrentMonthData} isDisabled={!showContent} - isPreviousMonthData={isPreviousMonthData} onSelect={this.handleOnDateRangeSelected} /> diff --git a/src/routes/details/ociDetails/ociDetails.tsx b/src/routes/details/ociDetails/ociDetails.tsx index 8ec21e892..5e1027978 100644 --- a/src/routes/details/ociDetails/ociDetails.tsx +++ b/src/routes/details/ociDetails/ociDetails.tsx @@ -377,7 +377,6 @@ class OciDetails extends React.Component { currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} query={query} diff --git a/src/routes/details/ocpDetails/detailsHeader.tsx b/src/routes/details/ocpDetails/detailsHeader.tsx index e3de2b0dd..ca043529b 100644 --- a/src/routes/details/ocpDetails/detailsHeader.tsx +++ b/src/routes/details/ocpDetails/detailsHeader.tsx @@ -15,9 +15,9 @@ import { connect } from 'react-redux'; import { ComputedReportItemValueType } from 'routes/components/charts/common'; import { CostDistribution } from 'routes/components/costDistribution'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; import { EmptyValueState } from 'routes/components/state/emptyValueState'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedOcpReportItemsParams } from 'routes/utils/computedReport/getComputedOcpReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOcpReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -39,7 +39,6 @@ interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCurrencySelect(value: string); onCostDistributionSelect(value: string); onDateRangeSelected(value: string); @@ -107,7 +106,6 @@ class DetailsHeaderBase extends React.Component diff --git a/src/routes/details/ocpDetails/ocpDetails.tsx b/src/routes/details/ocpDetails/ocpDetails.tsx index ee8e9eb6c..91c5cd048 100644 --- a/src/routes/details/ocpDetails/ocpDetails.tsx +++ b/src/routes/details/ocpDetails/ocpDetails.tsx @@ -453,7 +453,6 @@ class OcpDetails extends React.Component { currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCostDistributionSelect={() => handleOnCostDistributionSelect(query, router)} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} diff --git a/src/routes/details/rhelDetails/detailsHeader.tsx b/src/routes/details/rhelDetails/detailsHeader.tsx index 690f0a6b4..e07876024 100644 --- a/src/routes/details/rhelDetails/detailsHeader.tsx +++ b/src/routes/details/rhelDetails/detailsHeader.tsx @@ -13,9 +13,9 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; +import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; import { EmptyValueState } from 'routes/components/state/emptyValueState'; -import { DateRange } from 'routes/details/components/dateRange'; import type { ComputedRhelReportItemsParams } from 'routes/utils/computedReport/getComputedRhelReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedRhelReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -36,7 +36,6 @@ interface DetailsHeaderOwnProps { currency?: string; groupBy?: string; isCurrentMonthData?: boolean; - isPreviousMonthData?: boolean; onCurrencySelect(value: string); onGroupBySelect(value: string); query?: Query; @@ -103,7 +102,6 @@ class DetailsHeaderBase extends React.Component { isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, - isPreviousMonthData, onCurrencySelect, onGroupBySelect, providers, @@ -172,7 +170,6 @@ class DetailsHeaderBase extends React.Component { dateRangeType={currentDateRangeType} isCurrentMonthData={isCurrentMonthData} isDisabled={!showContent} - isPreviousMonthData={isPreviousMonthData} onSelect={this.handleOnDateRangeSelected} /> diff --git a/src/routes/details/rhelDetails/rhelDetails.tsx b/src/routes/details/rhelDetails/rhelDetails.tsx index 25f61854e..98ad4ed78 100644 --- a/src/routes/details/rhelDetails/rhelDetails.tsx +++ b/src/routes/details/rhelDetails/rhelDetails.tsx @@ -431,7 +431,6 @@ class RhelDetails extends React.Component { currency={currency} groupBy={groupById} isCurrentMonthData={isCurrentMonthData} - isPreviousMonthData={isPreviousMonthData} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onGroupBySelect={this.handleOnGroupBySelect} query={query} diff --git a/src/routes/explorer/explorer.styles.ts b/src/routes/explorer/explorer.styles.ts index f2feb3dd9..611083247 100644 --- a/src/routes/explorer/explorer.styles.ts +++ b/src/routes/explorer/explorer.styles.ts @@ -3,6 +3,10 @@ import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg' import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; export const styles = { + alert: { + marginLeft: global_spacer_lg.value, + marginRight: global_spacer_lg.value, + }, chartContainer: { backgroundColor: global_BackgroundColor_light_100.value, marginLeft: global_spacer_lg.value, diff --git a/src/routes/explorer/explorer.tsx b/src/routes/explorer/explorer.tsx index d99b98f43..a0bac8023 100644 --- a/src/routes/explorer/explorer.tsx +++ b/src/routes/explorer/explorer.tsx @@ -1,4 +1,4 @@ -import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import { Alert, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; @@ -21,11 +21,12 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; +import { ProviderDetails } from 'routes/details/components/providerDetails'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedExplorerReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; -import type { DateRangeType } from 'routes/utils/dateRange'; -import { getDateRangeFromQuery, getDateRangeTypeDefault } from 'routes/utils/dateRange'; +import { DateRangeType, getDateRange } from 'routes/utils/dateRange'; +import { getDateRangeTypeDefault } from 'routes/utils/dateRange'; import { getGroupByCostCategory, getGroupById, getGroupByOrgValue, getGroupByTagKey } from 'routes/utils/groupBy'; import { filterProviders, hasData } from 'routes/utils/providers'; import { getRouteForQuery } from 'routes/utils/query'; @@ -44,6 +45,7 @@ import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { userAccessQuery, userAccessSelectors } from 'store/userAccess'; +import { getSinceDateRangeString } from 'utils/dates'; import { awsCategoryPrefix, noPrefix, orgUnitIdKey, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -66,6 +68,7 @@ import { ExplorerToolbar } from './explorerToolbar'; import { baseQuery, getGroupByDefault, + getIsDataAvailable, getPerspectiveDefault, getReportPathsType, getReportType, @@ -82,6 +85,10 @@ interface ExplorerStateProps { dateRangeType: DateRangeType; gcpProviders: Providers; ibmProviders: Providers; + isAccountInfoEmptyStateToggleEnabled?: boolean; + isCurrentMonthData?: boolean; + isDataAvailable?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; isFinsightsToggleEnabled?: boolean; ocpProviders: Providers; perspective: PerspectiveType; @@ -109,6 +116,7 @@ interface ExplorerState { columns?: any[]; endDate?: Date; isAllSelected?: boolean; + isDateRangeSelected?: boolean; isExportModalOpen?: boolean; rows?: any[]; selectedItems?: ComputedReportItem[]; @@ -123,21 +131,13 @@ class Explorer extends React.Component { protected defaultState: ExplorerState = { columns: [], isAllSelected: false, + isDateRangeSelected: false, isExportModalOpen: false, rows: [], selectedItems: [], }; public state: ExplorerState = { ...this.defaultState }; - constructor(stateProps, dispatchProps) { - super(stateProps, dispatchProps); - this.handleOnBulkSelect = this.handleOnBulkSelect.bind(this); - this.handleOnExportModalClose = this.handleOnExportModalClose.bind(this); - this.handleOnExportModalOpen = this.handleOnExportModalOpen.bind(this); - this.handleOnPerspectiveClick = this.handleOnPerspectiveClick.bind(this); - this.handleOnSelect = this.handleOnSelect.bind(this); - } - public componentDidMount() { this.updateReport(); } @@ -179,6 +179,50 @@ class Explorer extends React.Component { return computedItems; }; + private getEmptyProviderState = () => { + const { isAccountInfoEmptyStateToggleEnabled } = this.props; + + const { perspective } = this.props; + + let providerType; + switch (perspective) { + case PerspectiveType.aws: + case PerspectiveType.awsOcp: + providerType = ProviderType.aws; + break; + case PerspectiveType.azure: + case PerspectiveType.azureOcp: + providerType = ProviderType.azure; + break; + case PerspectiveType.gcp: + case PerspectiveType.gcpOcp: + providerType = ProviderType.gcp; + break; + case PerspectiveType.ibm: + case PerspectiveType.ibmOcp: + providerType = ProviderType.ibm; + break; + case PerspectiveType.oci: + providerType = ProviderType.oci; + break; + case PerspectiveType.ocp: + case PerspectiveType.ocpCloud: + providerType = ProviderType.ocp; + break; + case PerspectiveType.rhel: + providerType = ProviderType.rhel; + break; + } + + return ( + : undefined + } + /> + ); + }; + private getExportModal = (computedItems: ComputedReportItem[]) => { const { perspective, query, report, reportQueryString } = this.props; const { isAllSelected, isExportModalOpen, selectedItems } = this.state; @@ -252,7 +296,7 @@ class Explorer extends React.Component { }; private getTable = () => { - const { costDistribution, perspective, query, report, reportFetchStatus, router } = this.props; + const { costDistribution, dateRangeType, perspective, query, report, reportFetchStatus, router } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -263,6 +307,7 @@ class Explorer extends React.Component { return ( { }); }; + private handleOnDateRangeSelect = () => { + this.setState({ isDateRangeSelected: true }); + }; + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; @@ -443,9 +492,13 @@ class Explorer extends React.Component { costDistribution, costType, currency, + dateRangeType, gcpProviders, ibmProviders, intl, + isCurrentMonthData, + isDataAvailable, + isDetailsDateRangeToggleEnabled, ocpProviders, providersFetchStatus, perspective, @@ -456,6 +509,7 @@ class Explorer extends React.Component { reportFetchStatus, router, } = this.props; + const { isDateRangeSelected } = this.state; // Note: No need to test OCP on cloud here, since that requires at least one provider const noAwsProviders = !this.isAwsAvailable() && providersFetchStatus === FetchStatus.complete; @@ -511,6 +565,7 @@ class Explorer extends React.Component { costDistribution={costDistribution} costType={costType} currency={currency} + dateRangeType={dateRangeType} groupBy={ groupByCostCategory ? `${awsCategoryPrefix}${groupByCostCategory}` @@ -518,10 +573,12 @@ class Explorer extends React.Component { ? `${tagPrefix}${groupByTagKey}` : groupById } + isCurrentMonthData={isCurrentMonthData} onCostDistributionSelect={() => handleOnCostDistributionSelect(query, router)} onCostTypeSelect={() => handleOnCostTypeSelect(query, router)} onCurrencySelect={() => handleOnCurrencySelect(query, router)} onDatePickerSelect={this.handleOnDatePickerSelect} + onDateRangeSelect={this.handleOnDateRangeSelect} onFilterAdded={filter => handleOnFilterAdded(query, router, filter)} onFilterRemoved={filter => handleOnFilterRemoved(query, router, filter)} onGroupBySelect={this.handleOnGroupBySelect} @@ -529,39 +586,77 @@ class Explorer extends React.Component { perspective={perspective} report={report} /> - {itemsTotal > 0 && ( -
-
- + {!isDataAvailable && isDetailsDateRangeToggleEnabled ? ( + this.getEmptyProviderState() + ) : ( + <> + {isDetailsDateRangeToggleEnabled ? ( +
+ {!isCurrentMonthData && !isDateRangeSelected && dateRangeType === DateRangeType.previousMonth && ( + + )} +
+ +
+
+ ) : ( + itemsTotal > 0 && ( +
+
+ +
+
+ ) + )} +
+
{this.getToolbar(computedItems)}
+ {this.getExportModal(computedItems)} + {reportFetchStatus === FetchStatus.inProgress ? ( + + ) : ( + <> +
{this.getTable()}
+
+
{this.getPagination(isDisabled, true)}
+
+ + )}
-
+ )} -
-
{this.getToolbar(computedItems)}
- {this.getExportModal(computedItems)} - {reportFetchStatus === FetchStatus.inProgress ? ( - - ) : ( - <> -
{this.getTable()}
-
-
{this.getPagination(isDisabled, true)}
-
- - )} -
); } @@ -598,15 +693,12 @@ const mapStateToProps = createMapStateToProps( - (state, { costType, currency, perspective, router }) => { + (state, { costType, currency, dateRangeType, perspective, router }) => { const queryFromRoute = parseQuery(router.location.search); - const { end_date, start_date } = getDateRangeFromQuery(queryFromRoute); - const groupBy = queryFromRoute.group_by ? getGroupById(queryFromRoute) : getGroupByDefault(perspective); const group_by = queryFromRoute.group_by ? queryFromRoute.group_by : { [groupBy]: '*' }; // Ensure group_by key is not undefined const costDistribution = perspective === PerspectiveType.ocp && groupBy === 'project' ? getCostDistribution() : undefined; + const { end_date, start_date } = getDateRange(dateRangeType); const query: any = { ...queryFromRoute, diff --git a/src/routes/explorer/explorerDatePicker.tsx b/src/routes/explorer/explorerDatePicker.tsx index 5d97c0d29..340fb6b9e 100644 --- a/src/routes/explorer/explorerDatePicker.tsx +++ b/src/routes/explorer/explorerDatePicker.tsx @@ -1,13 +1,11 @@ import type { DatePickerRef } from '@patternfly/react-core'; import { DatePicker } from '@patternfly/react-core'; -import type { Query } from 'api/queries/query'; -import { parseQuery } from 'api/queries/query'; import messages from 'locales/messages'; import type { FormEvent } from 'react'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; -import { DateRangeType, getDateRangeFromQuery, getDateRangeTypeDefault } from 'routes/utils/dateRange'; +import { DateRangeType, getDateRange } from 'routes/utils/dateRange'; import { formatDate, getLast90DaysDate, getToday } from 'utils/dates'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -15,6 +13,7 @@ import { withRouter } from 'utils/router'; import { styles } from './explorerDatePicker.styles'; interface ExplorerDatePickerOwnProps extends RouterComponentProps, WrappedComponentProps { + dateRangeType?: DateRangeType; onSelect(startDate: Date, endDate: Date); } @@ -37,10 +36,8 @@ class ExplorerDatePickerBase extends React.Component(); public componentDidMount() { - const { router } = this.props; - const queryFromRoute = parseQuery(router.location.search); - const dateRangeType = getDateRangeTypeDefault(queryFromRoute); - const { end_date, start_date } = getDateRangeFromQuery(queryFromRoute); + const { dateRangeType } = this.props; + const { end_date, start_date } = getDateRange(dateRangeType); if (this.startDateRef?.current) { this.startDateRef.current.setCalendarOpen(dateRangeType !== DateRangeType.custom); diff --git a/src/routes/explorer/explorerDateRange.tsx b/src/routes/explorer/explorerDateRange.tsx deleted file mode 100644 index d431476bf..000000000 --- a/src/routes/explorer/explorerDateRange.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import type { SelectWrapperOption } from 'routes/components/selectWrapper'; -import { SelectWrapper } from 'routes/components/selectWrapper'; - -interface ExplorerDateRangeOwnProps { - dateRangeType?: string; - isDisabled?: boolean; - onSelect(value: string); - options?: { - label: MessageDescriptor; - value: string; - }[]; -} - -interface ExplorerDateRangeState { - // TBD... -} - -type ExplorerDateRangeProps = ExplorerDateRangeOwnProps & WrappedComponentProps; - -class ExplorerDateRangeBase extends React.Component { - protected defaultState: ExplorerDateRangeState = { - // TBD... - }; - public state: ExplorerDateRangeState = { ...this.defaultState }; - - private getSelect = () => { - const { dateRangeType, isDisabled } = this.props; - - const selectOptions = this.getSelectOptions(); - const selection = selectOptions.find(option => option.value === dateRangeType); - - return ( - - ); - }; - - private getSelectOptions = (): SelectWrapperOption[] => { - const { intl, options } = this.props; - - const selectOptions: SelectWrapperOption[] = []; - - options.map(option => { - selectOptions.push({ - toString: () => intl.formatMessage(option.label, { value: option.value }), - value: option.value, - }); - }); - return selectOptions; - }; - - private handleOnSelect = (_evt, selection: SelectWrapperOption) => { - const { onSelect } = this.props; - - if (onSelect) { - onSelect(selection.value); - } - }; - - public render() { - return this.getSelect(); - } -} - -const ExplorerDateRange = injectIntl(ExplorerDateRangeBase); - -export { ExplorerDateRange }; diff --git a/src/routes/explorer/explorerFilter.tsx b/src/routes/explorer/explorerFilter.tsx index d110d2b38..91a59d9a4 100644 --- a/src/routes/explorer/explorerFilter.tsx +++ b/src/routes/explorer/explorerFilter.tsx @@ -4,7 +4,7 @@ import type { ToolbarChipGroup } from '@patternfly/react-core'; import type { Org, OrgPathsType } from 'api/orgs/org'; import { OrgType } from 'api/orgs/org'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery } from 'api/queries/query'; +import { getQuery } from 'api/queries/query'; import type { Resource, ResourcePathsType } from 'api/resources/resource'; import { ResourceType } from 'api/resources/resource'; import type { Tag, TagPathsType } from 'api/tags/tag'; @@ -15,7 +15,8 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { DataToolbar } from 'routes/components/dataToolbar'; -import { DateRangeType, getDateRangeFromQuery, getDateRangeTypeDefault } from 'routes/utils/dateRange'; +import { DateRange } from 'routes/components/dateRange'; +import { DateRangeType, getDateRange, getDateRangeById } from 'routes/utils/dateRange'; import { isEqual } from 'routes/utils/equal'; import type { Filter } from 'routes/utils/filter'; import { getRouteForQuery } from 'routes/utils/query'; @@ -31,10 +32,8 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; import { ExplorerDatePicker } from './explorerDatePicker'; -import { ExplorerDateRange } from './explorerDateRange'; import { styles } from './explorerFilter.styles'; import { - dateRangeOptions, getGroupByOptions, getOrgReportPathsType, getResourcePathsType, @@ -43,8 +42,11 @@ import { } from './explorerUtils'; interface ExplorerFilterOwnProps extends RouterComponentProps, WrappedComponentProps { + dateRangeType: DateRangeType; groupBy: string; + isCurrentMonthData?: boolean; isDisabled?: boolean; + onDateRangeSelect(value: string); onFilterAdded(filter: Filter); onFilterRemoved(filter: Filter); perspective: PerspectiveType; @@ -53,7 +55,6 @@ interface ExplorerFilterOwnProps extends RouterComponentProps, WrappedComponentP } interface ExplorerFilterStateProps { - dateRangeType: DateRangeType; isOcpCloudGroupBysToggleEnabled?: boolean; orgPathsType?: OrgPathsType; orgQueryString?: string; @@ -77,7 +78,7 @@ interface ExplorerFilterDispatchProps { interface ExplorerFilterState { categoryOptions?: ToolbarChipGroup[]; - currentDateRangeType?: string; + currentDateRange: DateRangeType; showDatePicker?: boolean; } @@ -89,6 +90,7 @@ const tagType = TagType.tag; export class ExplorerFilterBase extends React.Component { protected defaultState: ExplorerFilterState = { + currentDateRange: this.props.dateRangeType, showDatePicker: false, }; public state: ExplorerFilterState = { ...this.defaultState }; @@ -99,25 +101,32 @@ export class ExplorerFilterBase extends React.Component { + this.updateDateRange(currentDateRange); + }); } } @@ -159,26 +168,30 @@ export class ExplorerFilterBase extends React.Component { - const { isDisabled } = this.props; - const { currentDateRangeType } = this.state; + const { isCurrentMonthData, isDisabled } = this.props; + const { currentDateRange } = this.state; return ( - ); }; private getDatePickerComponent = () => { + const { dateRangeType } = this.props; const { showDatePicker } = this.state; - return showDatePicker ? : undefined; + return showDatePicker ? ( + + ) : undefined; }; - private handleOnDatePickerSelected = (startDate: Date, endDate: Date) => { + private handleOnDatePickerSelect = (startDate: Date, endDate: Date) => { const { query, router } = this.props; const { start_date, end_date } = formatStartEndDate(startDate, endDate); @@ -192,23 +205,34 @@ export class ExplorerFilterBase extends React.Component { - const { query, router } = this.props; + private handleOnDateRangeSelect = (value: string) => { + const { onDateRangeSelect } = this.props; + const currentDateRange = getDateRangeById(value); const showDatePicker = value === DateRangeType.custom; - this.setState({ currentDateRangeType: value, showDatePicker }, () => { + this.setState({ currentDateRange, showDatePicker }, () => { if (!showDatePicker) { - const newQuery = { - ...JSON.parse(JSON.stringify(query)), - dateRangeType: value, - start_date: undefined, - end_date: undefined, - }; - router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + this.updateDateRange(currentDateRange); + } + // Clear inline alert + if (onDateRangeSelect) { + onDateRangeSelect(currentDateRange); } }); }; + private updateDateRange = (value: string) => { + const { query, router } = this.props; + + const newQuery = { + ...JSON.parse(JSON.stringify(query)), + dateRangeType: value, + start_date: undefined, + end_date: undefined, + }; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }; + private updateReport = () => { const { fetchOrg, @@ -253,8 +277,8 @@ export class ExplorerFilterBase extends React.Component( - (state, { perspective, router }) => { - const queryFromRoute = parseQuery(router.location.search); - const dateRangeType = getDateRangeTypeDefault(queryFromRoute); - const { end_date, start_date } = getDateRangeFromQuery(queryFromRoute); + (state, { dateRangeType, perspective }) => { + const { end_date, start_date } = getDateRange(dateRangeType); // Omitting key_only to share a single request -- the toolbar needs key values const orgQueryString = getQuery({ @@ -327,7 +349,6 @@ const mapStateToProps = createMapStateToProps { protected defaultState: ExplorerHeaderState = { - // TBD... + currentPerspective: this.props.perspective, }; public state: ExplorerHeaderState = { ...this.defaultState }; @@ -253,13 +257,16 @@ class ExplorerHeaderBase extends React.Component diff --git a/src/routes/explorer/explorerTable.tsx b/src/routes/explorer/explorerTable.tsx index 7089bd11e..f8e5fe07f 100644 --- a/src/routes/explorer/explorerTable.tsx +++ b/src/routes/explorer/explorerTable.tsx @@ -24,7 +24,6 @@ import { Tr, } from '@patternfly/react-table'; import type { Query } from 'api/queries/query'; -import { parseQuery } from 'api/queries/query'; import type { Report } from 'api/reports/report'; import { format } from 'date-fns'; import messages from 'locales/messages'; @@ -36,7 +35,7 @@ import { ComputedReportItemType, ComputedReportItemValueType } from 'routes/comp import { EmptyFilterState } from 'routes/components/state/emptyFilterState'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; -import { getDateRangeFromQuery } from 'routes/utils/dateRange'; +import { type DateRangeType, getDateRange } from 'routes/utils/dateRange'; import { createMapStateToProps } from 'store/common'; import { formatCurrency } from 'utils/format'; import { classificationDefault, classificationUnallocated, noPrefix } from 'utils/props'; @@ -48,6 +47,7 @@ import { PerspectiveType } from './explorerUtils'; interface ExplorerTableOwnProps extends RouterComponentProps, WrappedComponentProps { costDistribution?: string; + dateRangeType?: DateRangeType; groupBy: string; groupByCostCategory?: string; groupByOrg?: string; @@ -584,15 +584,16 @@ class ExplorerTableBase extends React.Component((state, { router }) => { - const queryFromRoute = parseQuery(router.location.search); - const { end_date, start_date } = getDateRangeFromQuery(queryFromRoute); +const mapStateToProps = createMapStateToProps( + (state, { dateRangeType }) => { + const { end_date, start_date } = getDateRange(dateRangeType); - return { - end_date, - start_date, - }; -}); + return { + end_date, + start_date, + }; + } +); const mapDispatchToProps: ExplorerTableDispatchProps = {}; diff --git a/src/routes/explorer/explorerUtils.ts b/src/routes/explorer/explorerUtils.ts index 2b811af38..c2d6d6271 100644 --- a/src/routes/explorer/explorerUtils.ts +++ b/src/routes/explorer/explorerUtils.ts @@ -1,4 +1,3 @@ -import type { MessageDescriptor } from '@formatjs/intl/src/types'; import { OrgPathsType } from 'api/orgs/org'; import type { Providers } from 'api/providers'; import type { Query } from 'api/queries/query'; @@ -6,14 +5,13 @@ import { ReportPathsType, ReportType } from 'api/reports/report'; import { ResourcePathsType } from 'api/resources/resource'; import { TagPathsType } from 'api/tags/tag'; import type { UserAccess } from 'api/userAccess'; -import messages from 'locales/messages'; import type { ComputedAwsReportItemsParams } from 'routes/utils/computedReport/getComputedAwsReportItems'; import type { ComputedAzureReportItemsParams } from 'routes/utils/computedReport/getComputedAzureReportItems'; import type { ComputedGcpReportItemsParams } from 'routes/utils/computedReport/getComputedGcpReportItems'; import type { ComputedIbmReportItemsParams } from 'routes/utils/computedReport/getComputedIbmReportItems'; import type { ComputedOciReportItemsParams } from 'routes/utils/computedReport/getComputedOciReportItems'; import type { ComputedOcpReportItemsParams } from 'routes/utils/computedReport/getComputedOcpReportItems'; -import { hasCloudProvider } from 'routes/utils/providers'; +import { hasCloudProvider, hasCurrentMonthData, hasData, hasPreviousMonthData } from 'routes/utils/providers'; import { hasAwsAccess, hasAzureAccess, @@ -55,19 +53,6 @@ export const baseQuery: Query = { }, }; -export const dateRangeOptions: { - label: MessageDescriptor; - value: string; -}[] = [ - { label: messages.explorerDateRange, value: 'current_month_to_date' }, - { label: messages.explorerDateRange, value: 'previous_month' }, - { label: messages.explorerDateRange, value: 'previous_month_to_date' }, - { label: messages.explorerDateRange, value: 'last_thirty_days' }, - { label: messages.explorerDateRange, value: 'last_sixty_days' }, - { label: messages.explorerDateRange, value: 'last_ninety_days' }, - { label: messages.explorerDateRange, value: 'custom' }, -]; - export const groupByAwsOptions: { label: string; value: ComputedAwsReportItemsParams['idKey']; @@ -167,6 +152,7 @@ export const getPerspectiveDefault = ({ const perspective = queryFromRoute.perspective; // Upon page refresh, perspective param takes precedence + // Todo: Add ocp here? switch (perspective) { case PerspectiveType.aws: case PerspectiveType.awsOcp: @@ -289,6 +275,78 @@ export const getGroupByOptions = (perspective: string, isOcpCloudGroupBysToggleE return result; }; +export const getIsDataAvailable = ({ + awsProviders, + azureProviders, + ociProviders, + gcpProviders, + ibmProviders, + ocpProviders, + perspective, + rhelProviders, +}: { + awsProviders: Providers; + azureProviders: Providers; + ociProviders: Providers; + gcpProviders: Providers; + ibmProviders: Providers; + ocpProviders: Providers; + perspective: string; + rhelProviders: Providers; +}) => { + let isCurrentMonthData; + let isDataAvailable; + let isPreviousMonthData; + + switch (perspective) { + case PerspectiveType.aws: + case PerspectiveType.awsOcp: + isDataAvailable = hasData(awsProviders); + isCurrentMonthData = hasCurrentMonthData(awsProviders); + isPreviousMonthData = hasPreviousMonthData(awsProviders); + break; + case PerspectiveType.azure: + case PerspectiveType.azureOcp: + isDataAvailable = hasData(azureProviders); + isCurrentMonthData = hasCurrentMonthData(azureProviders); + isPreviousMonthData = hasPreviousMonthData(azureProviders); + break; + case PerspectiveType.gcp: + case PerspectiveType.gcpOcp: + isDataAvailable = hasData(gcpProviders); + isCurrentMonthData = hasCurrentMonthData(gcpProviders); + isPreviousMonthData = hasPreviousMonthData(gcpProviders); + break; + case PerspectiveType.ibm: + case PerspectiveType.ibmOcp: + isDataAvailable = hasData(ibmProviders); + isCurrentMonthData = hasCurrentMonthData(ibmProviders); + isPreviousMonthData = hasPreviousMonthData(ibmProviders); + break; + case PerspectiveType.oci: + isDataAvailable = hasData(ociProviders); + isCurrentMonthData = hasCurrentMonthData(ociProviders); + isPreviousMonthData = isPreviousMonthData(ociProviders); + break; + case PerspectiveType.ocp: + case PerspectiveType.ocpCloud: + isDataAvailable = hasData(ocpProviders); + isCurrentMonthData = hasCurrentMonthData(ocpProviders); + isPreviousMonthData = hasPreviousMonthData(ocpProviders); + break; + case PerspectiveType.rhel: + isDataAvailable = hasData(rhelProviders); + isCurrentMonthData = hasCurrentMonthData(rhelProviders); + isPreviousMonthData = hasPreviousMonthData(rhelProviders); + break; + } + return { + isCurrentMonthData, + isDataAvailable, + isPreviousMonthData, + }; +}; + export const getOrgReportPathsType = (perspective: string) => { let result; switch (perspective) { diff --git a/src/routes/utils/dateRange.ts b/src/routes/utils/dateRange.ts index 85b59d694..f27fbbbd7 100644 --- a/src/routes/utils/dateRange.ts +++ b/src/routes/utils/dateRange.ts @@ -19,6 +19,25 @@ export const enum DateRangeType { lastThirtyDays = 'last_thirty_days', // Last 30 days (Dec 18 - Jan 17) } +export const getDateRangeById = (value: string) => { + switch (value) { + case 'current_month_to_date': + return DateRangeType.currentMonthToDate; + case 'custom': + return DateRangeType.custom; + case 'previous_month': + return DateRangeType.previousMonth; + case 'previous_month_to_date': + return DateRangeType.previousMonthToDate; + case 'last_ninety_days': + return DateRangeType.lastNinetyDays; + case 'last_sixty_days': + return DateRangeType.lastSixtyDays; + case 'last_thirty_days': + return DateRangeType.lastThirtyDays; + } +}; + export const getDateRange = (dateRangeType: DateRangeType, isFormatted = true) => { const endDate = new Date(); const startDate = new Date(); From 093a4ceb6b63597d7c8aff4ea2fa9a83c705f9da Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Fri, 4 Oct 2024 16:47:41 -0400 Subject: [PATCH 03/22] Show chart skeleton https://issues.redhat.com/browse/COST-5573 --- .../featureToggle/featureToggle.tsx | 8 +++ .../costExplorerChart/costExplorerChart.tsx | 37 ++++++----- src/routes/explorer/explorer.tsx | 5 +- src/routes/explorer/explorerChart.tsx | 12 +++- src/routes/explorer/explorerSkeletonData.tsx | 61 +++++++++++++++++++ .../__snapshots__/featureToggle.test.ts.snap | 1 + .../featureToggle/featureToggleActions.ts | 1 + .../featureToggle/featureToggleReducer.ts | 3 + .../featureToggle/featureToggleSelectors.ts | 2 + 9 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 src/routes/explorer/explorerSkeletonData.tsx diff --git a/src/components/featureToggle/featureToggle.tsx b/src/components/featureToggle/featureToggle.tsx index 6d5c7c48d..d29dc709a 100644 --- a/src/components/featureToggle/featureToggle.tsx +++ b/src/components/featureToggle/featureToggle.tsx @@ -7,6 +7,7 @@ import { FeatureToggleActions } from 'store/featureToggle'; export const enum FeatureToggle { accountInfoEmptyState = 'cost-management.ui.account-info-empty-state', // https://issues.redhat.com/browse/COST-5335 awsEc2Instances = 'cost-management.ui.aws-ec2-instances', // https://issues.redhat.com/browse/COST-4855 + chartSkeleton = 'cost-management.ui.chart-skeleton', // https://issues.redhat.com/browse/COST-5573 debug = 'cost-management.ui.debug', detailsDateRange = 'cost-management.ui.details-date-range', // https://issues.redhat.com/browse/COST-5563 exports = 'cost-management.ui.exports', // Async exports https://issues.redhat.com/browse/COST-2223 @@ -28,6 +29,10 @@ export const useIsAwsEc2InstancesToggleEnabled = () => { return useIsToggleEnabled(FeatureToggle.awsEc2Instances); }; +export const useIsChartSkeletonToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.chartSkeleton); +}; + export const useIsDebugToggleEnabled = () => { return useIsToggleEnabled(FeatureToggle.debug); }; @@ -59,6 +64,7 @@ export const useFeatureToggle = () => { const isAccountInfoEmptyStateToggleEnabled = useIsAccountInfoEmptyStateToggleEnabled(); const isAwsEc2InstancesToggleEnabled = useIsAwsEc2InstancesToggleEnabled(); + const isChartSkeletonToggleEnabled = useIsChartSkeletonToggleEnabled(); const isDebugToggleEnabled = useIsDebugToggleEnabled(); const isDetailsDateRangeToggleEnabled = useIsDetailsDateRangeToggleEnabled(); const isExportsToggleEnabled = useIsExportsToggleEnabled(); @@ -78,6 +84,7 @@ export const useFeatureToggle = () => { FeatureToggleActions.setFeatureToggle({ isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled, + isChartSkeletonToggleEnabled, isDebugToggleEnabled, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -93,6 +100,7 @@ export const useFeatureToggle = () => { }, [ isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled, + isChartSkeletonToggleEnabled, isDebugToggleEnabled, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, diff --git a/src/routes/components/charts/costExplorerChart/costExplorerChart.tsx b/src/routes/components/charts/costExplorerChart/costExplorerChart.tsx index c1681b4ef..3d395a9dd 100644 --- a/src/routes/components/charts/costExplorerChart/costExplorerChart.tsx +++ b/src/routes/components/charts/costExplorerChart/costExplorerChart.tsx @@ -15,7 +15,6 @@ import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; -import { default as ChartTheme } from 'routes/components/charts/chartTheme'; import { getMaxValue } from 'routes/components/charts/common/chartDatum'; import type { ChartSeries } from 'routes/components/charts/common/chartUtils'; import { @@ -38,6 +37,7 @@ interface CostExplorerChartOwnProps { baseHeight?: number; formatOptions?: FormatOptions; formatter?: Formatter; + isSkeleton?: boolean; legendItemsPerRow?: number; name?: string; padding?: any; @@ -97,7 +97,7 @@ class CostExplorerChartBase extends React.Component { - const { top1stData, top2ndData, top3rdData, top4thData, top5thData, top6thData } = this.props; + const { isSkeleton, top1stData, top2ndData, top3rdData, top4thData, top5thData, top6thData } = this.props; const series: ChartSeries[] = []; if (top1stData && top1stData.length) { @@ -108,13 +108,13 @@ class CostExplorerChartBase extends React.Component { const maxChars = 20; - return str.length > maxChars ? str.substring(0, maxChars - 1) + '...' : str; + return str?.length > maxChars ? str.substring(0, maxChars - 1) + '...' : str; }; private getTickValue = (t: number) => { @@ -430,7 +430,7 @@ class CostExplorerChartBase extends React.Component {series && series.length > 0 && ( diff --git a/src/routes/explorer/explorer.tsx b/src/routes/explorer/explorer.tsx index a0bac8023..859038f22 100644 --- a/src/routes/explorer/explorer.tsx +++ b/src/routes/explorer/explorer.tsx @@ -86,6 +86,7 @@ interface ExplorerStateProps { gcpProviders: Providers; ibmProviders: Providers; isAccountInfoEmptyStateToggleEnabled?: boolean; + isChartSkeletonToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDataAvailable?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -496,6 +497,7 @@ class Explorer extends React.Component { gcpProviders, ibmProviders, intl, + isChartSkeletonToggleEnabled, isCurrentMonthData, isDataAvailable, isDetailsDateRangeToggleEnabled, @@ -620,7 +622,7 @@ class Explorer extends React.Component {
) : ( - itemsTotal > 0 && ( + (itemsTotal > 0 || isChartSkeletonToggleEnabled) && (
0 ? datums[0] : []} top2ndData={datums.length > 1 ? datums[1] : []} top3rdData={datums.length > 2 ? datums[2] : []} diff --git a/src/routes/explorer/explorerSkeletonData.tsx b/src/routes/explorer/explorerSkeletonData.tsx new file mode 100644 index 000000000..1bac47285 --- /dev/null +++ b/src/routes/explorer/explorerSkeletonData.tsx @@ -0,0 +1,61 @@ +const top1stData = [ + 1108.53, 1108.12, 1109.35, 1109.71, 1109.73, 1109.81, 1109.69, 1108.75, 1109.03, 1108.87, 1108.62, 1108.57, 1108.95, + 1109.85, 1109.83, 1109.78, 1109.57, 1108.47, 1108.53, 1108.9, 1109.8, 1109.91, 1111.42, 1109.74, 1109.61, 1109.6, + 1109.73, 1112.55, 1109.65, 1109.78, 231.21, +]; +const top2ndData = [ + 1343.03, 903.56, 953.58, 609.13, 1185.89, 1501.3, 887.25, 598.46, 913.52, 1957.66, 1645.23, 544.67, 1203.28, 1251.59, + 552.12, 673.74, 753.94, 1392.65, 435.82, 1269.72, 1288.04, 936.65, 823.59, 824.14, 823.54, 824.06, 480.64, 417.32, + 1003.14, 1001.25, 583.4, +]; +const top3rdData = [ + 374.82, 383.76, 319.87, 104.59, 118.03, 337.04, 823.11, 579.26, 295.51, 531.44, 105.67, 104.6, 971.38, 286.35, 568.39, + 238.03, 192.37, 60.09, 78.2, 237.03, 209.01, 224.73, 326.58, 200.96, 68.54, 99.04, 300.24, 232.8, 107.08, 267.67, + 55.62, +]; +const top4thData = [ + 120.66, 60.11, 8.44, 8.45, 20.26, 75.93, 32.08, 11.92, 22.96, 67.38, 25.19, 17.91, 67.42, 47.74, 67.47, 67.31, 71.37, + 52.86, 11.64, 26.41, 31.85, 67.31, 19.95, 59.17, 7.32, 7.32, 7.33, 328.35, 8.08, 8.69, 1.19, +]; +const top5thData = [ + 123.35, 123.04, 123.13, 123.06, 124.51, 123.46, 123.21, 123.22, 123.28, 123.14, 123.19, 129.31, 123.18, 123.42, + 123.56, 123.46, 123.16, 123.19, 126.51, 123.28, 123.44, 123.38, 123.43, 123.38, 123.25, 129.4, 123.7, 123.12, 123.15, + 119.73, 21.57, +]; +const top6thData = [ + 1535.93, 1403.04, 1356.65, 1282.12, 1264.35, 1286.7, 1511.64, 1388.88, 1370.51, 1319.67, 1155.13, 975.7, 1030.38, + 1129.76, 1218.14, 1117.55, 1196.87, 997.1, 830.46, 1031.18, 1215.34, 1373, 1405.28, 1431.94, 1164.6, 1031.98, 1360.85, + 1548.71, 1206.01, 1179.92, +]; + +const getData = (name: string, values: number[], count: number) => { + const datum = []; + + let index = 0; + while (count > index) { + datum.push({ + date: `${index}`, + key: name, + name, + units: 'USD', + x: `${index}`, + y: values[index % values.length], + }); + index++; + } + return datum; +}; + +export const getExplorerSkeletonData = (count = 0) => { + // There will always be at least one day + const days = count ? count : 1; + + return [ + getData('top1stData', top1stData, days), + getData('top2ndData', top2ndData, days), + getData('top3rdData', top3rdData, days), + getData('top4thData', top4thData, days), + getData('top5thData', top5thData, days), + getData('top6thData', top6thData, days), // Others + ]; +}; diff --git a/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap b/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap index dc8c364c8..1a3791d3e 100644 --- a/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap +++ b/src/store/featureToggle/__snapshots__/featureToggle.test.ts.snap @@ -5,6 +5,7 @@ exports[`default state 1`] = ` "hasFeatureToggle": false, "isAccountInfoEmptyStateToggleEnabled": false, "isAwsEc2InstancesToggleEnabled": false, + "isChartSkeletonToggleEnabled": false, "isDebugToggleEnabled": false, "isDetailsDateRangeToggleEnabled": false, "isExportsToggleEnabled": false, diff --git a/src/store/featureToggle/featureToggleActions.ts b/src/store/featureToggle/featureToggleActions.ts index ccfc3591e..f96b48e44 100644 --- a/src/store/featureToggle/featureToggleActions.ts +++ b/src/store/featureToggle/featureToggleActions.ts @@ -3,6 +3,7 @@ import { createAction } from 'typesafe-actions'; export interface FeatureToggleActionMeta { isAccountInfoEmptyStateToggleEnabled?: boolean; isAwsEc2InstancesToggleEnabled?: boolean; + isChartSkeletonToggleEnabled?: boolean; isDebugToggleEnabled?: boolean; isDetailsDateRangeToggleEnabled?: boolean; isExportsToggleEnabled?: boolean; diff --git a/src/store/featureToggle/featureToggleReducer.ts b/src/store/featureToggle/featureToggleReducer.ts index 6397851ea..8682e0721 100644 --- a/src/store/featureToggle/featureToggleReducer.ts +++ b/src/store/featureToggle/featureToggleReducer.ts @@ -10,6 +10,7 @@ export type FeatureToggleState = Readonly<{ hasFeatureToggle: boolean; isAccountInfoEmptyStateToggleEnabled: boolean; isAwsEc2InstancesToggleEnabled: boolean; + isChartSkeletonToggleEnabled: boolean; isDebugToggleEnabled: boolean; isDetailsDateRangeToggleEnabled: boolean; isExportsToggleEnabled: boolean; @@ -22,6 +23,7 @@ export const defaultState: FeatureToggleState = { hasFeatureToggle: false, isAccountInfoEmptyStateToggleEnabled: false, isAwsEc2InstancesToggleEnabled: false, + isChartSkeletonToggleEnabled: false, isDebugToggleEnabled: false, isDetailsDateRangeToggleEnabled: false, isExportsToggleEnabled: false, @@ -40,6 +42,7 @@ export function FeatureToggleReducer(state = defaultState, action: FeatureToggle hasFeatureToggle: true, isAccountInfoEmptyStateToggleEnabled: action.payload.isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled: action.payload.isAwsEc2InstancesToggleEnabled, + isChartSkeletonToggleEnabled: action.payload.isChartSkeletonToggleEnabled, isDebugToggleEnabled: action.payload.isDebugToggleEnabled, isDetailsDateRangeToggleEnabled: action.payload.isDetailsDateRangeToggleEnabled, isExportsToggleEnabled: action.payload.isExportsToggleEnabled, diff --git a/src/store/featureToggle/featureToggleSelectors.ts b/src/store/featureToggle/featureToggleSelectors.ts index b1e09901b..887068c80 100644 --- a/src/store/featureToggle/featureToggleSelectors.ts +++ b/src/store/featureToggle/featureToggleSelectors.ts @@ -10,6 +10,8 @@ export const selectIsAccountInfoEmptyStateToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isAccountInfoEmptyStateToggleEnabled; export const selectIsAwsEc2InstancesToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isAwsEc2InstancesToggleEnabled; +export const selectIsChartSkeletonToggleEnabled = (state: RootState) => + selectFeatureToggleState(state).isChartSkeletonToggleEnabled; export const selectIsDebugToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isDebugToggleEnabled; export const selectIsDetailsDateRangeToggleEnabled = (state: RootState) => selectFeatureToggleState(state).isDetailsDateRangeToggleEnabled; From 5876c14e53495f8dbb4702c432bb6b69984663d9 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Sat, 5 Oct 2024 15:12:19 -0400 Subject: [PATCH 04/22] Fix pagination "per page" toggle https://issues.redhat.com/browse/COST-5563 --- src/routes/details/awsDetails/awsDetails.tsx | 6 ++---- src/routes/details/azureDetails/azureDetails.tsx | 6 ++---- src/routes/details/gcpDetails/gcpDetails.tsx | 6 ++---- src/routes/details/ibmDetails/ibmDetails.tsx | 6 ++---- src/routes/details/ociDetails/ociDetails.tsx | 6 ++---- src/routes/details/ocpDetails/ocpDetails.tsx | 6 ++---- src/routes/details/rhelDetails/rhelDetails.tsx | 6 ++---- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index e7f4fcd19..6a31c734e 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -485,11 +485,9 @@ const mapStateToProps = createMapStateToProps Date: Sat, 5 Oct 2024 15:09:47 -0400 Subject: [PATCH 05/22] Show integration status in details page tables https://issues.redhat.com/browse/COST-5386 --- .../featureToggle/featureToggle.tsx | 8 ++++ src/routes/details/awsDetails/awsDetails.tsx | 13 ++++- .../details/awsDetails/detailsTable.tsx | 33 +++++++++++-- .../details/azureDetails/azureDetails.tsx | 13 ++++- .../details/azureDetails/detailsTable.tsx | 48 ++++++++++++++++--- .../components/overallStatus.tsx | 21 +++++++- .../providerDetailsContent.tsx | 7 ++- .../providerDetails/providerDetailsModal.tsx | 25 ++++++++-- .../providerDetails/providerDetailsTable.tsx | 6 +-- .../details/gcpDetails/detailsTable.tsx | 44 +++++++++++++++-- src/routes/details/gcpDetails/gcpDetails.tsx | 13 ++++- .../details/ibmDetails/detailsTable.tsx | 44 +++++++++++++++-- src/routes/details/ibmDetails/ibmDetails.tsx | 13 ++++- .../details/ociDetails/detailsTable.tsx | 46 ++++++++++++++++-- src/routes/details/ociDetails/ociDetails.tsx | 13 ++++- .../details/ocpBreakdown/ocpBreakdown.tsx | 2 +- .../details/ocpDetails/detailsTable.tsx | 24 +++++++++- src/routes/details/ocpDetails/ocpDetails.tsx | 15 +++++- .../details/rhelDetails/detailsTable.tsx | 32 ++++++++++--- .../details/rhelDetails/rhelDetails.tsx | 13 ++++- .../__snapshots__/featureToggle.test.ts.snap | 1 + .../featureToggle/featureToggleActions.ts | 1 + .../featureToggle/featureToggleReducer.ts | 3 ++ .../featureToggle/featureToggleSelectors.ts | 2 + 24 files changed, 392 insertions(+), 48 deletions(-) diff --git a/src/components/featureToggle/featureToggle.tsx b/src/components/featureToggle/featureToggle.tsx index d29dc709a..ca7741019 100644 --- a/src/components/featureToggle/featureToggle.tsx +++ b/src/components/featureToggle/featureToggle.tsx @@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux'; import { FeatureToggleActions } from 'store/featureToggle'; export const enum FeatureToggle { + accountInfoDetails = 'cost-management.ui.account-info-details', // https://issues.redhat.com/browse/COST-5386 accountInfoEmptyState = 'cost-management.ui.account-info-empty-state', // https://issues.redhat.com/browse/COST-5335 awsEc2Instances = 'cost-management.ui.aws-ec2-instances', // https://issues.redhat.com/browse/COST-4855 chartSkeleton = 'cost-management.ui.chart-skeleton', // https://issues.redhat.com/browse/COST-5573 @@ -21,6 +22,10 @@ const useIsToggleEnabled = (toggle: FeatureToggle) => { return client.isEnabled(toggle); }; +export const useIsAccountInfoDetailsToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.accountInfoDetails); +}; + export const useIsAccountInfoEmptyStateToggleEnabled = () => { return useIsToggleEnabled(FeatureToggle.accountInfoEmptyState); }; @@ -62,6 +67,7 @@ export const useFeatureToggle = () => { const dispatch = useDispatch(); const { auth } = useChrome(); + const isAccountInfoDetailsToggleEnabled = useIsAccountInfoDetailsToggleEnabled(); const isAccountInfoEmptyStateToggleEnabled = useIsAccountInfoEmptyStateToggleEnabled(); const isAwsEc2InstancesToggleEnabled = useIsAwsEc2InstancesToggleEnabled(); const isChartSkeletonToggleEnabled = useIsChartSkeletonToggleEnabled(); @@ -82,6 +88,7 @@ export const useFeatureToggle = () => { // Workaround for code that doesn't use hooks dispatch( FeatureToggleActions.setFeatureToggle({ + isAccountInfoDetailsToggleEnabled, isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled, isChartSkeletonToggleEnabled, @@ -98,6 +105,7 @@ export const useFeatureToggle = () => { fetchUser(identity => console.log('User identity:', identity)); } }, [ + isAccountInfoDetailsToggleEnabled, isAccountInfoEmptyStateToggleEnabled, isAwsEc2InstancesToggleEnabled, isChartSkeletonToggleEnabled, diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index 6a31c734e..c27e37963 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -56,6 +56,7 @@ import { DetailsToolbar } from './detailsToolbar'; interface AwsDetailsStateProps { costType: string; currency?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -215,7 +216,15 @@ class AwsDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; + const { + isAccountInfoDetailsToggleEnabled, + query, + report, + reportFetchStatus, + reportQueryString, + router, + timeScopeValue, + } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -238,6 +247,7 @@ class AwsDetails extends React.Component { groupByCostCategory={groupByCostCategory} groupByTagKey={groupByTagKey} groupByOrg={groupByOrg} + isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -524,6 +534,7 @@ const mapStateToProps = createMapStateToProps ), }, + { + hidden: !(isGroupByAccount && isAccountInfoDetailsToggleEnabled), + value: ( + + ), + }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, @@ -211,9 +232,15 @@ class DetailsTableBase extends React.Component !column.hidden); + const filteredRows = rows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + this.setState({ - columns, - rows, + columns: filteredColumns, + rows: filteredRows, }); }; diff --git a/src/routes/details/azureDetails/azureDetails.tsx b/src/routes/details/azureDetails/azureDetails.tsx index 2d2f0ca4f..049562495 100644 --- a/src/routes/details/azureDetails/azureDetails.tsx +++ b/src/routes/details/azureDetails/azureDetails.tsx @@ -52,6 +52,7 @@ import { DetailsToolbar } from './detailsToolbar'; interface AzureDetailsStateProps { currency?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -202,7 +203,15 @@ class AzureDetails extends React.Component }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; + const { + isAccountInfoDetailsToggleEnabled, + query, + report, + reportFetchStatus, + reportQueryString, + router, + timeScopeValue, + } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -215,6 +224,7 @@ class AzureDetails extends React.Component filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} + isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleOnSelect} @@ -468,6 +478,7 @@ const mapStateToProps = createMapStateToProps { - const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = - this.props; + const { + breadcrumbPath, + groupBy, + groupByTagKey, + intl, + isAccountInfoDetailsToggleEnabled, + isAllSelected, + query, + report, + router, + selectedItems, + } = this.props; if (!report) { return; } + const isGroupBySubscriptionGuid = groupBy === 'subscription_guid'; + const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -112,10 +127,14 @@ class DetailsTableBase extends React.Component ), }, + { + hidden: !(isGroupBySubscriptionGuid && isAccountInfoDetailsToggleEnabled), + value: ( + + ), + }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, @@ -183,9 +213,15 @@ class DetailsTableBase extends React.Component !column.hidden); + const filteredRows = rows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + this.setState({ - columns, - rows, + columns: filteredColumns, + rows: filteredRows, }); }; diff --git a/src/routes/details/components/providerDetails/components/overallStatus.tsx b/src/routes/details/components/providerDetails/components/overallStatus.tsx index 00e4bc719..c2a6b3359 100644 --- a/src/routes/details/components/providerDetails/components/overallStatus.tsx +++ b/src/routes/details/components/providerDetails/components/overallStatus.tsx @@ -24,9 +24,11 @@ import { styles } from './component.styles'; interface OverallStatusOwnProps { clusterId?: string; isLastUpdated?: boolean; + isLastUpdatedStatus?: boolean; isStatusMsg?: boolean; providerId?: string; providerType: ProviderType; + uuId?: string; } interface OverallStatusStateProps { @@ -41,9 +43,11 @@ type OverallStatusProps = OverallStatusOwnProps; const OverallStatus: React.FC = ({ clusterId, isLastUpdated, + isLastUpdatedStatus, isStatusMsg, providerId, providerType, + uuId, }: OverallStatusProps) => { const { providers, providersError } = useMapToProps(); const intl = useIntl(); @@ -55,7 +59,10 @@ const OverallStatus: React.FC = ({ // Filter OCP providers to skip an extra API request const filteredProviders = filterProviders(providers, providerType)?.data?.filter(data => data.status !== null); const provider = filteredProviders?.find( - val => providerId === val.id || (clusterId && val.authentication?.credentials?.cluster_id === clusterId) + val => + providerId === val.id || + (clusterId && val.authentication?.credentials?.cluster_id === clusterId) || + uuId === val.uuid ); const cloudProvider = providers?.data?.find(val => val.uuid === provider?.infrastructure?.uuid); @@ -122,6 +129,18 @@ const OverallStatus: React.FC = ({ if (isLastUpdated) { return overallStatus.lastUpdated ? formatDate(overallStatus.lastUpdated) : null; } + if (isLastUpdatedStatus) { + return ( + <> + {getOverallStatusIcon(overallStatus.status)} + + {overallStatus.lastUpdated + ? formatDate(overallStatus.lastUpdated) + : intl.formatMessage(messages.statusMsg, { value: overallStatus.status })} + + + ); + } if (overallStatus.msg && overallStatus.status) { return ( <> diff --git a/src/routes/details/components/providerDetails/providerDetailsContent.tsx b/src/routes/details/components/providerDetails/providerDetailsContent.tsx index 1730c91e0..33d1628db 100644 --- a/src/routes/details/components/providerDetails/providerDetailsContent.tsx +++ b/src/routes/details/components/providerDetails/providerDetailsContent.tsx @@ -22,6 +22,7 @@ interface ProviderDetailsContentOwnProps { clusterId?: string; providerId?: string; providerType: ProviderType; + uuId?: string; } interface ProviderDetailsContentStateProps { @@ -37,6 +38,7 @@ const ProviderDetailsContent: React.FC = ({ clusterId, providerId, providerType, + uuId, }: ProviderDetailsContentProps) => { const intl = useIntl(); @@ -59,7 +61,10 @@ const ProviderDetailsContent: React.FC = ({ // Filter OCP providers to skip an extra API request const filteredProviders = filterProviders(providers, providerType)?.data?.filter(data => data.status !== null); const provider = filteredProviders?.find( - val => providerId === val.id || (clusterId && val.authentication?.credentials?.cluster_id === clusterId) + val => + providerId === val.id || + (clusterId && val.authentication?.credentials?.cluster_id === clusterId) || + uuId === val.uuid ); if (providerType === ProviderType.ocp) { diff --git a/src/routes/details/components/providerDetails/providerDetailsModal.tsx b/src/routes/details/components/providerDetails/providerDetailsModal.tsx index aa3765084..0354e9359 100644 --- a/src/routes/details/components/providerDetails/providerDetailsModal.tsx +++ b/src/routes/details/components/providerDetails/providerDetailsModal.tsx @@ -11,18 +11,22 @@ import { ProviderDetailsContent } from './providerDetailsContent'; interface ProviderDetailsModalOwnProps { clusterId?: string; - showStatus?: boolean; + isLastUpdatedStatus?: boolean; + isOverallStatus?: boolean; providerId?: string; providerType: ProviderType; + uuId?: string; } type ProviderDetailsModalProps = ProviderDetailsModalOwnProps; const ProviderDetailsModal: React.FC = ({ clusterId, + isOverallStatus = true, + isLastUpdatedStatus, providerId, providerType, - showStatus = true, + uuId, }: ProviderDetailsModalProps) => { const intl = useIntl(); const [isOpen, setIsOpen] = useState(false); @@ -40,14 +44,27 @@ const ProviderDetailsModal: React.FC = ({ return ( <> - {showStatus && } + {isOverallStatus && ( + + )} - + diff --git a/src/routes/details/components/providerDetails/providerDetailsTable.tsx b/src/routes/details/components/providerDetails/providerDetailsTable.tsx index 2997dc3dd..e4e2188ee 100644 --- a/src/routes/details/components/providerDetails/providerDetailsTable.tsx +++ b/src/routes/details/components/providerDetails/providerDetailsTable.tsx @@ -47,9 +47,9 @@ const ProviderDetailsTable: React.FC = ({ providers, newRows.push({ cells: [ { value: }, - { value: }, - { value: }, - { value: }, + { value: }, + { value: }, + { value: }, ], item, }); diff --git a/src/routes/details/gcpDetails/detailsTable.tsx b/src/routes/details/gcpDetails/detailsTable.tsx index 6e4b06069..09debd7c6 100644 --- a/src/routes/details/gcpDetails/detailsTable.tsx +++ b/src/routes/details/gcpDetails/detailsTable.tsx @@ -1,5 +1,6 @@ import 'routes/components/dataTable/dataTable.scss'; +import { ProviderType } from 'api/providers'; import type { Query } from 'api/queries/query'; import type { GcpReport } from 'api/reports/gcpReports'; import { ReportPathsType, ReportType } from 'api/reports/report'; @@ -13,6 +14,7 @@ import { DataTable } from 'routes/components/dataTable'; import { styles } from 'routes/components/dataTable/dataTable.styles'; import { EmptyValueState } from 'routes/components/state/emptyValueState'; import { Actions } from 'routes/details/components/actions'; +import { ProviderDetailsModal } from 'routes/details/components/providerDetails'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getBreakdownPath } from 'routes/utils/paths'; @@ -28,6 +30,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro filterBy?: any; groupBy: string; groupByTagKey?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAllSelected?: boolean; isLoading?: boolean; onSelect(items: ComputedReportItem[], isSelected: boolean); @@ -74,12 +77,24 @@ class DetailsTableBase extends React.Component { - const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = - this.props; + const { + breadcrumbPath, + groupBy, + groupByTagKey, + intl, + isAccountInfoDetailsToggleEnabled, + isAllSelected, + query, + report, + router, + selectedItems, + } = this.props; if (!report) { return; } + const isGroupByAccount = groupBy === 'account'; + const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -116,6 +131,10 @@ class DetailsTableBase extends React.Component ), }, + { + hidden: !(isGroupByAccount && isAccountInfoDetailsToggleEnabled), + value: ( + + ), + }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, @@ -183,9 +213,15 @@ class DetailsTableBase extends React.Component !column.hidden); + const filteredRows = rows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + this.setState({ - columns, - rows, + columns: filteredColumns, + rows: filteredRows, }); }; diff --git a/src/routes/details/gcpDetails/gcpDetails.tsx b/src/routes/details/gcpDetails/gcpDetails.tsx index 6de79c0f7..b14e10925 100644 --- a/src/routes/details/gcpDetails/gcpDetails.tsx +++ b/src/routes/details/gcpDetails/gcpDetails.tsx @@ -52,6 +52,7 @@ import { styles } from './gcpDetails.styles'; interface GcpDetailsStateProps { currency?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -202,7 +203,15 @@ class GcpDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; + const { + isAccountInfoDetailsToggleEnabled, + query, + report, + reportFetchStatus, + reportQueryString, + router, + timeScopeValue, + } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); const groupByTagKey = getGroupByTagKey(query); @@ -214,6 +223,7 @@ class GcpDetails extends React.Component { filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} + isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -469,6 +479,7 @@ const mapStateToProps = createMapStateToProps { - const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = - this.props; + const { + breadcrumbPath, + groupBy, + groupByTagKey, + intl, + isAccountInfoDetailsToggleEnabled, + isAllSelected, + query, + report, + router, + selectedItems, + } = this.props; if (!report) { return; } + const isGroupByAccount = groupBy === 'account'; + const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -116,6 +131,10 @@ class DetailsTableBase extends React.Component ), }, + { + hidden: !(isGroupByAccount && isAccountInfoDetailsToggleEnabled), + value: ( + + ), + }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, @@ -183,9 +213,15 @@ class DetailsTableBase extends React.Component !column.hidden); + const filteredRows = rows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + this.setState({ - columns, - rows, + columns: filteredColumns, + rows: filteredRows, }); }; diff --git a/src/routes/details/ibmDetails/ibmDetails.tsx b/src/routes/details/ibmDetails/ibmDetails.tsx index c7e1e31e4..fbfdb3c2e 100644 --- a/src/routes/details/ibmDetails/ibmDetails.tsx +++ b/src/routes/details/ibmDetails/ibmDetails.tsx @@ -53,6 +53,7 @@ import { styles } from './ibmDetails.styles'; interface IbmDetailsStateProps { currency?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -203,7 +204,15 @@ class IbmDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; + const { + isAccountInfoDetailsToggleEnabled, + query, + report, + reportFetchStatus, + reportQueryString, + router, + timeScopeValue, + } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -216,6 +225,7 @@ class IbmDetails extends React.Component { filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} + isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -471,6 +481,7 @@ const mapStateToProps = createMapStateToProps { - const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = - this.props; + const { + breadcrumbPath, + groupBy, + groupByTagKey, + intl, + isAccountInfoDetailsToggleEnabled, + isAllSelected, + query, + report, + router, + selectedItems, + } = this.props; if (!report) { return; } + const isGroupByPayerTenantId = groupBy === 'payer_tenant_id'; + const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -116,6 +131,10 @@ class DetailsTableBase extends React.Component ), }, + { + hidden: !(isGroupByPayerTenantId && isAccountInfoDetailsToggleEnabled), + value: ( + + ), + }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, @@ -183,9 +213,15 @@ class DetailsTableBase extends React.Component !column.hidden); + const filteredRows = rows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + this.setState({ - columns, - rows, + columns: filteredColumns, + rows: filteredRows, }); }; diff --git a/src/routes/details/ociDetails/ociDetails.tsx b/src/routes/details/ociDetails/ociDetails.tsx index f7c8476fa..402134f38 100644 --- a/src/routes/details/ociDetails/ociDetails.tsx +++ b/src/routes/details/ociDetails/ociDetails.tsx @@ -52,6 +52,7 @@ import { styles } from './ociDetails.styles'; interface OciDetailsStateProps { currency?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -202,7 +203,15 @@ class OciDetails extends React.Component { }; private getTable = () => { - const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; + const { + isAccountInfoDetailsToggleEnabled, + query, + report, + reportFetchStatus, + reportQueryString, + router, + timeScopeValue, + } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -215,6 +224,7 @@ class OciDetails extends React.Component { filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} + isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -470,6 +480,7 @@ const mapStateToProps = createMapStateToProps : undefined, dataDetailsComponent: groupBy === 'cluster' ? ( - + ) : undefined, costDistribution, costOverviewComponent: ( diff --git a/src/routes/details/ocpDetails/detailsTable.tsx b/src/routes/details/ocpDetails/detailsTable.tsx index 1ea78f600..d0f35af03 100644 --- a/src/routes/details/ocpDetails/detailsTable.tsx +++ b/src/routes/details/ocpDetails/detailsTable.tsx @@ -2,6 +2,7 @@ import 'routes/components/dataTable/dataTable.scss'; import { Label, Tooltip } from '@patternfly/react-core'; import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent'; +import { ProviderType } from 'api/providers'; import type { Query } from 'api/queries/query'; import type { OcpReport, OcpReportItem } from 'api/reports/ocpReports'; import { ReportPathsType, ReportType } from 'api/reports/report'; @@ -15,6 +16,7 @@ import { DataTable } from 'routes/components/dataTable'; import { styles } from 'routes/components/dataTable/dataTable.styles'; import { EmptyValueState } from 'routes/components/state/emptyValueState'; import { Actions } from 'routes/details/components/actions'; +import { ProviderDetailsModal } from 'routes/details/components/providerDetails'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getBreakdownPath } from 'routes/utils/paths'; @@ -38,6 +40,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro groupBy: string; groupByTagKey: string; hiddenColumns?: Set; + isAccountInfoDetailsToggleEnabled?: boolean; isAllSelected?: boolean; isLoading?: boolean; onSelect(items: ComputedReportItem[], isSelected: boolean); @@ -100,6 +103,7 @@ class DetailsTableBase extends React.Component({ @@ -126,7 +131,7 @@ class DetailsTableBase extends React.Component ), }, + { + hidden: !(isGroupByCluster && isAccountInfoDetailsToggleEnabled), + value: ( + + ), + }, { value: monthOverMonth, id: DetailsTableColumnIds.monthOverMonth }, { value: InfrastructureCost, diff --git a/src/routes/details/ocpDetails/ocpDetails.tsx b/src/routes/details/ocpDetails/ocpDetails.tsx index 91d6bdcad..bfccb8d9a 100644 --- a/src/routes/details/ocpDetails/ocpDetails.tsx +++ b/src/routes/details/ocpDetails/ocpDetails.tsx @@ -59,6 +59,7 @@ export interface OcpDetailsStateProps { costDistribution?: string; currency?: string; currentDateRangeType?: string; + isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -246,8 +247,16 @@ class OcpDetails extends React.Component { }; private getTable = () => { - const { costDistribution, query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = - this.props; + const { + costDistribution, + isAccountInfoDetailsToggleEnabled, + query, + report, + reportFetchStatus, + reportQueryString, + router, + timeScopeValue, + } = this.props; const { hiddenColumns, isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -262,6 +271,7 @@ class OcpDetails extends React.Component { groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} hiddenColumns={hiddenColumns} + isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleOnSelect} @@ -556,6 +566,7 @@ const mapStateToProps = createMapStateToProps; + isAccountInfoDetailsToggleEnabled?: boolean; isAllSelected?: boolean; isLoading?: boolean; onSelect(items: ComputedReportItem[], isSelected: boolean); @@ -89,6 +92,7 @@ class DetailsTableBase extends React.Component
@@ -630,6 +621,7 @@ class Explorer extends React.Component { costType={costType} currency={currency} dateRangeType={dateRangeType} + endDate={endDate} groupBy={ groupByCostCategory ? `${awsCategoryPrefix}${groupByCostCategory}` @@ -638,6 +630,7 @@ class Explorer extends React.Component { : groupById } perspective={perspective} + startDate={startDate} />
@@ -729,11 +722,10 @@ const mapStateToProps = createMapStateToProps { - const { end_date, start_date } = this.props; + const { endDate, startDate } = this.props; const result = []; items.map(datums => { @@ -215,8 +213,8 @@ class ExplorerChartBase extends React.Component( - (state, { costType, currency, dateRangeType, perspective, router }) => { + (state, { costType, currency, endDate, perspective, router, startDate }) => { const queryFromRoute = parseQuery(router.location.search); const groupBy = queryFromRoute.group_by ? getGroupById(queryFromRoute) : getGroupByDefault(perspective); @@ -286,7 +284,6 @@ const mapStateToProps = createMapStateToProps(); public componentDidMount() { - const { dateRangeType } = this.props; - const { end_date, start_date } = getDateRange(dateRangeType); + const { dateRangeType, endDate, startDate } = this.props; + // const queryFromRoute = parseQuery(router.location.search); + // + // // Query dates are undefined until a selection is made + // const end_date = queryFromRoute.end_date; + // const start_date = queryFromRoute.start_date; if (this.startDateRef?.current) { this.startDateRef.current.setCalendarOpen(dateRangeType !== DateRangeType.custom); } - if (dateRangeType === DateRangeType.custom) { + if (dateRangeType === DateRangeType.custom && endDate && startDate) { this.setState({ - startDate: new Date(start_date + 'T00:00:00'), - endDate: new Date(end_date + 'T00:00:00'), + startDate: new Date(startDate + 'T00:00:00'), + endDate: new Date(endDate + 'T00:00:00'), }); } } diff --git a/src/routes/explorer/explorerFilter.tsx b/src/routes/explorer/explorerFilter.tsx index 91a59d9a4..eb188a75e 100644 --- a/src/routes/explorer/explorerFilter.tsx +++ b/src/routes/explorer/explorerFilter.tsx @@ -16,7 +16,7 @@ import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { DataToolbar } from 'routes/components/dataToolbar'; import { DateRange } from 'routes/components/dateRange'; -import { DateRangeType, getDateRange, getDateRangeById } from 'routes/utils/dateRange'; +import { DateRangeType, getDateRangeById } from 'routes/utils/dateRange'; import { isEqual } from 'routes/utils/equal'; import type { Filter } from 'routes/utils/filter'; import { getRouteForQuery } from 'routes/utils/query'; @@ -43,6 +43,7 @@ import { interface ExplorerFilterOwnProps extends RouterComponentProps, WrappedComponentProps { dateRangeType: DateRangeType; + endDate?: boolean; groupBy: string; isCurrentMonthData?: boolean; isDisabled?: boolean; @@ -52,6 +53,7 @@ interface ExplorerFilterOwnProps extends RouterComponentProps, WrappedComponentP perspective: PerspectiveType; pagination?: React.ReactNode; query?: Query; + startDate?: boolean; } interface ExplorerFilterStateProps { @@ -100,6 +102,7 @@ export class ExplorerFilterBase extends React.Component { this.updateDateRange(currentDateRange); }); @@ -183,12 +187,17 @@ export class ExplorerFilterBase extends React.Component { - const { dateRangeType } = this.props; + const { dateRangeType, endDate, startDate } = this.props; const { showDatePicker } = this.state; return showDatePicker ? ( - - ) : undefined; + + ) : null; }; private handleOnDatePickerSelect = (startDate: Date, endDate: Date) => { @@ -213,10 +222,11 @@ export class ExplorerFilterBase extends React.Component { if (!showDatePicker) { this.updateDateRange(currentDateRange); - } - // Clear inline alert - if (onDateRangeSelect) { - onDateRangeSelect(currentDateRange); + + // Clear inline alert + if (onDateRangeSelect) { + onDateRangeSelect(currentDateRange); + } } }); }; @@ -298,13 +308,11 @@ export class ExplorerFilterBase extends React.Component( - (state, { dateRangeType, perspective }) => { - const { end_date, start_date } = getDateRange(dateRangeType); - + (state, { endDate, perspective, startDate }) => { // Omitting key_only to share a single request -- the toolbar needs key values const orgQueryString = getQuery({ - end_date, - start_date, + end_date: endDate, + start_date: startDate, limit: 1000, }); @@ -335,8 +343,8 @@ const mapStateToProps = createMapStateToProps ); diff --git a/src/routes/explorer/explorerTable.tsx b/src/routes/explorer/explorerTable.tsx index f8e5fe07f..9497ac551 100644 --- a/src/routes/explorer/explorerTable.tsx +++ b/src/routes/explorer/explorerTable.tsx @@ -35,7 +35,6 @@ import { ComputedReportItemType, ComputedReportItemValueType } from 'routes/comp import { EmptyFilterState } from 'routes/components/state/emptyFilterState'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; -import { type DateRangeType, getDateRange } from 'routes/utils/dateRange'; import { createMapStateToProps } from 'store/common'; import { formatCurrency } from 'utils/format'; import { classificationDefault, classificationUnallocated, noPrefix } from 'utils/props'; @@ -47,7 +46,7 @@ import { PerspectiveType } from './explorerUtils'; interface ExplorerTableOwnProps extends RouterComponentProps, WrappedComponentProps { costDistribution?: string; - dateRangeType?: DateRangeType; + endDate?: string; groupBy: string; groupByCostCategory?: string; groupByOrg?: string; @@ -60,11 +59,11 @@ interface ExplorerTableOwnProps extends RouterComponentProps, WrappedComponentPr query: Query; report: Report; selectedItems?: ComputedReportItem[]; + startDate?: string; } interface ExplorerTableStateProps { - end_date?: string; - start_date?: string; + // TBD... } interface ExplorerTableDispatchProps { @@ -118,7 +117,7 @@ class ExplorerTableBase extends React.Component { const { costDistribution, - end_date, + endDate, groupBy, groupByCostCategory, groupByOrg, @@ -127,7 +126,7 @@ class ExplorerTableBase extends React.Component( - (state, { dateRangeType }) => { - const { end_date, start_date } = getDateRange(dateRangeType); - - return { - end_date, - start_date, - }; - } -); +const mapStateToProps = createMapStateToProps(() => { + return { + // TBD + }; +}); const mapDispatchToProps: ExplorerTableDispatchProps = {}; diff --git a/src/routes/utils/dateRange.ts b/src/routes/utils/dateRange.ts index f27fbbbd7..34a331320 100644 --- a/src/routes/utils/dateRange.ts +++ b/src/routes/utils/dateRange.ts @@ -75,24 +75,22 @@ export const getDateRange = (dateRangeType: DateRangeType, isFormatted = true) = return dateRange; }; -export const getDateRangeTypeDefault = (queryFromRoute: Query) => { - return queryFromRoute.dateRangeType || DateRangeType.currentMonthToDate; +export const getDateRangeTypeDefault = (queryFromRoute: Query, defaultToPreviousMonth: boolean): DateRangeType => { + if (queryFromRoute.dateRangeType) { + return queryFromRoute.dateRangeType; + } + return defaultToPreviousMonth ? DateRangeType.previousMonth : DateRangeType.currentMonthToDate; }; -export const getDateRangeFromQuery = (queryFromRoute: Query) => { - let end_date; - let start_date; - - if (queryFromRoute.dateRangeType === DateRangeType.custom) { - end_date = queryFromRoute.end_date; - start_date = queryFromRoute.start_date; - } - if (!(end_date && start_date)) { - const dateRangeType = getDateRangeTypeDefault(queryFromRoute); - return getDateRange(dateRangeType); - } +export const getDateRangeFromQuery = (queryFromRoute: Query, defaultToPreviousMonth: boolean = false) => { + const dateRangeType = getDateRangeTypeDefault(queryFromRoute, defaultToPreviousMonth); + const dateRange = + dateRangeType === DateRangeType.custom + ? { start_date: queryFromRoute.start_date, end_date: queryFromRoute.end_date } + : getDateRange(dateRangeType); return { - end_date, - start_date, + dateRangeType, + end_date: dateRange.end_date, + start_date: dateRange.start_date, }; }; From 5f1916b808129455654720bd2d73783365f55e93 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Mon, 7 Oct 2024 15:31:44 -0400 Subject: [PATCH 09/22] Missing filter placeholder for EC2 instance and OS https://issues.redhat.com/browse/COST-5583 --- locales/data.json | 16 ++++++++++++++++ locales/translations.json | 2 +- src/locales/messages.ts | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/locales/data.json b/locales/data.json index 6a3a556cd..d805615e9 100644 --- a/locales/data.json +++ b/locales/data.json @@ -7746,6 +7746,14 @@ } ] }, + "instance": { + "value": [ + { + "type": 0, + "value": "Filter by instance" + } + ] + }, "name": { "value": [ { @@ -7762,6 +7770,14 @@ } ] }, + "operating_system": { + "value": [ + { + "type": 0, + "value": "Filter by operating system" + } + ] + }, "org_unit_id": { "value": [ { diff --git a/locales/translations.json b/locales/translations.json index bc07615a3..d4e481c04 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -327,7 +327,7 @@ "filterByInputAriaLabel": "{value, select, account {Input for account name} aws_category {Input for cost category name} cluster {Input for cluster name} gcp_project {Input for GCP project name} name {Input for name} node {Input for node name} org_unit_id {Input for organizational unit name} payer_tenant_id {Input for account name} product_service {Input for service_name} project {Input for project name} region {Input for region name} resource_location {Input for region name} service {Input for service name} service_name {Input for service_name} subscription_guid {Input for account name} status {Input for status value} tag {Input for tag name} tag_key {Input for tag key} tag_key_child {Input for child tag key} tag_key_parent {Input for parent tag key} other {}}", "filterByOrgUnitAriaLabel": "Organizational units", "filterByOrgUnitPlaceholder": "Choose unit", - "filterByPlaceholder": "{value, select, account {Filter by account} aws_category {Filter by cost category} cluster {Filter by cluster} container {Filter by container} description {Filter by description} gcp_project {Filter by GCP project} group {Filter by group} name {Filter by name} node {Filter by node} org_unit_id {Filter by organizational unit} payer_tenant_id {Filter by account} persistent_volume_claim {Filter by persistent volume claim} product_service {Filter by service} project {Filter by project} region {Filter by region} resource_location {Filter by region} service {Filter by service} service_name {Filter by service} source_type {Filter by integration} status {Filter by status} storage_class {Filter by StorageClass} subscription_guid {Filter by account} workload {Filter by workload name} workload_type {Filter by workload type} tag {Filter by tag} tag_key {Filter by tag key} tag_key_child {Filter by child tag key} tag_key_parent {Filter by parent tag key} other {}}", + "filterByPlaceholder": "{value, select, account {Filter by account} aws_category {Filter by cost category} cluster {Filter by cluster} container {Filter by container} description {Filter by description} gcp_project {Filter by GCP project} group {Filter by group} instance {Filter by instance} name {Filter by name} node {Filter by node} operating_system {Filter by operating system} org_unit_id {Filter by organizational unit} payer_tenant_id {Filter by account} persistent_volume_claim {Filter by persistent volume claim} product_service {Filter by service} project {Filter by project} region {Filter by region} resource_location {Filter by region} service {Filter by service} service_name {Filter by service} source_type {Filter by integration} status {Filter by status} storage_class {Filter by StorageClass} subscription_guid {Filter by account} workload {Filter by workload name} workload_type {Filter by workload type} tag {Filter by tag} tag_key {Filter by tag key} tag_key_child {Filter by child tag key} tag_key_parent {Filter by parent tag key} other {}}", "filterByTagKeyAriaLabel": "Tag keys", "filterByTagValueAriaLabel": "Tag values", "filterByTagValueButtonAriaLabel": "Filter button for tag value", diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 2b8e29f99..ce7f24b6e 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -2028,8 +2028,10 @@ export default defineMessages({ 'description {Filter by description} ' + 'gcp_project {Filter by GCP project} ' + 'group {Filter by group} ' + + 'instance {Filter by instance} ' + 'name {Filter by name} ' + 'node {Filter by node} ' + + 'operating_system {Filter by operating system} ' + 'org_unit_id {Filter by organizational unit} ' + 'payer_tenant_id {Filter by account} ' + 'persistent_volume_claim {Filter by persistent volume claim} ' + From c563b1aa6031408a5b7510c7d0f18dc3a2d32a56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:45:02 +0000 Subject: [PATCH 10/22] (chore): Bump the lint-dependencies group with 2 updates Bumps the lint-dependencies group with 2 updates: [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [eslint-plugin-formatjs](https://github.com/formatjs/formatjs). Updates `@typescript-eslint/parser` from 8.8.0 to 8.8.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.1/packages/parser) Updates `eslint-plugin-formatjs` from 5.0.0 to 5.0.2 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Commits](https://github.com/formatjs/formatjs/compare/eslint-plugin-formatjs@5.0.0...vue-intl@5.0.2) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: lint-dependencies - dependency-name: eslint-plugin-formatjs dependency-type: direct:development update-type: version-update:semver-patch dependency-group: lint-dependencies ... Signed-off-by: dependabot[bot] --- package-lock.json | 200 +++++++++++++++++++++++++++++++++++++++++----- package.json | 4 +- 2 files changed, 183 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19216cd39..6cf9b77c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,11 +61,11 @@ "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.8.0", - "@typescript-eslint/parser": "^8.8.0", + "@typescript-eslint/parser": "^8.8.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", "eslint": "^9.12.0", - "eslint-plugin-formatjs": "^5.0.0", + "eslint-plugin-formatjs": "^5.0.2", "eslint-plugin-jest-dom": "^5.4.0", "eslint-plugin-jsdoc": "^50.3.1", "eslint-plugin-markdown": "^5.1.0", @@ -1004,12 +1004,12 @@ } }, "node_modules/@formatjs/ts-transformer": { - "version": "3.13.14", - "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.14.tgz", - "integrity": "sha512-TP/R54lxQ9Drzzimxrrt6yBT/xBofTgYl5wSTpyKe3Aq9vIBVcFmS6EOqycj0X34KGu3EpDPGO0ng8ZQZGLIFg==", + "version": "3.13.15", + "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.15.tgz", + "integrity": "sha512-Fy+EHfzzkiPzvRGt6c0h/2sa0iasCV2sYMgWM4d6imCWAKzUO571bf+ZdJWnAf3LsXtvnMd68At0WDC7aAuBCg==", "dev": true, "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/icu-messageformat-parser": "2.7.9", "@types/json-stable-stringify": "^1.0.32", "@types/node": "14 || 16 || 17", "chalk": "^4.0.0", @@ -1026,6 +1026,38 @@ } } }, + "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/ecma402-abstract": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz", + "integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==", + "dev": true, + "dependencies": { + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.9", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz", + "integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==", + "dev": true, + "dependencies": { + "@formatjs/ecma402-abstract": "2.1.0", + "@formatjs/icu-skeleton-parser": "1.8.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz", + "integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==", + "dev": true, + "dependencies": { + "@formatjs/ecma402-abstract": "2.1.0", + "tslib": "^2.4.0" + } + }, "node_modules/@formatjs/ts-transformer/node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", @@ -4286,16 +4318,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", - "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4" }, "engines": { @@ -4314,6 +4345,105 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", @@ -8278,13 +8408,13 @@ } }, "node_modules/eslint-plugin-formatjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.0.0.tgz", - "integrity": "sha512-ZIQGwa2mF6MU2AWzRi1aigaBzFMRESJfp+KRC7Tm0qm13UTG0GsDkDsxFruf2y/acrbusAvUwkuSwHmyTtVNmw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.0.2.tgz", + "integrity": "sha512-fDKKgcF1OsvA9NBy+6MobzOMTVn6PTCfAeavITnZrL+bzhpWO7Mc3pGkQOYqa+IjTgAnO4xHUz4yvSuVzqLKhg==", "dev": true, "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.8", - "@formatjs/ts-transformer": "3.13.14", + "@formatjs/icu-messageformat-parser": "2.7.9", + "@formatjs/ts-transformer": "3.13.15", "@types/eslint": "9", "@types/picomatch": "^2.3.0", "@typescript-eslint/utils": "8.5.0", @@ -8299,6 +8429,38 @@ "eslint": "9" } }, + "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/ecma402-abstract": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz", + "integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==", + "dev": true, + "dependencies": { + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.9", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz", + "integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==", + "dev": true, + "dependencies": { + "@formatjs/ecma402-abstract": "2.1.0", + "@formatjs/icu-skeleton-parser": "1.8.3", + "tslib": "^2.4.0" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz", + "integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==", + "dev": true, + "dependencies": { + "@formatjs/ecma402-abstract": "2.1.0", + "tslib": "^2.4.0" + } + }, "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/scope-manager": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", diff --git a/package.json b/package.json index d9d035a7d..8e4f51ea7 100644 --- a/package.json +++ b/package.json @@ -103,11 +103,11 @@ "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.8.0", - "@typescript-eslint/parser": "^8.8.0", + "@typescript-eslint/parser": "^8.8.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", "eslint": "^9.12.0", - "eslint-plugin-formatjs": "^5.0.0", + "eslint-plugin-formatjs": "^5.0.2", "eslint-plugin-jest-dom": "^5.4.0", "eslint-plugin-jsdoc": "^50.3.1", "eslint-plugin-markdown": "^5.1.0", From d8c2c43c3de64b68392d7c5cb033960f802dc6e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:58:37 +0000 Subject: [PATCH 11/22] Bump cookie and express Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `cookie` from 0.6.0 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1) Updates `express` from 4.21.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cf9b77c0..950157382 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6617,11 +6617,10 @@ "dev": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9865,18 +9864,17 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, - "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", From db1410ee1f25741a7340b04ec3de400280c7efdc Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Wed, 9 Oct 2024 13:58:30 -0400 Subject: [PATCH 12/22] Dependency updates --- package-lock.json | 552 ++++++++++++++++++++++++---------------------- package.json | 22 +- 2 files changed, 296 insertions(+), 278 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cf9b77c0..c4271a1ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@redhat-cloud-services/frontend-components-translations": "^3.2.8", "@redhat-cloud-services/frontend-components-utilities": "^4.0.17", "@redhat-cloud-services/rbac-client": "^2.2.5", - "@reduxjs/toolkit": "^2.2.7", + "@reduxjs/toolkit": "^2.2.8", "@unleash/proxy-client-react": "^4.3.1", "axios": "^1.7.7", "date-fns": "^4.1.0", @@ -31,23 +31,23 @@ "qs": "^6.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-intl": "6.7.0", + "react-intl": "6.7.2", "react-redux": "^9.1.2", "react-router-dom": "^6.26.2", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", - "victory": "^37.1.1" + "victory": "^37.1.2" }, "devDependencies": { "@eslint/compat": "^1.2.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@formatjs/cli": "^6.2.12", - "@formatjs/ecma402-abstract": "^2.0.0", - "@formatjs/icu-messageformat-parser": "^2.7.8", + "@formatjs/cli": "^6.2.14", + "@formatjs/ecma402-abstract": "^2.1.0", + "@formatjs/icu-messageformat-parser": "^2.7.9", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4", - "@redhat-cloud-services/frontend-components-config": "^6.3.0", + "@redhat-cloud-services/frontend-components-config": "^6.3.1", "@redhat-cloud-services/tsc-transform-imports": "^1.0.16", "@swc/core": "^1.7.26", "@swc/jest": "^0.2.36", @@ -60,7 +60,7 @@ "@types/react-dom": "^18.3.0", "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/eslint-plugin": "^8.8.1", "@typescript-eslint/parser": "^8.8.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", @@ -76,7 +76,7 @@ "eslint-plugin-sort-keys-fix": "^1.1.2", "eslint-plugin-testing-library": "^6.3.0", "git-revision-webpack-plugin": "^5.0.0", - "globals": "^15.10.0", + "globals": "^15.11.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -89,7 +89,7 @@ "swc_mut_cjs_exports": "^0.99.0", "ts-jest": "^29.2.5", "ts-patch": "^3.2.1", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "webpack-bundle-analyzer": "^4.10.2" }, "engines": { @@ -866,10 +866,11 @@ } }, "node_modules/@formatjs/cli": { - "version": "6.2.12", - "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.12.tgz", - "integrity": "sha512-bt1NEgkeYN8N9zWcpsPu3fZ57vv+biA+NtIQBlyOZnCp1bcvh+vNTXvmwF4C5qxqDtCylpOIb3yi3Ktgp4v0JQ==", + "version": "6.2.14", + "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.14.tgz", + "integrity": "sha512-1eSJAUQLgL9dgsrXJ1PzMAguXOPpJa1dJmMkF8GZo0jnKkavq2cdljJt2vXDeSvqWmKwqY9h4/bELr9dTOoHMw==", "dev": true, + "license": "MIT", "bin": { "formatjs": "bin/formatjs" }, @@ -914,10 +915,12 @@ } }, "node_modules/@formatjs/ecma402-abstract": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", - "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz", + "integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==", + "license": "MIT", "dependencies": { + "@formatjs/fast-memoize": "2.2.0", "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } @@ -932,36 +935,38 @@ } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.8", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", - "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "version": "2.7.9", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz", + "integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==", + "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", - "@formatjs/icu-skeleton-parser": "1.8.2", + "@formatjs/ecma402-abstract": "2.1.0", + "@formatjs/icu-skeleton-parser": "1.8.3", "tslib": "^2.4.0" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", - "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz", + "integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==", + "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/ecma402-abstract": "2.1.0", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.5.tgz", - "integrity": "sha512-f9qPNNgLrh2KvoFvHGIfcPTmNGbyy7lyyV4/P6JioDqtTE7Akdmgt+ZzVndr+yMLZnssUShyTMXxM/6aV9eVuQ==", + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.7.tgz", + "integrity": "sha512-26rNxo2nwQbbsVkV54ngml9XIA7bBzfQmELG6FFFF8eKzqzFrLKZzF8JBoBpPHgML4HKEUbGCQaBaARpKCN/sw==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/ecma402-abstract": "2.1.0", "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.8", - "@formatjs/intl-displaynames": "6.6.8", - "@formatjs/intl-listformat": "7.5.7", - "intl-messageformat": "10.5.14", + "@formatjs/icu-messageformat-parser": "2.7.9", + "@formatjs/intl-displaynames": "6.6.9", + "@formatjs/intl-listformat": "7.5.8", + "intl-messageformat": "10.6.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -974,23 +979,23 @@ } }, "node_modules/@formatjs/intl-displaynames": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.8.tgz", - "integrity": "sha512-Lgx6n5KxN16B3Pb05z3NLEBQkGoXnGjkTBNCZI+Cn17YjHJ3fhCeEJJUqRlIZmJdmaXQhjcQVDp6WIiNeRYT5g==", + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.9.tgz", + "integrity": "sha512-2hmS+YJwiXB1deNYXO2/pY7Zv4QUrZHghZxkcnWxBLEODk990h9cNbkjNg/u/RaDeCRkKVrZ3ERTdBcgkTvn2Q==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/ecma402-abstract": "2.1.0", "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } }, "node_modules/@formatjs/intl-listformat": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.7.tgz", - "integrity": "sha512-MG2TSChQJQT9f7Rlv+eXwUFiG24mKSzmF144PLb8m8OixyXqn4+YWU+5wZracZGCgVTVmx8viCf7IH3QXoiB2g==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.8.tgz", + "integrity": "sha512-WzMiw6nA2uP0ZqbbuPs7tQ+gmYRTbE20lwur4QcKp5K5cgPhkCzRAhovkDFLhrc885c3p7Wjigx8kyg0hypmZw==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/ecma402-abstract": "2.1.0", "@formatjs/intl-localematcher": "0.5.4", "tslib": "^2.4.0" } @@ -1008,6 +1013,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.15.tgz", "integrity": "sha512-Fy+EHfzzkiPzvRGt6c0h/2sa0iasCV2sYMgWM4d6imCWAKzUO571bf+ZdJWnAf3LsXtvnMd68At0WDC7aAuBCg==", "dev": true, + "license": "MIT", "dependencies": { "@formatjs/icu-messageformat-parser": "2.7.9", "@types/json-stable-stringify": "^1.0.32", @@ -1062,13 +1068,15 @@ "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@formatjs/ts-transformer/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1084,6 +1092,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1100,6 +1109,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1111,13 +1121,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@formatjs/ts-transformer/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1127,6 +1139,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2649,9 +2662,9 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.3.0.tgz", - "integrity": "sha512-BXoTUI5k3WUOVIc7VS9HadTr83tvudKxslSpgsiQuG6wgSM7LtbHdO5qmI9fD0W7kFyh2acwvHhj7oZlEdmL5w==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.3.1.tgz", + "integrity": "sha512-WLItTdGoIrc0s7QsrjGpcxELQljHSZXrPCTyf9y9fv1QopOtQRoWSIAMolDzABquWQ98HPeMyz4XsJRF7PCX1Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2985,9 +2998,10 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz", - "integrity": "sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.8.tgz", + "integrity": "sha512-eK/ieXftPRQfaBSmzsamXEyDwkntMTY0e9SG5ETsEOv5JIPKhu3mj992t6B8FJjlnSrZBAAqdT8oMkPe4j+P9g==", + "license": "MIT", "dependencies": { "immer": "^10.0.3", "redux": "^5.0.1", @@ -4048,7 +4062,8 @@ "version": "1.0.36", "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.36.tgz", "integrity": "sha512-b7bq23s4fgBB76n34m2b3RBf6M369B0Z9uRR8aHTMd8kZISRkmDEpPD8hhpYvDFzr3bJCPES96cm3Q6qRNDbQw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -4284,17 +4299,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", - "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", + "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/type-utils": "8.8.0", - "@typescript-eslint/utils": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/type-utils": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4445,14 +4460,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", - "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0" + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4463,14 +4478,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", - "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", + "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/utils": "8.8.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -4488,9 +4503,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", - "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", "dev": true, "license": "MIT", "engines": { @@ -4502,14 +4517,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", - "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4544,16 +4559,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", - "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", + "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0" + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4567,13 +4582,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", - "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/types": "8.8.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -8412,6 +8427,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.0.2.tgz", "integrity": "sha512-fDKKgcF1OsvA9NBy+6MobzOMTVn6PTCfAeavITnZrL+bzhpWO7Mc3pGkQOYqa+IjTgAnO4xHUz4yvSuVzqLKhg==", "dev": true, + "license": "MIT", "dependencies": { "@formatjs/icu-messageformat-parser": "2.7.9", "@formatjs/ts-transformer": "3.13.15", @@ -10702,9 +10718,9 @@ } }, "node_modules/globals": { - "version": "15.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.10.0.tgz", - "integrity": "sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==", + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", "dev": true, "license": "MIT", "engines": { @@ -11587,14 +11603,14 @@ } }, "node_modules/intl-messageformat": { - "version": "10.5.14", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", - "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.6.0.tgz", + "integrity": "sha512-AYKl/DY1nl75pJU8EK681JOVL40uQTNJe3yEMXKfydDFoz+5hNrM/PqjchueSMKGKCZKBVgeexqZwy3uC2B36Q==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/ecma402-abstract": "2.1.0", "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/icu-messageformat-parser": "2.7.9", "tslib": "^2.4.0" } }, @@ -14613,6 +14629,7 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "isarray": "^2.0.5", @@ -14675,6 +14692,7 @@ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "dev": true, + "license": "Public Domain", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -17146,20 +17164,20 @@ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, "node_modules/react-intl": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.7.0.tgz", - "integrity": "sha512-f5QhjuKb+WEqiAbL5hDqUs2+sSRkF0vxkTbJ4A8ompt55XTyOHcrDlCXGq4o73ywFFrpgz+78C9IXegSLlya2A==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.7.2.tgz", + "integrity": "sha512-v/lvAORTE70welhzqoIi1YI1yHvGE4/QX4W3JYNZoqRxH8ab8Q/Ed4Zem/ZVPZJN4byQ52U+2GESLy0zvY6IBw==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", - "@formatjs/icu-messageformat-parser": "2.7.8", - "@formatjs/intl": "2.10.5", - "@formatjs/intl-displaynames": "6.6.8", - "@formatjs/intl-listformat": "7.5.7", + "@formatjs/ecma402-abstract": "2.1.0", + "@formatjs/icu-messageformat-parser": "2.7.9", + "@formatjs/intl": "2.10.7", + "@formatjs/intl-displaynames": "6.6.9", + "@formatjs/intl-listformat": "7.5.8", "@types/hoist-non-react-statics": "^3.3.1", "@types/react": "16 || 17 || 18", "hoist-non-react-statics": "^3.3.2", - "intl-messageformat": "10.5.14", + "intl-messageformat": "10.6.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -19710,9 +19728,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -20003,386 +20021,386 @@ } }, "node_modules/victory": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory/-/victory-37.1.1.tgz", - "integrity": "sha512-3tyIZ79YVd9bxS3KocGa6UuQdCA4Kenqzh3Th7QBB7Am96MHXVyePsYwhg0KorOmKqocQxYgLShGIjEHT1Qv+w==", - "license": "MIT", - "dependencies": { - "victory-area": "37.1.1", - "victory-axis": "37.1.1", - "victory-bar": "37.1.1", - "victory-box-plot": "37.1.1", - "victory-brush-container": "37.1.1", - "victory-brush-line": "37.1.1", - "victory-candlestick": "37.1.1", - "victory-canvas": "37.1.1", - "victory-chart": "37.1.1", - "victory-core": "37.1.1", - "victory-create-container": "37.1.1", - "victory-cursor-container": "37.1.1", - "victory-errorbar": "37.1.1", - "victory-group": "37.1.1", - "victory-histogram": "37.1.1", - "victory-legend": "37.1.1", - "victory-line": "37.1.1", - "victory-pie": "37.1.1", - "victory-polar-axis": "37.1.1", - "victory-scatter": "37.1.1", - "victory-selection-container": "37.1.1", - "victory-shared-events": "37.1.1", - "victory-stack": "37.1.1", - "victory-tooltip": "37.1.1", - "victory-voronoi": "37.1.1", - "victory-voronoi-container": "37.1.1", - "victory-zoom-container": "37.1.1" + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory/-/victory-37.1.2.tgz", + "integrity": "sha512-V1YkJiWQ/vu5MSZ/Yf9/AJQeE+N1p1eUW6r5xJgOjbhioIbnL7FBTyJW1AXqqDZN9WdyECI3TkFQ1l/QbgztoA==", + "license": "MIT", + "dependencies": { + "victory-area": "37.1.2", + "victory-axis": "37.1.2", + "victory-bar": "37.1.2", + "victory-box-plot": "37.1.2", + "victory-brush-container": "37.1.2", + "victory-brush-line": "37.1.2", + "victory-candlestick": "37.1.2", + "victory-canvas": "37.1.2", + "victory-chart": "37.1.2", + "victory-core": "37.1.2", + "victory-create-container": "37.1.2", + "victory-cursor-container": "37.1.2", + "victory-errorbar": "37.1.2", + "victory-group": "37.1.2", + "victory-histogram": "37.1.2", + "victory-legend": "37.1.2", + "victory-line": "37.1.2", + "victory-pie": "37.1.2", + "victory-polar-axis": "37.1.2", + "victory-scatter": "37.1.2", + "victory-selection-container": "37.1.2", + "victory-shared-events": "37.1.2", + "victory-stack": "37.1.2", + "victory-tooltip": "37.1.2", + "victory-voronoi": "37.1.2", + "victory-voronoi-container": "37.1.2", + "victory-zoom-container": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-area": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-area/-/victory-area-37.1.1.tgz", - "integrity": "sha512-9OVILTIT5DW/BsMksZ1xCjmNrT0iIhsHnumeNJDvvfzWUeqLyYPwmqp8e2wRraj1VRhRAAgZGXAHi7XA3rJkgQ==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-area/-/victory-area-37.1.2.tgz", + "integrity": "sha512-72i02xTD47i7P+X02AHhZ32yO16VcM1h/7gulgAioLEx+8m3zShBKu46Md/vqmbyS2Bypr3xpUvd+8mCDIvCbw==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1", - "victory-vendor": "37.1.1" + "victory-core": "37.1.2", + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-axis": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-axis/-/victory-axis-37.1.1.tgz", - "integrity": "sha512-LqlXoAHNxvS/GdAKR6YSHZf0I9egMZf84kqUb7dG3NNLE8M1XnaEkYlfIOJsL+vsZJqm4kqoe67yI56eqIY5Hw==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-axis/-/victory-axis-37.1.2.tgz", + "integrity": "sha512-TuivC84cHrFoDetWDhU2VXQ34froIXBrtjYYPdmwBrMEFSu+FfrakYWUr3r25XNEPyOyk4z3a8lL/sqrxWYSRQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-bar": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-bar/-/victory-bar-37.1.1.tgz", - "integrity": "sha512-1e1QtVDMgFRwXZDrt9nT1Fqv57yHL9Z9ssA2mgyzV/wi/HRneuUXE958Q/t59z4cTEkRYwNrUE3dODBCpxXMKw==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-bar/-/victory-bar-37.1.2.tgz", + "integrity": "sha512-VJDE+TGSgyIchvln189cPMuG3LYqa8zCjHa8kYValP3bFTa5c+D1Y8R/FjVger40uEL3aQz1teHJODJCOxuXGA==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1", - "victory-vendor": "37.1.1" + "victory-core": "37.1.2", + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-box-plot": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-box-plot/-/victory-box-plot-37.1.1.tgz", - "integrity": "sha512-cdmAxg1Sqt/c2lbPJdD8+4qBNj8UMav8fLtsGd/uCNHWYzv52+0g9B8ToE6ImsKyBFRGnW+c0BD5vKbtyW6tJw==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-box-plot/-/victory-box-plot-37.1.2.tgz", + "integrity": "sha512-i7JIjpaPTr3uaoW6ibfX4PrH1QcUeLXNxeCbmPRb+Hs+ug0d16J4RELPCaeNo/ZNg4rEzfueNTvExsYFIpHaWw==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1", - "victory-vendor": "37.1.1" + "victory-core": "37.1.2", + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-brush-container": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-37.1.1.tgz", - "integrity": "sha512-iZkp/r7uzkc7UN3EgAWe4aDDEFHe7BQs0nv/mmyFeFYIXG5e2uiKs28OsZnfgp6CDIHDqUoV8DAGOccotUbUaQ==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-37.1.2.tgz", + "integrity": "sha512-pJrMSo815UJxOT5OTXnq1tI5qQxQLnrlgDRNF8pxVF9dSxm7BhETjZSQfZgcLmCe3N931U19j8oCxw8sMSpJJw==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-brush-line": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-brush-line/-/victory-brush-line-37.1.1.tgz", - "integrity": "sha512-nsuJW7VFYFO2R+i0wveC4nizOhLj/UcTHwv98J6PYt3c0LQXa04YMFOfrRuKV/+Qsrj4DOVO3/GU6/PSUwozlQ==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-brush-line/-/victory-brush-line-37.1.2.tgz", + "integrity": "sha512-Bq9JGu/o4p/NQ/ZOASUm6MmomS+2b0EvAHjULa06z7nsElNePpedTYPk2aAb7mr4sJZe6u/AsDMthG+C8Zc32Q==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-candlestick": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-candlestick/-/victory-candlestick-37.1.1.tgz", - "integrity": "sha512-M5ftMbFi8HM9QYLrPb1DfrHOYKCwnDkxe8ct8MjE2ibsnKNCxUrwjJbkh0QXPa4ndk5y4jl98T9FmJS1Q14nPg==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-candlestick/-/victory-candlestick-37.1.2.tgz", + "integrity": "sha512-X+pLwvdIj/+nrvk1bZxhdJ9UBj7QLN4jdkIPDl6ekjfZ9Ylhi8/I/ttAkBu+7w7ilpGudIK6fr7PVHyZyYU6TA==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-canvas": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-canvas/-/victory-canvas-37.1.1.tgz", - "integrity": "sha512-nq+du3x2D8sdRfNNg2idieElJbwq7vI2DO5FoFyFyowX6plXjOXoJZAOX/+7GTBQ4FP7tktNka5AQ9z8u5Sxbw==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-canvas/-/victory-canvas-37.1.2.tgz", + "integrity": "sha512-4Qmz7YpFBj2KaBSe+j5zLVrKAJLG3HtXVVaKI3oUzw4GzHlYXf77dJLYe2EqJVEFCMgVsmASqE3xVTklioMV7g==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-bar": "37.1.1", - "victory-core": "37.1.1" + "victory-bar": "37.1.2", + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-chart": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-chart/-/victory-chart-37.1.1.tgz", - "integrity": "sha512-p//04lKzUX1ocXmp9RWmQMOsQUcP7m1CsrYkBOvqzD1sjgMhDzTqZdn38rMUzW0bpbCs0Tl6wbOzxMN+/PA8fQ==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-chart/-/victory-chart-37.1.2.tgz", + "integrity": "sha512-efV7lnqwu4+zLzB6aY3jjYbbfYJ9+1VC6uwx+8AGjbb8vGkbByUOKC6Fhdcuca2mLqNQHM0Ynvevs3+4XrBz1g==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-axis": "37.1.1", - "victory-core": "37.1.1", - "victory-polar-axis": "37.1.1", - "victory-shared-events": "37.1.1" + "victory-axis": "37.1.2", + "victory-core": "37.1.2", + "victory-polar-axis": "37.1.2", + "victory-shared-events": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-core": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-37.1.1.tgz", - "integrity": "sha512-4UK1S1+9CFBn1Nwu18JsOf2EtaTI/DOE4Eoi5byLd6kFO8/luSbaLvc7BDPxiLpSj0BGiX/Hbqs12T2gPaEnAA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-37.1.2.tgz", + "integrity": "sha512-9fskAQw9MvYEBL+0cDk2lihKyECdrh+8HGicDfSKGOWtsSLk+x7R6PKCpOzhmSgc9fG+HjWYfs2uTWSPNTjF9A==", "license": "MIT", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "37.1.1" + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-create-container": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-create-container/-/victory-create-container-37.1.1.tgz", - "integrity": "sha512-t/soXK97TcP4yxHYwvfCWJW9jGlRyYS4zdhjLe9Q2iETY0ngiVk+bpETZVPMgubPxq3JPaogMQKgd+1hDWjBMg==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-create-container/-/victory-create-container-37.1.2.tgz", + "integrity": "sha512-GvWA+N3SXf6he+hW1IQqaRjKG7XSV9WBr06mZixRVyOeHJGGZ5g7Vsse1WrwUz5/DM8HcqF34PTfhTs39v6zaw==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-brush-container": "37.1.1", - "victory-core": "37.1.1", - "victory-cursor-container": "37.1.1", - "victory-selection-container": "37.1.1", - "victory-voronoi-container": "37.1.1", - "victory-zoom-container": "37.1.1" + "victory-brush-container": "37.1.2", + "victory-core": "37.1.2", + "victory-cursor-container": "37.1.2", + "victory-selection-container": "37.1.2", + "victory-voronoi-container": "37.1.2", + "victory-zoom-container": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-cursor-container": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-cursor-container/-/victory-cursor-container-37.1.1.tgz", - "integrity": "sha512-m2YS7nmAcGHatVhuqjuJW7jXRXutI0e1pBz9PbHm692HNAJbMfFTJAKtgPXUj5wYVae4OAr6f0551/ekkcL7xQ==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-cursor-container/-/victory-cursor-container-37.1.2.tgz", + "integrity": "sha512-GqOVB/Emas/ODw7Sb7FX1FmUyH3jb5eNF+2sR+DdYfDMTFmjVUyqGkSpi1bIgHoSWTrdG9C2tkxA69gI9JDtLA==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-errorbar": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-errorbar/-/victory-errorbar-37.1.1.tgz", - "integrity": "sha512-1nDaa6zT/OaA99DYwznwEwbD3lHfsnBV0UbUlQn91Hv99sg0Rvyk9cZinQWTZ0nNf8cNBYOzZlpFxY35XbQVSA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-errorbar/-/victory-errorbar-37.1.2.tgz", + "integrity": "sha512-sgs1nla57Ctt9slG5WXWdxqTXtTdKcZM+u83C5j1ceKKmMjCiqiNYmMQpF7yz7Nj2ewJTrOzZON9h2zgurr2Cg==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-group": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-group/-/victory-group-37.1.1.tgz", - "integrity": "sha512-170CnQ6+doT8VUPZzcq6IIluSMSYqactT9J0ANSDEwHsO/+r0tFwez44FtA4/DgdDh5ObWQ6VfQx330urMG5bA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-group/-/victory-group-37.1.2.tgz", + "integrity": "sha512-Zwdvs6pSfF02xax8rQbahSqRP7eua4mS0so0gFYr/M2sNiKN4hxnM72j3dLo9nQ63kQpYhcUZe8U/hEjlhHxYQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "37.1.1", - "victory-shared-events": "37.1.1" + "victory-core": "37.1.2", + "victory-shared-events": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-histogram": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-histogram/-/victory-histogram-37.1.1.tgz", - "integrity": "sha512-2KnfdQYaO+MELM/PB3saPHcUf+tHg0SwbaLHKRk+Im7+aQRUlprlHH7sHZJM/TYXCkJdqbQQNoW6R9VK/kQiGg==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-histogram/-/victory-histogram-37.1.2.tgz", + "integrity": "sha512-IGeQZ2HGuvmMyYxoKOczIILNH6ARDJaWcDG3h5BX4jP4JH2+eWeEukCVHGT3b1VM1OFxuvPijJrePXYzKgQ+AQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-bar": "37.1.1", - "victory-core": "37.1.1", - "victory-vendor": "37.1.1" + "victory-bar": "37.1.2", + "victory-core": "37.1.2", + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-legend": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-legend/-/victory-legend-37.1.1.tgz", - "integrity": "sha512-8F51DbYzG+jkMJoGp2Ulqqxgoq00TWgvQcBTZptdrN2PFlc2b1Ug7z3lbK1ziUCunrVbHQpAhge0onDoRyn1Vg==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-legend/-/victory-legend-37.1.2.tgz", + "integrity": "sha512-dmwwHtFpEXPIelY9iH1a2Q/V2Ji8DaF0a2g+hLH4SM/xbA9YwjP2/9DIQcwS7/OVl4l1AnSbLFcu5RyDPJ0kww==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-line": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-line/-/victory-line-37.1.1.tgz", - "integrity": "sha512-YLR9/i7BwN3taBvHCfmc5hA0po16QFQuFnO61NPNCBZtv8kNf39m3BpDTDYMeuEgEBCnMw0znR0C1NASZcJDWQ==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-line/-/victory-line-37.1.2.tgz", + "integrity": "sha512-DjttWkQG0iZtQ9SM/KphN168dQUgw1xwyr0qR1aN12VtVb1jspI1LkBH8XqUeYXgfuI1vKQJWcV/zMOK2Q1n8g==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1", - "victory-vendor": "37.1.1" + "victory-core": "37.1.2", + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-pie": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-37.1.1.tgz", - "integrity": "sha512-GWHR4prUq6ZNeMd0IEywHvvWn3dkn7vS3fkLMVTKitpbMIRPGlFxo5gLTkAQv3nnA/762GLSyELbcFgFQXOQUA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-37.1.2.tgz", + "integrity": "sha512-lwPMAtkcGDJ4gdpKFmR7hRnowJZIGQ6XIvyPj7Ir+QfL6ew64kl7YiIsQpDnC4zqwAjDPIbIW/kRROiSKRjXjQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1", - "victory-vendor": "37.1.1" + "victory-core": "37.1.2", + "victory-vendor": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-polar-axis": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-polar-axis/-/victory-polar-axis-37.1.1.tgz", - "integrity": "sha512-I9okmw1MauiucV6WxylHDOZtW5mgrozYmfglOSR6fnQ9gcxPoXSgBNxo801kyV2/pu8BP6dD07Uz1QLbCh3KSA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-polar-axis/-/victory-polar-axis-37.1.2.tgz", + "integrity": "sha512-cvELVQ5MwDjDfC/n/g8QVfUhexLNKcp7kXxbjp6IGbzQMCfNtROHaVaHaISNH7/EV5zinwBhNj0+ISWatROtrQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-scatter": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-scatter/-/victory-scatter-37.1.1.tgz", - "integrity": "sha512-2jt0HgYnLngw8oVAY5Tcq2MEHVc3FDo47gMQf7LysFvsuCtBLvgkaDuRPnF+8Ty3hP/7qwjV9tgM7Ui2cSfZSg==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-scatter/-/victory-scatter-37.1.2.tgz", + "integrity": "sha512-6orfcqdfZCuTHqf/wE+B+sQbpzf2/TyEvLZhvYIXFr5GzdVu39psNl74K3GQ2Ky0db+e6oLEHV8nZYO2IvWoWg==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-selection-container": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-selection-container/-/victory-selection-container-37.1.1.tgz", - "integrity": "sha512-5FYlMQNt7uV+EfndtCTYkE5/yjnHo243ZnBiUzXmvXU+IBCjzXmcOeyqyn7IY7+p1fvA2Hc698mDLGydd8QJrA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-selection-container/-/victory-selection-container-37.1.2.tgz", + "integrity": "sha512-1sp1CV9LrBADnsBcFgVQuYUNCLeANuybtOS9/5TvPPELBGWQQ55nBN3mH/laVPDy9gGyPARh1lmdPgREHmSkmQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-shared-events": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-shared-events/-/victory-shared-events-37.1.1.tgz", - "integrity": "sha512-hMZI4GMLNWoIQ/Yso/tiTKpx5wUgNi2iwozrxWDesr11I5uqwutkBeHpIBMBwsGRWy6plkMyBp9lCf2Etkxm4A==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-shared-events/-/victory-shared-events-37.1.2.tgz", + "integrity": "sha512-fpgpe6eI0A9dD39ZsFaid3sXdrCf1WIzFnpkNFT6hBYrDDD5Fd2/2SgqOxuul64PlYJAk6NOY+F1agmEtmB+/Q==", "license": "MIT", "dependencies": { "json-stringify-safe": "^5.0.1", "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-stack": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-stack/-/victory-stack-37.1.1.tgz", - "integrity": "sha512-jIHV7xRZW8jEuOGjrEreIh/u1mddDix98NmIJnd2+qMk1EuWIHngC2neCKQ0iF3wc8eAMuaK8gGr6ksSkpsqPA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-stack/-/victory-stack-37.1.2.tgz", + "integrity": "sha512-H3FWiv3c6s/++PB3pBZ/9r8mcry1FHg8JK+03DZhRKHtJIti/38iIYUUiFOoQKmjVUQ7wrLdftYiemy3st77Dg==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "37.1.1", - "victory-shared-events": "37.1.1" + "victory-core": "37.1.2", + "victory-shared-events": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-tooltip": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-tooltip/-/victory-tooltip-37.1.1.tgz", - "integrity": "sha512-n5TTR92jIDaeXSADV+edevcMcNLz1iPwzQr7CNX38vWU6RWf/FRcdiBlBNg3v4rNh41+sO8jjMQhjOpDti6Rvw==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-tooltip/-/victory-tooltip-37.1.2.tgz", + "integrity": "sha512-j1r1t83X0epSwivhf4eYSD2DoWRVy5fkINbLk4sVnnV2EUT4Lt4yH3uelIhYQuT4Y+Ez9KFLoQvR6bfwmHyfZw==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-vendor": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.1.1.tgz", - "integrity": "sha512-WDnoGOSqmgyFgY/+7v4i40Vc/I/iOqc9JpUniWO9TvLCWAVEmwAjKxrorBlxEv+vQxQuhxGKOf3PcJqfjZqA9g==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.1.2.tgz", + "integrity": "sha512-kZ2UVcoINrisEW7JDaxws2v17D4n4ShRzsPUcYnF37/avByNbjzybhvs8JrqO6+vUmoP2W1DrTEI2L/86PEQjw==", "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", @@ -20402,43 +20420,43 @@ } }, "node_modules/victory-voronoi": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-voronoi/-/victory-voronoi-37.1.1.tgz", - "integrity": "sha512-LIGT4JLP+9GxzvA1rka3W8iHXx8TXvGDzcgDhj3E14dSjkDkYaX0/tyBBirHo7T3IFHThAO6GNPsfMrCzz8Z9w==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-voronoi/-/victory-voronoi-37.1.2.tgz", + "integrity": "sha512-rbihVJMDLmrMKfm6mbzTft9BbaJWZkymFkYxZZT0ZdHjsyaFm7t3jjrtvG1cq6HsTI10AfCh7iWmD9aky69eMQ==", "license": "MIT", "dependencies": { "d3-voronoi": "^1.1.4", "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-voronoi-container": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-voronoi-container/-/victory-voronoi-container-37.1.1.tgz", - "integrity": "sha512-OIiT/KroQCvPaITEGcZfPd7B5Byw2vjo52RiUfzdg5WfCvqxuOURnvXsv6lh8nTNS/VI9uWaxHYdATXqXtNgfA==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-voronoi-container/-/victory-voronoi-container-37.1.2.tgz", + "integrity": "sha512-uFnZmRWp+QP7mH9jqetmoSR/KYhnFr4sFGR9+HrQkUbOzBQpT7Q2SNrDcr5l29Hm7Lb+3iUuF/l0E//EzuS+Ig==", "license": "MIT", "dependencies": { "delaunay-find": "0.0.6", "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "37.1.1", - "victory-tooltip": "37.1.1" + "victory-core": "37.1.2", + "victory-tooltip": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-zoom-container": { - "version": "37.1.1", - "resolved": "https://registry.npmjs.org/victory-zoom-container/-/victory-zoom-container-37.1.1.tgz", - "integrity": "sha512-pBW64iT9zlFqmo468+MXkqNwJuuM+Q/+5/llFCKBoMA6wE1SwpkgHQ8RITWQUDCY9dR3y/bJFLEQg2aqoFB8/g==", + "version": "37.1.2", + "resolved": "https://registry.npmjs.org/victory-zoom-container/-/victory-zoom-container-37.1.2.tgz", + "integrity": "sha512-OI0AgskIpruWaFWF1BkJWi4UZGyEJ+ol3uzlIMk3tPmYkuw5Gh4pTW6kEw/0E1BP+PwJjv+IRGBbT46/YxV3UQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.19", - "victory-core": "37.1.1" + "victory-core": "37.1.2" }, "peerDependencies": { "react": ">=16.6.0" diff --git a/package.json b/package.json index 8e4f51ea7..0afaaa241 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@redhat-cloud-services/frontend-components-translations": "^3.2.8", "@redhat-cloud-services/frontend-components-utilities": "^4.0.17", "@redhat-cloud-services/rbac-client": "^2.2.5", - "@reduxjs/toolkit": "^2.2.7", + "@reduxjs/toolkit": "^2.2.8", "@unleash/proxy-client-react": "^4.3.1", "axios": "^1.7.7", "date-fns": "^4.1.0", @@ -73,23 +73,23 @@ "qs": "^6.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-intl": "6.7.0", + "react-intl": "6.7.2", "react-redux": "^9.1.2", "react-router-dom": "^6.26.2", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", - "victory": "^37.1.1" + "victory": "^37.1.2" }, "devDependencies": { "@eslint/compat": "^1.2.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@formatjs/cli": "^6.2.12", - "@formatjs/ecma402-abstract": "^2.0.0", - "@formatjs/icu-messageformat-parser": "^2.7.8", + "@formatjs/cli": "^6.2.14", + "@formatjs/ecma402-abstract": "^2.1.0", + "@formatjs/icu-messageformat-parser": "^2.7.9", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4", - "@redhat-cloud-services/frontend-components-config": "^6.3.0", + "@redhat-cloud-services/frontend-components-config": "^6.3.1", "@redhat-cloud-services/tsc-transform-imports": "^1.0.16", "@swc/core": "^1.7.26", "@swc/jest": "^0.2.36", @@ -102,7 +102,7 @@ "@types/react-dom": "^18.3.0", "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/eslint-plugin": "^8.8.1", "@typescript-eslint/parser": "^8.8.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", @@ -118,7 +118,7 @@ "eslint-plugin-sort-keys-fix": "^1.1.2", "eslint-plugin-testing-library": "^6.3.0", "git-revision-webpack-plugin": "^5.0.0", - "globals": "^15.10.0", + "globals": "^15.11.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -131,11 +131,11 @@ "swc_mut_cjs_exports": "^0.99.0", "ts-jest": "^29.2.5", "ts-patch": "^3.2.1", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "webpack-bundle-analyzer": "^4.10.2" }, "overrides": { - "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/eslint-plugin": "^8.8.1", "eslint": "^9.12.0", "redux": "^5.0.1" }, From eb3c8c40fbb55b14b771e4365a5f4d7ffdc1207a Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 10 Oct 2024 09:37:39 -0400 Subject: [PATCH 13/22] Group-by in Cost Explorer is missing "Tag" option after switching perspectives https://issues.redhat.com/browse/COST-5585 --- src/routes/components/groupBy/groupBy.tsx | 36 +++++++++++------------ src/routes/explorer/explorerHeader.tsx | 3 ++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/routes/components/groupBy/groupBy.tsx b/src/routes/components/groupBy/groupBy.tsx index 7bb0378a6..d97bea0bc 100644 --- a/src/routes/components/groupBy/groupBy.tsx +++ b/src/routes/components/groupBy/groupBy.tsx @@ -15,7 +15,6 @@ import { connect } from 'react-redux'; import type { SelectWrapperOption } from 'routes/components/selectWrapper'; import { SelectWrapper } from 'routes/components/selectWrapper'; import type { PerspectiveType } from 'routes/explorer/explorerUtils'; -import { getDateRangeFromQuery } from 'routes/utils/dateRange'; import { getTimeScopeValue } from 'routes/utils/timeScope'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; @@ -31,6 +30,7 @@ import { GroupByOrg } from './groupByOrg'; import { GroupBySelect } from './groupBySelect'; interface GroupByOwnProps extends RouterComponentProps, WrappedComponentProps { + endDate?: string; getIdKeyForGroupBy: (groupBy: Query['group_by']) => string; groupBy?: string; isDisabled?: boolean; @@ -45,6 +45,7 @@ interface GroupByOwnProps extends RouterComponentProps, WrappedComponentProps { showCostCategories?: boolean; showOrgs?: boolean; showTags?: boolean; + startDate?: string; tagPathsType: TagPathsType; } @@ -325,28 +326,25 @@ class GroupByBase extends React.Component { } const mapStateToProps = createMapStateToProps( - (state, { orgPathsType, router, resourcePathsType, tagPathsType }) => { + (state, { endDate, orgPathsType, router, resourcePathsType, startDate, tagPathsType }) => { const queryFromRoute = parseQuery(router.location.search); const timeScopeValue = getTimeScopeValue(queryFromRoute); + // Use start and end dates with Cost Explorer // Default to current month filter for details pages - let tagFilter: any = { - filter: { - resolution: 'monthly', - time_scope_units: 'month', - time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, - }, - }; - - // Replace with start and end dates for Cost Explorer - if (queryFromRoute.dateRangeType) { - const { end_date, start_date } = getDateRangeFromQuery(queryFromRoute); - - tagFilter = { - end_date, - start_date, - }; - } + const tagFilter = + startDate && endDate + ? { + end_date: endDate, + start_date: startDate, + } + : { + filter: { + resolution: 'monthly', + time_scope_units: 'month', + time_scope_value: timeScopeValue !== undefined ? timeScopeValue : -1, + }, + }; // Note: Omitting key_only would help to share a single, cached request -- the toolbar requires key values // However, for better server-side performance, we chose to use key_only here. diff --git a/src/routes/explorer/explorerHeader.tsx b/src/routes/explorer/explorerHeader.tsx index f7ca49ed1..a6d23ad0c 100644 --- a/src/routes/explorer/explorerHeader.tsx +++ b/src/routes/explorer/explorerHeader.tsx @@ -175,6 +175,7 @@ class ExplorerHeaderBase extends React.Component{this.getPerspective(noProviders)} From 2b11ecf6b782de5dc43d3253685df61b341bcfac Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 10 Oct 2024 09:38:05 -0400 Subject: [PATCH 14/22] Date range menu appears blank after change of perspective https://issues.redhat.com/browse/COST-5586 --- src/routes/explorer/explorer.tsx | 13 ++++++ src/routes/explorer/explorerDatePicker.tsx | 7 +--- src/routes/explorer/explorerFilter.tsx | 49 ++++------------------ 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/routes/explorer/explorer.tsx b/src/routes/explorer/explorer.tsx index 5d8d5a2af..85a6cdf00 100644 --- a/src/routes/explorer/explorer.tsx +++ b/src/routes/explorer/explorer.tsx @@ -375,6 +375,18 @@ class Explorer extends React.Component { } }; + private handleOnDateRangeSelect = (dateRangeType: string) => { + const { query, router } = this.props; + + const newQuery = { + ...JSON.parse(JSON.stringify(query)), + dateRangeType, + start_date: undefined, + end_date: undefined, + }; + router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); + }; + private handleOnExportModalClose = (isOpen: boolean) => { this.setState({ isExportModalOpen: isOpen }); }; @@ -569,6 +581,7 @@ class Explorer extends React.Component { onCostDistributionSelect={() => handleOnCostDistributionSelect(query, router)} onCostTypeSelect={() => handleOnCostTypeSelect(query, router)} onCurrencySelect={() => handleOnCurrencySelect(query, router)} + onDateRangeSelect={this.handleOnDateRangeSelect} onFilterAdded={filter => handleOnFilterAdded(query, router, filter)} onFilterRemoved={filter => handleOnFilterRemoved(query, router, filter)} onGroupBySelect={this.handleOnGroupBySelect} diff --git a/src/routes/explorer/explorerDatePicker.tsx b/src/routes/explorer/explorerDatePicker.tsx index e73eb1ed3..813280a84 100644 --- a/src/routes/explorer/explorerDatePicker.tsx +++ b/src/routes/explorer/explorerDatePicker.tsx @@ -16,7 +16,7 @@ interface ExplorerDatePickerOwnProps extends RouterComponentProps, WrappedCompon dateRangeType?: DateRangeType; endDate?: string; onSelect(startDate: Date, endDate: Date); - startDate?: Date; + startDate?: string; } interface ExplorerDatePickerState { @@ -39,11 +39,6 @@ class ExplorerDatePickerBase extends React.Component(router.location.search); - // - // // Query dates are undefined until a selection is made - // const end_date = queryFromRoute.end_date; - // const start_date = queryFromRoute.start_date; if (this.startDateRef?.current) { this.startDateRef.current.setCalendarOpen(dateRangeType !== DateRangeType.custom); diff --git a/src/routes/explorer/explorerFilter.tsx b/src/routes/explorer/explorerFilter.tsx index eb188a75e..d3bb98ac9 100644 --- a/src/routes/explorer/explorerFilter.tsx +++ b/src/routes/explorer/explorerFilter.tsx @@ -43,7 +43,7 @@ import { interface ExplorerFilterOwnProps extends RouterComponentProps, WrappedComponentProps { dateRangeType: DateRangeType; - endDate?: boolean; + endDate?: string; groupBy: string; isCurrentMonthData?: boolean; isDisabled?: boolean; @@ -53,7 +53,7 @@ interface ExplorerFilterOwnProps extends RouterComponentProps, WrappedComponentP perspective: PerspectiveType; pagination?: React.ReactNode; query?: Query; - startDate?: boolean; + startDate?: string; } interface ExplorerFilterStateProps { @@ -80,7 +80,6 @@ interface ExplorerFilterDispatchProps { interface ExplorerFilterState { categoryOptions?: ToolbarChipGroup[]; - currentDateRange: DateRangeType; showDatePicker?: boolean; } @@ -92,24 +91,20 @@ const tagType = TagType.tag; export class ExplorerFilterBase extends React.Component { protected defaultState: ExplorerFilterState = { - currentDateRange: this.props.dateRangeType, showDatePicker: false, }; public state: ExplorerFilterState = { ...this.defaultState }; public componentDidMount() { - const { dateRangeType } = this.props; - this.updateReport(); this.setState({ - currentDateRange: dateRangeType, categoryOptions: this.getCategoryOptions(), - showDatePicker: dateRangeType === DateRangeType.custom, + showDatePicker: this.props.dateRangeType === DateRangeType.custom, }); } public componentDidUpdate(prevProps: ExplorerFilterProps) { - const { dateRangeType, orgReport, perspective, query, tagReport } = this.props; + const { dateRangeType, orgReport, query, tagReport } = this.props; if (query && !isEqual(query, prevProps.query)) { this.updateReport(); @@ -120,18 +115,10 @@ export class ExplorerFilterBase extends React.Component { - this.updateDateRange(currentDateRange); - }); - } } private getCategoryOptions = (): ToolbarChipGroup[] => { @@ -172,12 +159,11 @@ export class ExplorerFilterBase extends React.Component { - const { isCurrentMonthData, isDisabled } = this.props; - const { currentDateRange } = this.state; + const { dateRangeType, isCurrentMonthData, isDisabled } = this.props; return ( { - if (!showDatePicker) { - this.updateDateRange(currentDateRange); - - // Clear inline alert - if (onDateRangeSelect) { - onDateRangeSelect(currentDateRange); - } + this.setState({ showDatePicker }, () => { + if (onDateRangeSelect && !showDatePicker) { + onDateRangeSelect(currentDateRange); } }); }; - private updateDateRange = (value: string) => { - const { query, router } = this.props; - - const newQuery = { - ...JSON.parse(JSON.stringify(query)), - dateRangeType: value, - start_date: undefined, - end_date: undefined, - }; - router.navigate(getRouteForQuery(newQuery, router.location, true), { replace: true }); - }; - private updateReport = () => { const { fetchOrg, From 9e70014d27aebe1356cc74b559b0e5718c2ba9c9 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Fri, 11 Oct 2024 13:41:28 -0400 Subject: [PATCH 15/22] Improve resource type search in Cost explorer (AWS and OCP on AWS) https://issues.redhat.com/browse/COST-5596 --- src/api/resources/awsOcpResource.test.ts | 2 +- src/api/resources/awsOcpResource.ts | 2 +- src/routes/components/resourceTypeahead/resourceInput.tsx | 1 + src/routes/explorer/explorerFilter.tsx | 1 + src/routes/explorer/explorerToolbar.tsx | 3 +-- src/routes/explorer/explorerUtils.ts | 3 ++- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/api/resources/awsOcpResource.test.ts b/src/api/resources/awsOcpResource.test.ts index 512d1c169..bc714262b 100644 --- a/src/api/resources/awsOcpResource.test.ts +++ b/src/api/resources/awsOcpResource.test.ts @@ -5,5 +5,5 @@ import { ResourceType } from './resource'; test('runExport API request for OCP on AWS', () => { runResource(ResourceType.account, ''); - expect(axiosInstance.get).toBeCalledWith('resource-types/aws-accounts/'); + expect(axiosInstance.get).toBeCalledWith('resource-types/aws-accounts/?openshift=true'); }); diff --git a/src/api/resources/awsOcpResource.ts b/src/api/resources/awsOcpResource.ts index b2e0cad63..63344a835 100644 --- a/src/api/resources/awsOcpResource.ts +++ b/src/api/resources/awsOcpResource.ts @@ -15,6 +15,6 @@ export const ResourceTypePaths: Partial> = { export function runResource(resourceType: ResourceType, query: string) { const path = ResourceTypePaths[resourceType]; - const queryString = query ? `?${query}` : ''; + const queryString = query ? `?openshift=true;${query}` : '?openshift=true'; return axiosInstance.get(`${path}${queryString}`); } diff --git a/src/routes/components/resourceTypeahead/resourceInput.tsx b/src/routes/components/resourceTypeahead/resourceInput.tsx index b7d22a467..f2f2011e6 100644 --- a/src/routes/components/resourceTypeahead/resourceInput.tsx +++ b/src/routes/components/resourceTypeahead/resourceInput.tsx @@ -64,6 +64,7 @@ const ResourceInput: React.FC = ({ onChange={onChange} onFocus={() => setIsOpen(true)} onKeyDown={handleOnTextInputKeyDown} + onBlur={onClear} placeholder={placeholder} /> {search && search.length && ( diff --git a/src/routes/explorer/explorerFilter.tsx b/src/routes/explorer/explorerFilter.tsx index d3bb98ac9..7800d9846 100644 --- a/src/routes/explorer/explorerFilter.tsx +++ b/src/routes/explorer/explorerFilter.tsx @@ -130,6 +130,7 @@ export class ExplorerFilterBase extends React.Component 0) { diff --git a/src/routes/explorer/explorerToolbar.tsx b/src/routes/explorer/explorerToolbar.tsx index a9805fed9..8d13df404 100644 --- a/src/routes/explorer/explorerToolbar.tsx +++ b/src/routes/explorer/explorerToolbar.tsx @@ -1,4 +1,3 @@ -import type { ToolbarChipGroup } from '@patternfly/react-core'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; @@ -33,7 +32,7 @@ interface ExplorerToolbarDispatchProps { } interface ExplorerToolbarState { - categoryOptions?: ToolbarChipGroup[]; + // TBD... } type ExplorerToolbarProps = ExplorerToolbarOwnProps & diff --git a/src/routes/explorer/explorerUtils.ts b/src/routes/explorer/explorerUtils.ts index 5cf3aae8c..7c00dafcd 100644 --- a/src/routes/explorer/explorerUtils.ts +++ b/src/routes/explorer/explorerUtils.ts @@ -56,8 +56,9 @@ export const baseQuery: Query = { export const groupByAwsOptions: { label: string; value: ComputedAwsReportItemsParams['idKey']; + resourceKey?: string; }[] = [ - { label: 'account', value: 'account' }, + { label: 'account', value: 'account', resourceKey: 'account_alias' }, { label: 'service', value: 'service' }, { label: 'region', value: 'region' }, ]; From dbd79f3bf983324e3c6bbb22ef930d0435de60c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 10:20:09 +0000 Subject: [PATCH 16/22] (chore): Bump the lint-dependencies group with 2 updates Bumps the lint-dependencies group with 2 updates: [eslint-plugin-formatjs](https://github.com/formatjs/formatjs) and [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc). Updates `eslint-plugin-formatjs` from 5.0.2 to 5.1.0 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Commits](https://github.com/formatjs/formatjs/compare/vue-intl@5.0.2...eslint-plugin-formatjs@5.1.0) Updates `eslint-plugin-jsdoc` from 50.3.1 to 50.4.0 - [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases) - [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc) - [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v50.3.1...v50.4.0) --- updated-dependencies: - dependency-name: eslint-plugin-formatjs dependency-type: direct:development update-type: version-update:semver-minor dependency-group: lint-dependencies - dependency-name: eslint-plugin-jsdoc dependency-type: direct:development update-type: version-update:semver-minor dependency-group: lint-dependencies ... Signed-off-by: dependabot[bot] --- package-lock.json | 196 ++++++++++++++++++++++++---------------------- package.json | 4 +- 2 files changed, 104 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ce208c8c..c88f92d2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,9 +65,9 @@ "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", "eslint": "^9.12.0", - "eslint-plugin-formatjs": "^5.0.2", + "eslint-plugin-formatjs": "^5.1.0", "eslint-plugin-jest-dom": "^5.4.0", - "eslint-plugin-jsdoc": "^50.3.1", + "eslint-plugin-jsdoc": "^50.4.0", "eslint-plugin-markdown": "^5.1.0", "eslint-plugin-patternfly-react": "^5.4.0", "eslint-plugin-prettier": "^5.2.1", @@ -658,9 +658,9 @@ "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==" }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", - "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", + "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", "dev": true, "dependencies": { "comment-parser": "1.4.1", @@ -1009,18 +1009,17 @@ } }, "node_modules/@formatjs/ts-transformer": { - "version": "3.13.15", - "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.15.tgz", - "integrity": "sha512-Fy+EHfzzkiPzvRGt6c0h/2sa0iasCV2sYMgWM4d6imCWAKzUO571bf+ZdJWnAf3LsXtvnMd68At0WDC7aAuBCg==", + "version": "3.13.16", + "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.16.tgz", + "integrity": "sha512-ZIV7KB2EQ5w9k7yrwSsdGdoOgqlXNd2sfG317pbJPHDgIo04sxoRzZPayCiNo7VWaRyqkVYUpME94rd54FDvuw==", "dev": true, - "license": "MIT", "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.9", + "@formatjs/icu-messageformat-parser": "2.7.10", "@types/json-stable-stringify": "^1.0.32", - "@types/node": "14 || 16 || 17", + "@types/node": "14 || 16 || 17 || 18", "chalk": "^4.0.0", "json-stable-stringify": "^1.0.1", - "tslib": "^2.4.0", + "tslib": "^2.7.0", "typescript": "5" }, "peerDependencies": { @@ -1033,50 +1032,69 @@ } }, "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/ecma402-abstract": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz", - "integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", + "integrity": "sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==", "dev": true, "dependencies": { - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" + "@formatjs/fast-memoize": "2.2.1", + "@formatjs/intl-localematcher": "0.5.5", + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/fast-memoize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", + "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", + "dev": true, + "dependencies": { + "tslib": "^2.7.0" } }, "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.9", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz", - "integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==", + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.10.tgz", + "integrity": "sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==", "dev": true, "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/icu-skeleton-parser": "1.8.3", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/icu-skeleton-parser": "1.8.4", + "tslib": "^2.7.0" } }, "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz", - "integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", + "integrity": "sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==", "dev": true, "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", + "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "dev": true, + "dependencies": { + "tslib": "^2.7.0" } }, "node_modules/@formatjs/ts-transformer/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "version": "18.19.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz", + "integrity": "sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw==", "dev": true, - "license": "MIT" + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@formatjs/ts-transformer/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1092,7 +1110,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1109,7 +1126,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1121,15 +1137,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@formatjs/ts-transformer/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -1139,7 +1153,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2354,12 +2367,6 @@ "react-dom": "^17 || ^18" } }, - "node_modules/@patternfly/react-charts/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, "node_modules/@patternfly/react-component-groups": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.4.0.tgz", @@ -2395,12 +2402,6 @@ "react-dom": "^17 || ^18" } }, - "node_modules/@patternfly/react-core/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, "node_modules/@patternfly/react-icons": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.4.0.tgz", @@ -2435,12 +2436,6 @@ "react-dom": "^17 || ^18" } }, - "node_modules/@patternfly/react-table/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, "node_modules/@patternfly/react-tokens": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.4.0.tgz", @@ -4062,8 +4057,7 @@ "version": "1.0.36", "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.36.tgz", "integrity": "sha512-b7bq23s4fgBB76n34m2b3RBf6M369B0Z9uRR8aHTMd8kZISRkmDEpPD8hhpYvDFzr3bJCPES96cm3Q6qRNDbQw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -8422,21 +8416,20 @@ } }, "node_modules/eslint-plugin-formatjs": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.0.2.tgz", - "integrity": "sha512-fDKKgcF1OsvA9NBy+6MobzOMTVn6PTCfAeavITnZrL+bzhpWO7Mc3pGkQOYqa+IjTgAnO4xHUz4yvSuVzqLKhg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.1.0.tgz", + "integrity": "sha512-IN3/MKq5XumXqgta7OnvixcEoQCyxLAsNVwzsOQIqQqughcHwkvv7JHiIurt+7V6IjBYDyfTiH7opZggR+t+7w==", "dev": true, - "license": "MIT", "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.9", - "@formatjs/ts-transformer": "3.13.15", + "@formatjs/icu-messageformat-parser": "2.7.10", + "@formatjs/ts-transformer": "3.13.16", "@types/eslint": "9", "@types/picomatch": "^2.3.0", "@typescript-eslint/utils": "8.5.0", "emoji-regex": "^10.2.1", "magic-string": "^0.30.0", "picomatch": "^2.3.1", - "tslib": "2.6.2", + "tslib": "^2.7.0", "typescript": "5", "unicode-emoji-utils": "^1.2.0" }, @@ -8445,35 +8438,53 @@ } }, "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/ecma402-abstract": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz", - "integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", + "integrity": "sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==", "dev": true, "dependencies": { - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" + "@formatjs/fast-memoize": "2.2.1", + "@formatjs/intl-localematcher": "0.5.5", + "tslib": "^2.7.0" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/fast-memoize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", + "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", + "dev": true, + "dependencies": { + "tslib": "^2.7.0" } }, "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.9", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz", - "integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==", + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.10.tgz", + "integrity": "sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==", "dev": true, "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/icu-skeleton-parser": "1.8.3", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/icu-skeleton-parser": "1.8.4", + "tslib": "^2.7.0" } }, "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz", - "integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", + "integrity": "sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==", "dev": true, "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "tslib": "^2.7.0" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", + "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "dev": true, + "dependencies": { + "tslib": "^2.7.0" } }, "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/scope-manager": { @@ -8866,13 +8877,12 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.3.1.tgz", - "integrity": "sha512-SY9oUuTMr6aWoJggUS40LtMjsRzJPB5ZT7F432xZIHK3EfHF+8i48GbUBpwanrtlL9l1gILNTHK9o8gEhYLcKA==", + "version": "50.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.4.0.tgz", + "integrity": "sha512-UT7mfB4x9XgBTCrg8VxsOnZ+uglKG09RkAVfaIRHSF/YdiXx32ix9B0IZ8aiGAvloGNkBMSi19mTQUk3aEAMQA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.48.0", + "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.6", @@ -14627,7 +14637,6 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "isarray": "^2.0.5", @@ -14690,7 +14699,6 @@ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "dev": true, - "license": "Public Domain", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -19571,9 +19579,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index 0afaaa241..680f8f01a 100644 --- a/package.json +++ b/package.json @@ -107,9 +107,9 @@ "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", "eslint": "^9.12.0", - "eslint-plugin-formatjs": "^5.0.2", + "eslint-plugin-formatjs": "^5.1.0", "eslint-plugin-jest-dom": "^5.4.0", - "eslint-plugin-jsdoc": "^50.3.1", + "eslint-plugin-jsdoc": "^50.4.0", "eslint-plugin-markdown": "^5.1.0", "eslint-plugin-patternfly-react": "^5.4.0", "eslint-plugin-prettier": "^5.2.1", From 18c6bb76785c3bece7f62ffaca63190668b88d3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 10:22:23 +0000 Subject: [PATCH 17/22] (chore): Bump the ci-dependencies group with 5 updates Bumps the ci-dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [react-intl](https://github.com/formatjs/formatjs) | `6.7.2` | `6.8.0` | | [@formatjs/cli](https://github.com/formatjs/formatjs) | `6.2.14` | `6.2.15` | | [@formatjs/ecma402-abstract](https://github.com/formatjs/formatjs) | `2.1.0` | `2.2.0` | | [@formatjs/icu-messageformat-parser](https://github.com/formatjs/formatjs/tree/HEAD/packages/icu-messageformat-parser) | `2.7.9` | `2.7.10` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.3.0` | `18.3.1` | Updates `react-intl` from 6.7.2 to 6.8.0 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Commits](https://github.com/formatjs/formatjs/commits/react-intl@6.8.0) Updates `@formatjs/cli` from 6.2.14 to 6.2.15 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Commits](https://github.com/formatjs/formatjs/commits/@formatjs/cli@6.2.15) Updates `@formatjs/ecma402-abstract` from 2.1.0 to 2.2.0 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Commits](https://github.com/formatjs/formatjs/compare/v2.1.0...@formatjs/ecma402-abstract@2.2.0) Updates `@formatjs/icu-messageformat-parser` from 2.7.9 to 2.7.10 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Changelog](https://github.com/formatjs/formatjs/blob/main/packages/icu-messageformat-parser/CHANGELOG.md) - [Commits](https://github.com/formatjs/formatjs/commits/@formatjs/icu-messageformat-parser@2.7.10/packages/icu-messageformat-parser) Updates `@types/react-dom` from 18.3.0 to 18.3.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) --- updated-dependencies: - dependency-name: react-intl dependency-type: direct:production update-type: version-update:semver-minor dependency-group: ci-dependencies - dependency-name: "@formatjs/cli" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ci-dependencies - dependency-name: "@formatjs/ecma402-abstract" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: ci-dependencies - dependency-name: "@formatjs/icu-messageformat-parser" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ci-dependencies - dependency-name: "@types/react-dom" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: ci-dependencies ... Signed-off-by: dependabot[bot] --- package-lock.json | 235 +++++++++++++++++++++++++++++++--------------- package.json | 6 +- 2 files changed, 161 insertions(+), 80 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ce208c8c..018639287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "qs": "^6.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-intl": "6.7.2", + "react-intl": "6.8.0", "react-redux": "^9.1.2", "react-router-dom": "^6.26.2", "redux": "^5.0.1", @@ -43,7 +43,7 @@ "@eslint/compat": "^1.2.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@formatjs/cli": "^6.2.14", + "@formatjs/cli": "^6.2.15", "@formatjs/ecma402-abstract": "^2.1.0", "@formatjs/icu-messageformat-parser": "^2.7.9", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4", @@ -57,7 +57,7 @@ "@types/jest": "^29.5.13", "@types/qs": "^6.9.16", "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.0", + "@types/react-dom": "^18.3.1", "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.8.1", @@ -866,11 +866,10 @@ } }, "node_modules/@formatjs/cli": { - "version": "6.2.14", - "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.14.tgz", - "integrity": "sha512-1eSJAUQLgL9dgsrXJ1PzMAguXOPpJa1dJmMkF8GZo0jnKkavq2cdljJt2vXDeSvqWmKwqY9h4/bELr9dTOoHMw==", + "version": "6.2.15", + "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.15.tgz", + "integrity": "sha512-s31YblAseSVqgFvY2EoIZaaEycifR/CadvMj1WcNvFvHK+2Xn02OuSX1jiKM/Nx29hX2x8k0raFJ6PtnXZgjtQ==", "dev": true, - "license": "MIT", "bin": { "formatjs": "bin/formatjs" }, @@ -915,59 +914,87 @@ } }, "node_modules/@formatjs/ecma402-abstract": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.1.0.tgz", - "integrity": "sha512-SE2V2PE03K9U/YQZ3nxEOysRkQ/CfSwLHR789Uk9N0PTiWT6I+17UTDI97zYEwC1mbnjefqmtjbL8nunjPwGjw==", - "license": "MIT", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", + "integrity": "sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==", "dependencies": { - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" + "@formatjs/fast-memoize": "2.2.1", + "@formatjs/intl-localematcher": "0.5.5", + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/fast-memoize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", + "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", + "dependencies": { + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", + "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "dependencies": { + "tslib": "^2.7.0" } }, + "node_modules/@formatjs/ecma402-abstract/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@formatjs/fast-memoize": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.9", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.9.tgz", - "integrity": "sha512-9Z5buDRMsTbplXknvRlDmnpWhZrayNVcVvkH0+SSz8Ll4XD/7Tcn8m1IjxM3iBJSwQbxwxb7/g0Fkx3d4j2osw==", - "license": "MIT", + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.10.tgz", + "integrity": "sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==", "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/icu-skeleton-parser": "1.8.3", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/icu-skeleton-parser": "1.8.4", + "tslib": "^2.7.0" } }, + "node_modules/@formatjs/icu-messageformat-parser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.3.tgz", - "integrity": "sha512-TsKAP013ayZFbWWR2KWy+f9QVZh0yDFTPK3yE4OqU2gnzafvmKTodRtJLVpfZmpXWJ5y7BWD1AsyT14mcbLzig==", - "license": "MIT", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", + "integrity": "sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==", "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "tslib": "^2.7.0" } }, + "node_modules/@formatjs/icu-skeleton-parser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@formatjs/intl": { - "version": "2.10.7", - "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.7.tgz", - "integrity": "sha512-26rNxo2nwQbbsVkV54ngml9XIA7bBzfQmELG6FFFF8eKzqzFrLKZzF8JBoBpPHgML4HKEUbGCQaBaARpKCN/sw==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.9", - "@formatjs/intl-displaynames": "6.6.9", - "@formatjs/intl-listformat": "7.5.8", - "intl-messageformat": "10.6.0", - "tslib": "^2.4.0" + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.8.tgz", + "integrity": "sha512-eY8r8RMmrRTTkLdbNBOZLFGXN3OnrEmInaNt8s4msIVfo+xuLqAqvB3W1jevj0I9QjU6ueIP7tEk+1rj6Xbv5A==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/fast-memoize": "2.2.1", + "@formatjs/icu-messageformat-parser": "2.7.10", + "@formatjs/intl-displaynames": "6.6.10", + "@formatjs/intl-listformat": "7.5.9", + "intl-messageformat": "10.7.0", + "tslib": "^2.7.0" }, "peerDependencies": { "typescript": "^4.7 || 5" @@ -979,35 +1006,73 @@ } }, "node_modules/@formatjs/intl-displaynames": { - "version": "6.6.9", - "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.9.tgz", - "integrity": "sha512-2hmS+YJwiXB1deNYXO2/pY7Zv4QUrZHghZxkcnWxBLEODk990h9cNbkjNg/u/RaDeCRkKVrZ3ERTdBcgkTvn2Q==", - "license": "MIT", + "version": "6.6.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.10.tgz", + "integrity": "sha512-tUz5qT61og1WwMM0K1/p46J69gLl1YJbty8xhtbigDA9LhbBmW2ShDg4ld+aqJTwCq4WK3Sj0VlFCKvFYeY3rQ==", "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/intl-localematcher": "0.5.5", + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", + "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "dependencies": { + "tslib": "^2.7.0" } }, + "node_modules/@formatjs/intl-displaynames/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@formatjs/intl-listformat": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.8.tgz", - "integrity": "sha512-WzMiw6nA2uP0ZqbbuPs7tQ+gmYRTbE20lwur4QcKp5K5cgPhkCzRAhovkDFLhrc885c3p7Wjigx8kyg0hypmZw==", - "license": "MIT", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.9.tgz", + "integrity": "sha512-HqtGkxUh2Uz0oGVTxHAvPZ3EGxc8+ol5+Bx7S9xB97d4PEJJd9oOgHrerIghHA0gtIjsNKBFUae3P0My+F6YUA==", "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/intl-localematcher": "0.5.5", + "tslib": "^2.7.0" } }, + "node_modules/@formatjs/intl-listformat/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", + "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "dependencies": { + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/intl-listformat/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@formatjs/intl-localematcher": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dev": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@formatjs/intl/node_modules/@formatjs/fast-memoize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", + "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", + "dependencies": { + "tslib": "^2.7.0" + } + }, + "node_modules/@formatjs/intl/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@formatjs/ts-transformer": { "version": "3.13.15", "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.15.tgz", @@ -4153,9 +4218,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "dev": true, "dependencies": { "@types/react": "*" @@ -11601,17 +11666,29 @@ } }, "node_modules/intl-messageformat": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.6.0.tgz", - "integrity": "sha512-AYKl/DY1nl75pJU8EK681JOVL40uQTNJe3yEMXKfydDFoz+5hNrM/PqjchueSMKGKCZKBVgeexqZwy3uC2B36Q==", - "license": "BSD-3-Clause", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.0.tgz", + "integrity": "sha512-2P06M9jFTqJnEQzE072VGPjbAx6ZG1YysgopAwc8ui0ajSjtwX1MeQ6bXFXIzKcNENJTizKkcJIcZ0zlpl1zSg==", "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.9", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/fast-memoize": "2.2.1", + "@formatjs/icu-messageformat-parser": "2.7.10", + "tslib": "^2.7.0" + } + }, + "node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", + "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", + "dependencies": { + "tslib": "^2.7.0" } }, + "node_modules/intl-messageformat/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -17162,21 +17239,20 @@ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, "node_modules/react-intl": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.7.2.tgz", - "integrity": "sha512-v/lvAORTE70welhzqoIi1YI1yHvGE4/QX4W3JYNZoqRxH8ab8Q/Ed4Zem/ZVPZJN4byQ52U+2GESLy0zvY6IBw==", - "license": "BSD-3-Clause", - "dependencies": { - "@formatjs/ecma402-abstract": "2.1.0", - "@formatjs/icu-messageformat-parser": "2.7.9", - "@formatjs/intl": "2.10.7", - "@formatjs/intl-displaynames": "6.6.9", - "@formatjs/intl-listformat": "7.5.8", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.8.0.tgz", + "integrity": "sha512-rx/UcAtlmrYWaPfrgAIDu7VsJoyUPFPdftIFUnOSOj/LHR6ACTU3tunfk69c4LGygQ592YxilBXDWH6rKlTu6Q==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.10", + "@formatjs/intl": "2.10.8", + "@formatjs/intl-displaynames": "6.6.10", + "@formatjs/intl-listformat": "7.5.9", "@types/hoist-non-react-statics": "^3.3.1", - "@types/react": "16 || 17 || 18", + "@types/react": "^18.3.11", "hoist-non-react-statics": "^3.3.2", - "intl-messageformat": "10.6.0", - "tslib": "^2.4.0" + "intl-messageformat": "10.7.0", + "tslib": "^2.7.0" }, "peerDependencies": { "react": "^16.6.0 || 17 || 18", @@ -17188,6 +17264,11 @@ } } }, + "node_modules/react-intl/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 0afaaa241..77a833e01 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "qs": "^6.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-intl": "6.7.2", + "react-intl": "6.8.0", "react-redux": "^9.1.2", "react-router-dom": "^6.26.2", "redux": "^5.0.1", @@ -85,7 +85,7 @@ "@eslint/compat": "^1.2.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@formatjs/cli": "^6.2.14", + "@formatjs/cli": "^6.2.15", "@formatjs/ecma402-abstract": "^2.1.0", "@formatjs/icu-messageformat-parser": "^2.7.9", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4", @@ -99,7 +99,7 @@ "@types/jest": "^29.5.13", "@types/qs": "^6.9.16", "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.0", + "@types/react-dom": "^18.3.1", "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.8.1", From f529a37953cf2cad82028114d04823dbb83c7d94 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Mon, 14 Oct 2024 18:12:31 -0400 Subject: [PATCH 18/22] Fix & symbol --- src/api/resources/awsOcpResource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/resources/awsOcpResource.ts b/src/api/resources/awsOcpResource.ts index 63344a835..da004220d 100644 --- a/src/api/resources/awsOcpResource.ts +++ b/src/api/resources/awsOcpResource.ts @@ -15,6 +15,6 @@ export const ResourceTypePaths: Partial> = { export function runResource(resourceType: ResourceType, query: string) { const path = ResourceTypePaths[resourceType]; - const queryString = query ? `?openshift=true;${query}` : '?openshift=true'; + const queryString = query ? `?openshift=true&${query}` : '?openshift=true'; return axiosInstance.get(`${path}${queryString}`); } From 84e1de9cb48062f4b335a130c8dddbe383a340b4 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Mon, 14 Oct 2024 22:02:10 -0400 Subject: [PATCH 19/22] Dependency updates --- package-lock.json | 662 ++++++---------------------------------------- package.json | 23 +- test/testEnv.ts | 4 - 3 files changed, 85 insertions(+), 604 deletions(-) diff --git a/package-lock.json b/package-lock.json index bff88ef3b..c3473b480 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@redhat-cloud-services/frontend-components-translations": "^3.2.8", "@redhat-cloud-services/frontend-components-utilities": "^4.0.17", "@redhat-cloud-services/rbac-client": "^2.2.5", - "@reduxjs/toolkit": "^2.2.8", + "@reduxjs/toolkit": "^2.3.0", "@unleash/proxy-client-react": "^4.3.1", "axios": "^1.7.7", "date-fns": "^4.1.0", @@ -33,7 +33,7 @@ "react-dom": "^18.3.1", "react-intl": "6.8.0", "react-redux": "^9.1.2", - "react-router-dom": "^6.26.2", + "react-router-dom": "^6.27.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", @@ -43,9 +43,9 @@ "@eslint/compat": "^1.2.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@formatjs/cli": "^6.2.15", - "@formatjs/ecma402-abstract": "^2.1.0", - "@formatjs/icu-messageformat-parser": "^2.7.9", + "@formatjs/ecma402-abstract": "^2.2.0", + "@formatjs/fast-memoize": "^2.2.1", + "@formatjs/intl-localematcher": "^0.5.5", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4", "@redhat-cloud-services/frontend-components-config": "^6.3.1", "@redhat-cloud-services/tsc-transform-imports": "^1.0.16", @@ -60,14 +60,13 @@ "@types/react-dom": "^18.3.1", "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^8.8.1", - "@typescript-eslint/parser": "^8.8.1", - "aphrodite": "^2.4.0", + "@typescript-eslint/eslint-plugin": "^8.9.0", + "@typescript-eslint/parser": "^8.9.0", "copy-webpack-plugin": "^12.0.2", "eslint": "^9.12.0", "eslint-plugin-formatjs": "^5.1.0", "eslint-plugin-jest-dom": "^5.4.0", - "eslint-plugin-jsdoc": "^50.4.0", + "eslint-plugin-jsdoc": "^50.4.1", "eslint-plugin-markdown": "^5.1.0", "eslint-plugin-patternfly-react": "^5.4.0", "eslint-plugin-prettier": "^5.2.1", @@ -82,15 +81,13 @@ "jest-environment-jsdom": "^29.7.0", "jest-mock-axios": "^4.7.3", "jest-transform-stub": "^2.0.0", - "jws": "^4.0.0", "npm-run-all": "^4.1.5", "prettier": "^3.3.3", "rimraf": "^6.0.1", "swc_mut_cjs_exports": "^0.99.0", "ts-jest": "^29.2.5", "ts-patch": "^3.2.1", - "typescript": "^5.6.3", - "webpack-bundle-analyzer": "^4.10.2" + "typescript": "^5.6.3" }, "engines": { "node": ">=20.15.0", @@ -865,54 +862,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@formatjs/cli": { - "version": "6.2.15", - "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.15.tgz", - "integrity": "sha512-s31YblAseSVqgFvY2EoIZaaEycifR/CadvMj1WcNvFvHK+2Xn02OuSX1jiKM/Nx29hX2x8k0raFJ6PtnXZgjtQ==", - "dev": true, - "bin": { - "formatjs": "bin/formatjs" - }, - "engines": { - "node": ">= 16" - }, - "peerDependencies": { - "@glimmer/env": "^0.1.7", - "@glimmer/reference": "^0.91.1 || ^0.92.0", - "@glimmer/syntax": "^0.92.0", - "@glimmer/validator": "^0.92.0", - "@vue/compiler-core": "^3.4.0", - "content-tag": "^2.0.1", - "ember-template-recast": "^6.1.4", - "vue": "^3.4.0" - }, - "peerDependenciesMeta": { - "@glimmer/env": { - "optional": true - }, - "@glimmer/reference": { - "optional": true - }, - "@glimmer/syntax": { - "optional": true - }, - "@glimmer/validator": { - "optional": true - }, - "@vue/compiler-core": { - "optional": true - }, - "content-tag": { - "optional": true - }, - "ember-template-recast": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", @@ -923,35 +872,13 @@ "tslib": "^2.7.0" } }, - "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/fast-memoize": { + "node_modules/@formatjs/fast-memoize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", - "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/ecma402-abstract/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", - "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", - "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.7.0" } }, "node_modules/@formatjs/icu-messageformat-parser": { @@ -964,11 +891,6 @@ "tslib": "^2.7.0" } }, - "node_modules/@formatjs/icu-messageformat-parser/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, "node_modules/@formatjs/icu-skeleton-parser": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", @@ -978,11 +900,6 @@ "tslib": "^2.7.0" } }, - "node_modules/@formatjs/icu-skeleton-parser/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, "node_modules/@formatjs/intl": { "version": "2.10.8", "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.8.tgz", @@ -1015,19 +932,6 @@ "tslib": "^2.7.0" } }, - "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", - "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/intl-displaynames/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, "node_modules/@formatjs/intl-listformat": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.9.tgz", @@ -1038,41 +942,15 @@ "tslib": "^2.7.0" } }, - "node_modules/@formatjs/intl-listformat/node_modules/@formatjs/intl-localematcher": { + "node_modules/@formatjs/intl-localematcher": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", + "license": "MIT", "dependencies": { "tslib": "^2.7.0" } }, - "node_modules/@formatjs/intl-listformat/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", - "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", - "dev": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@formatjs/intl/node_modules/@formatjs/fast-memoize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", - "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/intl/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, "node_modules/@formatjs/ts-transformer": { "version": "3.13.16", "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.16.tgz", @@ -1096,56 +974,6 @@ } } }, - "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/ecma402-abstract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", - "integrity": "sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==", - "dev": true, - "dependencies": { - "@formatjs/fast-memoize": "2.2.1", - "@formatjs/intl-localematcher": "0.5.5", - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/fast-memoize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", - "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", - "dev": true, - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.10.tgz", - "integrity": "sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==", - "dev": true, - "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "@formatjs/icu-skeleton-parser": "1.8.4", - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", - "integrity": "sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==", - "dev": true, - "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "tslib": "^2.7.0" - } - }, - "node_modules/@formatjs/ts-transformer/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", - "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", - "dev": true, - "dependencies": { - "tslib": "^2.7.0" - } - }, "node_modules/@formatjs/ts-transformer/node_modules/@types/node": { "version": "18.19.55", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz", @@ -2635,12 +2463,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.25", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", - "dev": true - }, "node_modules/@redhat-cloud-services/eslint-config-redhat-cloud-services": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/eslint-config-redhat-cloud-services/-/eslint-config-redhat-cloud-services-2.0.4.tgz", @@ -3058,9 +2880,9 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.8.tgz", - "integrity": "sha512-eK/ieXftPRQfaBSmzsamXEyDwkntMTY0e9SG5ETsEOv5JIPKhu3mj992t6B8FJjlnSrZBAAqdT8oMkPe4j+P9g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz", + "integrity": "sha512-WC7Yd6cNGfHx8zf+iu+Q1UPTfEcXhQ+ATi7CV1hlrSAaQBdlPzg7Ww/wJHNQem7qG9rxmWoFCDCPubSvFObGzA==", "license": "MIT", "dependencies": { "immer": "^10.0.3", @@ -3082,9 +2904,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", - "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -4358,17 +4180,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", - "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.9.0.tgz", + "integrity": "sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/type-utils": "8.8.1", - "@typescript-eslint/utils": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/type-utils": "8.9.0", + "@typescript-eslint/utils": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4392,15 +4214,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", - "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.9.0.tgz", + "integrity": "sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/typescript-estree": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", "debug": "^4.3.4" }, "engines": { @@ -4419,114 +4242,15 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", - "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", - "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", - "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", - "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.8.1", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", - "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.9.0.tgz", + "integrity": "sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1" + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4537,14 +4261,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", - "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.9.0.tgz", + "integrity": "sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/typescript-estree": "8.9.0", + "@typescript-eslint/utils": "8.9.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -4562,9 +4286,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", - "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.9.0.tgz", + "integrity": "sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==", "dev": true, "license": "MIT", "engines": { @@ -4576,14 +4300,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", - "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.9.0.tgz", + "integrity": "sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/visitor-keys": "8.9.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4618,16 +4342,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", - "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.9.0.tgz", + "integrity": "sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1" + "@typescript-eslint/scope-manager": "8.9.0", + "@typescript-eslint/types": "8.9.0", + "@typescript-eslint/typescript-estree": "8.9.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4641,13 +4365,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", - "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.9.0.tgz", + "integrity": "sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/types": "8.9.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -5106,17 +4830,6 @@ "node": ">= 8" } }, - "node_modules/aphrodite": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/aphrodite/-/aphrodite-2.4.0.tgz", - "integrity": "sha512-1rVRlLco+j1YAT5aKEE8Wuw5zWV+tI41/quEheJAG0vNaGHE64iJ/a2SiVMz8Uc80VdP2/Hjlfd2bPJOWsqJuQ==", - "dev": true, - "dependencies": { - "asap": "^2.0.3", - "inline-style-prefixer": "^5.1.0", - "string-hash": "^1.1.3" - } - }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -5335,12 +5048,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", @@ -6988,16 +6695,6 @@ "node": ">= 8" } }, - "node_modules/css-in-js-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", - "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", - "dev": true, - "dependencies": { - "hyphenate-style-name": "^1.0.2", - "isobject": "^3.0.1" - } - }, "node_modules/css-jss": { "version": "10.10.0", "resolved": "https://registry.npmjs.org/css-jss/-/css-jss-10.10.0.tgz", @@ -7361,12 +7058,6 @@ "url": "https://github.com/sponsors/kossnocorp" } }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true - }, "node_modules/debounce-promise": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", @@ -7823,12 +7514,6 @@ "tslib": "^2.0.3" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -8502,56 +8187,6 @@ "eslint": "9" } }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/ecma402-abstract": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.0.tgz", - "integrity": "sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==", - "dev": true, - "dependencies": { - "@formatjs/fast-memoize": "2.2.1", - "@formatjs/intl-localematcher": "0.5.5", - "tslib": "^2.7.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/fast-memoize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", - "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", - "dev": true, - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.10.tgz", - "integrity": "sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==", - "dev": true, - "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "@formatjs/icu-skeleton-parser": "1.8.4", - "tslib": "^2.7.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.4.tgz", - "integrity": "sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==", - "dev": true, - "dependencies": { - "@formatjs/ecma402-abstract": "2.2.0", - "tslib": "^2.7.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.5.tgz", - "integrity": "sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==", - "dev": true, - "dependencies": { - "tslib": "^2.7.0" - } - }, "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/scope-manager": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", @@ -8942,10 +8577,11 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.4.0.tgz", - "integrity": "sha512-UT7mfB4x9XgBTCrg8VxsOnZ+uglKG09RkAVfaIRHSF/YdiXx32ix9B0IZ8aiGAvloGNkBMSi19mTQUk3aEAMQA==", + "version": "50.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.4.1.tgz", + "integrity": "sha512-OXIq+JJQPCLAKL473/esioFOwbXyRE5MAQ4HbZjcp3e+K3zdxt2uDpGs3FR+WezUXNStzEtTfgx15T+JFrVwBA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.49.0", "are-docs-informative": "^0.0.2", @@ -10861,21 +10497,6 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -11538,15 +11159,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/inline-style-prefixer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-5.1.2.tgz", - "integrity": "sha512-PYUF+94gDfhy+LsQxM0g3d6Hge4l1pAqOSOiZuHWzMvQEGsbRQ/ck2WioLqrY2ZkHyPgVUXxn+hrkF7D6QUGbA==", - "dev": true, - "dependencies": { - "css-in-js-utils": "^2.0.0" - } - }, "node_modules/inquirer": { "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", @@ -11686,19 +11298,6 @@ "tslib": "^2.7.0" } }, - "node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.1.tgz", - "integrity": "sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==", - "dependencies": { - "tslib": "^2.7.0" - } - }, - "node_modules/intl-messageformat/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -15671,15 +15270,6 @@ "node": ">=10" } }, - "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -17272,11 +16862,6 @@ } } }, - "node_modules/react-intl/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -17336,12 +16921,12 @@ } }, "node_modules/react-router": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", - "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.19.2" + "@remix-run/router": "1.20.0" }, "engines": { "node": ">=14.0.0" @@ -17351,13 +16936,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz", - "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.19.2", - "react-router": "6.26.2" + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" }, "engines": { "node": ">=14.0.0" @@ -18378,20 +17963,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dev": true, - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -18650,12 +18221,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==", - "dev": true - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -19239,15 +18804,6 @@ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -20677,74 +20233,6 @@ } } }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "0.5.7", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-cli": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", diff --git a/package.json b/package.json index 49cafbad5..7f6d75470 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@redhat-cloud-services/frontend-components-translations": "^3.2.8", "@redhat-cloud-services/frontend-components-utilities": "^4.0.17", "@redhat-cloud-services/rbac-client": "^2.2.5", - "@reduxjs/toolkit": "^2.2.8", + "@reduxjs/toolkit": "^2.3.0", "@unleash/proxy-client-react": "^4.3.1", "axios": "^1.7.7", "date-fns": "^4.1.0", @@ -75,7 +75,7 @@ "react-dom": "^18.3.1", "react-intl": "6.8.0", "react-redux": "^9.1.2", - "react-router-dom": "^6.26.2", + "react-router-dom": "^6.27.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", @@ -85,9 +85,9 @@ "@eslint/compat": "^1.2.0", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", - "@formatjs/cli": "^6.2.15", - "@formatjs/ecma402-abstract": "^2.1.0", - "@formatjs/icu-messageformat-parser": "^2.7.9", + "@formatjs/ecma402-abstract": "^2.2.0", + "@formatjs/fast-memoize": "^2.2.1", + "@formatjs/intl-localematcher": "^0.5.5", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.4", "@redhat-cloud-services/frontend-components-config": "^6.3.1", "@redhat-cloud-services/tsc-transform-imports": "^1.0.16", @@ -102,14 +102,13 @@ "@types/react-dom": "^18.3.1", "@types/react-redux": "^7.1.34", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^8.8.1", - "@typescript-eslint/parser": "^8.8.1", - "aphrodite": "^2.4.0", + "@typescript-eslint/eslint-plugin": "^8.9.0", + "@typescript-eslint/parser": "^8.9.0", "copy-webpack-plugin": "^12.0.2", "eslint": "^9.12.0", "eslint-plugin-formatjs": "^5.1.0", "eslint-plugin-jest-dom": "^5.4.0", - "eslint-plugin-jsdoc": "^50.4.0", + "eslint-plugin-jsdoc": "^50.4.1", "eslint-plugin-markdown": "^5.1.0", "eslint-plugin-patternfly-react": "^5.4.0", "eslint-plugin-prettier": "^5.2.1", @@ -124,18 +123,16 @@ "jest-environment-jsdom": "^29.7.0", "jest-mock-axios": "^4.7.3", "jest-transform-stub": "^2.0.0", - "jws": "^4.0.0", "npm-run-all": "^4.1.5", "prettier": "^3.3.3", "rimraf": "^6.0.1", "swc_mut_cjs_exports": "^0.99.0", "ts-jest": "^29.2.5", "ts-patch": "^3.2.1", - "typescript": "^5.6.3", - "webpack-bundle-analyzer": "^4.10.2" + "typescript": "^5.6.3" }, "overrides": { - "@typescript-eslint/eslint-plugin": "^8.8.1", + "@typescript-eslint/eslint-plugin": "^8.9.0", "eslint": "^9.12.0", "redux": "^5.0.1" }, diff --git a/test/testEnv.ts b/test/testEnv.ts index 4ddc8f58d..5c1d8e485 100644 --- a/test/testEnv.ts +++ b/test/testEnv.ts @@ -1,7 +1,3 @@ -import { StyleSheetTestUtils } from 'aphrodite'; - -StyleSheetTestUtils.suppressStyleInjection(); - export interface Global { insights: any; } From a471c089482092f1ec39efb6b4fc7fda5a41df97 Mon Sep 17 00:00:00 2001 From: "red-hat-konflux[bot]" <126015336+red-hat-konflux[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:08:00 +0000 Subject: [PATCH 20/22] chore(deps): update konflux references Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com> --- .tekton/koku-frontend-pull-request.yaml | 10 +++++----- .tekton/koku-frontend-push.yaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.tekton/koku-frontend-pull-request.yaml b/.tekton/koku-frontend-pull-request.yaml index 0d1164f6e..b55962e88 100644 --- a/.tekton/koku-frontend-pull-request.yaml +++ b/.tekton/koku-frontend-pull-request.yaml @@ -299,7 +299,7 @@ spec: - name: name value: buildah - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:e107cfdf4ee68741ad366b2768cd33e2d5f99569b639f95f50df8b9835c2d144 + value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:2d6e09f356059ccfd8aada165d5b020e1eb025aeac4717a1ea2de239bed2a0d7 - name: kind value: task resolver: bundles @@ -402,7 +402,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:37b9187c1d5f6672bbc9c61d88fc71a3ee688076cb16edef42d1ff92a59027fb + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:28fee4bf5da87f2388c973d9336086749cad8436003f9a514e22ac99735e056b - name: kind value: task resolver: bundles @@ -444,7 +444,7 @@ spec: - name: name value: sast-snyk-check - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:69ae591831f0f96d31c85d360273c1ce436ae1dbbfa3d0b22a083cb228c9e82c + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:c1ea706405f9ae146e31baef4abfea49b1e855a75bfc44c33eb0eb29516831b3 - name: kind value: task resolver: bundles @@ -469,7 +469,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:5ac9b24cff7cfb391bc54cd5135536892090354862327d1028fa08872d759c03 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:1e29eebe916b81b7100138d62db0e03e22d03657274d37041c59cbaca5fdbf7d - name: kind value: task resolver: bundles @@ -510,7 +510,7 @@ spec: - name: name value: push-dockerfile - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:0d2b6d31dc8bc02c5493d7d28a163bb6c867be5f86c3a82388b0d5c69e18d352 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:674e70f7d724aaf1dd631ba9be2998ab0305fb3e0d9ec361351cc5e57bcdd3ec - name: kind value: task resolver: bundles diff --git a/.tekton/koku-frontend-push.yaml b/.tekton/koku-frontend-push.yaml index f37a7be7c..84d07898d 100644 --- a/.tekton/koku-frontend-push.yaml +++ b/.tekton/koku-frontend-push.yaml @@ -296,7 +296,7 @@ spec: - name: name value: buildah - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:e107cfdf4ee68741ad366b2768cd33e2d5f99569b639f95f50df8b9835c2d144 + value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:2d6e09f356059ccfd8aada165d5b020e1eb025aeac4717a1ea2de239bed2a0d7 - name: kind value: task resolver: bundles @@ -399,7 +399,7 @@ spec: - name: name value: clair-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:37b9187c1d5f6672bbc9c61d88fc71a3ee688076cb16edef42d1ff92a59027fb + value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:28fee4bf5da87f2388c973d9336086749cad8436003f9a514e22ac99735e056b - name: kind value: task resolver: bundles @@ -441,7 +441,7 @@ spec: - name: name value: sast-snyk-check - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:69ae591831f0f96d31c85d360273c1ce436ae1dbbfa3d0b22a083cb228c9e82c + value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:c1ea706405f9ae146e31baef4abfea49b1e855a75bfc44c33eb0eb29516831b3 - name: kind value: task resolver: bundles @@ -466,7 +466,7 @@ spec: - name: name value: clamav-scan - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:5ac9b24cff7cfb391bc54cd5135536892090354862327d1028fa08872d759c03 + value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:1e29eebe916b81b7100138d62db0e03e22d03657274d37041c59cbaca5fdbf7d - name: kind value: task resolver: bundles @@ -507,7 +507,7 @@ spec: - name: name value: push-dockerfile - name: bundle - value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:0d2b6d31dc8bc02c5493d7d28a163bb6c867be5f86c3a82388b0d5c69e18d352 + value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:674e70f7d724aaf1dd631ba9be2998ab0305fb3e0d9ec361351cc5e57bcdd3ec - name: kind value: task resolver: bundles From 19b79bd5afc6eadbc5a9d0cbad2dee4afc68d214 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Tue, 15 Oct 2024 10:30:44 -0400 Subject: [PATCH 21/22] Remove onBlur --- src/routes/components/resourceTypeahead/resourceInput.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/components/resourceTypeahead/resourceInput.tsx b/src/routes/components/resourceTypeahead/resourceInput.tsx index f2f2011e6..b7d22a467 100644 --- a/src/routes/components/resourceTypeahead/resourceInput.tsx +++ b/src/routes/components/resourceTypeahead/resourceInput.tsx @@ -64,7 +64,6 @@ const ResourceInput: React.FC = ({ onChange={onChange} onFocus={() => setIsOpen(true)} onKeyDown={handleOnTextInputKeyDown} - onBlur={onClear} placeholder={placeholder} /> {search && search.length && ( From 672fd98668e8f616876124ad800ec109b6037fd7 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Tue, 15 Oct 2024 10:25:09 -0400 Subject: [PATCH 22/22] Add integration status to details pages https://issues.redhat.com/browse/COST-5386 --- locales/data.json | 30 +++- locales/translations.json | 5 +- src/locales/messages.ts | 25 ++- .../components/page/noData/noData.styles.ts | 9 ++ .../components/page/noData/noDataState.tsx | 5 +- src/routes/details/awsDetails/awsDetails.tsx | 17 +- .../awsDetails/detailsHeader.styles.ts | 3 + .../details/awsDetails/detailsHeader.tsx | 15 +- .../details/awsDetails/detailsTable.tsx | 19 --- .../details/azureDetails/azureDetails.tsx | 17 +- .../azureDetails/detailsHeader.styles.ts | 3 + .../details/azureDetails/detailsHeader.tsx | 35 ++-- .../details/azureDetails/detailsTable.tsx | 32 +--- .../components/providerDetails/index.ts | 2 - .../components/cloudData.tsx | 10 +- .../components/clusterData.tsx | 10 +- .../components/component.styles.ts | 3 + .../components/finalization.tsx | 6 +- .../components/overallStatus.tsx | 151 +++++++++++++----- .../components/sourceLink.tsx | 2 +- .../components/providerStatus/index.ts | 3 + .../providerBreakdownContent.tsx} | 6 +- .../providerBreakdownModal.tsx} | 24 +-- .../providerStatus/providerDetailsContent.tsx | 56 +++++++ .../providerStatus/providerDetailsModal.tsx | 67 ++++++++ .../providerStatus.styles.ts} | 16 +- .../providerStatus.tsx} | 26 ++- .../providerTable.tsx} | 24 ++- .../utils/format.ts | 0 .../utils/icon.tsx | 0 .../utils/normailize.ts | 0 .../utils/status.ts | 2 +- .../utils/variant.ts | 0 .../gcpDetails/detailsHeader.styles.ts | 3 + .../details/gcpDetails/detailsHeader.tsx | 35 ++-- .../details/gcpDetails/detailsTable.tsx | 34 +--- src/routes/details/gcpDetails/gcpDetails.tsx | 17 +- .../ibmDetails/detailsHeader.styles.ts | 3 + .../details/ibmDetails/detailsHeader.tsx | 35 ++-- .../details/ibmDetails/detailsTable.tsx | 34 +--- src/routes/details/ibmDetails/ibmDetails.tsx | 17 +- .../ociDetails/detailsHeader.styles.ts | 3 + .../details/ociDetails/detailsHeader.tsx | 35 ++-- .../details/ociDetails/detailsTable.tsx | 34 +--- src/routes/details/ociDetails/ociDetails.tsx | 17 +- .../components/cloudIntegration.tsx | 2 +- .../details/ocpBreakdown/ocpBreakdown.tsx | 4 +- .../ocpDetails/detailsHeader.styles.ts | 3 + .../details/ocpDetails/detailsHeader.tsx | 37 +++-- .../details/ocpDetails/detailsTable.tsx | 20 --- src/routes/details/ocpDetails/ocpDetails.tsx | 19 +-- .../rhelDetails/detailsHeader.styles.ts | 3 + .../details/rhelDetails/detailsHeader.tsx | 35 ++-- .../details/rhelDetails/detailsTable.tsx | 20 --- .../details/rhelDetails/rhelDetails.tsx | 17 +- src/routes/explorer/explorer.tsx | 4 +- 56 files changed, 566 insertions(+), 488 deletions(-) create mode 100644 src/routes/components/page/noData/noData.styles.ts delete mode 100644 src/routes/details/components/providerDetails/index.ts rename src/routes/details/components/{providerDetails => providerStatus}/components/cloudData.tsx (91%) rename src/routes/details/components/{providerDetails => providerStatus}/components/clusterData.tsx (91%) rename src/routes/details/components/{providerDetails => providerStatus}/components/component.styles.ts (94%) rename src/routes/details/components/{providerDetails => providerStatus}/components/finalization.tsx (92%) rename src/routes/details/components/{providerDetails => providerStatus}/components/overallStatus.tsx (57%) rename src/routes/details/components/{providerDetails => providerStatus}/components/sourceLink.tsx (91%) create mode 100644 src/routes/details/components/providerStatus/index.ts rename src/routes/details/components/{providerDetails/providerDetailsContent.tsx => providerStatus/providerBreakdownContent.tsx} (95%) rename src/routes/details/components/{providerDetails/providerDetailsModal.tsx => providerStatus/providerBreakdownModal.tsx} (75%) create mode 100644 src/routes/details/components/providerStatus/providerDetailsContent.tsx create mode 100644 src/routes/details/components/providerStatus/providerDetailsModal.tsx rename src/routes/details/components/{providerDetails/providerDetails.styles.ts => providerStatus/providerStatus.styles.ts} (51%) rename src/routes/details/components/{providerDetails/providerDetails.tsx => providerStatus/providerStatus.tsx} (75%) rename src/routes/details/components/{providerDetails/providerDetailsTable.tsx => providerStatus/providerTable.tsx} (65%) rename src/routes/details/components/{providerDetails => providerStatus}/utils/format.ts (100%) rename src/routes/details/components/{providerDetails => providerStatus}/utils/icon.tsx (100%) rename src/routes/details/components/{providerDetails => providerStatus}/utils/normailize.ts (100%) rename src/routes/details/components/{providerDetails => providerStatus}/utils/status.ts (97%) rename src/routes/details/components/{providerDetails => providerStatus}/utils/variant.ts (100%) diff --git a/locales/data.json b/locales/data.json index d805615e9..bf3cae29b 100644 --- a/locales/data.json +++ b/locales/data.json @@ -152,6 +152,12 @@ "value": "Back" } ], + "backToIntegrations": [ + { + "type": 0, + "value": "Back to integrations status" + } + ], "breakdownBackToDetails": [ { "options": { @@ -10394,6 +10400,18 @@ "value": "Integration" } ], + "integrationsDetails": [ + { + "type": 0, + "value": "Integrations details" + } + ], + "integrationsStatus": [ + { + "type": 0, + "value": "Integrations status" + } + ], "lastProcessed": [ { "type": 0, @@ -11837,12 +11855,6 @@ "value": "Rate must be a positive number" } ], - "providerDetails": [ - { - "type": 0, - "value": "Integrations details" - } - ], "pvcTitle": [ { "type": 0, @@ -13589,6 +13601,12 @@ "value": "vCPU" } ], + "viewAll": [ + { + "type": 0, + "value": "View all" + } + ], "volumeTitle": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index d4e481c04..4a951916d 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -19,6 +19,7 @@ "azureDetailsTitle": "Microsoft Azure Details", "azureOcpDashboardCostTitle": "Microsoft Azure filtered by OpenShift cost", "back": "Back", + "backToIntegrations": "Back to integrations status", "breakdownBackToDetails": "{groupBy, select, account {Back to {value} account details} aws_category {Back to {value} cost category details} cluster {Back to {value} cluster details} gcp_project {Back to {value} GCP project details} node {Back to {value} node details} org_unit_id {Back to {value} organizational unit details} payer_tenant_id {Back to {value} account details} product_service {Back to {value} service details} project {Back to {value} project details} region {Back to {value} region details} resource_location {Back to {value} region details} service {Back to {value} service details} service_name {Back to {value} service details} subscription_guid {Back to {value} account details} tag {Back to {value} tag details} other {}}", "breakdownBackToDetailsAriaLabel": "Back to details", "breakdownBackToOptimizations": "Back to optimizations", @@ -368,6 +369,8 @@ "infrastructure": "Infrastructure", "instances": "Instances", "integration": "Integration", + "integrationsDetails": "Integrations details", + "integrationsStatus": "Integrations status", "lastProcessed": "Last processed", "lastUpdated": "Last updated", "learnMore": "Learn more", @@ -508,7 +511,6 @@ "priceListEmptyRateDesc": "To add rates to the price list, click on the \"Add\" rate button above.", "priceListNumberRate": "Rate must be a number", "priceListPosNumberRate": "Rate must be a positive number", - "providerDetails": "Integrations details", "pvcTitle": "Persistent Volume Claims", "rate": "Rate", "rawCostDesc": "The costs reported by a cloud provider without any cost model calculations applied.", @@ -635,6 +637,7 @@ "valueUnits": "{value} {units}", "various": "Various", "vcpuTitle": "vCPU", + "viewAll": "View all", "volumeTitle": "Volume", "workerUnallocated": "Worker unallocated", "workerUnallocatedDesc": "Distribute unused and non-reserved resource costs to projects", diff --git a/src/locales/messages.ts b/src/locales/messages.ts index ce7f24b6e..f42057b34 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -101,6 +101,11 @@ export default defineMessages({ description: 'Back', id: 'back', }, + backToIntegrations: { + defaultMessage: 'Back to integrations status', + description: 'Back to integrations status', + id: 'backToIntegrations', + }, breakdownBackToDetails: { defaultMessage: '{groupBy, select, ' + @@ -2382,6 +2387,16 @@ export default defineMessages({ description: 'Integration', id: 'integration', }, + integrationsDetails: { + defaultMessage: 'Integrations details', + description: 'Integrations details', + id: 'integrationsDetails', + }, + integrationsStatus: { + defaultMessage: 'Integrations status', + description: 'Integrations status', + id: 'integrationsStatus', + }, lastProcessed: { defaultMessage: 'Last processed', description: 'Last processed', @@ -3156,11 +3171,6 @@ export default defineMessages({ description: 'Rate must be a positive number', id: 'priceListPosNumberRate', }, - providerDetails: { - defaultMessage: 'Integrations details', - description: 'Integrations details', - id: 'providerDetails', - }, pvcTitle: { defaultMessage: 'Persistent Volume Claims', description: 'Persistent Volume Claims', @@ -3885,6 +3895,11 @@ export default defineMessages({ description: 'vCPU', id: 'vcpuTitle', }, + viewAll: { + defaultMessage: 'View all', + description: 'View all', + id: 'viewAll', + }, volumeTitle: { defaultMessage: 'Volume', description: 'Volume', diff --git a/src/routes/components/page/noData/noData.styles.ts b/src/routes/components/page/noData/noData.styles.ts new file mode 100644 index 000000000..1cb3c59b6 --- /dev/null +++ b/src/routes/components/page/noData/noData.styles.ts @@ -0,0 +1,9 @@ +import global_spacer_xl from '@patternfly/react-tokens/dist/js/global_spacer_xl'; +import type React from 'react'; + +export const styles = { + details: { + marginBottom: global_spacer_xl.value, + marginTop: global_spacer_xl.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/components/page/noData/noDataState.tsx b/src/routes/components/page/noData/noDataState.tsx index 7ce759d65..5efd0c998 100644 --- a/src/routes/components/page/noData/noDataState.tsx +++ b/src/routes/components/page/noData/noDataState.tsx @@ -1,4 +1,5 @@ import { + Bullseye, Button, EmptyState, EmptyStateBody, @@ -13,6 +14,8 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; +import { styles } from './noData.styles'; + interface NoDataStateOwnProps { detailsComponent?: React.ReactNode; showReload?: boolean; @@ -33,7 +36,7 @@ class NoDataStateBase extends React.Component { /> {intl.formatMessage(messages.noDataStateDesc, { - status: detailsComponent, + status: detailsComponent ? {detailsComponent} : '', })} diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index c27e37963..72dca13cb 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -20,7 +20,7 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAwsReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; @@ -56,7 +56,6 @@ import { DetailsToolbar } from './detailsToolbar'; interface AwsDetailsStateProps { costType: string; currency?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -216,15 +215,7 @@ class AwsDetails extends React.Component { }; private getTable = () => { - const { - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -247,7 +238,6 @@ class AwsDetails extends React.Component { groupByCostCategory={groupByCostCategory} groupByTagKey={groupByTagKey} groupByOrg={groupByOrg} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -414,7 +404,7 @@ class AwsDetails extends React.Component { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -534,7 +524,6 @@ const mapStateToProps = createMapStateToProps { currency, groupBy, intl, + isAccountInfoDetailsToggleEnabled, isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -142,7 +145,14 @@ class DetailsHeaderBase extends React.Component { - + {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} + ), }, - { - hidden: !(isGroupByAccount && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, diff --git a/src/routes/details/azureDetails/azureDetails.tsx b/src/routes/details/azureDetails/azureDetails.tsx index 049562495..10105c4c4 100644 --- a/src/routes/details/azureDetails/azureDetails.tsx +++ b/src/routes/details/azureDetails/azureDetails.tsx @@ -18,7 +18,7 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAzureReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; @@ -52,7 +52,6 @@ import { DetailsToolbar } from './detailsToolbar'; interface AzureDetailsStateProps { currency?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -203,15 +202,7 @@ class AzureDetails extends React.Component }; private getTable = () => { - const { - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -224,7 +215,6 @@ class AzureDetails extends React.Component filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleOnSelect} @@ -372,7 +362,7 @@ class AzureDetails extends React.Component return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -478,7 +468,6 @@ const mapStateToProps = createMapStateToProps { currency, groupBy, intl, + isAccountInfoDetailsToggleEnabled, isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -126,19 +129,24 @@ class DetailsHeaderBase extends React.Component { + {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} - -
- -
+ + {isDetailsDateRangeToggleEnabled && ( @@ -185,6 +193,7 @@ const mapStateToProps = createMapStateToProps { - const { - breadcrumbPath, - groupBy, - groupByTagKey, - intl, - isAccountInfoDetailsToggleEnabled, - isAllSelected, - query, - report, - router, - selectedItems, - } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } @@ -131,10 +118,6 @@ class DetailsTableBase extends React.Component ), }, - { - hidden: !(isGroupBySubscriptionGuid && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, diff --git a/src/routes/details/components/providerDetails/index.ts b/src/routes/details/components/providerDetails/index.ts deleted file mode 100644 index 61e26dae5..000000000 --- a/src/routes/details/components/providerDetails/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ProviderDetails } from './providerDetails'; -export { ProviderDetailsModal } from './providerDetailsModal'; diff --git a/src/routes/details/components/providerDetails/components/cloudData.tsx b/src/routes/details/components/providerStatus/components/cloudData.tsx similarity index 91% rename from src/routes/details/components/providerDetails/components/cloudData.tsx rename to src/routes/details/components/providerStatus/components/cloudData.tsx index f8fbc4fe2..65bb70343 100644 --- a/src/routes/details/components/providerDetails/components/cloudData.tsx +++ b/src/routes/details/components/providerStatus/components/cloudData.tsx @@ -3,11 +3,11 @@ import type { Provider } from 'api/providers'; import messages from 'locales/messages'; import React from 'react'; import { useIntl } from 'react-intl'; -import { SourceLink } from 'routes/details/components/providerDetails/components/sourceLink'; -import { formatDate } from 'routes/details/components/providerDetails/utils/format'; -import { getProgressStepIcon } from 'routes/details/components/providerDetails/utils/icon'; -import { getProviderAvailability } from 'routes/details/components/providerDetails/utils/status'; -import { getProgressStepVariant } from 'routes/details/components/providerDetails/utils/variant'; +import { SourceLink } from 'routes/details/components/providerStatus/components/sourceLink'; +import { formatDate } from 'routes/details/components/providerStatus/utils/format'; +import { getProgressStepIcon } from 'routes/details/components/providerStatus/utils/icon'; +import { getProviderAvailability } from 'routes/details/components/providerStatus/utils/status'; +import { getProgressStepVariant } from 'routes/details/components/providerStatus/utils/variant'; import { styles } from './component.styles'; diff --git a/src/routes/details/components/providerDetails/components/clusterData.tsx b/src/routes/details/components/providerStatus/components/clusterData.tsx similarity index 91% rename from src/routes/details/components/providerDetails/components/clusterData.tsx rename to src/routes/details/components/providerStatus/components/clusterData.tsx index 3a4ae83e8..256be3f22 100644 --- a/src/routes/details/components/providerDetails/components/clusterData.tsx +++ b/src/routes/details/components/providerStatus/components/clusterData.tsx @@ -3,11 +3,11 @@ import type { Provider } from 'api/providers'; import messages from 'locales/messages'; import React from 'react'; import { useIntl } from 'react-intl'; -import { SourceLink } from 'routes/details/components/providerDetails/components/sourceLink'; -import { formatDate } from 'routes/details/components/providerDetails/utils/format'; -import { getProgressStepIcon } from 'routes/details/components/providerDetails/utils/icon'; -import { getProviderAvailability } from 'routes/details/components/providerDetails/utils/status'; -import { getProgressStepVariant } from 'routes/details/components/providerDetails/utils/variant'; +import { SourceLink } from 'routes/details/components/providerStatus/components/sourceLink'; +import { formatDate } from 'routes/details/components/providerStatus/utils/format'; +import { getProgressStepIcon } from 'routes/details/components/providerStatus/utils/icon'; +import { getProviderAvailability } from 'routes/details/components/providerStatus/utils/status'; +import { getProgressStepVariant } from 'routes/details/components/providerStatus/utils/variant'; import { styles } from './component.styles'; diff --git a/src/routes/details/components/providerDetails/components/component.styles.ts b/src/routes/details/components/providerStatus/components/component.styles.ts similarity index 94% rename from src/routes/details/components/providerDetails/components/component.styles.ts rename to src/routes/details/components/providerStatus/components/component.styles.ts index 44aa60ec0..5cf0feefc 100644 --- a/src/routes/details/components/providerDetails/components/component.styles.ts +++ b/src/routes/details/components/providerStatus/components/component.styles.ts @@ -6,6 +6,9 @@ import global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm' import type React from 'react'; export const styles = { + count: { + marginRight: global_spacer_sm.value, + }, description: { color: global_disabled_color_100.value, fontSize: global_FontSize_xs.value, diff --git a/src/routes/details/components/providerDetails/components/finalization.tsx b/src/routes/details/components/providerStatus/components/finalization.tsx similarity index 92% rename from src/routes/details/components/providerDetails/components/finalization.tsx rename to src/routes/details/components/providerStatus/components/finalization.tsx index 6c9d784d5..0e37e59ae 100644 --- a/src/routes/details/components/providerDetails/components/finalization.tsx +++ b/src/routes/details/components/providerStatus/components/finalization.tsx @@ -4,9 +4,9 @@ import { ProviderType } from 'api/providers'; import messages from 'locales/messages'; import React from 'react'; import { useIntl } from 'react-intl'; -import { formatDate } from 'routes/details/components/providerDetails/utils/format'; -import { getProgressStepIcon } from 'routes/details/components/providerDetails/utils/icon'; -import { getProgressStepVariant } from 'routes/details/components/providerDetails/utils/variant'; +import { formatDate } from 'routes/details/components/providerStatus/utils/format'; +import { getProgressStepIcon } from 'routes/details/components/providerStatus/utils/icon'; +import { getProgressStepVariant } from 'routes/details/components/providerStatus/utils/variant'; import { styles } from './component.styles'; diff --git a/src/routes/details/components/providerDetails/components/overallStatus.tsx b/src/routes/details/components/providerStatus/components/overallStatus.tsx similarity index 57% rename from src/routes/details/components/providerDetails/components/overallStatus.tsx rename to src/routes/details/components/providerStatus/components/overallStatus.tsx index c2a6b3359..6b74777c7 100644 --- a/src/routes/details/components/providerDetails/components/overallStatus.tsx +++ b/src/routes/details/components/providerStatus/components/overallStatus.tsx @@ -7,13 +7,13 @@ import React from 'react'; import type { MessageDescriptor } from 'react-intl'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; -import { formatDate } from 'routes/details/components/providerDetails/utils/format'; -import { getOverallStatusIcon } from 'routes/details/components/providerDetails/utils/icon'; +import { formatDate } from 'routes/details/components/providerStatus/utils/format'; +import { getOverallStatusIcon } from 'routes/details/components/providerStatus/utils/icon'; import { getProviderAvailability, getProviderStatus, StatusType, -} from 'routes/details/components/providerDetails/utils/status'; +} from 'routes/details/components/providerStatus/utils/status'; import { filterProviders } from 'routes/utils/providers'; import type { RootState } from 'store'; import type { FetchStatus } from 'store/common'; @@ -24,7 +24,6 @@ import { styles } from './component.styles'; interface OverallStatusOwnProps { clusterId?: string; isLastUpdated?: boolean; - isLastUpdatedStatus?: boolean; isStatusMsg?: boolean; providerId?: string; providerType: ProviderType; @@ -43,7 +42,6 @@ type OverallStatusProps = OverallStatusOwnProps; const OverallStatus: React.FC = ({ clusterId, isLastUpdated, - isLastUpdatedStatus, isStatusMsg, providerId, providerType, @@ -52,21 +50,15 @@ const OverallStatus: React.FC = ({ const { providers, providersError } = useMapToProps(); const intl = useIntl(); - if (!providers || providersError) { - return null; - } - - // Filter OCP providers to skip an extra API request - const filteredProviders = filterProviders(providers, providerType)?.data?.filter(data => data.status !== null); - const provider = filteredProviders?.find( - val => - providerId === val.id || - (clusterId && val.authentication?.credentials?.cluster_id === clusterId) || - uuId === val.uuid - ); - const cloudProvider = providers?.data?.find(val => val.uuid === provider?.infrastructure?.uuid); + // Filter providers to skip an extra API request + const getFilteredProviders = () => { + return filterProviders(providers, providerType)?.data?.filter(data => data.status !== null); + }; - const getOverallStatus = (): { lastUpdated: string; msg: MessageDescriptor; status: StatusType } => { + const getOverallStatus = ( + provider, + cloudProvider + ): { lastUpdated: string; msg: MessageDescriptor; status: StatusType } => { let lastUpdated; let msg; let status; @@ -124,36 +116,111 @@ const OverallStatus: React.FC = ({ return { lastUpdated, msg, status }; }; - const overallStatus = getOverallStatus(); + const getAllStatus = () => { + let completeCount = 0; + let failedCount = 0; + let inProgressCount = 0; + let pausedCount = 0; + let pendingCount = 0; - if (isLastUpdated) { - return overallStatus.lastUpdated ? formatDate(overallStatus.lastUpdated) : null; - } - if (isLastUpdatedStatus) { + const overallProviderStatus = []; + const filteredProviders = getFilteredProviders(); + + filteredProviders.map(provider => { + const cloudProvider = providers?.data?.find(val => val.uuid === provider?.infrastructure?.uuid); + overallProviderStatus.push(getOverallStatus(provider, cloudProvider)); + }); + + overallProviderStatus.map(overallStatus => { + if (overallStatus.status === StatusType.failed) { + failedCount++; + } + if (overallStatus.status === StatusType.paused) { + pausedCount++; + } + if (overallStatus.status === StatusType.inProgress) { + inProgressCount++; + } + if (overallStatus.status === StatusType.pending) { + pendingCount++; + } + if (overallStatus.status === StatusType.complete) { + completeCount++; + } + }); return ( <> - {getOverallStatusIcon(overallStatus.status)} - - {overallStatus.lastUpdated - ? formatDate(overallStatus.lastUpdated) - : intl.formatMessage(messages.statusMsg, { value: overallStatus.status })} - + {completeCount > 0 && ( + <> + {completeCount} + {getOverallStatusIcon(StatusType.complete)} + + )} + {failedCount > 0 && ( + <> + {failedCount} + {getOverallStatusIcon(StatusType.failed)} + + )} + {inProgressCount > 0 && ( + <> + {inProgressCount} + {getOverallStatusIcon(StatusType.inProgress)} + + )} + {pausedCount > 0 && ( + <> + {pausedCount} + {getOverallStatusIcon(StatusType.paused)} + + )} + {pendingCount > 0 && ( + <> + {pendingCount} + {getOverallStatusIcon(StatusType.pending)} + + )} ); - } - if (overallStatus.msg && overallStatus.status) { - return ( - <> - {getOverallStatusIcon(overallStatus.status)} - - {isStatusMsg - ? intl.formatMessage(messages.statusMsg, { value: overallStatus.status }) - : intl.formatMessage(overallStatus.msg)} - - + }; + + const getStatus = () => { + const filteredProviders = getFilteredProviders(); + const provider = filteredProviders?.find( + val => + providerId === val.id || + (clusterId && val.authentication?.credentials?.cluster_id === clusterId) || + uuId === val.uuid ); + const cloudProvider = providers?.data?.find(val => val.uuid === provider?.infrastructure?.uuid); + const overallStatus = getOverallStatus(provider, cloudProvider); + + if (isLastUpdated) { + return overallStatus.lastUpdated ? formatDate(overallStatus.lastUpdated) : null; + } + if (overallStatus.msg && overallStatus.status) { + return ( + <> + {getOverallStatusIcon(overallStatus.status)} + + {isStatusMsg + ? intl.formatMessage(messages.statusMsg, { value: overallStatus.status }) + : intl.formatMessage(overallStatus.msg)} + + + ); + } + return null; + }; + + if (!providers || providersError) { + return null; + } + if (providerId || clusterId || uuId) { + return getStatus(); + } else { + return getAllStatus(); } - return null; }; const useMapToProps = (): OverallStatusStateProps => { diff --git a/src/routes/details/components/providerDetails/components/sourceLink.tsx b/src/routes/details/components/providerStatus/components/sourceLink.tsx similarity index 91% rename from src/routes/details/components/providerDetails/components/sourceLink.tsx rename to src/routes/details/components/providerStatus/components/sourceLink.tsx index e02c4a60a..2b97d1ece 100644 --- a/src/routes/details/components/providerDetails/components/sourceLink.tsx +++ b/src/routes/details/components/providerStatus/components/sourceLink.tsx @@ -2,7 +2,7 @@ import type { Provider } from 'api/providers'; import messages from 'locales/messages'; import React from 'react'; import { useIntl } from 'react-intl'; -import { normalize } from 'routes/details/components/providerDetails/utils/normailize'; +import { normalize } from 'routes/details/components/providerStatus/utils/normailize'; import { getReleasePath } from 'utils/paths'; import { styles } from './component.styles'; diff --git a/src/routes/details/components/providerStatus/index.ts b/src/routes/details/components/providerStatus/index.ts new file mode 100644 index 000000000..019d75fc8 --- /dev/null +++ b/src/routes/details/components/providerStatus/index.ts @@ -0,0 +1,3 @@ +export { ProviderBreakdownModal } from './providerBreakdownModal'; +export { ProviderDetailsModal } from './providerDetailsModal'; +export { ProviderStatus } from './providerStatus'; diff --git a/src/routes/details/components/providerDetails/providerDetailsContent.tsx b/src/routes/details/components/providerStatus/providerBreakdownContent.tsx similarity index 95% rename from src/routes/details/components/providerDetails/providerDetailsContent.tsx rename to src/routes/details/components/providerStatus/providerBreakdownContent.tsx index 33d1628db..98db75cdd 100644 --- a/src/routes/details/components/providerDetails/providerDetailsContent.tsx +++ b/src/routes/details/components/providerStatus/providerBreakdownContent.tsx @@ -16,7 +16,7 @@ import { providersQuery, providersSelectors } from 'store/providers'; import { CloudData } from './components/cloudData'; import { ClusterData } from './components/clusterData'; import { Finalization } from './components/finalization'; -import { styles } from './providerDetails.styles'; +import { styles } from './providerStatus.styles'; interface ProviderDetailsContentOwnProps { clusterId?: string; @@ -34,7 +34,7 @@ interface ProviderDetailsContentStateProps { type ProviderDetailsContentProps = ProviderDetailsContentOwnProps; -const ProviderDetailsContent: React.FC = ({ +const ProviderBreakdownContent: React.FC = ({ clusterId, providerId, providerType, @@ -106,4 +106,4 @@ const useMapToProps = (): ProviderDetailsContentStateProps => { }; }; -export { ProviderDetailsContent }; +export { ProviderBreakdownContent }; diff --git a/src/routes/details/components/providerDetails/providerDetailsModal.tsx b/src/routes/details/components/providerStatus/providerBreakdownModal.tsx similarity index 75% rename from src/routes/details/components/providerDetails/providerDetailsModal.tsx rename to src/routes/details/components/providerStatus/providerBreakdownModal.tsx index 0354e9359..46f997758 100644 --- a/src/routes/details/components/providerDetails/providerDetailsModal.tsx +++ b/src/routes/details/components/providerStatus/providerBreakdownModal.tsx @@ -6,12 +6,11 @@ import React, { useState } from 'react'; import { useIntl } from 'react-intl'; import { OverallStatus } from './components/overallStatus'; -import { styles } from './providerDetails.styles'; -import { ProviderDetailsContent } from './providerDetailsContent'; +import { ProviderBreakdownContent } from './providerBreakdownContent'; +import { styles } from './providerStatus.styles'; interface ProviderDetailsModalOwnProps { clusterId?: string; - isLastUpdatedStatus?: boolean; isOverallStatus?: boolean; providerId?: string; providerType: ProviderType; @@ -20,10 +19,9 @@ interface ProviderDetailsModalOwnProps { type ProviderDetailsModalProps = ProviderDetailsModalOwnProps; -const ProviderDetailsModal: React.FC = ({ +const ProviderBreakdownModal: React.FC = ({ clusterId, - isOverallStatus = true, - isLastUpdatedStatus, + isOverallStatus, providerId, providerType, uuId, @@ -36,7 +34,7 @@ const ProviderDetailsModal: React.FC = ({ }; const handleOnClick = () => { - setIsOpen(!isOpen); + setIsOpen(true); }; // PatternFly modal appends to document.body, which is outside the scoped "costManagement" dom tree. @@ -45,13 +43,7 @@ const ProviderDetailsModal: React.FC = ({ return ( <> {isOverallStatus && ( - + )} + + + ) : ( + + ); +}; + +export { ProviderDetailsContent }; diff --git a/src/routes/details/components/providerStatus/providerDetailsModal.tsx b/src/routes/details/components/providerStatus/providerDetailsModal.tsx new file mode 100644 index 000000000..024604d2f --- /dev/null +++ b/src/routes/details/components/providerStatus/providerDetailsModal.tsx @@ -0,0 +1,67 @@ +import { Button, ButtonVariant } from '@patternfly/react-core'; +import { Modal, ModalBody, ModalHeader, ModalVariant } from '@patternfly/react-core/next'; +import type { ProviderType } from 'api/providers'; +import messages from 'locales/messages'; +import React, { useState } from 'react'; +import { useIntl } from 'react-intl'; + +import { OverallStatus } from './components/overallStatus'; +import { ProviderDetailsContent } from './providerDetailsContent'; +import { styles } from './providerStatus.styles'; + +interface ProviderDetailsModalOwnProps { + providerType: ProviderType; +} + +type ProviderDetailsModalProps = ProviderDetailsModalOwnProps; + +const ProviderDetailsModal: React.FC = ({ providerType }: ProviderDetailsModalProps) => { + const intl = useIntl(); + const [isOpen, setIsOpen] = useState(false); + const [title, setTitle] = useState(messages.integrationsStatus); + const [variant, setVariant] = useState(ModalVariant.medium); + + const handleOnClose = () => { + setIsOpen(false); + }; + + const handleOnClick = () => { + setVariant(ModalVariant.medium); + setIsOpen(true); + }; + + const handleOnBackClick = () => { + setVariant(ModalVariant.medium); + setTitle(messages.integrationsStatus); + }; + + const handleOnDetailsClick = () => { + setVariant(ModalVariant.small); + setTitle(messages.integrationsDetails); + }; + + // PatternFly modal appends to document.body, which is outside the scoped "costManagement" dom tree. + // Use className="costManagement" to override PatternFly styles or append the modal to an element within the tree + + return ( + <> + {intl.formatMessage(messages.integrationsStatus)} + + + + + + + + + + ); +}; + +export { ProviderDetailsModal }; diff --git a/src/routes/details/components/providerDetails/providerDetails.styles.ts b/src/routes/details/components/providerStatus/providerStatus.styles.ts similarity index 51% rename from src/routes/details/components/providerDetails/providerDetails.styles.ts rename to src/routes/details/components/providerStatus/providerStatus.styles.ts index 944909be1..0c8be632b 100644 --- a/src/routes/details/components/providerDetails/providerDetails.styles.ts +++ b/src/routes/details/components/providerStatus/providerStatus.styles.ts @@ -1,17 +1,21 @@ import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_FontSize_sm from '@patternfly/react-tokens/dist/js/global_FontSize_sm'; import global_FontSize_xs from '@patternfly/react-tokens/dist/js/global_FontSize_xs'; -import global_spacer_xl from '@patternfly/react-tokens/dist/js/global_spacer_xl'; import type React from 'react'; export const styles = { + backButton: { + paddingBottom: global_FontSize_sm.var, + paddingLeft: 0, + paddingTop: 0, + }, dataDetailsButton: { - fontSize: global_FontSize_xs.value, + fontSize: global_FontSize_xs.var, }, loading: { - backgroundColor: global_BackgroundColor_light_100.value, + backgroundColor: global_BackgroundColor_light_100.var, }, - detailsTable: { - marginBottom: global_spacer_xl.value, - marginTop: global_spacer_xl.value, + statusLabel: { + marginRight: global_FontSize_xs.var, }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/details/components/providerDetails/providerDetails.tsx b/src/routes/details/components/providerStatus/providerStatus.tsx similarity index 75% rename from src/routes/details/components/providerDetails/providerDetails.tsx rename to src/routes/details/components/providerStatus/providerStatus.tsx index f191acd4b..2942d2edf 100644 --- a/src/routes/details/components/providerDetails/providerDetails.tsx +++ b/src/routes/details/components/providerStatus/providerStatus.tsx @@ -1,4 +1,3 @@ -import { Bullseye } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; @@ -14,28 +13,29 @@ import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; -import { styles } from './providerDetails.styles'; -import { ProviderDetailsTable } from './providerDetailsTable'; +import { styles } from './providerStatus.styles'; +import { ProviderTable } from './providerTable'; -interface ProviderDetailsOwnProps { +interface ProviderStatusOwnProps { + onClick?: (providerId: string) => void; providerType: ProviderType; } -interface ProviderDetailsStateProps { +interface ProviderStatusStateProps { providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; providersQueryString: string; } -type ProviderDetailsProps = ProviderDetailsOwnProps; +type ProviderStatusProps = ProviderStatusOwnProps; -const ProviderDetails: React.FC = ({ providerType }: ProviderDetailsProps) => { +const ProviderStatus: React.FC = ({ onClick, providerType }: ProviderStatusProps) => { const intl = useIntl(); const { providers, providersError, providersFetchStatus } = useMapToProps(); - const title = intl.formatMessage(messages.providerDetails); + const title = intl.formatMessage(messages.integrationsDetails); if (providersError) { return ; @@ -55,14 +55,10 @@ const ProviderDetails: React.FC = ({ providerType }: Provi return; } - return ( - - - - ); + return ; }; -const useMapToProps = (): ProviderDetailsStateProps => { +const useMapToProps = (): ProviderStatusStateProps => { // PermissionsWrapper has already made an API request const providersQueryString = getProvidersQuery(providersQuery); const providers = useSelector((state: RootState) => @@ -83,4 +79,4 @@ const useMapToProps = (): ProviderDetailsStateProps => { }; }; -export { ProviderDetails }; +export { ProviderStatus }; diff --git a/src/routes/details/components/providerDetails/providerDetailsTable.tsx b/src/routes/details/components/providerStatus/providerTable.tsx similarity index 65% rename from src/routes/details/components/providerDetails/providerDetailsTable.tsx rename to src/routes/details/components/providerStatus/providerTable.tsx index e4e2188ee..ace42feda 100644 --- a/src/routes/details/components/providerDetails/providerDetailsTable.tsx +++ b/src/routes/details/components/providerStatus/providerTable.tsx @@ -1,3 +1,4 @@ +import { Button, ButtonVariant } from '@patternfly/react-core'; import type { Provider, ProviderType } from 'api/providers'; import messages from 'locales/messages'; import React, { useEffect, useState } from 'react'; @@ -6,16 +7,19 @@ import { DataTable } from 'routes/components/dataTable'; import { OverallStatus } from './components/overallStatus'; import { SourceLink } from './components/sourceLink'; -import { ProviderDetailsModal } from './providerDetailsModal'; +import { ProviderBreakdownModal } from './providerBreakdownModal'; +import { styles } from './providerStatus.styles'; -interface ProviderDetailsTableOwnProps { +interface ProviderTableOwnProps { + onClick?: (id: string) => void; providers?: Provider[]; providerType?: ProviderType; + showContent?: boolean; } -type ProviderDetailsTableProps = ProviderDetailsTableOwnProps; +type ProviderTableProps = ProviderTableOwnProps; -const ProviderDetailsTable: React.FC = ({ providers, providerType }) => { +const ProviderTable: React.FC = ({ onClick, providers, providerType }) => { const intl = useIntl(); const [columns, setColumns] = useState([]); const [rows, setRows] = useState([]); @@ -49,7 +53,15 @@ const ProviderDetailsTable: React.FC = ({ providers, { value: }, { value: }, { value: }, - { value: }, + { + value: onClick ? ( + + ) : ( + + ), + }, ], item, }); @@ -66,4 +78,4 @@ const ProviderDetailsTable: React.FC = ({ providers, return rows.length ? : null; }; -export { ProviderDetailsTable }; +export { ProviderTable }; diff --git a/src/routes/details/components/providerDetails/utils/format.ts b/src/routes/details/components/providerStatus/utils/format.ts similarity index 100% rename from src/routes/details/components/providerDetails/utils/format.ts rename to src/routes/details/components/providerStatus/utils/format.ts diff --git a/src/routes/details/components/providerDetails/utils/icon.tsx b/src/routes/details/components/providerStatus/utils/icon.tsx similarity index 100% rename from src/routes/details/components/providerDetails/utils/icon.tsx rename to src/routes/details/components/providerStatus/utils/icon.tsx diff --git a/src/routes/details/components/providerDetails/utils/normailize.ts b/src/routes/details/components/providerStatus/utils/normailize.ts similarity index 100% rename from src/routes/details/components/providerDetails/utils/normailize.ts rename to src/routes/details/components/providerStatus/utils/normailize.ts diff --git a/src/routes/details/components/providerDetails/utils/status.ts b/src/routes/details/components/providerStatus/utils/status.ts similarity index 97% rename from src/routes/details/components/providerDetails/utils/status.ts rename to src/routes/details/components/providerStatus/utils/status.ts index 7da4f4a90..68c6fdd79 100644 --- a/src/routes/details/components/providerDetails/utils/status.ts +++ b/src/routes/details/components/providerStatus/utils/status.ts @@ -2,7 +2,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/components/providerDetails/utils/normailize'; +import { normalize } from 'routes/details/components/providerStatus/utils/normailize'; export const enum StatusType { complete = 'complete', diff --git a/src/routes/details/components/providerDetails/utils/variant.ts b/src/routes/details/components/providerStatus/utils/variant.ts similarity index 100% rename from src/routes/details/components/providerDetails/utils/variant.ts rename to src/routes/details/components/providerStatus/utils/variant.ts diff --git a/src/routes/details/gcpDetails/detailsHeader.styles.ts b/src/routes/details/gcpDetails/detailsHeader.styles.ts index d55ff51cc..b4c026fd7 100644 --- a/src/routes/details/gcpDetails/detailsHeader.styles.ts +++ b/src/routes/details/gcpDetails/detailsHeader.styles.ts @@ -26,6 +26,9 @@ export const styles = { perspectiveContainer: { alignItems: 'unset', }, + status: { + marginBottom: global_spacer_sm.var, + }, title: { paddingBottom: global_spacer_sm.var, }, diff --git a/src/routes/details/gcpDetails/detailsHeader.tsx b/src/routes/details/gcpDetails/detailsHeader.tsx index dbfd4b42b..8c5056494 100644 --- a/src/routes/details/gcpDetails/detailsHeader.tsx +++ b/src/routes/details/gcpDetails/detailsHeader.tsx @@ -15,6 +15,7 @@ import { connect } from 'react-redux'; import { Currency } from 'routes/components/currency'; import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; +import { ProviderDetailsModal } from 'routes/details/components/providerStatus'; import type { ComputedGcpReportItemsParams } from 'routes/utils/computedReport/getComputedGcpReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedGcpReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -43,7 +44,8 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isDetailsDateRangeToggleEnabled: boolean; + isAccountInfoDetailsToggleEnabled?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; @@ -97,6 +99,7 @@ class DetailsHeaderBase extends React.Component { currency, groupBy, intl, + isAccountInfoDetailsToggleEnabled, isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -127,19 +130,24 @@ class DetailsHeaderBase extends React.Component {
+ {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} - -
- -
+ + {isDetailsDateRangeToggleEnabled && ( @@ -186,6 +194,7 @@ const mapStateToProps = createMapStateToProps { - const { - breadcrumbPath, - groupBy, - groupByTagKey, - intl, - isAccountInfoDetailsToggleEnabled, - isAllSelected, - query, - report, - router, - selectedItems, - } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } - const isGroupByAccount = groupBy === 'account'; - const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -131,10 +116,6 @@ class DetailsTableBase extends React.Component ), }, - { - hidden: !(isGroupByAccount && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, diff --git a/src/routes/details/gcpDetails/gcpDetails.tsx b/src/routes/details/gcpDetails/gcpDetails.tsx index b14e10925..47d558765 100644 --- a/src/routes/details/gcpDetails/gcpDetails.tsx +++ b/src/routes/details/gcpDetails/gcpDetails.tsx @@ -18,7 +18,7 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedGcpReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; @@ -52,7 +52,6 @@ import { styles } from './gcpDetails.styles'; interface GcpDetailsStateProps { currency?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -203,15 +202,7 @@ class GcpDetails extends React.Component { }; private getTable = () => { - const { - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); const groupByTagKey = getGroupByTagKey(query); @@ -223,7 +214,6 @@ class GcpDetails extends React.Component { filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -372,7 +362,7 @@ class GcpDetails extends React.Component { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -479,7 +469,6 @@ const mapStateToProps = createMapStateToProps { currency, groupBy, intl, + isAccountInfoDetailsToggleEnabled, isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -127,19 +130,24 @@ class DetailsHeaderBase extends React.Component {
+ {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} - -
- -
+ + {isDetailsDateRangeToggleEnabled && ( @@ -186,6 +194,7 @@ const mapStateToProps = createMapStateToProps { - const { - breadcrumbPath, - groupBy, - groupByTagKey, - intl, - isAccountInfoDetailsToggleEnabled, - isAllSelected, - query, - report, - router, - selectedItems, - } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } - const isGroupByAccount = groupBy === 'account'; - const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -131,10 +116,6 @@ class DetailsTableBase extends React.Component ), }, - { - hidden: !(isGroupByAccount && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, diff --git a/src/routes/details/ibmDetails/ibmDetails.tsx b/src/routes/details/ibmDetails/ibmDetails.tsx index fbfdb3c2e..2c3e319ae 100644 --- a/src/routes/details/ibmDetails/ibmDetails.tsx +++ b/src/routes/details/ibmDetails/ibmDetails.tsx @@ -18,7 +18,7 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedIbmReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; @@ -53,7 +53,6 @@ import { styles } from './ibmDetails.styles'; interface IbmDetailsStateProps { currency?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -204,15 +203,7 @@ class IbmDetails extends React.Component { }; private getTable = () => { - const { - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -225,7 +216,6 @@ class IbmDetails extends React.Component { filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -374,7 +364,7 @@ class IbmDetails extends React.Component { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -481,7 +471,6 @@ const mapStateToProps = createMapStateToProps { currency, groupBy, intl, + isAccountInfoDetailsToggleEnabled, isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -126,19 +129,24 @@ class DetailsHeaderBase extends React.Component {
+ {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} - -
- -
+ + {isDetailsDateRangeToggleEnabled && ( @@ -185,6 +193,7 @@ const mapStateToProps = createMapStateToProps { - const { - breadcrumbPath, - groupBy, - groupByTagKey, - intl, - isAccountInfoDetailsToggleEnabled, - isAllSelected, - query, - report, - router, - selectedItems, - } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } - const isGroupByPayerTenantId = groupBy === 'payer_tenant_id'; - const rows = []; const computedItems = getUnsortedComputedReportItems({ report, @@ -131,10 +116,6 @@ class DetailsTableBase extends React.Component ), }, - { - hidden: !(isGroupByPayerTenantId && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value: monthOverMonth }, { value: cost, style: styles.managedColumn }, { value: actions }, diff --git a/src/routes/details/ociDetails/ociDetails.tsx b/src/routes/details/ociDetails/ociDetails.tsx index 402134f38..7fe9de029 100644 --- a/src/routes/details/ociDetails/ociDetails.tsx +++ b/src/routes/details/ociDetails/ociDetails.tsx @@ -18,7 +18,7 @@ import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOciReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; @@ -52,7 +52,6 @@ import { styles } from './ociDetails.styles'; interface OciDetailsStateProps { currency?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -203,15 +202,7 @@ class OciDetails extends React.Component { }; private getTable = () => { - const { - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -224,7 +215,6 @@ class OciDetails extends React.Component { filterBy={query.filter_by} groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -373,7 +363,7 @@ class OciDetails extends React.Component { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -480,7 +470,6 @@ const mapStateToProps = createMapStateToProps : undefined, dataDetailsComponent: groupBy === 'cluster' ? ( - + ) : undefined, costDistribution, costOverviewComponent: ( diff --git a/src/routes/details/ocpDetails/detailsHeader.styles.ts b/src/routes/details/ocpDetails/detailsHeader.styles.ts index caa2a81fe..1f2fb619b 100644 --- a/src/routes/details/ocpDetails/detailsHeader.styles.ts +++ b/src/routes/details/ocpDetails/detailsHeader.styles.ts @@ -36,6 +36,9 @@ export const styles = { infoTitle: { fontWeight: 'bold', }, + status: { + marginBottom: global_spacer_sm.var, + }, title: { paddingBottom: global_spacer_sm.var, }, diff --git a/src/routes/details/ocpDetails/detailsHeader.tsx b/src/routes/details/ocpDetails/detailsHeader.tsx index ca043529b..49ec67ab6 100644 --- a/src/routes/details/ocpDetails/detailsHeader.tsx +++ b/src/routes/details/ocpDetails/detailsHeader.tsx @@ -18,6 +18,7 @@ import { Currency } from 'routes/components/currency'; import { DateRange } from 'routes/components/dateRange'; import { GroupBy } from 'routes/components/groupBy'; import { EmptyValueState } from 'routes/components/state/emptyValueState'; +import { ProviderDetailsModal } from 'routes/details/components/providerStatus'; import type { ComputedOcpReportItemsParams } from 'routes/utils/computedReport/getComputedOcpReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOcpReportItems'; import { DateRangeType } from 'routes/utils/dateRange'; @@ -49,7 +50,8 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isDetailsDateRangeToggleEnabled: boolean; + isAccountInfoDetailsToggleEnabled?: boolean; + isDetailsDateRangeToggleEnabled?: boolean; isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; @@ -103,6 +105,7 @@ class DetailsHeaderBase extends React.Component - - -
- -
+ {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} + + + {showCostDistribution && ( @@ -226,6 +234,7 @@ const mapStateToProps = createMapStateToProps; - isAccountInfoDetailsToggleEnabled?: boolean; isAllSelected?: boolean; isLoading?: boolean; onSelect(items: ComputedReportItem[], isSelected: boolean); @@ -103,7 +100,6 @@ class DetailsTableBase extends React.Component({ @@ -182,10 +177,6 @@ class DetailsTableBase extends React.Component ), }, - { - hidden: !(isGroupByCluster && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value: monthOverMonth, id: DetailsTableColumnIds.monthOverMonth }, { value: InfrastructureCost, diff --git a/src/routes/details/ocpDetails/ocpDetails.tsx b/src/routes/details/ocpDetails/ocpDetails.tsx index bfccb8d9a..276b6e5f0 100644 --- a/src/routes/details/ocpDetails/ocpDetails.tsx +++ b/src/routes/details/ocpDetails/ocpDetails.tsx @@ -22,7 +22,7 @@ import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; import type { ColumnManagementModalOption } from 'routes/details/components/columnManagement'; import { ColumnManagementModal, initHiddenColumns } from 'routes/details/components/columnManagement'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOcpReportItems'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; @@ -59,7 +59,6 @@ export interface OcpDetailsStateProps { costDistribution?: string; currency?: string; currentDateRangeType?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -247,16 +246,8 @@ class OcpDetails extends React.Component { }; private getTable = () => { - const { - costDistribution, - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { costDistribution, query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = + this.props; const { hiddenColumns, isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -271,7 +262,6 @@ class OcpDetails extends React.Component { groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} hiddenColumns={hiddenColumns} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleOnSelect} @@ -448,7 +438,7 @@ class OcpDetails extends React.Component { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -566,7 +556,6 @@ const mapStateToProps = createMapStateToProps { currency, groupBy, intl, + isAccountInfoDetailsToggleEnabled, isCurrentMonthData, isDetailsDateRangeToggleEnabled, isExportsToggleEnabled, @@ -150,19 +153,24 @@ class DetailsHeaderBase extends React.Component { + {isAccountInfoDetailsToggleEnabled && ( + + + + + + )} - -
- -
+ + {isDetailsDateRangeToggleEnabled && ( @@ -214,6 +222,7 @@ const mapStateToProps = createMapStateToProps; - isAccountInfoDetailsToggleEnabled?: boolean; isAllSelected?: boolean; isLoading?: boolean; onSelect(items: ComputedReportItem[], isSelected: boolean); @@ -92,7 +89,6 @@ class DetailsTableBase extends React.Component ), }, - { - hidden: !(isGroupByCluster && isAccountInfoDetailsToggleEnabled), - value: ( - - ), - }, { value:
{monthOverMonth}
, id: DetailsTableColumnIds.monthOverMonth }, { value:
{InfrastructureCost}
, diff --git a/src/routes/details/rhelDetails/rhelDetails.tsx b/src/routes/details/rhelDetails/rhelDetails.tsx index dd1916e17..952840e91 100644 --- a/src/routes/details/rhelDetails/rhelDetails.tsx +++ b/src/routes/details/rhelDetails/rhelDetails.tsx @@ -21,7 +21,7 @@ import { NoProviders } from 'routes/components/page/noProviders'; import { NotAvailable } from 'routes/components/page/notAvailable'; import type { ColumnManagementModalOption } from 'routes/details/components/columnManagement'; import { ColumnManagementModal, initHiddenColumns } from 'routes/details/components/columnManagement'; -import { ProviderDetails } from 'routes/details/components/providerDetails'; +import { ProviderStatus } from 'routes/details/components/providerStatus'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getComputedReportItems'; import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedRhelReportItems'; @@ -55,7 +55,6 @@ import { styles } from './rhelDetails.styles'; interface RhelDetailsStateProps { currency?: string; - isAccountInfoDetailsToggleEnabled?: boolean; isAccountInfoEmptyStateToggleEnabled?: boolean; isCurrentMonthData?: boolean; isDetailsDateRangeToggleEnabled?: boolean; @@ -243,15 +242,7 @@ class RhelDetails extends React.Component { }; private getTable = () => { - const { - isAccountInfoDetailsToggleEnabled, - query, - report, - reportFetchStatus, - reportQueryString, - router, - timeScopeValue, - } = this.props; + const { query, report, reportFetchStatus, reportQueryString, router, timeScopeValue } = this.props; const { hiddenColumns, isAllSelected, selectedItems } = this.state; const groupById = getIdKeyForGroupBy(query.group_by); @@ -264,7 +255,6 @@ class RhelDetails extends React.Component { groupBy={groupByTagKey ? `${tagPrefix}${groupByTagKey}` : groupById} groupByTagKey={groupByTagKey} hiddenColumns={hiddenColumns} - isAccountInfoDetailsToggleEnabled={isAccountInfoDetailsToggleEnabled} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} onSelect={this.handleonSelect} @@ -427,7 +417,7 @@ class RhelDetails extends React.Component { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } title={title} /> @@ -534,7 +524,6 @@ const mapStateToProps = createMapStateToProps { return ( : undefined + isAccountInfoEmptyStateToggleEnabled ? : undefined } /> );