From af2eafe9d99495e268f5a874b6ca6529fbec6358 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Tue, 19 Sep 2023 14:13:01 -0400 Subject: [PATCH 01/33] Refactor to support optimizations breakdown --- .../optimzations/optimizationsLink.tsx | 2 +- src/components/pageTitle/pageTitle.tsx | 17 +- src/components/permissions/permissions.tsx | 17 +- src/routes.tsx | 61 +++-- .../page/notAuthorized/notAuthorizedState.tsx | 21 +- .../details/awsDetails/detailsTable.tsx | 2 +- .../details/azureDetails/detailsTable.tsx | 2 +- .../components/breakdown/breakdownHeader.tsx | 2 +- .../details/gcpDetails/detailsTable.tsx | 2 +- .../details/ibmDetails/detailsTable.tsx | 2 +- .../details/ociDetails/detailsTable.tsx | 2 +- .../ocpBreakdown/optimizationsBreakdown.tsx | 2 +- .../ocpDetails/detailsOptimization.tsx | 2 +- .../details/ocpDetails/detailsTable.tsx | 2 +- .../details/rhelDetails/detailsTable.tsx | 2 +- src/routes/optimizations/index.ts | 1 - .../optimizationsBreakdown/index.ts | 1 + .../optimizationsBreakdown.styles.ts} | 0 .../optimizationsBreakdown.tsx | 239 ++++++++++++++++++ .../optimizationsBreakdownHeader.styles.ts} | 0 .../optimizationsBreakdownHeader.tsx} | 32 ++- .../optimizationsDetails/index.ts | 1 + .../optimizationsDetails.styles.ts | 7 + .../optimizationsDetails.tsx} | 21 +- .../optimizationsDetailsHeader.styles.ts | 25 ++ .../optimizationsDetailsHeader.tsx | 74 ++++++ .../optimizationsSummary.tsx | 2 +- 27 files changed, 453 insertions(+), 88 deletions(-) delete mode 100644 src/routes/optimizations/index.ts create mode 100644 src/routes/optimizations/optimizationsBreakdown/index.ts rename src/routes/optimizations/{optimizations.styles.ts => optimizationsBreakdown/optimizationsBreakdown.styles.ts} (100%) create mode 100644 src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx rename src/routes/optimizations/{optimizationsHeader.styles.ts => optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts} (100%) rename src/routes/optimizations/{optimizationsHeader.tsx => optimizationsBreakdown/optimizationsBreakdownHeader.tsx} (61%) create mode 100644 src/routes/optimizations/optimizationsDetails/index.ts create mode 100644 src/routes/optimizations/optimizationsDetails/optimizationsDetails.styles.ts rename src/routes/optimizations/{optimizations.tsx => optimizationsDetails/optimizationsDetails.tsx} (92%) create mode 100644 src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts create mode 100644 src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx diff --git a/src/components/drawers/optimzations/optimizationsLink.tsx b/src/components/drawers/optimzations/optimizationsLink.tsx index f9dac9387..eb2fe532f 100644 --- a/src/components/drawers/optimzations/optimizationsLink.tsx +++ b/src/components/drawers/optimzations/optimizationsLink.tsx @@ -92,7 +92,7 @@ class OptimizationsLinkBase extends React.Component const isDisabled = computedItems.length === 0; const breakdownPath = getBreakdownPath({ - basePath: formatPath(routes.ocpDetailsBreakdown.path), + basePath: formatPath(routes.ocpBreakdown.path), groupBy: 'project', id: project, isOptimizationsPath: true, diff --git a/src/components/pageTitle/pageTitle.tsx b/src/components/pageTitle/pageTitle.tsx index 9fd7f88a5..d21ddba9a 100644 --- a/src/components/pageTitle/pageTitle.tsx +++ b/src/components/pageTitle/pageTitle.tsx @@ -15,34 +15,35 @@ const PageTitleBase: React.FC = ({ children = null, intl }) => { const usePageTitle = () => { const pathname = usePathname(); switch (pathname) { + case formatPath(routes.awsBreakdown.path): case formatPath(routes.awsDetails.path): - case formatPath(routes.awsDetailsBreakdown.path): return messages.pageTitleAws; + case formatPath(routes.azureBreakdown.path): case formatPath(routes.azureDetails.path): - case formatPath(routes.azureDetailsBreakdown.path): return messages.pageTitleAzure; case formatPath(routes.costModel.basePath): return messages.pageTitleCostModels; case formatPath(routes.explorer.path): return messages.pageTitleExplorer; + case formatPath(routes.gcpBreakdown.path): case formatPath(routes.gcpDetails.path): - case formatPath(routes.gcpDetailsBreakdown.path): return messages.pageTitleGcp; + case formatPath(routes.ibmBreakdown.path): case formatPath(routes.ibmDetails.path): - case formatPath(routes.ibmDetailsBreakdown.path): return messages.pageTitleIbm; + case formatPath(routes.ociBreakdown.path): case formatPath(routes.ociDetails.path): - case formatPath(routes.ociDetailsBreakdown.path): return messages.pageTitleOci; + case formatPath(routes.ocpBreakdown.path): case formatPath(routes.ocpDetails.path): - case formatPath(routes.ocpDetailsBreakdown.path): return messages.pageTitleOcp; - case formatPath(routes.optimizations.path): + case formatPath(routes.optimizationsDetails.path): + case formatPath(routes.optimizationsBreakdown.path): return messages.pageTitleOptimizations; case formatPath(routes.overview.path): return messages.pageTitleOverview; + case formatPath(routes.rhelBreakdown.path): case formatPath(routes.rhelDetails.path): - case formatPath(routes.rhelDetailsBreakdown.path): return messages.pageTitleRhel; case formatPath(routes.settings.path): return messages.pageTitleSettings; diff --git a/src/components/permissions/permissions.tsx b/src/components/permissions/permissions.tsx index ffb88f655..3731e3d20 100644 --- a/src/components/permissions/permissions.tsx +++ b/src/components/permissions/permissions.tsx @@ -73,30 +73,31 @@ const PermissionsBase: React.FC = ({ case formatPath(routes.explorer.path): case formatPath(routes.overview.path): return aws || azure || costModel || gcp || ibm || ocp || oci; + case formatPath(routes.awsBreakdown.path): case formatPath(routes.awsDetails.path): - case formatPath(routes.awsDetailsBreakdown.path): return aws; + case formatPath(routes.azureBreakdown.path): case formatPath(routes.azureDetails.path): - case formatPath(routes.azureDetailsBreakdown.path): return azure; case formatPath(routes.costModel.basePath): return costModel; + case formatPath(routes.gcpBreakdown.path): case formatPath(routes.gcpDetails.path): - case formatPath(routes.gcpDetailsBreakdown.path): return gcp; + case formatPath(routes.ociBreakdown.path): case formatPath(routes.ociDetails.path): - case formatPath(routes.ociDetailsBreakdown.path): return oci; + case formatPath(routes.ibmBreakdown.path): case formatPath(routes.ibmDetails.path): - case formatPath(routes.ibmDetailsBreakdown.path): return ibm; + case formatPath(routes.ocpBreakdown.path): case formatPath(routes.ocpDetails.path): - case formatPath(routes.ocpDetailsBreakdown.path): return ocp; - case formatPath(routes.optimizations.path): + case formatPath(routes.optimizationsBreakdown.path): + case formatPath(routes.optimizationsDetails.path): return ros; + case formatPath(routes.rhelBreakdown.path): case formatPath(routes.rhelDetails.path): - case formatPath(routes.rhelDetailsBreakdown.path): return rhel; case formatPath(routes.settings.path): return settings; diff --git a/src/routes.tsx b/src/routes.tsx index 2eb4874ab..dce5b4bac 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -18,29 +18,34 @@ const OciBreakdown = lazy(() => import(/* webpackChunkName: "ociBreakdown" */ 'r const OciDetails = lazy(() => import(/* webpackChunkName: "ociDetails" */ 'routes/details/ociDetails')); const OcpBreakdown = lazy(() => import(/* webpackChunkName: "ocpBreakdown" */ 'routes/details/ocpBreakdown')); const OcpDetails = lazy(() => import(/* webpackChunkName: "ocpDetails" */ 'routes/details/ocpDetails')); -const Optimizations = lazy(() => import(/* webpackChunkName: "recommendations" */ 'routes/optimizations')); +const OptimizationsBreakdown = lazy( + () => import(/* webpackChunkName: "recommendations" */ 'routes/optimizations/optimizationsBreakdown') +); +const OptimizationsDetails = lazy( + () => import(/* webpackChunkName: "recommendations" */ 'routes/optimizations/optimizationsDetails') +); const Overview = lazy(() => import(/* webpackChunkName: "overview" */ 'routes/overview')); const RhelDetails = lazy(() => import(/* webpackChunkName: "rhelDetails" */ 'routes/details/rhelDetails')); const RhelBreakdown = lazy(() => import(/* webpackChunkName: "rhelBreakdown" */ 'routes/details/rhelBreakdown')); const Settings = lazy(() => import(/* webpackChunkName: "overview" */ 'routes/settings')); const routes = { + awsBreakdown: { + element: userAccess(AwsBreakdown), + path: '/aws/breakdown', + }, awsDetails: { element: userAccess(AwsDetails), path: '/aws', }, - awsDetailsBreakdown: { - element: userAccess(AwsBreakdown), - path: '/aws/breakdown', + azureBreakdown: { + element: userAccess(AzureBreakdown), + path: '/azure/breakdown', }, azureDetails: { element: userAccess(AzureDetails), path: '/azure', }, - azureDetailsBreakdown: { - element: userAccess(AzureBreakdown), - path: '/azure/breakdown', - }, costModel: { // Note: Order matters here (i.e., dynamic segment must be defined after costModelsDetails) basePath: `/settings/cost-model`, @@ -51,54 +56,58 @@ const routes = { element: userAccess(Explorer), path: '/explorer', }, + gcpBreakdown: { + element: userAccess(GcpBreakdown), + path: '/gcp/breakdown', + }, gcpDetails: { element: userAccess(GcpDetails), path: '/gcp', }, - gcpDetailsBreakdown: { - element: userAccess(GcpBreakdown), - path: '/gcp/breakdown', + ibmBreakdown: { + element: userAccess(IbmBreakdown), + path: '/ibm/breakdown', }, ibmDetails: { element: userAccess(IbmDetails), path: '/ibm', }, - ibmDetailsBreakdown: { - element: userAccess(IbmBreakdown), - path: '/ibm/breakdown', + ociBreakdown: { + element: userAccess(OciBreakdown), + path: '/oci/breakdown', }, ociDetails: { element: userAccess(OciDetails), path: '/oci', }, - ociDetailsBreakdown: { - element: userAccess(OciBreakdown), - path: '/oci/breakdown', + ocpBreakdown: { + element: userAccess(OcpBreakdown), + path: '/ocp/breakdown', }, ocpDetails: { element: userAccess(OcpDetails), path: '/ocp', }, - ocpDetailsBreakdown: { - element: userAccess(OcpBreakdown), - path: '/ocp/breakdown', + optimizationsBreakdown: { + element: userAccess(OptimizationsBreakdown), + path: '/optimizations/breakdown', }, - optimizations: { - element: userAccess(Optimizations), + optimizationsDetails: { + element: userAccess(OptimizationsDetails), path: '/optimizations', }, overview: { element: userAccess(Overview), path: '/', }, + rhelBreakdown: { + element: userAccess(RhelBreakdown), + path: '/rhel/breakdown', + }, rhelDetails: { element: userAccess(RhelDetails), path: '/rhel', }, - rhelDetailsBreakdown: { - element: userAccess(RhelBreakdown), - path: '/rhel/breakdown', - }, settings: { element: userAccess(Settings), path: '/settings', diff --git a/src/routes/components/page/notAuthorized/notAuthorizedState.tsx b/src/routes/components/page/notAuthorized/notAuthorizedState.tsx index 8f2d2d3af..b5ae2bd1b 100644 --- a/src/routes/components/page/notAuthorized/notAuthorizedState.tsx +++ b/src/routes/components/page/notAuthorized/notAuthorizedState.tsx @@ -19,40 +19,41 @@ class NotAuthorizedStateBase extends React.Component { if (isOptimizationsPath) { return ( - + {intl.formatMessage(messages.breakdownBackToOptimizations)} ); diff --git a/src/routes/details/gcpDetails/detailsTable.tsx b/src/routes/details/gcpDetails/detailsTable.tsx index c15f4fde6..65fa3cc8d 100644 --- a/src/routes/details/gcpDetails/detailsTable.tsx +++ b/src/routes/details/gcpDetails/detailsTable.tsx @@ -134,7 +134,7 @@ class DetailsTableBase extends React.Component = () => { + const [query, setQuery] = useState({ ...baseQuery }); + const intl = useIntl(); + + const { closeOptimizationsDrawer, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ + query, + }); + + const getPagination = (isDisabled = false, isBottom = false) => { + const count = report && report.meta ? report.meta.count : 0; + const limit = report && report.meta ? report.meta.limit : baseQuery.limit; + const offset = report && report.meta ? report.meta.offset : baseQuery.offset; + const page = Math.trunc(offset / limit + 1); + + return ( + handleOnPerPageSelect(perPage)} + onSetPage={(event, pageNumber) => handleOnSetPage(pageNumber)} + page={page} + perPage={limit} + titles={{ + paginationTitle: intl.formatMessage(messages.paginationTitle, { + title: intl.formatMessage(messages.openShift), + placement: isBottom ? 'bottom' : 'top', + }), + }} + variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} + widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + /> + ); + }; + + const getTable = () => { + return ( + handleOnSort(sortType, isSortAscending)} + orderBy={query.order_by} + report={report} + reportQueryString={reportQueryString} + /> + ); + }; + + const getToolbar = () => { + const itemsPerPage = report && report.meta ? report.meta.limit : 0; + const itemsTotal = report && report.meta ? report.meta.count : 0; + const isDisabled = itemsTotal === 0; + + return ( + handleOnFilterAdded(filter)} + onFilterRemoved={filter => handleOnFilterRemoved(filter)} + pagination={getPagination(isDisabled)} + query={query} + /> + ); + }; + + const handleOnFilterAdded = filter => { + const newQuery = queryUtils.handleOnFilterAdded(query, filter); + setQuery(newQuery); + closeOptimizationsDrawer(); + }; + + const handleOnFilterRemoved = filter => { + const newQuery = queryUtils.handleOnFilterRemoved(query, filter); + setQuery(newQuery); + closeOptimizationsDrawer(); + }; + + const handleOnPerPageSelect = perPage => { + const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); + setQuery(newQuery); + closeOptimizationsDrawer(); + }; + + const handleOnSetPage = pageNumber => { + const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true); + setQuery(newQuery); + closeOptimizationsDrawer(); + }; + + const handleOnSort = (sortType, isSortAscending) => { + const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); + setQuery(newQuery); + closeOptimizationsDrawer(); + }; + + const itemsTotal = report && report.meta ? report.meta.count : 0; + const isDisabled = itemsTotal === 0; + const title = intl.formatMessage(messages.optimizations); + const hasOptimizations = report && report.meta && report.meta.count > 0; + + if (reportError) { + return ; + } + if (!query.filter_by && !hasOptimizations && reportFetchStatus === FetchStatus.complete) { + return ; + } + return ( +
+ + + {getToolbar()} + {reportFetchStatus === FetchStatus.inProgress ? ( + + ) : ( + <> + {getTable()} + {getPagination(isDisabled, true)} + + )} + +
+ ); +}; + +const useQueryFromRoute = () => { + const location = useLocation(); + return parseQuery(location.search); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = ({ query }: OptimizationsBreakdownMapProps): OptimizationsBreakdownStateProps => { + const dispatch: ThunkDispatch = useDispatch(); + const queryFromRoute = useQueryFromRoute(); + + const groupBy = getGroupById(queryFromRoute); + const groupByValue = getGroupByValue(queryFromRoute); + const order_by = getOrderById(query) || getOrderById(baseQuery); + const order_how = getOrderByValue(query) || getOrderByValue(baseQuery); + + const reportQuery = { + ...(groupBy && { + [groupBy]: groupByValue, // Flattened project filter + }), + ...query.filter_by, // Flattened filter by + limit: query.limit, + offset: query.offset, + order_by, // Flattened order by + order_how, // Flattened order how + }; + const reportQueryString = getQuery(reportQuery); + const report = useSelector((state: RootState) => + rosSelectors.selectRos(state, reportPathsType, reportType, reportQueryString) + ); + const reportFetchStatus = useSelector((state: RootState) => + rosSelectors.selectRosFetchStatus(state, reportPathsType, reportType, reportQueryString) + ); + const reportError = useSelector((state: RootState) => + rosSelectors.selectRosError(state, reportPathsType, reportType, reportQueryString) + ); + + useEffect(() => { + if (!reportError && reportFetchStatus !== FetchStatus.inProgress) { + dispatch(rosActions.fetchRosReport(reportPathsType, reportType, reportQueryString)); + } + }, [query]); + + return { + closeOptimizationsDrawer: uiActions.closeOptimizationsDrawer, + report, + reportError, + reportFetchStatus, + reportQueryString, + }; +}; + +export default OptimizationsBreakdown; diff --git a/src/routes/optimizations/optimizationsHeader.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts similarity index 100% rename from src/routes/optimizations/optimizationsHeader.styles.ts rename to src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts diff --git a/src/routes/optimizations/optimizationsHeader.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx similarity index 61% rename from src/routes/optimizations/optimizationsHeader.tsx rename to src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx index 7b2766f19..f3745b93f 100644 --- a/src/routes/optimizations/optimizationsHeader.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx @@ -7,23 +7,28 @@ import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { createMapStateToProps } from 'store/common'; -import { styles } from './optimizationsHeader.styles'; +import { styles } from './optimizationsBreakdownHeader.styles'; -interface OptimizationsHeaderOwnProps { +interface OptimizationsBreakdownHeaderOwnProps { // TBD... } -interface OptimizationsHeaderStateProps { +interface OptimizationsBreakdownHeaderStateProps { // TBD... } -interface OptimizationsHeaderState {} +interface OptimizationsBreakdownHeaderState {} -type OptimizationsHeaderProps = OptimizationsHeaderOwnProps & OptimizationsHeaderStateProps & WrappedComponentProps; +type OptimizationsBreakdownHeaderProps = OptimizationsBreakdownHeaderOwnProps & + OptimizationsBreakdownHeaderStateProps & + WrappedComponentProps; -class OptimizationsHeaderBase extends React.Component { - protected defaultState: OptimizationsHeaderState = {}; - public state: OptimizationsHeaderState = { ...this.defaultState }; +class OptimizationsBreakdownHeaderBase extends React.Component< + OptimizationsBreakdownHeaderProps, + OptimizationsBreakdownHeaderState +> { + protected defaultState: OptimizationsBreakdownHeaderState = {}; + public state: OptimizationsBreakdownHeaderState = { ...this.defaultState }; public render() { const { intl } = this.props; @@ -55,13 +60,16 @@ class OptimizationsHeaderBase extends React.Component(() => { +const mapStateToProps = createMapStateToProps< + OptimizationsBreakdownHeaderOwnProps, + OptimizationsBreakdownHeaderStateProps +>(() => { return { // TBD... }; }); -const OptimizationsHeader = injectIntl(connect(mapStateToProps, {})(OptimizationsHeaderBase)); +const OptimizationsBreakdownHeader = injectIntl(connect(mapStateToProps, {})(OptimizationsBreakdownHeaderBase)); -export { OptimizationsHeader }; -export type { OptimizationsHeaderProps }; +export { OptimizationsBreakdownHeader }; +export type { OptimizationsBreakdownHeaderProps }; diff --git a/src/routes/optimizations/optimizationsDetails/index.ts b/src/routes/optimizations/optimizationsDetails/index.ts new file mode 100644 index 000000000..7a95b829d --- /dev/null +++ b/src/routes/optimizations/optimizationsDetails/index.ts @@ -0,0 +1 @@ +export { default } from './optimizationsDetails'; diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.styles.ts b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.styles.ts new file mode 100644 index 000000000..9c04e466e --- /dev/null +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.styles.ts @@ -0,0 +1,7 @@ +import type React from 'react'; + +export const styles = { + container: { + minHeight: '100vh', + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizations.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx similarity index 92% rename from src/routes/optimizations/optimizations.tsx rename to src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx index 4807a3bf0..fa185705c 100644 --- a/src/routes/optimizations/optimizations.tsx +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx @@ -16,7 +16,6 @@ import { OptimizationsTable, OptimizationsToolbar } from 'routes/components/opti import { Loading } from 'routes/components/page/loading'; import { NoOptimizations } from 'routes/components/page/noOptimizations'; import { NotAvailable } from 'routes/components/page/notAvailable'; -// import { styles } from 'routes/optimizations/optimizations.styles'; import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; import { getOrderById, getOrderByValue } from 'routes/utils/orderBy'; import * as queryUtils from 'routes/utils/query'; @@ -25,14 +24,14 @@ import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; import { uiActions } from 'store/ui'; -import { styles } from './optimizations.styles'; -import { OptimizationsHeader } from './optimizationsHeader'; +import { styles } from './optimizationsDetails.styles'; +import { OptimizationsDetailsHeader } from './optimizationsDetailsHeader'; -interface OptimizationsOwnProps { +interface OptimizationsDetailsOwnProps { // TBD... } -export interface OptimizationsStateProps { +export interface OptimizationsDetailsStateProps { closeOptimizationsDrawer: typeof uiActions.closeOptimizationsDrawer; report: RosReport; reportError: AxiosError; @@ -40,11 +39,11 @@ export interface OptimizationsStateProps { reportQueryString: string; } -export interface OptimizationsMapProps { +export interface OptimizationsDetailsMapProps { query?: RosQuery; } -type OptimizationsProps = OptimizationsOwnProps; +type OptimizationsDetailsProps = OptimizationsDetailsOwnProps; const baseQuery: RosQuery = { limit: 10, @@ -57,7 +56,7 @@ const baseQuery: RosQuery = { const reportType = RosType.ros as any; const reportPathsType = RosPathsType.recommendations as any; -const Optimizations: React.FC = () => { +const OptimizationsDetails: React.FC = () => { const [query, setQuery] = useState({ ...baseQuery }); const intl = useIntl(); @@ -167,7 +166,7 @@ const Optimizations: React.FC = () => { } return (
- + {getToolbar()} {reportFetchStatus === FetchStatus.inProgress ? ( @@ -192,7 +191,7 @@ const useQueryFromRoute = () => { }; // eslint-disable-next-line no-empty-pattern -const useMapToProps = ({ query }: OptimizationsMapProps): OptimizationsStateProps => { +const useMapToProps = ({ query }: OptimizationsDetailsMapProps): OptimizationsDetailsStateProps => { const dispatch: ThunkDispatch = useDispatch(); const queryFromRoute = useQueryFromRoute(); @@ -237,4 +236,4 @@ const useMapToProps = ({ query }: OptimizationsMapProps): OptimizationsStateProp }; }; -export default Optimizations; +export default OptimizationsDetails; diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts b/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts new file mode 100644 index 000000000..2eb26e8f8 --- /dev/null +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts @@ -0,0 +1,25 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_FontSize_md from '@patternfly/react-tokens/dist/js/global_FontSize_md'; +import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg'; +import global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm'; +import type React from 'react'; + +export const styles = { + header: { + backgroundColor: global_BackgroundColor_light_100.var, + padding: global_spacer_lg.var, + }, + headerContent: { + display: 'flex', + justifyContent: 'space-between', + }, + infoIcon: { + fontSize: global_FontSize_md.value, + }, + infoTitle: { + fontWeight: 'bold', + }, + title: { + paddingBottom: global_spacer_sm.var, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx new file mode 100644 index 000000000..f08081988 --- /dev/null +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx @@ -0,0 +1,74 @@ +import { Button, ButtonVariant, Popover, Title, TitleSizes } from '@patternfly/react-core'; +import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon'; +import messages from 'locales/messages'; +import React from 'react'; +import type { WrappedComponentProps } from 'react-intl'; +import { injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import { createMapStateToProps } from 'store/common'; + +import { styles } from './optimizationsDetailsHeader.styles'; + +interface OptimizationsDetailsHeaderOwnProps { + // TBD... +} + +interface OptimizationsDetailsHeaderStateProps { + // TBD... +} + +interface OptimizationsDetailsHeaderState {} + +type OptimizationsDetailsHeaderProps = OptimizationsDetailsHeaderOwnProps & + OptimizationsDetailsHeaderStateProps & + WrappedComponentProps; + +class OptimizationsDetailsHeaderBase extends React.Component< + OptimizationsDetailsHeaderProps, + OptimizationsDetailsHeaderState +> { + protected defaultState: OptimizationsDetailsHeaderState = {}; + public state: OptimizationsDetailsHeaderState = { ...this.defaultState }; + + public render() { + const { intl } = this.props; + + return ( +
+
+ + {intl.formatMessage(messages.optimizations)} + <span style={styles.infoIcon}> + <Popover + aria-label={intl.formatMessage(messages.optimizationsInfoArialLabel)} + enableFlip + bodyContent={<p style={styles.infoTitle}>{intl.formatMessage(messages.optimizationsInfo)}</p>} + > + <Button + aria-label={intl.formatMessage(messages.optimizationsInfoButtonArialLabel)} + variant={ButtonVariant.plain} + > + <OutlinedQuestionCircleIcon /> + </Button> + </Popover> + </span> + +
+
+ ); + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const mapStateToProps = createMapStateToProps( + () => { + return { + // TBD... + }; + } +); + +const OptimizationsDetailsHeader = injectIntl(connect(mapStateToProps, {})(OptimizationsDetailsHeaderBase)); + +export { OptimizationsDetailsHeader }; +export type { OptimizationsDetailsHeaderProps }; diff --git a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx b/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx index 3d6b9588c..01e441600 100644 --- a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx +++ b/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx @@ -63,7 +63,7 @@ const OptimizationsSummaryBase: React.FC = ({ intl, r ) : count > 0 ? ( - {description} + {description} ) : ( description )} From 27fedc05bb6e2cb9dcf171c4e2e1b2ce6dbd7397 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Mon, 25 Sep 2023 09:17:18 -0400 Subject: [PATCH 02/33] Move optimizations drawer to full page --- src/api/queries/query.ts | 1 + src/routes/components/dataTable/dataTable.tsx | 5 +- .../optimizations/optimizationsTable.tsx | 52 +- .../optimizationsBreakdown.scss | 40 ++ .../optimizationsBreakdown.styles.ts | 23 + .../optimizationsBreakdown.tsx | 443 ++++++++++++------ .../optimizationsBreakdownHeader.tsx | 87 +++- .../optimizationsBreakdownToolbar.tsx | 91 ++++ src/routes/utils/paths.ts | 15 +- 9 files changed, 579 insertions(+), 178 deletions(-) create mode 100644 src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss create mode 100644 src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx diff --git a/src/api/queries/query.ts b/src/api/queries/query.ts index 564a94e97..b590187a9 100644 --- a/src/api/queries/query.ts +++ b/src/api/queries/query.ts @@ -23,6 +23,7 @@ export interface Query { filter?: any; filter_by?: any; group_by?: any; + id?: string; isPlatformCosts?: boolean; key?: string; key_only?: boolean; diff --git a/src/routes/components/dataTable/dataTable.tsx b/src/routes/components/dataTable/dataTable.tsx index 47d322661..1f2438af5 100644 --- a/src/routes/components/dataTable/dataTable.tsx +++ b/src/routes/components/dataTable/dataTable.tsx @@ -20,6 +20,7 @@ interface DataTableOwnProps { filterBy: any; isActionsCell?: boolean; isLoading?: boolean; + isSelectable?: boolean; onSelected(items: any[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy: any; @@ -108,7 +109,7 @@ class DataTable extends React.Component { }; public render() { - const { columns, intl, isActionsCell = false, isLoading, rows } = this.props; + const { columns, intl, isActionsCell = false, isLoading, isSelectable = true, rows } = this.props; return ( <> @@ -147,7 +148,7 @@ class DataTable extends React.Component { rows.map((row, rowIndex) => ( {row.cells.map((item, cellIndex) => - cellIndex === 0 ? ( + cellIndex === 0 && isSelectable ? ( { - const { groupBy, intl, report } = this.props; + const { groupBy, intl, report, router } = this.props; if (!report) { return; } @@ -127,7 +131,21 @@ class OptimizationsTableBase extends React.Component + {container} + + ), + }, { value: project, hidden: groupBy === 'project' }, { value: workload }, { value: workloadType }, @@ -174,18 +192,18 @@ class OptimizationsTableBase extends React.Component { - const { closeOptimizationsDrawer, isOpen, openOptimizationsDrawer } = this.props; - const { currentRow, rows } = this.state; - - this.setState({ currentRow: rowIndex }, () => { - if (currentRow === rowIndex && isOpen) { - closeOptimizationsDrawer(); - } else { - openOptimizationsDrawer(rows[rowIndex].optimization); - } - }); - }; + // private handleOnRowClick = (event: React.KeyboardEvent | React.MouseEvent, rowIndex: number) => { + // const { closeOptimizationsDrawer, isOpen, openOptimizationsDrawer } = this.props; + // const { currentRow, rows } = this.state; + // + // this.setState({ currentRow: rowIndex }, () => { + // if (currentRow === rowIndex && isOpen) { + // closeOptimizationsDrawer(); + // } else { + // openOptimizationsDrawer(rows[rowIndex].optimization); + // } + // }); + // }; private handleOnSort = (value: string, isSortAscending: boolean) => { const { closeOptimizationsDrawer, onSort } = this.props; @@ -201,16 +219,16 @@ class OptimizationsTableBase extends React.Component} filterBy={filterBy} isLoading={isLoading} isOptimizations + isSelectable={false} onSort={this.handleOnSort} orderBy={orderBy} rows={rows} - onRowClick={this.handleOnRowClick} /> ); } diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss new file mode 100644 index 000000000..fea98da1d --- /dev/null +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss @@ -0,0 +1,40 @@ +@import url("~@patternfly/patternfly/base/patternfly-variables.css"); + +.optimizationsOverride { + div { + display: block; + margin-right: 0; + margin-bottom: var(--pf-global--spacer--xs); + &.iconOverride { + &.decrease { + // color: var(--pf-global--success-color--100); + } + &.increase { + // color: var(--pf-global--danger-color--100); + } + .fa-equals { + margin-left: 25px; + position: relative; + } + .fa-sort-up { + margin-left: 25px; + position: relative; + top: 3px; + } + .fa-sort-down { + margin-left: 25px; + position: relative; + top: -3px; + } + .fa-sort-up::before { + // color: var(--pf-global--danger-color--100); + } + .fa-sort-down::before { + // color: var(--pf-global--success-color--100); + } + span { + margin-right: -17px !important; + } + } + } +} diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts index 9c04e466e..58ad432e2 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts @@ -1,7 +1,30 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_FontWeight_bold from '@patternfly/react-tokens/dist/js/global_FontWeight_bold'; +import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg'; import type React from 'react'; export const styles = { + bullseye: { + marginTop: global_spacer_lg.value, + }, container: { minHeight: '100vh', }, + firstColumn: { + width: '225px', + }, + tableContainer: { + marginTop: global_spacer_lg.value, + }, + toolbarContainer: { + backgroundColor: global_BackgroundColor_light_100.value, + paddingBottom: global_spacer_lg.value, + paddingTop: global_spacer_lg.value, + }, + value: { + fontWeight: global_FontWeight_bold.value, + }, + viewAllContainer: { + marginTop: global_spacer_lg.value, + }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx index 90ac89628..793ab00a1 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx @@ -1,183 +1,376 @@ -import { PageSection, Pagination, PaginationVariant } from '@patternfly/react-core'; +import './optimizationsBreakdown.scss'; + +import { Alert, List, ListItem, PageSection } from '@patternfly/react-core'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; +import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery } from 'api/queries/query'; +import { parseQuery } from 'api/queries/query'; import type { RosQuery } from 'api/queries/rosQuery'; -import type { RosReport } from 'api/ros/ros'; +import type { RecommendationItem, RecommendationReportData } from 'api/ros/recommendations'; import { RosPathsType, RosType } from 'api/ros/ros'; import type { AxiosError } from 'axios'; import messages from 'locales/messages'; import React, { useEffect, useState } from 'react'; +import type { WrappedComponentProps } from 'react-intl'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; -import { OptimizationsTable, OptimizationsToolbar } from 'routes/components/optimizations'; import { Loading } from 'routes/components/page/loading'; -import { NoOptimizations } from 'routes/components/page/noOptimizations'; -import { NotAvailable } from 'routes/components/page/notAvailable'; -import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; -import { getOrderById, getOrderByValue } from 'routes/utils/orderBy'; -import * as queryUtils from 'routes/utils/query'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; -import { uiActions } from 'store/ui'; +import { formatOptimization } from 'utils/format'; +import { getNotifications, hasRecommendation, hasRecommendationValues } from 'utils/recomendations'; +import type { RouterComponentProps } from 'utils/router'; import { styles } from './optimizationsBreakdown.styles'; import { OptimizationsBreakdownHeader } from './optimizationsBreakdownHeader'; -interface OptimizationsBreakdownOwnProps { - // TBD... +interface OptimizationsBreakdownOwnProps extends RouterComponentProps { + id?: string; +} + +interface OptimizationsBreakdownStateProps { + report?: RecommendationReportData; + reportError?: AxiosError; + reportFetchStatus?: FetchStatus; + reportQueryString?: string; } -export interface OptimizationsBreakdownStateProps { - closeOptimizationsDrawer: typeof uiActions.closeOptimizationsDrawer; - report: RosReport; - reportError: AxiosError; - reportFetchStatus: FetchStatus; - reportQueryString: string; +interface OptimizationsBreakdownDispatchProps { + fetchRosReport: typeof rosActions.fetchRosReport; } export interface OptimizationsBreakdownMapProps { query?: RosQuery; } -type OptimizationsBreakdownProps = OptimizationsBreakdownOwnProps; +type OptimizationsBreakdownProps = OptimizationsBreakdownOwnProps & + OptimizationsBreakdownStateProps & + OptimizationsBreakdownDispatchProps & + WrappedComponentProps; -const baseQuery: RosQuery = { - limit: 10, - offset: 0, - order_by: { - last_reported: 'desc', - }, -}; +// eslint-disable-next-line no-shadow +export const enum Interval { + short_term = 'short_term', // last 24 hrs + medium_term = 'medium_term', // last 7 days + long_term = 'long_term', // last 15 days +} const reportType = RosType.ros as any; -const reportPathsType = RosPathsType.recommendations as any; +const reportPathsType = RosPathsType.recommendation as any; const OptimizationsBreakdown: React.FC = () => { - const [query, setQuery] = useState({ ...baseQuery }); + const { report, reportFetchStatus } = useMapToProps(); const intl = useIntl(); - const { closeOptimizationsDrawer, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ - query, - }); + const getDefaultTerm = () => { + let result = Interval.short_term; + if (!(report && report.recommendations && report.recommendations.duration_based)) { + return result; + } + + const recommendation = report.recommendations.duration_based; + if (hasRecommendation(recommendation.short_term)) { + result = Interval.short_term; + } else if (hasRecommendation(recommendation.medium_term)) { + result = Interval.medium_term; + } else if (hasRecommendation(recommendation.long_term)) { + result = Interval.long_term; + } + return result as Interval; + }; + + const [currentInterval, setCurrentInterval] = useState(getDefaultTerm()); - const getPagination = (isDisabled = false, isBottom = false) => { - const count = report && report.meta ? report.meta.count : 0; - const limit = report && report.meta ? report.meta.limit : baseQuery.limit; - const offset = report && report.meta ? report.meta.offset : baseQuery.offset; - const page = Math.trunc(offset / limit + 1); + const getAlert = () => { + let notifications; + if (report?.recommendations?.duration_based?.[currentInterval]) { + notifications = getNotifications(report.recommendations.duration_based[currentInterval]); + } + + if (!notifications) { + return null; + } return ( - handleOnPerPageSelect(perPage)} - onSetPage={(event, pageNumber) => handleOnSetPage(pageNumber)} - page={page} - perPage={limit} - titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { - title: intl.formatMessage(messages.openShift), - placement: isBottom ? 'bottom' : 'top', - }), - }} - variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} - /> + + + {notifications.map((notification, index) => ( + {notification.message} + ))} + + ); }; - const getTable = () => { + const getChangeValue = (value, units = '') => { + // Show icon opposite of month over month + let iconOverride = 'iconOverride'; + if (value !== null && value < 0) { + iconOverride += ' decrease'; + } else if (value !== null && value > 0) { + iconOverride += ' increase'; + } return ( - handleOnSort(sortType, isSortAscending)} - orderBy={query.order_by} - report={report} - reportQueryString={reportQueryString} - /> +
+
+ {value < 0 ? ( + <> + + {intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + })} + + + + ) : value > 0 ? ( + <> + + {intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + })} + + + + ) : value === 0 ? ( + <> + + {intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + })} + + + + ) : ( + + )} +
+
); }; - const getToolbar = () => { - const itemsPerPage = report && report.meta ? report.meta.limit : 0; - const itemsTotal = report && report.meta ? report.meta.count : 0; - const isDisabled = itemsTotal === 0; + const getLimitsTable = () => { + if (!report) { + return null; + } + + const term = getRecommendationTerm(); + if (!hasRecommendation(term)) { + return null; + } + + const hasConfigLimitsCpu = hasRecommendationValues(term, 'config', 'limits', 'cpu'); + const hasConfigLimitsMemory = hasRecommendationValues(term, 'config', 'limits', 'memory'); + const hasCurrentLimitsCpu = hasRecommendationValues(term, 'current', 'limits', 'cpu'); + const hasCurrentLimitsMemory = hasRecommendationValues(term, 'current', 'limits', 'memory'); + const hasVariationLimitsCpu = hasRecommendationValues(term, 'variation', 'limits', 'cpu'); + const hasVariationLimitsMemory = hasRecommendationValues(term, 'variation', 'limits', 'memory'); + + const cpuConfigAmount = hasConfigLimitsCpu ? term.config.limits.cpu.amount : undefined; + const cpuConfigUnits = hasConfigLimitsCpu ? term.config.limits.cpu.format : undefined; + const cpuCurrentAmount = hasCurrentLimitsCpu ? term.current.limits.cpu.amount : undefined; + const cpuCurrentUnits = hasCurrentLimitsCpu ? term.current.limits.cpu.format : undefined; + const cpuVariation = hasVariationLimitsCpu ? term.variation.limits.cpu.amount : undefined; + const cpuVariationUnits = hasVariationLimitsCpu ? term.variation.limits.cpu.format : undefined; + + const memConfigAmount = hasConfigLimitsMemory ? term.config.limits.memory.amount : undefined; + const memConfigUnits = hasConfigLimitsMemory ? term.config.limits.memory.format : undefined; + const memCurrentAmount = hasCurrentLimitsMemory ? term.current.limits.memory.amount : undefined; + const memCurrentUnits = hasCurrentLimitsMemory ? term.current.limits.memory.format : undefined; + const memVariation = hasVariationLimitsMemory ? term.variation.limits.memory.amount : undefined; + const memVariationUnits = hasVariationLimitsMemory ? term.variation.limits.memory.format : undefined; return ( - handleOnFilterAdded(filter)} - onFilterRemoved={filter => handleOnFilterRemoved(filter)} - pagination={getPagination(isDisabled)} - query={query} - /> + + + + {intl.formatMessage(messages.limits)} + {intl.formatMessage(messages.current)} + {intl.formatMessage(messages.recommended)} + {intl.formatMessage(messages.change)} + + + + + {intl.formatMessage(messages.cpuTitle)} + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(cpuCurrentAmount), + units: cpuCurrentUnits, + })} + + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(cpuConfigAmount), + units: cpuConfigUnits, + })} + + {getChangeValue(cpuVariation, cpuVariationUnits)} + + + {intl.formatMessage(messages.memoryTitle)} + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(memCurrentAmount), + units: memCurrentUnits, + })} + + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(memConfigAmount), + units: memConfigUnits, + })} + + {getChangeValue(memVariation, memVariationUnits)} + + + ); }; - const handleOnFilterAdded = filter => { - const newQuery = queryUtils.handleOnFilterAdded(query, filter); - setQuery(newQuery); - closeOptimizationsDrawer(); + const getFormattedValue = value => { + return value !== undefined ? formatOptimization(value) : ; }; - const handleOnFilterRemoved = filter => { - const newQuery = queryUtils.handleOnFilterRemoved(query, filter); - setQuery(newQuery); - closeOptimizationsDrawer(); - }; + const getRecommendationTerm = (): RecommendationItem => { + if (!report) { + return undefined; + } - const handleOnPerPageSelect = perPage => { - const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); - setQuery(newQuery); - closeOptimizationsDrawer(); + let result; + switch (currentInterval) { + case Interval.short_term: + result = report.recommendations.duration_based.short_term; + break; + case Interval.medium_term: + result = report.recommendations.duration_based.medium_term; + break; + case Interval.long_term: + result = report.recommendations.duration_based.long_term; + break; + } + return result; }; - const handleOnSetPage = pageNumber => { - const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true); - setQuery(newQuery); - closeOptimizationsDrawer(); + const getRequestsTable = () => { + if (!report) { + return null; + } + const term = getRecommendationTerm(); + if (!hasRecommendation(term)) { + return null; + } + + const hasConfigRequestsCpu = hasRecommendationValues(term, 'config', 'requests', 'cpu'); + const hasConfigRequestsMemory = hasRecommendationValues(term, 'config', 'requests', 'memory'); + const hasCurrentLimitsCpu = hasRecommendationValues(term, 'current', 'requests', 'cpu'); + const hasCurrentLimitsMemory = hasRecommendationValues(term, 'current', 'requests', 'memory'); + const hasVariationRequestsCpu = hasRecommendationValues(term, 'variation', 'requests', 'cpu'); + const hasVariationRequestsMemory = hasRecommendationValues(term, 'variation', 'requests', 'memory'); + + const cpuConfigAmount = hasConfigRequestsCpu ? term.config.requests.cpu.amount : undefined; + const cpuConfigUnits = hasConfigRequestsCpu ? term.config.requests.cpu.format : undefined; + const cpuCurrentAmount = hasCurrentLimitsCpu ? term.current.requests.cpu.amount : undefined; + const cpuCurrentUnits = hasCurrentLimitsCpu ? term.current.requests.cpu.format : undefined; + const cpuVariation = hasVariationRequestsCpu ? term.variation.requests.cpu.amount : undefined; + const cpuVariationUnits = hasVariationRequestsCpu ? term.variation.requests.cpu.format : undefined; + + const memConfigAmount = hasConfigRequestsMemory ? term.config.requests.memory.amount : undefined; + const memConfigUnits = hasConfigRequestsMemory ? term.config.requests.memory.format : undefined; + const memCurrentAmount = hasCurrentLimitsMemory ? term.current.requests.memory.amount : undefined; + const memCurrentUnits = hasCurrentLimitsMemory ? term.current.requests.memory.format : undefined; + const memVariation = hasVariationRequestsMemory ? term.variation.requests.memory.amount : undefined; + const memVariationUnits = hasVariationRequestsMemory ? term.variation.requests.memory.format : undefined; + + return ( + + + + {intl.formatMessage(messages.requests)} + {intl.formatMessage(messages.current)} + {intl.formatMessage(messages.recommended)} + {intl.formatMessage(messages.change)} + + + + + {intl.formatMessage(messages.cpuTitle)} + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(cpuCurrentAmount), + units: cpuCurrentUnits, + })} + + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(cpuConfigAmount), + units: cpuConfigUnits, + })} + + {getChangeValue(cpuVariation, cpuVariationUnits)} + + + {intl.formatMessage(messages.memoryTitle)} + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(memCurrentAmount), + units: memCurrentUnits, + })} + + + {intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(memConfigAmount), + units: memConfigUnits, + })} + + {getChangeValue(memVariation, memVariationUnits)} + + + + ); }; - const handleOnSort = (sortType, isSortAscending) => { - const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); - setQuery(newQuery); - closeOptimizationsDrawer(); + const handleOnSelected = (value: Interval) => { + setCurrentInterval(value); }; - const itemsTotal = report && report.meta ? report.meta.count : 0; - const isDisabled = itemsTotal === 0; - const title = intl.formatMessage(messages.optimizations); - const hasOptimizations = report && report.meta && report.meta.count > 0; - - if (reportError) { - return ; - } - if (!query.filter_by && !hasOptimizations && reportFetchStatus === FetchStatus.complete) { - return ; - } + const isLoading = reportFetchStatus === FetchStatus.inProgress; + return (
- + - {getToolbar()} - {reportFetchStatus === FetchStatus.inProgress ? ( + {isLoading ? ( ) : ( <> - {getTable()} - {getPagination(isDisabled, true)} +
{getAlert()}
+
{getRequestsTable()}
+
{getLimitsTable()}
)}
@@ -191,27 +384,12 @@ const useQueryFromRoute = () => { }; // eslint-disable-next-line no-empty-pattern -const useMapToProps = ({ query }: OptimizationsBreakdownMapProps): OptimizationsBreakdownStateProps => { +const useMapToProps = (): OptimizationsBreakdownStateProps => { const dispatch: ThunkDispatch = useDispatch(); const queryFromRoute = useQueryFromRoute(); - const groupBy = getGroupById(queryFromRoute); - const groupByValue = getGroupByValue(queryFromRoute); - const order_by = getOrderById(query) || getOrderById(baseQuery); - const order_how = getOrderByValue(query) || getOrderByValue(baseQuery); - - const reportQuery = { - ...(groupBy && { - [groupBy]: groupByValue, // Flattened project filter - }), - ...query.filter_by, // Flattened filter by - limit: query.limit, - offset: query.offset, - order_by, // Flattened order by - order_how, // Flattened order how - }; - const reportQueryString = getQuery(reportQuery); - const report = useSelector((state: RootState) => + const reportQueryString = queryFromRoute ? queryFromRoute.id : ''; + const report: any = useSelector((state: RootState) => rosSelectors.selectRos(state, reportPathsType, reportType, reportQueryString) ); const reportFetchStatus = useSelector((state: RootState) => @@ -225,10 +403,9 @@ const useMapToProps = ({ query }: OptimizationsBreakdownMapProps): Optimizations if (!reportError && reportFetchStatus !== FetchStatus.inProgress) { dispatch(rosActions.fetchRosReport(reportPathsType, reportType, reportQueryString)); } - }, [query]); + }, [reportQueryString]); return { - closeOptimizationsDrawer: uiActions.closeOptimizationsDrawer, report, reportError, reportFetchStatus, diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx index f3745b93f..28fadff8d 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx @@ -1,16 +1,31 @@ -import { Button, ButtonVariant, Popover, Title, TitleSizes } from '@patternfly/react-core'; -import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon'; +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, + TitleSizes, +} from '@patternfly/react-core'; +import type { Query } from 'api/queries/query'; +import type { RecommendationReportData } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { createMapStateToProps } from 'store/common'; +import { getTimeFromNow } from 'utils/dates'; import { styles } from './optimizationsBreakdownHeader.styles'; +import { OptimizationsBreakdownToolbar } from './optimizationsBreakdownToolbar'; interface OptimizationsBreakdownHeaderOwnProps { - // TBD... + currentInterval?: string; + isDisabled?: boolean; + onSelected?: (value: string) => void; + query?: Query; + report?: RecommendationReportData; } interface OptimizationsBreakdownHeaderStateProps { @@ -30,29 +45,62 @@ class OptimizationsBreakdownHeaderBase extends React.Component< protected defaultState: OptimizationsBreakdownHeaderState = {}; public state: OptimizationsBreakdownHeaderState = { ...this.defaultState }; + private getDescription = () => { + const { intl, report } = this.props; + + const clusterAlias = report && report.cluster_alias ? report.cluster_alias : undefined; + const clusterUuid = report && report.cluster_uuid ? report.cluster_uuid : ''; + const cluster = clusterAlias ? clusterAlias : clusterUuid; + + const lastReported = report ? getTimeFromNow(report.last_reported) : ''; + const project = report && report.project ? report.project : ''; + const workload = report && report.workload ? report.workload : ''; + const workloadType = report && report.workload_type ? report.workload_type : ''; + + return ( + + + + {intl.formatMessage(messages.optimizationsValues, { value: 'last_reported' })} + + {lastReported} + + {intl.formatMessage(messages.optimizationsValues, { value: 'cluster' })} + + {cluster} + + {intl.formatMessage(messages.optimizationsValues, { value: 'project' })} + + {project} + + {intl.formatMessage(messages.optimizationsValues, { value: 'workload_type' })} + + {workloadType} + + {intl.formatMessage(messages.optimizationsValues, { value: 'workload' })} + + {workload} + + + ); + }; + public render() { - const { intl } = this.props; + const { currentInterval, isDisabled, onSelected, report } = this.props; return (
- {intl.formatMessage(messages.optimizations)} - <span style={styles.infoIcon}> - <Popover - aria-label={intl.formatMessage(messages.optimizationsInfoArialLabel)} - enableFlip - bodyContent={<p style={styles.infoTitle}>{intl.formatMessage(messages.optimizationsInfo)}</p>} - > - <Button - aria-label={intl.formatMessage(messages.optimizationsInfoButtonArialLabel)} - variant={ButtonVariant.plain} - > - <OutlinedQuestionCircleIcon /> - </Button> - </Popover> - </span> + {report ? report.container : null} + {this.getDescription()} +
); @@ -72,4 +120,3 @@ const mapStateToProps = createMapStateToProps< const OptimizationsBreakdownHeader = injectIntl(connect(mapStateToProps, {})(OptimizationsBreakdownHeaderBase)); export { OptimizationsBreakdownHeader }; -export type { OptimizationsBreakdownHeaderProps }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx new file mode 100644 index 000000000..9dc7b4980 --- /dev/null +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx @@ -0,0 +1,91 @@ +import type { Query } from 'api/queries/query'; +import type { RecommendationItems } from 'api/ros/recommendations'; +import messages from 'locales/messages'; +import React from 'react'; +import { connect } from 'react-redux'; +import { PerspectiveSelect } from 'routes/components/perspective/perspectiveSelect'; +import { createMapStateToProps } from 'store/common'; +import { hasNotification, hasRecommendation } from 'utils/recomendations'; + +import { Interval } from './optimizationsBreakdown'; + +interface OptimizationsBreakdownToolbarOwnProps { + currentInterval?: string; + isDisabled?: boolean; + onSelected?: (value: string) => void; + query?: Query; + recommendations?: RecommendationItems; +} + +interface OptimizationsBreakdownToolbarStateProps { + // TDB... +} + +interface OptimizationsBreakdownToolbarDispatchProps { + // TDB... +} + +interface OptimizationsBreakdownToolbarState {} + +type OptimizationsBreakdownToolbarProps = OptimizationsBreakdownToolbarOwnProps & + OptimizationsBreakdownToolbarStateProps & + OptimizationsBreakdownToolbarDispatchProps; + +export class OptimizationsBreakdownToolbarBase extends React.Component { + protected defaultState: OptimizationsBreakdownToolbarState = { + // TBD... + }; + public state: OptimizationsBreakdownToolbarState = { ...this.defaultState }; + + private getOptions = () => { + const { recommendations } = this.props; + + return [ + { + isDisabled: !hasRecommendation(recommendations?.short_term) && !hasNotification(recommendations?.short_term), + label: messages.optimizationsShortTerm, + value: Interval.short_term, + }, + { + isDisabled: !hasRecommendation(recommendations?.medium_term) && !hasNotification(recommendations?.medium_term), + label: messages.optimizationsMediumTerm, + value: Interval.medium_term, + }, + { + isDisabled: !hasRecommendation(recommendations?.long_term) && !hasNotification(recommendations?.long_term), + label: messages.optimizationsLongTerm, + value: Interval.long_term, + }, + ]; + }; + + public render() { + const { currentInterval, isDisabled, onSelected } = this.props; + + const options = this.getOptions(); + + return ( + + ); + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const mapStateToProps = createMapStateToProps< + OptimizationsBreakdownToolbarOwnProps, + OptimizationsBreakdownToolbarStateProps +>(() => { + return {}; +}); + +const mapDispatchToProps: OptimizationsBreakdownToolbarDispatchProps = {}; + +const OptimizationsBreakdownToolbar = connect(mapStateToProps, mapDispatchToProps)(OptimizationsBreakdownToolbarBase); + +export { OptimizationsBreakdownToolbar }; diff --git a/src/routes/utils/paths.ts b/src/routes/utils/paths.ts index a9d190514..adf585ba7 100644 --- a/src/routes/utils/paths.ts +++ b/src/routes/utils/paths.ts @@ -25,18 +25,21 @@ export const getBreakdownPath = ({ router: RouteComponentProps; title: string | number; // Used to display a title in the breakdown header }) => { - const queryFromRoute = parseQuery(router.location.search); - const state = JSON.stringify(queryFromRoute); // Ignores query prefix + const queryFromRoute = router ? parseQuery(router.location.search) : undefined; + const state = queryFromRoute ? JSON.stringify(queryFromRoute) : undefined; // Ignores query prefix const newQuery: any = { ...(description && description !== title && { [breakdownDescKey]: description }), ...(title && { [breakdownTitleKey]: title }), optimizationsPath: isOptimizationsPath ? true : undefined, optimizationsTab: isOptimizationsTab ? true : undefined, // Clear query params - group_by: { - [groupBy]: isPlatformCosts ? '*' : id, // Use ID here -- see https://github.com/project-koku/koku-ui/pull/2821 - }, + ...(groupBy && { + group_by: { + [groupBy]: isPlatformCosts ? '*' : id, // Use ID here -- see https://github.com/project-koku/koku-ui/pull/2821 + }, + }), + id, isPlatformCosts: isPlatformCosts ? true : undefined, - state: window.btoa(state), + ...(state && { state: window.btoa(state) }), }; return `${basePath}?${getQueryRoute(newQuery)}`; }; From fa92991e3bd6e83724d6f64147a0029de5853b4b Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Wed, 27 Sep 2023 14:45:21 -0400 Subject: [PATCH 03/33] dual code block components --- locales/data.json | 50 +- locales/translations.json | 9 +- package-lock.json | 21 +- package.json | 3 +- src/locales/messages.ts | 41 +- .../optimizations/optimizationsTable.tsx | 41 +- .../optimizations/optimizationsToolbar.tsx | 1 + .../optimizationsBreakdown.scss | 14 + .../optimizationsBreakdown.styles.ts | 37 +- .../optimizationsBreakdown.tsx | 499 +++++++++++------- .../optimizationsBreakdownHeader.styles.ts | 20 +- .../optimizationsBreakdownHeader.tsx | 45 +- .../optimizationsDetails.tsx | 33 +- src/routes/utils/paths.ts | 21 + src/utils/recomendations.ts | 12 + 15 files changed, 518 insertions(+), 329 deletions(-) diff --git a/locales/data.json b/locales/data.json index 15fa9371f..254c4b783 100644 --- a/locales/data.json +++ b/locales/data.json @@ -987,6 +987,18 @@ "value": "Clusters" } ], + "copyToClipboard": [ + { + "type": 0, + "value": "Copy to clipboard" + } + ], + "copyToClipboardSuccessfull": [ + { + "type": 0, + "value": "Successfully copied to clipboard!" + } + ], "cost": [ { "type": 0, @@ -1382,28 +1394,6 @@ "value": "Last updated" } ], - "costModelsPopover": [ - { - "type": 0, - "value": "A cost model allows you to associate a price to metrics provided by your sources to charge for utilization of resources. " - }, - { - "type": 1, - "value": "learnMore" - } - ], - "costModelsPopoverAriaLabel": [ - { - "type": 0, - "value": "Cost model info popover" - } - ], - "costModelsPopoverButtonAriaLabel": [ - { - "type": 0, - "value": "Opens a dialog with cost model info" - } - ], "costModelsRateTooLong": [ { "type": 0, @@ -2286,6 +2276,12 @@ "value": "Current" } ], + "currentConfiguration": [ + { + "type": 0, + "value": "Current configuration" + } + ], "dashboardCumulativeCostComparison": [ { "type": 0, @@ -10673,10 +10669,6 @@ "type": 1, "value": "value" }, - { - "type": 0, - "value": " " - }, { "type": 1, "value": "units" @@ -11260,6 +11252,12 @@ "value": "Recommended" } ], + "recommendedConfiguration": [ + { + "type": 0, + "value": "Recommended configuration" + } + ], "redHatStatusUrl": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index 007b7ab90..b6d034263 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -59,6 +59,8 @@ "chooseValuePlaceholder": "Choose value", "close": "Close", "clusters": "Clusters", + "copyToClipboard": "Copy to clipboard", + "copyToClipboardSuccessfull": "Successfully copied to clipboard!", "cost": "Cost", "costBreakdownAriaDesc": "Breakdown of markup, raw, and usage costs", "costBreakdownAriaLabel": "A description of markup, raw cost and usage cost", @@ -110,9 +112,6 @@ "costModelsFilterTagKey": "Filter by tag key", "costModelsInfoTooLong": "Should not exceed 100 characters", "costModelsLastUpdated": "Last updated", - "costModelsPopover": "A cost model allows you to associate a price to metrics provided by your sources to charge for utilization of resources. {learnMore}", - "costModelsPopoverAriaLabel": "Cost model info popover", - "costModelsPopoverButtonAriaLabel": "Opens a dialog with cost model info", "costModelsRateTooLong": "Should not exceed 10 decimals", "costModelsRefreshDialog": "Refresh this dialog", "costModelsRemoveTagLabel": "Remove tag value", @@ -188,6 +187,7 @@ "currencyOptions": "{units, select, AUD {AUD (A$) - Australian Dollar}CAD {CAD (CA$) - Canadian Dollar}CHF {CHF (CHF) - Swiss Franc}CNY {CNY (CN¥) - Chinese Yuan}DKK {DKK (DKK) - Danish Krone}EUR {EUR (€) - Euro}GBP {GBP (£) - British Pound}HKD {HKD (HK$) - Hong Kong Dollar}JPY {JPY (¥) - Japanese Yen}NOK {NOK (NOK) - Norwegian Krone}NZD {NZD (NZ$) - New Zealand Dollar}SEK {SEK (SEK) - Swedish Krona}SGD {SGD (SGD) - Singapore Dollar}USD {USD ($) - United States Dollar} ZAR {ZAR (ZAR) - South African Rand}other {}}", "currencyUnits": "{units, select, AUD {A$}CAD {CA$}CHF {CHF}CNY {CN¥}DKK {DKK}EUR {€}GBP {£}HKD {HK$}JPY {¥}NOK {NOK}NZD {NZ$}SEK {SEK}SGD {SGD}USD {$} ZAR {ZAR}other {}}", "current": "Current", + "currentConfiguration": "Current configuration", "dashboardCumulativeCostComparison": "Cumulative cost comparison ({units})", "dashboardDailyUsageComparison": "Daily usage comparison ({units})", "dashboardDatabaseTitle": "Database services cost", @@ -437,7 +437,7 @@ "optimizationsPerspective": "View optimizations based on", "optimizationsShortTerm": "Last 24 hrs", "optimizationsTableAriaLabel": "Optimizations table", - "optimizationsValue": "{value} {units}", + "optimizationsValue": "{value}{units}", "optimizationsValues": "{value, select, cluster {Cluster name} container {Container name} last_reported {Last reported} project {Project name} workload {Workload name} workload_type {Workload type} other {}}", "optimizationsViewAll": "View all optimizations for this project", "optimizationsViewAllDisabled": "This project has not reported data this month.", @@ -489,6 +489,7 @@ "readOnly": "Read only", "readOnlyPermissions": "You have read only permissions", "recommended": "Recommended", + "recommendedConfiguration": "Recommended configuration", "redHatStatusUrl": "https://status.redhat.com", "remove": "Remove", "removeProjects": "Remove projects", diff --git a/package-lock.json b/package-lock.json index 432c36b90..e6bb40955 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,8 @@ "typesafe-actions": "^5.1.0", "unleash-proxy-client": "^2.5.0", "victory-core": "^36.6.11", - "xstate": "^4.38.2" + "xstate": "^4.38.2", + "yaml": "^2.3.2" }, "devDependencies": { "@formatjs/cli": "^6.2.0", @@ -6336,6 +6337,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -20677,12 +20687,11 @@ "dev": true }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 7e9d1e101..10a1c132b 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,8 @@ "typesafe-actions": "^5.1.0", "unleash-proxy-client": "^2.5.0", "victory-core": "^36.6.11", - "xstate": "^4.38.2" + "xstate": "^4.38.2", + "yaml": "^2.3.2" }, "devDependencies": { "@formatjs/cli": "^6.2.0", diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 39c9bef17..6beb43e7d 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -345,6 +345,16 @@ export default defineMessages({ description: 'Clusters', id: 'clusters', }, + copyToClipboard: { + defaultMessage: 'Copy to clipboard', + description: 'Copy to clipboard', + id: 'copyToClipboard', + }, + copyToClipboardSuccessfull: { + defaultMessage: 'Successfully copied to clipboard!', + description: 'Successfully copied to clipboard!', + id: 'copyToClipboardSuccessfull', + }, cost: { defaultMessage: 'Cost', description: 'Cost', @@ -615,23 +625,6 @@ export default defineMessages({ description: 'Last updated', id: 'costModelsLastUpdated', }, - costModelsPopover: { - defaultMessage: - 'A cost model allows you to associate a price to metrics provided by your sources to charge for utilization of resources. {learnMore}', - description: - 'A cost model allows you to associate a price to metrics provided by your sources to charge for utilization of resources. {learnMore}', - id: 'costModelsPopover', - }, - costModelsPopoverAriaLabel: { - defaultMessage: 'Cost model info popover', - description: 'Cost model info popover', - id: 'costModelsPopoverAriaLabel', - }, - costModelsPopoverButtonAriaLabel: { - defaultMessage: 'Opens a dialog with cost model info', - description: 'Opens a dialog with cost model info', - id: 'costModelsPopoverButtonAriaLabel', - }, costModelsRateTooLong: { defaultMessage: 'Should not exceed 10 decimals', description: 'Should not exceed 10 decimals', @@ -1044,7 +1037,6 @@ export default defineMessages({ description: 'return the proper unit label based on key: "units"', id: 'currencyOptions', }, - // See https://www.localeplanet.com/icu/currency.html currencyUnits: { defaultMessage: @@ -1068,12 +1060,16 @@ export default defineMessages({ description: 'return the proper unit label based on key: "units"', id: 'currencyUnits', }, - current: { defaultMessage: 'Current', description: 'Current', id: 'current', }, + currentConfiguration: { + defaultMessage: 'Current configuration', + description: 'Current configuration', + id: 'currentConfiguration', + }, dashboardCumulativeCostComparison: { defaultMessage: 'Cumulative cost comparison ({units})', description: 'Cumulative cost comparison ({units})', @@ -2750,7 +2746,7 @@ export default defineMessages({ id: 'optimizationsTableAriaLabel', }, optimizationsValue: { - defaultMessage: '{value} {units}', + defaultMessage: '{value}{units}', description: '2 GiB', id: 'optimizationsValue', }, @@ -3039,6 +3035,11 @@ export default defineMessages({ description: 'Recommended', id: 'recommended', }, + recommendedConfiguration: { + defaultMessage: 'Recommended configuration', + description: 'Recommended configuration', + id: 'recommendedConfiguration', + }, redHatStatusUrl: { defaultMessage: 'https://status.redhat.com', description: 'Red Hat status url for cloud services', diff --git a/src/routes/components/optimizations/optimizationsTable.tsx b/src/routes/components/optimizations/optimizationsTable.tsx index a06adb715..463a9993f 100644 --- a/src/routes/components/optimizations/optimizationsTable.tsx +++ b/src/routes/components/optimizations/optimizationsTable.tsx @@ -3,7 +3,7 @@ import 'routes/components/dataTable/dataTable.scss'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import type { Query } from 'api/queries/query'; import { parseQuery } from 'api/queries/query'; -import type { RecommendationItems, RecommendationReport } from 'api/ros/recommendations'; +import type { RecommendationReport } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -15,12 +15,12 @@ import { DataTable } from 'routes/components/dataTable'; import { styles } from 'routes/components/dataTable/dataTable.styles'; import { NoOptimizationsState } from 'routes/components/page/noOptimizations/noOptimizationsState'; import { getGroupById } from 'routes/utils/groupBy'; -import { getBreakdownPath } from 'routes/utils/paths'; +import { getOptimizationsBreakdownPath } from 'routes/utils/paths'; import { createMapStateToProps } from 'store/common'; import { uiActions, uiSelectors } from 'store/ui'; import { getTimeFromNow } from 'utils/dates'; import { formatPath } from 'utils/paths'; -import { hasNotification } from 'utils/recomendations'; +import { hasWarning } from 'utils/recomendations'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -29,6 +29,7 @@ interface OptimizationsTableOwnProps extends RouterComponentProps { isLoading?: boolean; onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: RecommendationReport; reportQueryString: string; } @@ -75,7 +76,7 @@ class OptimizationsTableBase extends React.Component { - const { groupBy, intl, report, router } = this.props; + const { groupBy, intl, query, report } = this.props; if (!report) { return; } @@ -127,18 +128,17 @@ class OptimizationsTableBase extends React.Component @@ -153,11 +153,11 @@ class OptimizationsTableBase extends React.Component {cluster} - {showWarningIcon ? ( + {showWarningIcon && ( - ) : null} + )} ), hidden: groupBy === 'cluster', @@ -184,27 +184,6 @@ class OptimizationsTableBase extends React.Component { - return ( - hasNotification(recommendations.short_term) || - hasNotification(recommendations.medium_term) || - hasNotification(recommendations.long_term) - ); - }; - - // private handleOnRowClick = (event: React.KeyboardEvent | React.MouseEvent, rowIndex: number) => { - // const { closeOptimizationsDrawer, isOpen, openOptimizationsDrawer } = this.props; - // const { currentRow, rows } = this.state; - // - // this.setState({ currentRow: rowIndex }, () => { - // if (currentRow === rowIndex && isOpen) { - // closeOptimizationsDrawer(); - // } else { - // openOptimizationsDrawer(rows[rowIndex].optimization); - // } - // }); - // }; - private handleOnSort = (value: string, isSortAscending: boolean) => { const { closeOptimizationsDrawer, onSort } = this.props; diff --git a/src/routes/components/optimizations/optimizationsToolbar.tsx b/src/routes/components/optimizations/optimizationsToolbar.tsx index cc7ca84cb..d3b3140bb 100644 --- a/src/routes/components/optimizations/optimizationsToolbar.tsx +++ b/src/routes/components/optimizations/optimizationsToolbar.tsx @@ -87,6 +87,7 @@ export class OptimizationsToolbarBase extends React.Component ); } diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss index fea98da1d..2f87157b0 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss @@ -1,5 +1,19 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); +.leftCodeBlockOverride { + .pf-c-code-block__header, + .pf-c-code-block__content { + padding-right: 0; + } +} + +.rightCodeBlockOverride { + .pf-c-code-block__header, + .pf-c-code-block__content { + padding-left: 0; + } +} + .optimizationsOverride { div { display: block; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts index 58ad432e2..63c6df6aa 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts @@ -1,30 +1,35 @@ -import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; -import global_FontWeight_bold from '@patternfly/react-tokens/dist/js/global_FontWeight_bold'; +import global_danger_color_100 from '@patternfly/react-tokens/dist/js/global_danger_color_100'; import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg'; +import global_success_color_100 from '@patternfly/react-tokens/dist/js/global_success_color_100'; import type React from 'react'; export const styles = { - bullseye: { - marginTop: global_spacer_lg.value, + alertContainer: { + marginBottom: global_spacer_lg.value, + }, + codeBlock: { + display: 'flex', }, container: { minHeight: '100vh', }, - firstColumn: { - width: '225px', + currentActions: { + height: '36px', }, - tableContainer: { - marginTop: global_spacer_lg.value, + decrease: { + color: global_success_color_100.var, }, - toolbarContainer: { - backgroundColor: global_BackgroundColor_light_100.value, - paddingBottom: global_spacer_lg.value, - paddingTop: global_spacer_lg.value, + increase: { + color: global_danger_color_100.var, }, - value: { - fontWeight: global_FontWeight_bold.value, + leftCodeBlock: { + flexShrink: 1, + flexDirection: 'column', + minWidth: '225px', }, - viewAllContainer: { - marginTop: global_spacer_lg.value, + rightCodeBlock: { + display: 'flex', + flexGrow: 1, + flexDirection: 'column', }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx index 793ab00a1..8eafd40af 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx @@ -1,8 +1,23 @@ import './optimizationsBreakdown.scss'; -import { Alert, List, ListItem, PageSection } from '@patternfly/react-core'; +import { + Alert, + Card, + CardBody, + CardTitle, + ClipboardCopyButton, + CodeBlock, + CodeBlockAction, + CodeBlockCode, + Grid, + GridItem, + List, + ListItem, + PageSection, + Title, + TitleSizes, +} from '@patternfly/react-core'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; -import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import type { Query } from 'api/queries/query'; import { parseQuery } from 'api/queries/query'; import type { RosQuery } from 'api/queries/rosQuery'; @@ -17,13 +32,16 @@ import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; +import { routes } from 'routes'; import { Loading } from 'routes/components/page/loading'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; import { formatOptimization } from 'utils/format'; +import { formatPath } from 'utils/paths'; import { getNotifications, hasRecommendation, hasRecommendationValues } from 'utils/recomendations'; import type { RouterComponentProps } from 'utils/router'; +import YAML from 'yaml'; import { styles } from './optimizationsBreakdown.styles'; import { OptimizationsBreakdownHeader } from './optimizationsBreakdownHeader'; @@ -33,6 +51,7 @@ interface OptimizationsBreakdownOwnProps extends RouterComponentProps { } interface OptimizationsBreakdownStateProps { + queryFromRoute?: Query; report?: RecommendationReportData; reportError?: AxiosError; reportFetchStatus?: FetchStatus; @@ -63,7 +82,8 @@ const reportType = RosType.ros as any; const reportPathsType = RosPathsType.recommendation as any; const OptimizationsBreakdown: React.FC = () => { - const { report, reportFetchStatus } = useMapToProps(); + const [copied, setCopied] = React.useState(false); + const { queryFromRoute, report, reportFetchStatus } = useMapToProps(); const intl = useIntl(); const getDefaultTerm = () => { @@ -96,151 +116,126 @@ const OptimizationsBreakdown: React.FC = () => { } return ( - - - {notifications.map((notification, index) => ( - {notification.message} - ))} - - +
+ + + {notifications.map((notification, index) => ( + {notification.message} + ))} + + +
); }; - const getChangeValue = (value, units = '') => { - // Show icon opposite of month over month - let iconOverride = 'iconOverride'; - if (value !== null && value < 0) { - iconOverride += ' decrease'; - } else if (value !== null && value > 0) { - iconOverride += ' increase'; - } + const getChangeValue = (value, units) => { + const blockComment = `# `; return ( -
-
- {value < 0 ? ( - <> - - {intl.formatMessage(messages.optimizationsValue, { - value: formatOptimization(value), - units, - })} - - - - ) : value > 0 ? ( - <> - - {intl.formatMessage(messages.optimizationsValue, { - value: formatOptimization(value), - units, - })} - - - - ) : value === 0 ? ( - <> - - {intl.formatMessage(messages.optimizationsValue, { - value: formatOptimization(value), - units, - })} - - - - ) : ( - - )} -
-
+ <> + {value !== null && value < 0 ? ( + <> + {blockComment} + + {intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + })} + + + ) : value > 0 ? ( + <> + {blockComment} + + {intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + })} + + + ) : value === 0 ? ( + <> + {blockComment} + {intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + })} + + ) : ( + + )} + ); }; - const getLimitsTable = () => { - if (!report) { - return null; - } - + const getConfig = (key: 'config' | 'current') => { const term = getRecommendationTerm(); - if (!hasRecommendation(term)) { - return null; - } - const hasConfigLimitsCpu = hasRecommendationValues(term, 'config', 'limits', 'cpu'); - const hasConfigLimitsMemory = hasRecommendationValues(term, 'config', 'limits', 'memory'); - const hasCurrentLimitsCpu = hasRecommendationValues(term, 'current', 'limits', 'cpu'); - const hasCurrentLimitsMemory = hasRecommendationValues(term, 'current', 'limits', 'memory'); - const hasVariationLimitsCpu = hasRecommendationValues(term, 'variation', 'limits', 'cpu'); - const hasVariationLimitsMemory = hasRecommendationValues(term, 'variation', 'limits', 'memory'); + const hasConfigLimitsCpu = hasRecommendationValues(term, key, 'limits', 'cpu'); + const hasConfigLimitsMemory = hasRecommendationValues(term, key, 'limits', 'memory'); + const hasConfigRequestsCpu = hasRecommendationValues(term, key, 'requests', 'cpu'); + const hasConfigRequestsMemory = hasRecommendationValues(term, key, 'requests', 'memory'); + + const cpuConfigLimitsAmount = hasConfigLimitsCpu ? term[key].limits.cpu.amount : undefined; + const cpuConfigLimitsUnits = hasConfigLimitsCpu ? term[key].limits.cpu.format : undefined; + const cpuConfigRequestsAmount = hasConfigRequestsCpu ? term[key].requests.cpu.amount : undefined; + const cpuConfigRequestsUnits = hasConfigRequestsCpu ? term[key].requests.cpu.format : undefined; + + const memConfigLimitsAmount = hasConfigLimitsMemory ? term[key].limits.memory.amount : undefined; + const memConfigLimitsUnits = hasConfigLimitsMemory ? term[key].limits.memory.format : undefined; + const memConfigRequestsAmount = hasConfigRequestsMemory ? term[key].requests.memory.amount : undefined; + const memConfigRequestsUnits = hasConfigRequestsMemory ? term[key].requests.memory.format : undefined; + + return { + resources: { + requests: { + memory: intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(memConfigRequestsAmount), + units: memConfigRequestsUnits, + }), + cpu: intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(cpuConfigRequestsAmount), + units: cpuConfigRequestsUnits, + }), + }, + limits: { + memory: intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(memConfigLimitsAmount), + units: memConfigLimitsUnits, + }), + cpu: intl.formatMessage(messages.optimizationsValue, { + value: getFormattedValue(cpuConfigLimitsAmount), + units: cpuConfigLimitsUnits, + }), + }, + }, + }; + }; - const cpuConfigAmount = hasConfigLimitsCpu ? term.config.limits.cpu.amount : undefined; - const cpuConfigUnits = hasConfigLimitsCpu ? term.config.limits.cpu.format : undefined; - const cpuCurrentAmount = hasCurrentLimitsCpu ? term.current.limits.cpu.amount : undefined; - const cpuCurrentUnits = hasCurrentLimitsCpu ? term.current.limits.cpu.format : undefined; - const cpuVariation = hasVariationLimitsCpu ? term.variation.limits.cpu.amount : undefined; - const cpuVariationUnits = hasVariationLimitsCpu ? term.variation.limits.cpu.format : undefined; + const getCurrentConfig = () => { + const code = getConfig('current'); - const memConfigAmount = hasConfigLimitsMemory ? term.config.limits.memory.amount : undefined; - const memConfigUnits = hasConfigLimitsMemory ? term.config.limits.memory.format : undefined; - const memCurrentAmount = hasCurrentLimitsMemory ? term.current.limits.memory.amount : undefined; - const memCurrentUnits = hasCurrentLimitsMemory ? term.current.limits.memory.format : undefined; - const memVariation = hasVariationLimitsMemory ? term.variation.limits.memory.amount : undefined; - const memVariationUnits = hasVariationLimitsMemory ? term.variation.limits.memory.format : undefined; + return YAML.stringify(code, null, 2); + }; + const getCurrentConfigCodeBlock = () => { + const code = getCurrentConfig(); + if (code === null) { + return null; + } return ( - - - - {intl.formatMessage(messages.limits)} - {intl.formatMessage(messages.current)} - {intl.formatMessage(messages.recommended)} - {intl.formatMessage(messages.change)} - - - - - {intl.formatMessage(messages.cpuTitle)} - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(cpuCurrentAmount), - units: cpuCurrentUnits, - })} - - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(cpuConfigAmount), - units: cpuConfigUnits, - })} - - {getChangeValue(cpuVariation, cpuVariationUnits)} - - - {intl.formatMessage(messages.memoryTitle)} - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(memCurrentAmount), - units: memCurrentUnits, - })} - - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(memConfigAmount), - units: memConfigUnits, - })} - - {getChangeValue(memVariation, memVariationUnits)} - - - + + {code} + ); }; + // Returns empty element to force a header + const getEmptyActions = () => { + return
; + }; + const getFormattedValue = value => { - return value !== undefined ? formatOptimization(value) : ; + return value !== undefined ? formatOptimization(value) : undefined; }; const getRecommendationTerm = (): RecommendationItem => { @@ -263,94 +258,192 @@ const OptimizationsBreakdown: React.FC = () => { return result; }; - const getRequestsTable = () => { + const getOptimizationCards = () => { if (!report) { return null; } - const term = getRecommendationTerm(); - if (!hasRecommendation(term)) { + return ( + + + + + + {intl.formatMessage(messages.currentConfiguration)} + + + +
+
+ {getCurrentConfigCodeBlock()} +
+
+ {getWarningsCodeBlock()} +
+
+
+
+
+ + + + + {intl.formatMessage(messages.recommendedConfiguration)} + + + +
+
+ {getRecommendedConfigCodeBlock()} +
+
+ {getVariationConfigCodeBlock()} +
+
+
+
+
+
+ ); + }; + + const getRecommendedActions = () => { + const code = getRecommendedConfig(); + + return ( + + handleClipboardCopyOnClick(e, code)} + exitDelay={copied ? 1500 : 600} + maxWidth="110px" + variant="plain" + onTooltipHidden={() => setCopied(false)} + > + {intl.formatMessage(copied ? messages.copyToClipboardSuccessfull : messages.copyToClipboard)} + + + ); + }; + + const getRecommendedConfig = () => { + const code = getConfig('config'); + if (code === null) { + return null; + } + return YAML.stringify(code, null, 2); + }; + + const getRecommendedConfigCodeBlock = () => { + const code = getRecommendedConfig(); + if (code === null) { + return null; + } + return ( + + {code} + + ); + }; + + const getVariationConfigCodeBlock = () => { + const code = getVariationConfig(); + if (code === null) { return null; } + return ( + + {code} + + ); + }; - const hasConfigRequestsCpu = hasRecommendationValues(term, 'config', 'requests', 'cpu'); - const hasConfigRequestsMemory = hasRecommendationValues(term, 'config', 'requests', 'memory'); - const hasCurrentLimitsCpu = hasRecommendationValues(term, 'current', 'requests', 'cpu'); - const hasCurrentLimitsMemory = hasRecommendationValues(term, 'current', 'requests', 'memory'); + const getVariationConfig = () => { + const term = getRecommendationTerm(); + + const hasVariationLimitsCpu = hasRecommendationValues(term, 'variation', 'limits', 'cpu'); + const hasVariationLimitsMemory = hasRecommendationValues(term, 'variation', 'limits', 'memory'); const hasVariationRequestsCpu = hasRecommendationValues(term, 'variation', 'requests', 'cpu'); const hasVariationRequestsMemory = hasRecommendationValues(term, 'variation', 'requests', 'memory'); - const cpuConfigAmount = hasConfigRequestsCpu ? term.config.requests.cpu.amount : undefined; - const cpuConfigUnits = hasConfigRequestsCpu ? term.config.requests.cpu.format : undefined; - const cpuCurrentAmount = hasCurrentLimitsCpu ? term.current.requests.cpu.amount : undefined; - const cpuCurrentUnits = hasCurrentLimitsCpu ? term.current.requests.cpu.format : undefined; - const cpuVariation = hasVariationRequestsCpu ? term.variation.requests.cpu.amount : undefined; - const cpuVariationUnits = hasVariationRequestsCpu ? term.variation.requests.cpu.format : undefined; + const cpuVariationLimitsAmount = hasVariationLimitsCpu ? term.variation.limits.cpu.amount : undefined; + const cpuVariationLimitsUnits = hasVariationLimitsCpu ? term.variation.limits.cpu.format : undefined; + const memVariationLimitsAmount = hasVariationLimitsMemory ? term.variation.limits.memory.amount : undefined; + const memVariationLimitsUnits = hasVariationLimitsMemory ? term.variation.limits.memory.format : undefined; - const memConfigAmount = hasConfigRequestsMemory ? term.config.requests.memory.amount : undefined; - const memConfigUnits = hasConfigRequestsMemory ? term.config.requests.memory.format : undefined; - const memCurrentAmount = hasCurrentLimitsMemory ? term.current.requests.memory.amount : undefined; - const memCurrentUnits = hasCurrentLimitsMemory ? term.current.requests.memory.format : undefined; - const memVariation = hasVariationRequestsMemory ? term.variation.requests.memory.amount : undefined; - const memVariationUnits = hasVariationRequestsMemory ? term.variation.requests.memory.format : undefined; + const cpuVariationRequestsAmount = hasVariationRequestsCpu ? term.variation.requests.cpu.amount : undefined; + const cpuVariationRequestsUnits = hasVariationRequestsCpu ? term.variation.requests.cpu.format : undefined; + const memVariationRequestsAmount = hasVariationRequestsMemory ? term.variation.requests.memory.amount : undefined; + const memVariationRequestsUnits = hasVariationRequestsMemory ? term.variation.requests.memory.format : undefined; + + const cpuVariationLimitsChange = getChangeValue(cpuVariationLimitsAmount, cpuVariationLimitsUnits); + const memoryVariationLimitsChange = getChangeValue(memVariationLimitsAmount, memVariationLimitsUnits); + const cpuVariationRequestsChange = getChangeValue(cpuVariationRequestsAmount, cpuVariationRequestsUnits); + const memoryVariationRequestsChange = getChangeValue(memVariationRequestsAmount, memVariationRequestsUnits); return ( - - - - {intl.formatMessage(messages.requests)} - {intl.formatMessage(messages.current)} - {intl.formatMessage(messages.recommended)} - {intl.formatMessage(messages.change)} - - - - - {intl.formatMessage(messages.cpuTitle)} - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(cpuCurrentAmount), - units: cpuCurrentUnits, - })} - - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(cpuConfigAmount), - units: cpuConfigUnits, - })} - - {getChangeValue(cpuVariation, cpuVariationUnits)} - - - {intl.formatMessage(messages.memoryTitle)} - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(memCurrentAmount), - units: memCurrentUnits, - })} - - - {intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(memConfigAmount), - units: memConfigUnits, - })} - - {getChangeValue(memVariation, memVariationUnits)} - - - + <> +
+
+ {memoryVariationRequestsChange} +
+ {cpuVariationRequestsChange} +
+
+ {memoryVariationLimitsChange} +
+ {cpuVariationLimitsChange} + ); }; + const getWarningsConfig = () => { + const config = getConfig('current'); + + const getWarning = (value, defaultValue = null) => { + return !value ? : defaultValue; + }; + + return ( + <> +
+
+ {getWarning(config.resources.requests.memory)} +
+ {getWarning(config.resources.requests.cpu)} +
+
+ {getWarning(config.resources.limits.memory)} +
+ {getWarning(config.resources.limits.cpu,
)} + + ); + }; + + const getWarningsCodeBlock = () => { + const code = getWarningsConfig(); + if (code === null) { + return null; + } + return ( + + {code} + + ); + }; + + const handleClipboardCopyOnClick = (event, text) => { + navigator.clipboard.writeText(text.toString()); + setCopied(true); + }; + const handleOnSelected = (value: Interval) => { setCurrentInterval(value); }; const isLoading = reportFetchStatus === FetchStatus.inProgress; + const optimizationsURL = formatPath(routes.optimizationsDetails.path); return (
@@ -358,6 +451,8 @@ const OptimizationsBreakdown: React.FC = () => { currentInterval={currentInterval} isDisabled={isLoading} onSelected={handleOnSelected} + optimizationsURL={optimizationsURL} + queryFromRoute={queryFromRoute} report={report} /> @@ -368,9 +463,8 @@ const OptimizationsBreakdown: React.FC = () => { /> ) : ( <> -
{getAlert()}
-
{getRequestsTable()}
-
{getLimitsTable()}
+ {getAlert()} + {getOptimizationCards()} )}
@@ -406,6 +500,7 @@ const useMapToProps = (): OptimizationsBreakdownStateProps => { }, [reportQueryString]); return { + queryFromRoute, report, reportError, reportFetchStatus, diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts index 2eb26e8f8..36cfabaef 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts @@ -1,18 +1,18 @@ import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; import global_FontSize_md from '@patternfly/react-tokens/dist/js/global_FontSize_md'; 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'; import global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm'; import type React from 'react'; export const styles = { + description: { + marginTop: global_spacer_lg.var, + }, header: { backgroundColor: global_BackgroundColor_light_100.var, padding: global_spacer_lg.var, }, - headerContent: { - display: 'flex', - justifyContent: 'space-between', - }, infoIcon: { fontSize: global_FontSize_md.value, }, @@ -20,6 +20,16 @@ export const styles = { fontWeight: 'bold', }, title: { - paddingBottom: global_spacer_sm.var, + alignItems: 'center', + display: 'flex', + marginTop: global_spacer_md.var, + }, + toolbar: { + display: 'flex', + justifyContent: 'space-between', + marginTop: global_spacer_lg.var, + }, + warningIcon: { + paddingLeft: global_spacer_sm.var, }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx index 28fadff8d..29b7cc9eb 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx @@ -7,15 +7,19 @@ import { Title, TitleSizes, } from '@patternfly/react-core'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import type { Query } from 'api/queries/query'; +import { getQueryRoute } from 'api/queries/query'; import type { RecommendationReportData } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; import { createMapStateToProps } from 'store/common'; import { getTimeFromNow } from 'utils/dates'; +import { hasWarning } from 'utils/recomendations'; import { styles } from './optimizationsBreakdownHeader.styles'; import { OptimizationsBreakdownToolbar } from './optimizationsBreakdownToolbar'; @@ -24,7 +28,8 @@ interface OptimizationsBreakdownHeaderOwnProps { currentInterval?: string; isDisabled?: boolean; onSelected?: (value: string) => void; - query?: Query; + optimizationsURL?: string; + queryFromRoute?: Query; report?: RecommendationReportData; } @@ -45,6 +50,25 @@ class OptimizationsBreakdownHeaderBase extends React.Component< protected defaultState: OptimizationsBreakdownHeaderState = {}; public state: OptimizationsBreakdownHeaderState = { ...this.defaultState }; + private buildOptimizationsLink = url => { + const { queryFromRoute } = this.props; + + const newQuery = { + ...(queryFromRoute.state && { state: queryFromRoute.state }), + }; + return `${url}?${getQueryRoute(newQuery)}`; + }; + + private getBackToLink = () => { + const { optimizationsURL, intl } = this.props; + + return ( + + {intl.formatMessage(messages.breakdownBackToOptimizations)} + + ); + }; + private getDescription = () => { const { intl, report } = this.props; @@ -88,17 +112,28 @@ class OptimizationsBreakdownHeaderBase extends React.Component< public render() { const { currentInterval, isDisabled, onSelected, report } = this.props; + const recommendations = report ? report.recommendations.duration_based : undefined; + const showWarningIcon = hasWarning(recommendations); + return (
-
- + {this.getBackToLink()} + <div style={styles.title}> + <Title headingLevel="h1" size={TitleSizes['2xl']}> {report ? report.container : null} - {this.getDescription()} + {showWarningIcon && ( + + + + )} +
+
{this.getDescription()}
+
diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx index fa185705c..0c845889f 100644 --- a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx @@ -1,6 +1,6 @@ import { PageSection, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery } from 'api/queries/query'; +import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; import type { RosQuery } from 'api/queries/rosQuery'; import type { RosReport } from 'api/ros/ros'; import { RosPathsType, RosType } from 'api/ros/ros'; @@ -9,7 +9,7 @@ import messages from 'locales/messages'; import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; import { OptimizationsTable, OptimizationsToolbar } from 'routes/components/optimizations'; @@ -22,7 +22,6 @@ import * as queryUtils from 'routes/utils/query'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; -import { uiActions } from 'store/ui'; import { styles } from './optimizationsDetails.styles'; import { OptimizationsDetailsHeader } from './optimizationsDetailsHeader'; @@ -32,7 +31,7 @@ interface OptimizationsDetailsOwnProps { } export interface OptimizationsDetailsStateProps { - closeOptimizationsDrawer: typeof uiActions.closeOptimizationsDrawer; + queryState?: Query; report: RosReport; reportError: AxiosError; reportFetchStatus: FetchStatus; @@ -58,12 +57,23 @@ const reportPathsType = RosPathsType.recommendations as any; const OptimizationsDetails: React.FC = () => { const [query, setQuery] = useState({ ...baseQuery }); - const intl = useIntl(); - - const { closeOptimizationsDrawer, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ + const { queryState, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ query, }); + const intl = useIntl(); + const location = useLocation(); + const navigate = useNavigate(); + + // Restore state returned from breakdown page + useEffect(() => { + setQuery({ + ...query, + ...queryState, + }); + navigate(location.pathname, { replace: true }); + }, [reportQueryString]); + const getPagination = (isDisabled = false, isBottom = false) => { const count = report && report.meta ? report.meta.count : 0; const limit = report && report.meta ? report.meta.limit : baseQuery.limit; @@ -98,6 +108,7 @@ const OptimizationsDetails: React.FC = () => { isLoading={reportFetchStatus === FetchStatus.inProgress} onSort={(sortType, isSortAscending) => handleOnSort(sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} /> @@ -126,31 +137,26 @@ const OptimizationsDetails: React.FC = () => { const handleOnFilterAdded = filter => { const newQuery = queryUtils.handleOnFilterAdded(query, filter); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnFilterRemoved = filter => { const newQuery = queryUtils.handleOnFilterRemoved(query, filter); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnPerPageSelect = perPage => { const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnSetPage = pageNumber => { const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnSort = (sortType, isSortAscending) => { const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); setQuery(newQuery); - closeOptimizationsDrawer(); }; const itemsTotal = report && report.meta ? report.meta.count : 0; @@ -194,6 +200,7 @@ const useQueryFromRoute = () => { const useMapToProps = ({ query }: OptimizationsDetailsMapProps): OptimizationsDetailsStateProps => { const dispatch: ThunkDispatch = useDispatch(); const queryFromRoute = useQueryFromRoute(); + const queryState = parseQueryState(queryFromRoute); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); @@ -228,7 +235,7 @@ const useMapToProps = ({ query }: OptimizationsDetailsMapProps): OptimizationsDe }, [query]); return { - closeOptimizationsDrawer: uiActions.closeOptimizationsDrawer, + queryState, report, reportError, reportFetchStatus, diff --git a/src/routes/utils/paths.ts b/src/routes/utils/paths.ts index adf585ba7..0d902a964 100644 --- a/src/routes/utils/paths.ts +++ b/src/routes/utils/paths.ts @@ -44,6 +44,27 @@ export const getBreakdownPath = ({ return `${basePath}?${getQueryRoute(newQuery)}`; }; +export const getOptimizationsBreakdownPath = ({ + basePath, + id, + query, + title, +}: { + basePath: string; + id: string | number; // group_by[account]= param in the breakdown page + query?: Query; + title: string | number; // Used to display a title in the breakdown header +}) => { + const state = query ? JSON.stringify(query) : undefined; // Ignores query prefix + + const newQuery: any = { + id, + ...(state && { state: window.btoa(state) }), + ...(title && { [breakdownTitleKey]: title }), + }; + return `${basePath}?${getQueryRoute(newQuery)}`; +}; + export const getOrgBreakdownPath = ({ basePath, description, diff --git a/src/utils/recomendations.ts b/src/utils/recomendations.ts index 7765ccff8..e95142904 100644 --- a/src/utils/recomendations.ts +++ b/src/utils/recomendations.ts @@ -1,4 +1,5 @@ import type { Notification, RecommendationItem } from 'api/ros/recommendations'; +import type { RecommendationItems } from 'api/ros/recommendations'; export const getNotifications = (term: RecommendationItem): Notification[] => { if (!hasNotification(term)) { @@ -37,3 +38,14 @@ export const hasRecommendationValues = (term: RecommendationItem, key1: string, } return result; }; + +export const hasWarning = (recommendations: RecommendationItems) => { + if (!recommendations) { + return false; + } + return ( + hasNotification(recommendations.short_term) || + hasNotification(recommendations.medium_term) || + hasNotification(recommendations.long_term) + ); +}; From 5bf8856c97cd1c1831be1f2ca6ab6d74fda1f90c Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Fri, 29 Sep 2023 20:44:17 -0400 Subject: [PATCH 04/33] ROS 1.5 (iteration 1), using existing data https://issues.redhat.com/browse/COST-4217 --- .../components/drawers/optimzations/index.ts | 0 .../drawers/optimzations/optimizations.scss | 0 .../optimzations/optimizations.styles.ts | 0 .../optimzations/optimizationsContent.tsx | 0 .../optimzations/optimizationsDrawer.tsx | 0 .../optimzations/optimizationsLink.tsx | 3 +- .../optimzations/optimizationsToolbar.tsx | 0 locales/data.json | 48 +++ locales/translations.json | 2 + src/api/queries/query.ts | 5 +- .../drawers/commonDrawer/commonDrawer.tsx | 10 +- src/components/drawers/index.ts | 1 - src/components/permissions/permissions.tsx | 1 + src/locales/messages.ts | 10 + src/routes.tsx | 4 + .../components/dataToolbar/basicToolbar.tsx | 22 +- .../components/dataToolbar/utils/category.tsx | 4 +- src/routes/components/optimizations/index.ts | 1 + .../optimizations/optimizationsBadge.tsx | 88 +++++ .../optimizations/optimizationsTable.tsx | 156 +++----- .../optimizations/optimizationsToolbar.tsx | 32 +- .../details/awsBreakdown/awsBreakdown.tsx | 4 +- src/routes/details/awsDetails/awsDetails.tsx | 5 + .../details/awsDetails/detailsTable.tsx | 13 +- .../details/azureBreakdown/azureBreakdown.tsx | 4 +- .../details/azureDetails/azureDetails.tsx | 5 + .../details/azureDetails/detailsTable.tsx | 14 +- .../components/breakdown/breakdownBase.tsx | 17 + .../components/breakdown/breakdownHeader.tsx | 89 +++-- .../historicalDataCostChart.tsx | 4 +- .../historicalDataTrendChart.tsx | 4 +- .../historicalDataUsageChart.tsx | 4 +- .../components/summary/summaryCard.tsx | 4 +- .../summary/summaryModalContent.tsx | 4 +- src/routes/details/components/tag/tagLink.tsx | 4 +- .../details/components/tag/tagModal.tsx | 4 +- .../components/usageChart/usageChart.tsx | 4 +- .../details/gcpBreakdown/gcpBreakdown.tsx | 4 +- .../details/gcpDetails/detailsTable.tsx | 14 +- src/routes/details/gcpDetails/gcpDetails.tsx | 5 + .../details/ibmBreakdown/ibmBreakdown.tsx | 4 +- .../details/ibmDetails/detailsTable.tsx | 14 +- src/routes/details/ibmDetails/ibmDetails.tsx | 5 + .../details/ociBreakdown/ociBreakdown.tsx | 4 +- .../details/ociDetails/detailsTable.tsx | 14 +- src/routes/details/ociDetails/ociDetails.tsx | 5 + .../details/ocpBreakdown/ocpBreakdown.tsx | 10 +- ...down.tsx => ocpBreakdownOptimizations.tsx} | 45 ++- .../ocpBreakdown/optimizationsBadge.tsx | 102 ------ .../ocpDetails/detailsOptimization.tsx | 18 +- .../details/ocpDetails/detailsTable.tsx | 26 +- src/routes/details/ocpDetails/ocpDetails.tsx | 5 + .../details/rhelBreakdown/rhelBreakdown.tsx | 4 +- .../details/rhelDetails/detailsTable.tsx | 24 +- .../details/rhelDetails/rhelDetails.tsx | 5 + .../optimizationsBreakdown.styles.ts | 21 -- .../optimizationsBreakdown.tsx | 344 +----------------- .../optimizationsBreakdownConfiguration.tsx | 272 ++++++++++++++ .../optimizationsBreakdownHeader.tsx | 139 +++---- .../optimizationsBreakdownToolbar.tsx | 72 +--- .../optimizationsDetails.tsx | 31 +- .../optimizationsDetailsHeader.tsx | 89 ++--- src/routes/utils/paths.ts | 25 +- src/utils/props.ts | 1 + 64 files changed, 913 insertions(+), 959 deletions(-) rename {src => .archive}/components/drawers/optimzations/index.ts (100%) rename {src => .archive}/components/drawers/optimzations/optimizations.scss (100%) rename {src => .archive}/components/drawers/optimzations/optimizations.styles.ts (100%) rename {src => .archive}/components/drawers/optimzations/optimizationsContent.tsx (100%) rename {src => .archive}/components/drawers/optimzations/optimizationsDrawer.tsx (100%) rename {src => .archive}/components/drawers/optimzations/optimizationsLink.tsx (98%) rename {src => .archive}/components/drawers/optimzations/optimizationsToolbar.tsx (100%) create mode 100644 src/routes/components/optimizations/optimizationsBadge.tsx rename src/routes/details/ocpBreakdown/{optimizationsBreakdown.tsx => ocpBreakdownOptimizations.tsx} (82%) delete mode 100644 src/routes/details/ocpBreakdown/optimizationsBadge.tsx create mode 100644 src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx diff --git a/src/components/drawers/optimzations/index.ts b/.archive/components/drawers/optimzations/index.ts similarity index 100% rename from src/components/drawers/optimzations/index.ts rename to .archive/components/drawers/optimzations/index.ts diff --git a/src/components/drawers/optimzations/optimizations.scss b/.archive/components/drawers/optimzations/optimizations.scss similarity index 100% rename from src/components/drawers/optimzations/optimizations.scss rename to .archive/components/drawers/optimzations/optimizations.scss diff --git a/src/components/drawers/optimzations/optimizations.styles.ts b/.archive/components/drawers/optimzations/optimizations.styles.ts similarity index 100% rename from src/components/drawers/optimzations/optimizations.styles.ts rename to .archive/components/drawers/optimzations/optimizations.styles.ts diff --git a/src/components/drawers/optimzations/optimizationsContent.tsx b/.archive/components/drawers/optimzations/optimizationsContent.tsx similarity index 100% rename from src/components/drawers/optimzations/optimizationsContent.tsx rename to .archive/components/drawers/optimzations/optimizationsContent.tsx diff --git a/src/components/drawers/optimzations/optimizationsDrawer.tsx b/.archive/components/drawers/optimzations/optimizationsDrawer.tsx similarity index 100% rename from src/components/drawers/optimzations/optimizationsDrawer.tsx rename to .archive/components/drawers/optimzations/optimizationsDrawer.tsx diff --git a/src/components/drawers/optimzations/optimizationsLink.tsx b/.archive/components/drawers/optimzations/optimizationsLink.tsx similarity index 98% rename from src/components/drawers/optimzations/optimizationsLink.tsx rename to .archive/components/drawers/optimzations/optimizationsLink.tsx index eb2fe532f..4e3c4cb5f 100644 --- a/src/components/drawers/optimzations/optimizationsLink.tsx +++ b/.archive/components/drawers/optimzations/optimizationsLink.tsx @@ -82,7 +82,7 @@ class OptimizationsLinkBase extends React.Component }; public render() { - const { intl, isStandalone, project, report, router } = this.props; + const { intl, isStandalone, project, report } = this.props; if (!isStandalone || !report) { return null; @@ -97,7 +97,6 @@ class OptimizationsLinkBase extends React.Component id: project, isOptimizationsPath: true, isOptimizationsTab: true, - router, title: project, }); diff --git a/src/components/drawers/optimzations/optimizationsToolbar.tsx b/.archive/components/drawers/optimzations/optimizationsToolbar.tsx similarity index 100% rename from src/components/drawers/optimzations/optimizationsToolbar.tsx rename to .archive/components/drawers/optimzations/optimizationsToolbar.tsx diff --git a/locales/data.json b/locales/data.json index 254c4b783..d379cd769 100644 --- a/locales/data.json +++ b/locales/data.json @@ -409,6 +409,16 @@ "value": "Back to optimizations" } ], + "breakdownBackToOptimizationsProject": [ + { + "type": 0, + "value": "Back to optimizations for project " + }, + { + "type": 1, + "value": "value" + } + ], "breakdownBackToTitles": [ { "options": { @@ -10917,6 +10927,44 @@ "value": " % of cost" } ], + "percentPlus": [ + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 0, + "value": "+" + }, + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": "%" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": "%" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "count" + } + ], "percentSymbol": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index b6d034263..218b8a6a5 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -21,6 +21,7 @@ "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", + "breakdownBackToOptimizationsProject": "Back to optimizations for project {value}", "breakdownBackToTitles": "{value, select, aws {Amazon Web Services} azure {Microsoft Azure} oci {Oracle Cloud Infrastructure} gcp {Google Cloud Platform} ibm {IBM Cloud - Top 5 Costliest} ocp {OpenShift} other {}}", "breakdownCostOverviewTitle": "Cost overview", "breakdownHistoricalDataTitle": "Historical data", @@ -462,6 +463,7 @@ "paginationTitle": "{placement, select, top {{title} top pagination} bottom {{title} bottom pagination} other {{title} pagination}}", "percent": "{value} %", "percentOfCost": "{value} % of cost", + "percentPlus": "{count, plural, one {+{value}%} other {{value}%}}", "percentSymbol": "%", "percentTotalCost": "{value} {units} ({percent} %)", "perspective": "Perspective", diff --git a/src/api/queries/query.ts b/src/api/queries/query.ts index b590187a9..26728eb69 100644 --- a/src/api/queries/query.ts +++ b/src/api/queries/query.ts @@ -1,3 +1,4 @@ +import type * as H from 'history'; import { parse, stringify } from 'qs'; import { logicalAndPrefix, logicalOrPrefix } from 'utils/props'; @@ -132,6 +133,6 @@ export function parseQuery(query: string): T { return parseFilterByPrefix(parseGroupByPrefix(newQuery)); } -export function parseQueryState(query: Query): T { - return query && query.state ? JSON.parse(window.atob(query.state)) : undefined; +export function getQueryState(location: H.Location, key: string) { + return location && location.state && location.state[key] ? location.state[key] : undefined; } diff --git a/src/components/drawers/commonDrawer/commonDrawer.tsx b/src/components/drawers/commonDrawer/commonDrawer.tsx index fcd76d342..e3caea18b 100644 --- a/src/components/drawers/commonDrawer/commonDrawer.tsx +++ b/src/components/drawers/commonDrawer/commonDrawer.tsx @@ -1,6 +1,5 @@ import { Drawer, DrawerContent } from '@patternfly/react-core'; import { ExportsDrawer } from 'components/drawers'; -import { OptimizationsDrawer } from 'components/drawers'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; @@ -14,7 +13,6 @@ interface CommonDrawerOwnProps { interface CommonDrawerStateProps { isExportsDrawerOpen: boolean; - isOptimizationsDrawerOpen: boolean; } interface CommonDrawerDispatchProps { @@ -30,12 +28,10 @@ class CommonDrawerBase extends React.Component { private drawerRef = React.createRef(); private getPanelContent = () => { - const { isExportsDrawerOpen, isOptimizationsDrawerOpen } = this.props; + const { isExportsDrawerOpen } = this.props; if (isExportsDrawerOpen) { return ; - } else if (isOptimizationsDrawerOpen) { - return ; } return null; }; @@ -45,9 +41,9 @@ class CommonDrawerBase extends React.Component { }; public render() { - const { children, isExportsDrawerOpen, isOptimizationsDrawerOpen } = this.props; + const { children, isExportsDrawerOpen } = this.props; - const isExpanded = isExportsDrawerOpen || isOptimizationsDrawerOpen; + const isExpanded = isExportsDrawerOpen; // Sticky drawer is based on RHOSAK app, see: // https://github.com/redhat-developer/rhosak-ui/blob/main/apps/consoledot-rhosak/src/AppEntry.tsx#L30-L37 diff --git a/src/components/drawers/index.ts b/src/components/drawers/index.ts index 1486a2092..a9bc774a6 100644 --- a/src/components/drawers/index.ts +++ b/src/components/drawers/index.ts @@ -1,3 +1,2 @@ export * from './commonDrawer'; export * from './exports'; -export * from './optimzations'; diff --git a/src/components/permissions/permissions.tsx b/src/components/permissions/permissions.tsx index 3731e3d20..2a7c67a12 100644 --- a/src/components/permissions/permissions.tsx +++ b/src/components/permissions/permissions.tsx @@ -91,6 +91,7 @@ const PermissionsBase: React.FC = ({ case formatPath(routes.ibmDetails.path): return ibm; case formatPath(routes.ocpBreakdown.path): + case formatPath(routes.ocpBreakdownOptimizations.path): case formatPath(routes.ocpDetails.path): return ocp; case formatPath(routes.optimizationsBreakdown.path): diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 6beb43e7d..49e2a29ab 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -129,6 +129,11 @@ export default defineMessages({ description: 'Back to optimizations', id: 'breakdownBackToOptimizations', }, + breakdownBackToOptimizationsProject: { + defaultMessage: 'Back to optimizations for project {value}', + description: 'Back to optimizations for project {value}', + id: 'breakdownBackToOptimizationsProject', + }, breakdownBackToTitles: { defaultMessage: '{value, select, ' + @@ -2882,6 +2887,11 @@ export default defineMessages({ description: '{value} % of cost', id: 'percentOfCost', }, + percentPlus: { + defaultMessage: '{count, plural, one {+{value}%} other {{value}%}}', + description: 'Percent value with plus symbol', + id: 'percentPlus', + }, percentSymbol: { defaultMessage: '%', description: 'Percent symbol', diff --git a/src/routes.tsx b/src/routes.tsx index dce5b4bac..0475b1b77 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -84,6 +84,10 @@ const routes = { element: userAccess(OcpBreakdown), path: '/ocp/breakdown', }, + ocpBreakdownOptimizations: { + element: userAccess(OptimizationsBreakdown), + path: '/ocp/breakdown/optimizations', + }, ocpDetails: { element: userAccess(OcpDetails), path: '/ocp', diff --git a/src/routes/components/dataToolbar/basicToolbar.tsx b/src/routes/components/dataToolbar/basicToolbar.tsx index c3c45cc7f..76a62274b 100644 --- a/src/routes/components/dataToolbar/basicToolbar.tsx +++ b/src/routes/components/dataToolbar/basicToolbar.tsx @@ -248,17 +248,19 @@ export class BasicToolbarBase extends React.Component { - if (onFilterAdded) { - onFilterAdded(filter); + if (filter && filters) { + this.setState( + { + filters, + categoryInput: '', + }, + () => { + if (onFilterAdded) { + onFilterAdded(filter); + } } - } - ); + ); + } }; private handleOnCategoryInputSelect = (value, key) => { diff --git a/src/routes/components/dataToolbar/utils/category.tsx b/src/routes/components/dataToolbar/utils/category.tsx index 6380ceca4..b72ed6ce7 100644 --- a/src/routes/components/dataToolbar/utils/category.tsx +++ b/src/routes/components/dataToolbar/utils/category.tsx @@ -128,12 +128,12 @@ export const onCategoryInput = ({ key?: string; }) => { if (event && event.key && event.key !== 'Enter') { - return; + return {}; } const val = cleanInput(categoryInput); if (val.trim() === '') { - return; + return {}; } const isExcludes = currentExclude === ExcludeType.exclude; diff --git a/src/routes/components/optimizations/index.ts b/src/routes/components/optimizations/index.ts index 993983717..9d8c4f63d 100644 --- a/src/routes/components/optimizations/index.ts +++ b/src/routes/components/optimizations/index.ts @@ -1,2 +1,3 @@ +export * from './optimizationsBadge'; export * from './optimizationsTable'; export * from './optimizationsToolbar'; diff --git a/src/routes/components/optimizations/optimizationsBadge.tsx b/src/routes/components/optimizations/optimizationsBadge.tsx new file mode 100644 index 000000000..e32c75589 --- /dev/null +++ b/src/routes/components/optimizations/optimizationsBadge.tsx @@ -0,0 +1,88 @@ +import { Badge } from '@patternfly/react-core'; +import type { Query } from 'api/queries/query'; +import { getQuery } from 'api/queries/query'; +import { parseQuery } from 'api/queries/rosQuery'; +import type { RosReport } from 'api/ros/ros'; +import { RosPathsType, RosType } from 'api/ros/ros'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { rosActions, rosSelectors } from 'store/ros'; + +export interface OptimizationsBadgeOwnProps { + // TBD... +} + +export interface OptimizationsBadgeStateProps { + report?: RosReport; + reportError?: AxiosError; + reportFetchStatus?: FetchStatus; + reportQueryString?: string; +} + +type OptimizationsBadgeProps = OptimizationsBadgeOwnProps & OptimizationsBadgeStateProps; + +const reportPathsType = RosPathsType.recommendations; +const reportType = RosType.ros; + +const OptimizationsBadge: React.FC = () => { + const { report } = useMapToProps(); + const intl = useIntl(); + + const count = report && report.meta ? report.meta.count : 0; + + return {count}; +}; + +const useQueryFromRoute = () => { + const location = useLocation(); + return parseQuery(location.search); +}; + +const useMapToProps = (): OptimizationsBadgeStateProps => { + const dispatch: ThunkDispatch = useDispatch(); + const queryFromRoute = useQueryFromRoute(); + const groupBy = getGroupById(queryFromRoute); + const groupByValue = getGroupByValue(queryFromRoute); + + // Don't need pagination here + const reportQuery: any = { + ...(groupBy && { + [groupBy]: groupByValue, // project filter + }), + }; + + const reportQueryString = getQuery(reportQuery); + const report: any = useSelector((state: RootState) => + rosSelectors.selectRos(state, reportPathsType, reportType, reportQueryString) + ); + const reportFetchStatus = useSelector((state: RootState) => + rosSelectors.selectRosFetchStatus(state, reportPathsType, reportType, reportQueryString) + ); + const reportError = useSelector((state: RootState) => + rosSelectors.selectRosError(state, reportPathsType, reportType, reportQueryString) + ); + + useEffect(() => { + if (!reportError && reportFetchStatus !== FetchStatus.inProgress) { + dispatch(rosActions.fetchRosReport(reportPathsType, reportType, reportQueryString)); + } + }, [reportQueryString]); + + return { + report, + reportError, + reportFetchStatus, + reportQueryString, + }; +}; + +export { OptimizationsBadge }; diff --git a/src/routes/components/optimizations/optimizationsTable.tsx b/src/routes/components/optimizations/optimizationsTable.tsx index 463a9993f..6848e3bd3 100644 --- a/src/routes/components/optimizations/optimizationsTable.tsx +++ b/src/routes/components/optimizations/optimizationsTable.tsx @@ -2,30 +2,24 @@ import 'routes/components/dataTable/dataTable.scss'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import type { Query } from 'api/queries/query'; -import { parseQuery } from 'api/queries/query'; import type { RecommendationReport } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { routes } from 'routes'; +import { useIntl } from 'react-intl'; +import { Link, useLocation } from 'react-router-dom'; import { DataTable } from 'routes/components/dataTable'; import { styles } from 'routes/components/dataTable/dataTable.styles'; import { NoOptimizationsState } from 'routes/components/page/noOptimizations/noOptimizationsState'; -import { getGroupById } from 'routes/utils/groupBy'; import { getOptimizationsBreakdownPath } from 'routes/utils/paths'; -import { createMapStateToProps } from 'store/common'; -import { uiActions, uiSelectors } from 'store/ui'; import { getTimeFromNow } from 'utils/dates'; -import { formatPath } from 'utils/paths'; import { hasWarning } from 'utils/recomendations'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; -interface OptimizationsTableOwnProps extends RouterComponentProps { +interface OptimizationsTableOwnProps { + basePath?: string; + breadcrumbLabel?: string; + breadcrumbPath?: string; filterBy?: any; + groupBy?: string; isLoading?: boolean; onSort(value: string, isSortAscending: boolean); orderBy?: any; @@ -34,53 +28,24 @@ interface OptimizationsTableOwnProps extends RouterComponentProps { reportQueryString: string; } -interface OptimizationsTableState { - currentRow?: number; - columns?: any[]; - rows?: any[]; -} - -interface OptimizationsTableStateProps { - groupBy?: string; - isOpen?: boolean; -} - -interface OptimizationsTableDispatchProps { - closeOptimizationsDrawer: typeof uiActions.closeOptimizationsDrawer; - openOptimizationsDrawer: typeof uiActions.openOptimizationsDrawer; -} - -type OptimizationsTableProps = OptimizationsTableOwnProps & - OptimizationsTableStateProps & - OptimizationsTableDispatchProps & - WrappedComponentProps; - -class OptimizationsTableBase extends React.Component { - public state: OptimizationsTableState = { - columns: [], - rows: [], - }; - - public componentDidMount() { - this.initDatum(); - } - - public componentDidUpdate(prevProps: OptimizationsTableProps) { - const { report } = this.props; - const currentReport = report && report.data ? JSON.stringify(report.data) : ''; - const previousReport = prevProps.report && prevProps.report.data ? JSON.stringify(prevProps.report.data) : ''; - - if (previousReport !== currentReport) { - this.initDatum(); - } - } - - private initDatum = () => { - const { groupBy, intl, query, report } = this.props; - if (!report) { - return; - } - +type OptimizationsTableProps = OptimizationsTableOwnProps; + +const OptimizationsTable: React.FC = ({ + basePath, + breadcrumbLabel, + breadcrumbPath, + filterBy, + groupBy, + isLoading, + onSort, + orderBy, + query, + report, +}) => { + const intl = useIntl(); + const location = useLocation(); + + const initDatum = () => { const hasData = report && report.data && report.data.length > 0; const rows = []; @@ -136,11 +101,18 @@ class OptimizationsTableBase extends React.Component {container} @@ -178,57 +150,35 @@ class OptimizationsTableBase extends React.Component { - const { closeOptimizationsDrawer, onSort } = this.props; - - closeOptimizationsDrawer(); + const handleOnSort = (value: string, isSortAscending: boolean) => { if (onSort) { onSort(value, isSortAscending); } }; - public render() { - const { filterBy, isLoading, orderBy } = this.props; - const { columns, rows } = this.state; - - return ( - } - filterBy={filterBy} - isLoading={isLoading} - isOptimizations - isSelectable={false} - onSort={this.handleOnSort} - orderBy={orderBy} - rows={rows} - /> - ); + if (!report) { + return null; } -} - -const mapStateToProps = createMapStateToProps( - (state, { router }) => { - const queryFromRoute = parseQuery(router.location.search); - - return { - groupBy: getGroupById(queryFromRoute), - isOpen: uiSelectors.selectIsOptimizationsDrawerOpen(state), - }; - } -); - -const mapDispatchToProps: OptimizationsTableDispatchProps = { - closeOptimizationsDrawer: uiActions.closeOptimizationsDrawer, - openOptimizationsDrawer: uiActions.openOptimizationsDrawer, + const { columns, rows } = initDatum(); + + return ( + } + filterBy={filterBy} + isLoading={isLoading} + isSelectable={false} + onSort={handleOnSort} + orderBy={orderBy} + rows={rows} + /> + ); }; -const OptimizationsTable = injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(OptimizationsTableBase))); - export { OptimizationsTable }; diff --git a/src/routes/components/optimizations/optimizationsToolbar.tsx b/src/routes/components/optimizations/optimizationsToolbar.tsx index d3b3140bb..37894244c 100644 --- a/src/routes/components/optimizations/optimizationsToolbar.tsx +++ b/src/routes/components/optimizations/optimizationsToolbar.tsx @@ -4,10 +4,8 @@ import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; import { BasicToolbar } from 'routes/components/dataToolbar'; import type { Filter } from 'routes/utils/filter'; -import { createMapStateToProps } from 'store/common'; interface OptimizationsToolbarOwnProps { isDisabled?: boolean; @@ -20,24 +18,13 @@ interface OptimizationsToolbarOwnProps { query?: RosQuery; } -interface OptimizationsToolbarStateProps { - // TBD... -} - -interface OptimizationsToolbarDispatchProps { - // TBD... -} - interface OptimizationsToolbarState { categoryOptions?: ToolbarChipGroup[]; } -type OptimizationsToolbarProps = OptimizationsToolbarOwnProps & - OptimizationsToolbarStateProps & - OptimizationsToolbarDispatchProps & - WrappedComponentProps; +type OptimizationsToolbarProps = OptimizationsToolbarOwnProps & WrappedComponentProps; -export class OptimizationsToolbarBase extends React.Component { +class OptimizationsToolbarBase extends React.Component { protected defaultState: OptimizationsToolbarState = {}; public state: OptimizationsToolbarState = { ...this.defaultState }; @@ -93,19 +80,6 @@ export class OptimizationsToolbarBase extends React.Component(() => { - return { - // TBD... - }; -}); - -const mapDispatchToProps: OptimizationsToolbarDispatchProps = { - // TBD... -}; - -const OptimizationsToolbarConnect = connect(mapStateToProps, mapDispatchToProps)(OptimizationsToolbarBase); -const OptimizationsToolbar = injectIntl(OptimizationsToolbarConnect); +const OptimizationsToolbar = injectIntl(OptimizationsToolbarBase); export { OptimizationsToolbar }; -export type { OptimizationsToolbarProps }; diff --git a/src/routes/details/awsBreakdown/awsBreakdown.tsx b/src/routes/details/awsBreakdown/awsBreakdown.tsx index 1cb474c65..d505d0da9 100644 --- a/src/routes/details/awsBreakdown/awsBreakdown.tsx +++ b/src/routes/details/awsBreakdown/awsBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -39,7 +39,7 @@ const reportPathsType = ReportPathsType.aws; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = groupByOrgValue ? orgUnitIdKey : getGroupById(queryFromRoute); diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index 600036b5f..52c452089 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -14,6 +14,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; @@ -38,6 +39,7 @@ import { createMapStateToProps, FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCostType, getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { awsCategoryPrefix, logicalOrPrefix, noPrefix, orgUnitIdKey, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -226,6 +228,8 @@ class AwsDetails extends React.Component { return ( { onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/details/awsDetails/detailsTable.tsx b/src/routes/details/awsDetails/detailsTable.tsx index 50979db20..bea73b12a 100644 --- a/src/routes/details/awsDetails/detailsTable.tsx +++ b/src/routes/details/awsDetails/detailsTable.tsx @@ -1,5 +1,6 @@ import 'routes/components/dataTable/dataTable.scss'; +import type { Query } from 'api/queries/query'; import type { AwsReport } from 'api/reports/awsReports'; import { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -23,6 +24,7 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + breadcrumbPath?: string; filterBy?: any; groupBy: string; groupByCostCategory?: string; @@ -33,6 +35,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: AwsReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -69,12 +72,14 @@ class DetailsTableBase extends React.Component { const { + breadcrumbPath, groupBy, groupByCostCategory, groupByOrg, groupByTagKey, intl, isAllSelected, + query, report, router, selectedItems, @@ -165,10 +170,16 @@ class DetailsTableBase extends React.Component {label} diff --git a/src/routes/details/azureBreakdown/azureBreakdown.tsx b/src/routes/details/azureBreakdown/azureBreakdown.tsx index f14cea9f7..41e39baa7 100644 --- a/src/routes/details/azureBreakdown/azureBreakdown.tsx +++ b/src/routes/details/azureBreakdown/azureBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -39,7 +39,7 @@ const reportPathsType = ReportPathsType.azure; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/azureDetails/azureDetails.tsx b/src/routes/details/azureDetails/azureDetails.tsx index 869a2fad1..261925b25 100644 --- a/src/routes/details/azureDetails/azureDetails.tsx +++ b/src/routes/details/azureDetails/azureDetails.tsx @@ -12,6 +12,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; @@ -35,6 +36,7 @@ import { createMapStateToProps, FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -211,6 +213,8 @@ class AzureDetails extends React.Component return ( onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/details/azureDetails/detailsTable.tsx b/src/routes/details/azureDetails/detailsTable.tsx index aac7a3b0b..9a9fe25c3 100644 --- a/src/routes/details/azureDetails/detailsTable.tsx +++ b/src/routes/details/azureDetails/detailsTable.tsx @@ -1,5 +1,6 @@ import 'routes/components/dataTable/dataTable.scss'; +import type { Query } from 'api/queries/query'; import type { AzureReport } from 'api/reports/azureReports'; import { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -23,6 +24,7 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + breadcrumbPath?: string; filterBy?: any; isAllSelected?: boolean; groupBy: string; @@ -31,6 +33,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: AzureReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -66,7 +69,8 @@ class DetailsTableBase extends React.Component { - const { groupBy, groupByTagKey, intl, isAllSelected, report, selectedItems, router } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } @@ -138,9 +142,15 @@ class DetailsTableBase extends React.Component {label} diff --git a/src/routes/details/components/breakdown/breakdownBase.tsx b/src/routes/details/components/breakdown/breakdownBase.tsx index dd8973f83..ebd4dd543 100644 --- a/src/routes/details/components/breakdown/breakdownBase.tsx +++ b/src/routes/details/components/breakdown/breakdownBase.tsx @@ -2,6 +2,7 @@ import { Tab, TabContent, Tabs, TabTitleText } from '@patternfly/react-core'; import type { Providers } from 'api/providers'; import type { ProviderType } from 'api/providers'; import type { Query } from 'api/queries/query'; +import { getQueryState } from 'api/queries/query'; import type { Report } from 'api/reports/report'; import type { ReportPathsType, ReportType } from 'api/reports/report'; import type { TagPathsType } from 'api/tags/tag'; @@ -120,6 +121,17 @@ class BreakdownBase extends React.Component { if (newQuery || noReport || noLocation) { this.updateReport(); } + + // Clear state for optimizations breakdown only + const queryState = getQueryState(router.location, 'optimizations'); + if (queryState) { + router.navigate(`${router.location.pathname}${router.location.search}`, { + state: { + ...(router.location.state && router.location.state), + optimizations: undefined, + }, + }); + } } private getAvailableTabs = () => { @@ -297,6 +309,11 @@ class BreakdownBase extends React.Component { return ( <> { - private buildDetailsLink = url => { - const { groupBy, isOptimizationsPath, query } = this.props; - - let groupByKey = groupBy; - let value = '*'; - - // Retrieve org unit used by the details page - if (query[orgUnitIdKey]) { - groupByKey = orgUnitIdKey; - value = query[orgUnitIdKey]; - } - - const state = query.state ? window.atob(query.state) : undefined; - const newQuery = { - ...(state && JSON.parse(state)), - ...(!isOptimizationsPath && { - group_by: { - [groupByKey]: value, - }, - }), - }; - return `${url}?${getQueryRoute(newQuery)}`; - }; + // private buildDetailsLink = url => { + // const { groupBy, query, router } = this.props; + // + // let groupByKey = groupBy; + // let value = '*'; + // + // // Retrieve org unit used by the details page + // if (query[orgUnitIdKey]) { + // groupByKey = orgUnitIdKey; + // value = query[orgUnitIdKey]; + // } + // + // const queryState = getQueryState(router.location, 'details'); + // const newQuery = { + // ...(queryState && queryState), + // group_by: { + // [groupByKey]: value, + // }, + // }; + // return `${url}?${getQueryRoute(newQuery)}`; + // }; private getBackToLink = groupByKey => { - const { detailsURL, intl, isOptimizationsPath, tagPathsType } = this.props; - - if (isOptimizationsPath) { - return ( - - {intl.formatMessage(messages.breakdownBackToOptimizations)} - - ); + const { breadcrumb, intl, router, tagPathsType } = this.props; + + if (!breadcrumb) { + return null; } return ( - + {intl.formatMessage(messages.breakdownBackToDetails, { value: intl.formatMessage(messages.breakdownBackToTitles, { value: tagPathsType }), groupBy: groupByKey, @@ -103,6 +95,19 @@ class BreakdownHeader extends React.Component { ); }; + // private getBackToLink = groupByKey => { + // const { detailsURL, intl, router, tagPathsType } = this.props; + // + // return ( + // + // {intl.formatMessage(messages.breakdownBackToDetails, { + // value: intl.formatMessage(messages.breakdownBackToTitles, { value: tagPathsType }), + // groupBy: groupByKey, + // })} + // + // ); + // }; + private getTotalCost = () => { const { costDistribution, report } = this.props; @@ -217,15 +222,9 @@ class BreakdownHeader extends React.Component { } } -const mapStateToProps = createMapStateToProps( - (state, { router }) => { - const queryFromRoute = parseQuery(router.location.search); - - return { - isOptimizationsPath: queryFromRoute.optimizationsPath !== undefined, - }; - } -); +const mapStateToProps = createMapStateToProps(() => { + return {}; +}); const mapDispatchToProps: BreakdownHeaderDispatchProps = { // TDB diff --git a/src/routes/details/components/historicalData/historicalDataCostChart.tsx b/src/routes/details/components/historicalData/historicalDataCostChart.tsx index 88f11535c..53980af3b 100644 --- a/src/routes/details/components/historicalData/historicalDataCostChart.tsx +++ b/src/routes/details/components/historicalData/historicalDataCostChart.tsx @@ -1,6 +1,6 @@ import { Skeleton } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { Report } from 'api/reports/report'; import type { ReportPathsType, ReportType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -153,7 +153,7 @@ class HistoricalDataCostChartBase extends React.Component( (state, { costType, currency, reportPathsType, reportType, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/components/historicalData/historicalDataTrendChart.tsx b/src/routes/details/components/historicalData/historicalDataTrendChart.tsx index 43b67914b..27877f5b1 100644 --- a/src/routes/details/components/historicalData/historicalDataTrendChart.tsx +++ b/src/routes/details/components/historicalData/historicalDataTrendChart.tsx @@ -1,6 +1,6 @@ import { Skeleton } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { Report, ReportPathsType } from 'api/reports/report'; import { ReportType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -158,7 +158,7 @@ class HistoricalDataTrendChartBase extends React.Component( (state, { costType, currency, reportPathsType, reportType, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = groupByOrgValue ? orgUnitIdKey : getGroupById(queryFromRoute); diff --git a/src/routes/details/components/historicalData/historicalDataUsageChart.tsx b/src/routes/details/components/historicalData/historicalDataUsageChart.tsx index 086c37fde..e4dbba8ba 100644 --- a/src/routes/details/components/historicalData/historicalDataUsageChart.tsx +++ b/src/routes/details/components/historicalData/historicalDataUsageChart.tsx @@ -1,6 +1,6 @@ import { Skeleton } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { Report, ReportPathsType } from 'api/reports/report'; import { ReportType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -126,7 +126,7 @@ class HistoricalDataUsageChartBase extends React.Component( (state, { reportPathsType, reportType, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = getGroupById(queryFromRoute); diff --git a/src/routes/details/components/summary/summaryCard.tsx b/src/routes/details/components/summary/summaryCard.tsx index 9ef0725e8..2ea16ab36 100644 --- a/src/routes/details/components/summary/summaryCard.tsx +++ b/src/routes/details/components/summary/summaryCard.tsx @@ -11,7 +11,7 @@ import { TitleSizes, } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { OcpReport } from 'api/reports/ocpReports'; import type { ReportPathsType, ReportType } from 'api/reports/report'; import type { AxiosError } from 'axios'; @@ -224,7 +224,7 @@ class SummaryBase extends React.Component { const mapStateToProps = createMapStateToProps( (state, { costDistribution, costType, currency, reportGroupBy, reportPathsType, reportType, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = groupByOrgValue ? orgUnitIdKey : getGroupById(queryFromRoute); diff --git a/src/routes/details/components/summary/summaryModalContent.tsx b/src/routes/details/components/summary/summaryModalContent.tsx index 8dc915e16..0c4c5e7dd 100644 --- a/src/routes/details/components/summary/summaryModalContent.tsx +++ b/src/routes/details/components/summary/summaryModalContent.tsx @@ -1,6 +1,6 @@ import { Title, TitleSizes } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { Report, ReportPathsType } from 'api/reports/report'; import { ReportType } from 'api/reports/report'; import type { AxiosError } from 'axios'; @@ -109,7 +109,7 @@ class SummaryModalContentBase extends React.Component( (state, { costDistribution, costType, currency, reportGroupBy, reportPathsType, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = groupByOrgValue ? orgUnitIdKey : getGroupById(queryFromRoute); diff --git a/src/routes/details/components/tag/tagLink.tsx b/src/routes/details/components/tag/tagLink.tsx index bad1ee966..089ad005c 100644 --- a/src/routes/details/components/tag/tagLink.tsx +++ b/src/routes/details/components/tag/tagLink.tsx @@ -1,6 +1,6 @@ import { TagIcon } from '@patternfly/react-icons/dist/esm/icons/tag-icon'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { Tag, TagPathsType } from 'api/tags/tag'; import { TagType } from 'api/tags/tag'; import React from 'react'; @@ -110,7 +110,7 @@ class TagLinkBase extends React.Component { const mapStateToProps = createMapStateToProps((state, { router, tagPathsType }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = groupByOrgValue ? orgUnitIdKey : getGroupById(queryFromRoute); diff --git a/src/routes/details/components/tag/tagModal.tsx b/src/routes/details/components/tag/tagModal.tsx index 4cd7bee3d..48f4a972c 100644 --- a/src/routes/details/components/tag/tagModal.tsx +++ b/src/routes/details/components/tag/tagModal.tsx @@ -1,6 +1,6 @@ import { Modal } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { Tag, TagPathsType } from 'api/tags/tag'; import { TagType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -109,7 +109,7 @@ class TagModalBase extends React.Component { const mapStateToProps = createMapStateToProps( (state, { router, tagPathsType }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupByOrgValue = getGroupByOrgValue(queryFromRoute); const groupBy = groupByOrgValue ? orgUnitIdKey : getGroupById(queryFromRoute); diff --git a/src/routes/details/components/usageChart/usageChart.tsx b/src/routes/details/components/usageChart/usageChart.tsx index 0f7318a2c..92736b42d 100644 --- a/src/routes/details/components/usageChart/usageChart.tsx +++ b/src/routes/details/components/usageChart/usageChart.tsx @@ -5,7 +5,7 @@ import { Grid, GridItem, Skeleton } from '@patternfly/react-core'; import type { OcpQuery } from 'api/queries/ocpQuery'; import { parseQuery } from 'api/queries/ocpQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState } from 'api/queries/query'; import type { Report, ReportPathsType } from 'api/reports/report'; import { ReportType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -462,7 +462,7 @@ class UsageChartBase extends React.Component { const mapStateToProps = createMapStateToProps( (state, { reportPathsType, reportType, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/gcpBreakdown/gcpBreakdown.tsx b/src/routes/details/gcpBreakdown/gcpBreakdown.tsx index 3b641a7c4..ad4b1d90c 100644 --- a/src/routes/details/gcpBreakdown/gcpBreakdown.tsx +++ b/src/routes/details/gcpBreakdown/gcpBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -39,7 +39,7 @@ const reportPathsType = ReportPathsType.gcp; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/gcpDetails/detailsTable.tsx b/src/routes/details/gcpDetails/detailsTable.tsx index 65fa3cc8d..a71b8c969 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 type { Query } from 'api/queries/query'; import type { GcpReport } from 'api/reports/gcpReports'; import { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -23,6 +24,7 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + breadcrumbPath?: string; filterBy?: any; groupBy: string; groupByTagKey?: string; @@ -31,6 +33,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: GcpReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -66,7 +69,8 @@ class DetailsTableBase extends React.Component { - const { groupBy, groupByTagKey, intl, isAllSelected, report, router, selectedItems } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } @@ -138,9 +142,15 @@ class DetailsTableBase extends React.Component {label} diff --git a/src/routes/details/gcpDetails/gcpDetails.tsx b/src/routes/details/gcpDetails/gcpDetails.tsx index a0e3690ac..d324d2190 100644 --- a/src/routes/details/gcpDetails/gcpDetails.tsx +++ b/src/routes/details/gcpDetails/gcpDetails.tsx @@ -12,6 +12,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; @@ -35,6 +36,7 @@ import { createMapStateToProps, FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -210,6 +212,8 @@ class GcpDetails extends React.Component { return ( { onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/details/ibmBreakdown/ibmBreakdown.tsx b/src/routes/details/ibmBreakdown/ibmBreakdown.tsx index c8179d88f..70a7386e8 100644 --- a/src/routes/details/ibmBreakdown/ibmBreakdown.tsx +++ b/src/routes/details/ibmBreakdown/ibmBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -39,7 +39,7 @@ const reportPathsType = ReportPathsType.ibm; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/ibmDetails/detailsTable.tsx b/src/routes/details/ibmDetails/detailsTable.tsx index d58bc5342..d3cc55bc2 100644 --- a/src/routes/details/ibmDetails/detailsTable.tsx +++ b/src/routes/details/ibmDetails/detailsTable.tsx @@ -1,5 +1,6 @@ import 'routes/components/dataTable/dataTable.scss'; +import type { Query } from 'api/queries/query'; import type { IbmReport } from 'api/reports/ibmReports'; import { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -23,6 +24,7 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + breadcrumbPath?: string; filterBy?: any; groupBy: string; groupByTagKey?: string; @@ -31,6 +33,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: IbmReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -66,7 +69,8 @@ class DetailsTableBase extends React.Component { - const { groupBy, groupByTagKey, intl, isAllSelected, report, selectedItems, router } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } @@ -138,9 +142,15 @@ class DetailsTableBase extends React.Component {label} diff --git a/src/routes/details/ibmDetails/ibmDetails.tsx b/src/routes/details/ibmDetails/ibmDetails.tsx index 61010826c..15125c6d0 100644 --- a/src/routes/details/ibmDetails/ibmDetails.tsx +++ b/src/routes/details/ibmDetails/ibmDetails.tsx @@ -12,6 +12,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; @@ -36,6 +37,7 @@ import { createMapStateToProps, FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -212,6 +214,8 @@ class IbmDetails extends React.Component { return ( { onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/details/ociBreakdown/ociBreakdown.tsx b/src/routes/details/ociBreakdown/ociBreakdown.tsx index e4193ecab..4e8e83a87 100644 --- a/src/routes/details/ociBreakdown/ociBreakdown.tsx +++ b/src/routes/details/ociBreakdown/ociBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -39,7 +39,7 @@ const reportPathsType = ReportPathsType.oci; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/ociDetails/detailsTable.tsx b/src/routes/details/ociDetails/detailsTable.tsx index 754242dda..7f8264333 100644 --- a/src/routes/details/ociDetails/detailsTable.tsx +++ b/src/routes/details/ociDetails/detailsTable.tsx @@ -1,5 +1,6 @@ import 'routes/components/dataTable/dataTable.scss'; +import type { Query } from 'api/queries/query'; import type { OciReport } from 'api/reports/ociReports'; import { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -23,6 +24,7 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + breadcrumbPath?: string; filterBy?: any; isAllSelected?: boolean; groupBy: string; @@ -31,6 +33,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: OciReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -66,7 +69,8 @@ class DetailsTableBase extends React.Component { - const { groupBy, groupByTagKey, intl, isAllSelected, report, router, selectedItems } = this.props; + const { breadcrumbPath, groupBy, groupByTagKey, intl, isAllSelected, query, report, router, selectedItems } = + this.props; if (!report) { return; } @@ -138,9 +142,15 @@ class DetailsTableBase extends React.Component {label} diff --git a/src/routes/details/ociDetails/ociDetails.tsx b/src/routes/details/ociDetails/ociDetails.tsx index ce40b949d..cefd7f8ab 100644 --- a/src/routes/details/ociDetails/ociDetails.tsx +++ b/src/routes/details/ociDetails/ociDetails.tsx @@ -12,6 +12,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; @@ -35,6 +36,7 @@ import { createMapStateToProps, FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -211,6 +213,8 @@ class OciDetails extends React.Component { return ( { onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/details/ocpBreakdown/ocpBreakdown.tsx b/src/routes/details/ocpBreakdown/ocpBreakdown.tsx index 81f533c15..4d1b970b6 100644 --- a/src/routes/details/ocpBreakdown/ocpBreakdown.tsx +++ b/src/routes/details/ocpBreakdown/ocpBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -10,6 +10,7 @@ import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { routes } from 'routes'; +import { OptimizationsBadge } from 'routes/components/optimizations'; import type { BreakdownStateProps } from 'routes/details/components/breakdown'; import { BreakdownBase } from 'routes/details/components/breakdown'; import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; @@ -27,8 +28,7 @@ import { withRouter } from 'utils/router'; import { CostOverview } from './costOverview'; import { HistoricalData } from './historicalData'; -import { OptimizationsBadge } from './optimizationsBadge'; -import { OptimizationsBreakdown } from './optimizationsBreakdown'; +import { OcpBreakdownOptimizations } from './ocpBreakdownOptimizations'; interface BreakdownDispatchProps { closeOptimizationsDrawer?: typeof uiActions.closeOptimizationsDrawer; @@ -44,7 +44,7 @@ const reportPathsType = ReportPathsType.ocp; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); @@ -117,7 +117,7 @@ const mapStateToProps = createMapStateToProps, - optimizationsComponent: groupBy === 'project' && groupByValue !== '*' ? : undefined, + optimizationsComponent: groupBy === 'project' && groupByValue !== '*' ? : undefined, providers: filterProviders(providers, ProviderType.ocp), providersFetchStatus, providerType: ProviderType.ocp, diff --git a/src/routes/details/ocpBreakdown/optimizationsBreakdown.tsx b/src/routes/details/ocpBreakdown/ocpBreakdownOptimizations.tsx similarity index 82% rename from src/routes/details/ocpBreakdown/optimizationsBreakdown.tsx rename to src/routes/details/ocpBreakdown/ocpBreakdownOptimizations.tsx index 612ab6fea..1c710808d 100644 --- a/src/routes/details/ocpBreakdown/optimizationsBreakdown.tsx +++ b/src/routes/details/ocpBreakdown/ocpBreakdownOptimizations.tsx @@ -1,6 +1,6 @@ import { Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { RosQuery } from 'api/queries/rosQuery'; import type { RosReport } from 'api/ros/ros'; import { RosPathsType, RosType } from 'api/ros/ros'; @@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; +import { routes } from 'routes'; import { OptimizationsTable, OptimizationsToolbar } from 'routes/components/optimizations'; import { Loading } from 'routes/components/page/loading'; import { NoOptimizations } from 'routes/components/page/noOptimizations'; @@ -23,25 +24,27 @@ import * as queryUtils from 'routes/utils/query'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; -import { uiActions } from 'store/ui'; +import { formatPath } from 'utils/paths'; +import { breakdownTitleKey } from 'utils/props'; -interface OptimizationsBreakdownOwnProps { +interface OcpOptimizationsBreakdownOwnProps { // TBD... } -export interface OptimizationsBreakdownStateProps { - closeOptimizationsDrawer: typeof uiActions.closeOptimizationsDrawer; +export interface OcpOptimizationsBreakdownStateProps { + groupBy?: string; + project?: number | string; report: RosReport; reportError: AxiosError; reportFetchStatus: FetchStatus; reportQueryString: string; } -export interface OptimizationsBreakdownMapProps { +export interface OcpOptimizationsBreakdownMapProps { query?: RosQuery; } -type OptimizationsBreakdownProps = OptimizationsBreakdownOwnProps; +type OcpOptimizationsBreakdownProps = OcpOptimizationsBreakdownOwnProps; const baseQuery: RosQuery = { limit: 10, @@ -54,11 +57,13 @@ const baseQuery: RosQuery = { const reportType = RosType.ros as any; const reportPathsType = RosPathsType.recommendations as any; -const OptimizationsBreakdown: React.FC = () => { - const [query, setQuery] = useState({ ...baseQuery }); +const OcpBreakdownOptimizations: React.FC = () => { const intl = useIntl(); + const location = useLocation(); - const { closeOptimizationsDrawer, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ + const queryState = getQueryState(location, 'optimizations'); + const [query, setQuery] = useState({ ...baseQuery, ...(queryState && queryState) }); + const { groupBy, project, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ query, }); @@ -92,10 +97,15 @@ const OptimizationsBreakdown: React.FC = () => { const getTable = () => { return ( handleOnSort(sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} /> @@ -123,40 +133,34 @@ const OptimizationsBreakdown: React.FC = () => { const handleOnFilterAdded = filter => { const newQuery = queryUtils.handleOnFilterAdded(query, filter); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnFilterRemoved = filter => { const newQuery = queryUtils.handleOnFilterRemoved(query, filter); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnPerPageSelect = perPage => { const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnSetPage = pageNumber => { const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true); setQuery(newQuery); - closeOptimizationsDrawer(); }; const handleOnSort = (sortType, isSortAscending) => { const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); setQuery(newQuery); - closeOptimizationsDrawer(); }; const itemsTotal = report && report.meta ? report.meta.count : 0; const isDisabled = itemsTotal === 0; - const title = intl.formatMessage(messages.optimizations); const hasOptimizations = report && report.meta && report.meta.count > 0; if (reportError) { - return ; + return ; } if (!query.filter_by && !hasOptimizations && reportFetchStatus === FetchStatus.complete) { return ; @@ -185,7 +189,7 @@ const useQueryFromRoute = () => { }; // eslint-disable-next-line no-empty-pattern -const useMapToProps = ({ query }: OptimizationsBreakdownMapProps): OptimizationsBreakdownStateProps => { +const useMapToProps = ({ query }: OcpOptimizationsBreakdownMapProps): OcpOptimizationsBreakdownStateProps => { const dispatch: ThunkDispatch = useDispatch(); const queryFromRoute = useQueryFromRoute(); @@ -222,7 +226,8 @@ const useMapToProps = ({ query }: OptimizationsBreakdownMapProps): Optimizations }, [query]); return { - closeOptimizationsDrawer: uiActions.closeOptimizationsDrawer, + groupBy, + project: queryFromRoute[breakdownTitleKey], report, reportError, reportFetchStatus, @@ -230,4 +235,4 @@ const useMapToProps = ({ query }: OptimizationsBreakdownMapProps): Optimizations }; }; -export { OptimizationsBreakdown }; +export { OcpBreakdownOptimizations }; diff --git a/src/routes/details/ocpBreakdown/optimizationsBadge.tsx b/src/routes/details/ocpBreakdown/optimizationsBadge.tsx deleted file mode 100644 index 893f84a19..000000000 --- a/src/routes/details/ocpBreakdown/optimizationsBadge.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { Badge } from '@patternfly/react-core'; -import { getQuery } from 'api/queries/query'; -import type { RosQuery } from 'api/queries/rosQuery'; -import { parseQuery } from 'api/queries/rosQuery'; -import type { RosReport } from 'api/ros/ros'; -import { RosPathsType, RosType } from 'api/ros/ros'; -import type { AxiosError } from 'axios'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; -import type { FetchStatus } from 'store/common'; -import { createMapStateToProps } from 'store/common'; -import { rosActions, rosSelectors } from 'store/ros'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; - -export interface OptimizationsBadgeOwnProps extends RouterComponentProps, WrappedComponentProps { - // TBD... -} - -export interface OptimizationsBadgeStateProps { - report?: RosReport; - reportError?: AxiosError; - reportFetchStatus?: FetchStatus; - reportQueryString?: string; -} - -interface OptimizationsBadgeDispatchProps { - fetchReport: typeof rosActions.fetchRosReport; -} - -interface OptimizationsBadgeState {} - -type OptimizationsBadgeProps = OptimizationsBadgeOwnProps & - OptimizationsBadgeStateProps & - OptimizationsBadgeDispatchProps; - -const reportPathsType = RosPathsType.recommendations; -const reportType = RosType.ros; - -class OptimizationsBadgeBase extends React.Component { - protected defaultState: OptimizationsBadgeState = { - // TBD... - }; - public state: OptimizationsBadgeState = { ...this.defaultState }; - - public componentDidMount() { - this.updateReport(); - } - - private updateReport = () => { - const { fetchReport, reportQueryString } = this.props; - fetchReport(reportPathsType, reportType, reportQueryString); - }; - - public render() { - const { intl, report } = this.props; - - const count = report && report.meta ? report.meta.count : 0; - - return {count}; - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps( - (state, { router }) => { - const queryFromRoute = parseQuery(router.location.search); - const groupBy = getGroupById(queryFromRoute); - const groupByValue = getGroupByValue(queryFromRoute); - - // Don't need pagination here - const reportQuery: any = { - ...(groupBy && { - [groupBy]: groupByValue, // project filter - }), - }; - - const reportQueryString = getQuery(reportQuery); - const report = rosSelectors.selectRos(state, reportPathsType, reportType, reportQueryString); - const reportError = rosSelectors.selectRosError(state, reportPathsType, reportType, reportQueryString); - const reportFetchStatus = rosSelectors.selectRosFetchStatus(state, reportPathsType, reportType, reportQueryString); - - return { - report, - reportError, - reportFetchStatus, - reportQueryString, - } as any; - } -); - -const mapDispatchToProps: OptimizationsBadgeDispatchProps = { - fetchReport: rosActions.fetchRosReport, -}; - -const OptimizationsBadge = injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(OptimizationsBadgeBase))); - -export { OptimizationsBadge }; diff --git a/src/routes/details/ocpDetails/detailsOptimization.tsx b/src/routes/details/ocpDetails/detailsOptimization.tsx index ad234fb87..0a8e25282 100644 --- a/src/routes/details/ocpDetails/detailsOptimization.tsx +++ b/src/routes/details/ocpDetails/detailsOptimization.tsx @@ -1,3 +1,4 @@ +import type { Query } from 'api/queries/query'; import { getQuery } from 'api/queries/query'; import type { RosReport } from 'api/ros/ros'; import { RosPathsType, RosType } from 'api/ros/ros'; @@ -5,17 +6,18 @@ import type { AxiosError } from 'axios'; import React from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; -import { routes } from 'routes'; import { getBreakdownPath } from 'routes/utils/paths'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; -import { formatPath } from 'utils/paths'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; export interface DetailsOptimizationOwnProps { + basePath?: string; + breadcrumbPath?: string; project?: string; + query?: Query; } export interface DetailsOptimizationStateProps { @@ -55,7 +57,7 @@ class DetailsOptimization extends React.Component { - const { project, router } = this.props; + const { basePath, breadcrumbPath, project, query, router } = this.props; if (count === 0 || project === undefined) { return count; @@ -63,13 +65,19 @@ class DetailsOptimization extends React.Component {count} diff --git a/src/routes/details/ocpDetails/detailsTable.tsx b/src/routes/details/ocpDetails/detailsTable.tsx index 9c91a342b..729c6f27e 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 { ProviderType } from 'api/providers'; +import type { Query } from 'api/queries/query'; import type { OcpReport, OcpReportItem } from 'api/reports/ocpReports'; import { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -28,6 +29,8 @@ import { withRouter } from 'utils/router'; import DetailsOptimization from './detailsOptimization'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + basePath?: string; + breadcrumbPath?: string; costDistribution?: string; filterBy?: any; groupBy: string; @@ -39,6 +42,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: OcpReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -86,6 +90,8 @@ class DetailsTableBase extends React.Component { const { + basePath, + breadcrumbPath, costDistribution, groupBy, groupByTagKey, @@ -93,6 +99,7 @@ class DetailsTableBase extends React.Component {label} @@ -267,7 +280,14 @@ class DetailsTableBase extends React.Component, + value: !isPlatformCosts && !isDisabled && ( + + ), }, { value: monthOverMonth, id: DetailsTableColumnIds.monthOverMonth }, { diff --git a/src/routes/details/ocpDetails/ocpDetails.tsx b/src/routes/details/ocpDetails/ocpDetails.tsx index 1d58aedcf..53319da4d 100644 --- a/src/routes/details/ocpDetails/ocpDetails.tsx +++ b/src/routes/details/ocpDetails/ocpDetails.tsx @@ -13,6 +13,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ComputedReportItemValueType } from 'routes/components/charts/common'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; @@ -41,6 +42,7 @@ import { featureFlagsSelectors } from 'store/featureFlags'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCostDistribution, getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { noPrefix, platformCategoryKey, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -261,6 +263,8 @@ class OcpDetails extends React.Component { return ( { onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/details/rhelBreakdown/rhelBreakdown.tsx b/src/routes/details/rhelBreakdown/rhelBreakdown.tsx index ffc604dfa..e857f70a0 100644 --- a/src/routes/details/rhelBreakdown/rhelBreakdown.tsx +++ b/src/routes/details/rhelBreakdown/rhelBreakdown.tsx @@ -1,7 +1,7 @@ import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import { ReportPathsType, ReportType } from 'api/reports/report'; import { TagPathsType } from 'api/tags/tag'; import messages from 'locales/messages'; @@ -39,7 +39,7 @@ const reportPathsType = ReportPathsType.rhel; // eslint-disable-next-line @typescript-eslint/no-unused-vars const mapStateToProps = createMapStateToProps((state, { intl, router }) => { const queryFromRoute = parseQuery(router.location.search); - const queryState = parseQueryState(queryFromRoute); + const queryState = getQueryState(router.location, 'details'); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); diff --git a/src/routes/details/rhelDetails/detailsTable.tsx b/src/routes/details/rhelDetails/detailsTable.tsx index 5c15767bb..c25bb3e92 100644 --- a/src/routes/details/rhelDetails/detailsTable.tsx +++ b/src/routes/details/rhelDetails/detailsTable.tsx @@ -2,6 +2,7 @@ import 'routes/components/dataTable/dataTable.scss'; import { Label } from '@patternfly/react-core'; import { ProviderType } from 'api/providers'; +import type { Query } from 'api/queries/query'; import { ReportPathsType } from 'api/reports/report'; import type { RhelReport } from 'api/reports/rhelReports'; import messages from 'locales/messages'; @@ -25,6 +26,7 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps { + breadcrumbPath?: string; filterBy?: any; groupBy: string; groupByTagKey: string; @@ -34,6 +36,7 @@ interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentPro onSelected(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; + query?: Query; report: RhelReport; reportQueryString: string; selectedItems?: ComputedReportItem[]; @@ -79,7 +82,18 @@ class DetailsTableBase extends React.Component { - const { groupBy, groupByTagKey, hiddenColumns, intl, isAllSelected, report, router, selectedItems } = this.props; + const { + breadcrumbPath, + groupBy, + groupByTagKey, + hiddenColumns, + intl, + isAllSelected, + query, + report, + router, + selectedItems, + } = this.props; if (!report) { return; } @@ -195,9 +209,15 @@ class DetailsTableBase extends React.Component {label} diff --git a/src/routes/details/rhelDetails/rhelDetails.tsx b/src/routes/details/rhelDetails/rhelDetails.tsx index 2d0e462d9..a960f80f0 100644 --- a/src/routes/details/rhelDetails/rhelDetails.tsx +++ b/src/routes/details/rhelDetails/rhelDetails.tsx @@ -13,6 +13,7 @@ import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { routes } from 'routes'; import { ExportModal } from 'routes/components/export'; import { Loading } from 'routes/components/page/loading'; import { NoData } from 'routes/components/page/noData'; @@ -38,6 +39,7 @@ import { createMapStateToProps, FetchStatus } from 'store/common'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { getCurrency } from 'utils/localStorage'; +import { formatPath } from 'utils/paths'; import { noPrefix, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; @@ -254,6 +256,8 @@ class RhelDetails extends React.Component { return ( { onSelected={this.handleOnSelected} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} + query={query} report={report} reportQueryString={reportQueryString} selectedItems={selectedItems} diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts index 63c6df6aa..70f2be23a 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts @@ -1,35 +1,14 @@ -import global_danger_color_100 from '@patternfly/react-tokens/dist/js/global_danger_color_100'; import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg'; -import global_success_color_100 from '@patternfly/react-tokens/dist/js/global_success_color_100'; import type React from 'react'; export const styles = { alertContainer: { marginBottom: global_spacer_lg.value, }, - codeBlock: { - display: 'flex', - }, container: { minHeight: '100vh', }, currentActions: { height: '36px', }, - decrease: { - color: global_success_color_100.var, - }, - increase: { - color: global_danger_color_100.var, - }, - leftCodeBlock: { - flexShrink: 1, - flexDirection: 'column', - minWidth: '225px', - }, - rightCodeBlock: { - display: 'flex', - flexGrow: 1, - flexDirection: 'column', - }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx index 8eafd40af..6becbe4b9 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx @@ -1,23 +1,6 @@ import './optimizationsBreakdown.scss'; -import { - Alert, - Card, - CardBody, - CardTitle, - ClipboardCopyButton, - CodeBlock, - CodeBlockAction, - CodeBlockCode, - Grid, - GridItem, - List, - ListItem, - PageSection, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; +import { Alert, List, ListItem, PageSection } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; import { parseQuery } from 'api/queries/query'; import type { RosQuery } from 'api/queries/rosQuery'; @@ -26,24 +9,21 @@ import { RosPathsType, RosType } from 'api/ros/ros'; import type { AxiosError } from 'axios'; import messages from 'locales/messages'; import React, { useEffect, useState } from 'react'; -import type { WrappedComponentProps } from 'react-intl'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; -import { routes } from 'routes'; import { Loading } from 'routes/components/page/loading'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; -import { formatOptimization } from 'utils/format'; -import { formatPath } from 'utils/paths'; -import { getNotifications, hasRecommendation, hasRecommendationValues } from 'utils/recomendations'; +import { breadcrumbLabelKey } from 'utils/props'; +import { getNotifications, hasRecommendation } from 'utils/recomendations'; import type { RouterComponentProps } from 'utils/router'; -import YAML from 'yaml'; import { styles } from './optimizationsBreakdown.styles'; +import { OptimizationsBreakdownConfiguration } from './optimizationsBreakdownConfiguration'; import { OptimizationsBreakdownHeader } from './optimizationsBreakdownHeader'; interface OptimizationsBreakdownOwnProps extends RouterComponentProps { @@ -51,25 +31,18 @@ interface OptimizationsBreakdownOwnProps extends RouterComponentProps { } interface OptimizationsBreakdownStateProps { - queryFromRoute?: Query; + breadcrumbLabel?: string; report?: RecommendationReportData; reportError?: AxiosError; reportFetchStatus?: FetchStatus; reportQueryString?: string; } -interface OptimizationsBreakdownDispatchProps { - fetchRosReport: typeof rosActions.fetchRosReport; -} - export interface OptimizationsBreakdownMapProps { query?: RosQuery; } -type OptimizationsBreakdownProps = OptimizationsBreakdownOwnProps & - OptimizationsBreakdownStateProps & - OptimizationsBreakdownDispatchProps & - WrappedComponentProps; +type OptimizationsBreakdownProps = OptimizationsBreakdownOwnProps & OptimizationsBreakdownStateProps; // eslint-disable-next-line no-shadow export const enum Interval { @@ -82,9 +55,9 @@ const reportType = RosType.ros as any; const reportPathsType = RosPathsType.recommendation as any; const OptimizationsBreakdown: React.FC = () => { - const [copied, setCopied] = React.useState(false); - const { queryFromRoute, report, reportFetchStatus } = useMapToProps(); + const { breadcrumbLabel, report, reportFetchStatus } = useMapToProps(); const intl = useIntl(); + const location = useLocation(); const getDefaultTerm = () => { let result = Interval.short_term; @@ -128,116 +101,6 @@ const OptimizationsBreakdown: React.FC = () => { ); }; - const getChangeValue = (value, units) => { - const blockComment = `# `; - return ( - <> - {value !== null && value < 0 ? ( - <> - {blockComment} - - {intl.formatMessage(messages.optimizationsValue, { - value: formatOptimization(value), - units, - })} - - - ) : value > 0 ? ( - <> - {blockComment} - - {intl.formatMessage(messages.optimizationsValue, { - value: formatOptimization(value), - units, - })} - - - ) : value === 0 ? ( - <> - {blockComment} - {intl.formatMessage(messages.optimizationsValue, { - value: formatOptimization(value), - units, - })} - - ) : ( - - )} - - ); - }; - - const getConfig = (key: 'config' | 'current') => { - const term = getRecommendationTerm(); - - const hasConfigLimitsCpu = hasRecommendationValues(term, key, 'limits', 'cpu'); - const hasConfigLimitsMemory = hasRecommendationValues(term, key, 'limits', 'memory'); - const hasConfigRequestsCpu = hasRecommendationValues(term, key, 'requests', 'cpu'); - const hasConfigRequestsMemory = hasRecommendationValues(term, key, 'requests', 'memory'); - - const cpuConfigLimitsAmount = hasConfigLimitsCpu ? term[key].limits.cpu.amount : undefined; - const cpuConfigLimitsUnits = hasConfigLimitsCpu ? term[key].limits.cpu.format : undefined; - const cpuConfigRequestsAmount = hasConfigRequestsCpu ? term[key].requests.cpu.amount : undefined; - const cpuConfigRequestsUnits = hasConfigRequestsCpu ? term[key].requests.cpu.format : undefined; - - const memConfigLimitsAmount = hasConfigLimitsMemory ? term[key].limits.memory.amount : undefined; - const memConfigLimitsUnits = hasConfigLimitsMemory ? term[key].limits.memory.format : undefined; - const memConfigRequestsAmount = hasConfigRequestsMemory ? term[key].requests.memory.amount : undefined; - const memConfigRequestsUnits = hasConfigRequestsMemory ? term[key].requests.memory.format : undefined; - - return { - resources: { - requests: { - memory: intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(memConfigRequestsAmount), - units: memConfigRequestsUnits, - }), - cpu: intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(cpuConfigRequestsAmount), - units: cpuConfigRequestsUnits, - }), - }, - limits: { - memory: intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(memConfigLimitsAmount), - units: memConfigLimitsUnits, - }), - cpu: intl.formatMessage(messages.optimizationsValue, { - value: getFormattedValue(cpuConfigLimitsAmount), - units: cpuConfigLimitsUnits, - }), - }, - }, - }; - }; - - const getCurrentConfig = () => { - const code = getConfig('current'); - - return YAML.stringify(code, null, 2); - }; - - const getCurrentConfigCodeBlock = () => { - const code = getCurrentConfig(); - if (code === null) { - return null; - } - return ( - - {code} - - ); - }; - - // Returns empty element to force a header - const getEmptyActions = () => { - return
; - }; - - const getFormattedValue = value => { - return value !== undefined ? formatOptimization(value) : undefined; - }; - const getRecommendationTerm = (): RecommendationItem => { if (!report) { return undefined; @@ -258,201 +121,22 @@ const OptimizationsBreakdown: React.FC = () => { return result; }; - const getOptimizationCards = () => { - if (!report) { - return null; - } - return ( - - - - - - {intl.formatMessage(messages.currentConfiguration)} - - - -
-
- {getCurrentConfigCodeBlock()} -
-
- {getWarningsCodeBlock()} -
-
-
-
-
- - - - - {intl.formatMessage(messages.recommendedConfiguration)} - - - -
-
- {getRecommendedConfigCodeBlock()} -
-
- {getVariationConfigCodeBlock()} -
-
-
-
-
-
- ); - }; - - const getRecommendedActions = () => { - const code = getRecommendedConfig(); - - return ( - - handleClipboardCopyOnClick(e, code)} - exitDelay={copied ? 1500 : 600} - maxWidth="110px" - variant="plain" - onTooltipHidden={() => setCopied(false)} - > - {intl.formatMessage(copied ? messages.copyToClipboardSuccessfull : messages.copyToClipboard)} - - - ); - }; - - const getRecommendedConfig = () => { - const code = getConfig('config'); - if (code === null) { - return null; - } - return YAML.stringify(code, null, 2); - }; - - const getRecommendedConfigCodeBlock = () => { - const code = getRecommendedConfig(); - if (code === null) { - return null; - } - return ( - - {code} - - ); - }; - - const getVariationConfigCodeBlock = () => { - const code = getVariationConfig(); - if (code === null) { - return null; - } - return ( - - {code} - - ); - }; - - const getVariationConfig = () => { - const term = getRecommendationTerm(); - - const hasVariationLimitsCpu = hasRecommendationValues(term, 'variation', 'limits', 'cpu'); - const hasVariationLimitsMemory = hasRecommendationValues(term, 'variation', 'limits', 'memory'); - const hasVariationRequestsCpu = hasRecommendationValues(term, 'variation', 'requests', 'cpu'); - const hasVariationRequestsMemory = hasRecommendationValues(term, 'variation', 'requests', 'memory'); - - const cpuVariationLimitsAmount = hasVariationLimitsCpu ? term.variation.limits.cpu.amount : undefined; - const cpuVariationLimitsUnits = hasVariationLimitsCpu ? term.variation.limits.cpu.format : undefined; - const memVariationLimitsAmount = hasVariationLimitsMemory ? term.variation.limits.memory.amount : undefined; - const memVariationLimitsUnits = hasVariationLimitsMemory ? term.variation.limits.memory.format : undefined; - - const cpuVariationRequestsAmount = hasVariationRequestsCpu ? term.variation.requests.cpu.amount : undefined; - const cpuVariationRequestsUnits = hasVariationRequestsCpu ? term.variation.requests.cpu.format : undefined; - const memVariationRequestsAmount = hasVariationRequestsMemory ? term.variation.requests.memory.amount : undefined; - const memVariationRequestsUnits = hasVariationRequestsMemory ? term.variation.requests.memory.format : undefined; - - const cpuVariationLimitsChange = getChangeValue(cpuVariationLimitsAmount, cpuVariationLimitsUnits); - const memoryVariationLimitsChange = getChangeValue(memVariationLimitsAmount, memVariationLimitsUnits); - const cpuVariationRequestsChange = getChangeValue(cpuVariationRequestsAmount, cpuVariationRequestsUnits); - const memoryVariationRequestsChange = getChangeValue(memVariationRequestsAmount, memVariationRequestsUnits); - - return ( - <> -
-
- {memoryVariationRequestsChange} -
- {cpuVariationRequestsChange} -
-
- {memoryVariationLimitsChange} -
- {cpuVariationLimitsChange} - - ); - }; - - const getWarningsConfig = () => { - const config = getConfig('current'); - - const getWarning = (value, defaultValue = null) => { - return !value ? : defaultValue; - }; - - return ( - <> -
-
- {getWarning(config.resources.requests.memory)} -
- {getWarning(config.resources.requests.cpu)} -
-
- {getWarning(config.resources.limits.memory)} -
- {getWarning(config.resources.limits.cpu,
)} - - ); - }; - - const getWarningsCodeBlock = () => { - const code = getWarningsConfig(); - if (code === null) { - return null; - } - return ( - - {code} - - ); - }; - - const handleClipboardCopyOnClick = (event, text) => { - navigator.clipboard.writeText(text.toString()); - setCopied(true); - }; - const handleOnSelected = (value: Interval) => { setCurrentInterval(value); }; const isLoading = reportFetchStatus === FetchStatus.inProgress; - const optimizationsURL = formatPath(routes.optimizationsDetails.path); return (
@@ -464,7 +148,7 @@ const OptimizationsBreakdown: React.FC = () => { ) : ( <> {getAlert()} - {getOptimizationCards()} + )} @@ -500,7 +184,7 @@ const useMapToProps = (): OptimizationsBreakdownStateProps => { }, [reportQueryString]); return { - queryFromRoute, + breadcrumbLabel: queryFromRoute[breadcrumbLabelKey], report, reportError, reportFetchStatus, diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx new file mode 100644 index 000000000..b08ee10c5 --- /dev/null +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx @@ -0,0 +1,272 @@ +import './optimizationsBreakdown.scss'; + +import { + Card, + CardBody, + CardTitle, + ClipboardCopyButton, + CodeBlock, + CodeBlockAction, + CodeBlockCode, + Grid, + GridItem, + Title, + TitleSizes, +} from '@patternfly/react-core'; +import type { RecommendationItem } from 'api/ros/recommendations'; +import messages from 'locales/messages'; +import React, { useState } from 'react'; +import { useIntl } from 'react-intl'; +import { formatOptimization, formatPercentage } from 'utils/format'; +import { hasRecommendationValues } from 'utils/recomendations'; +import YAML from 'yaml'; + +import { styles } from './optimizationsBreakdown.styles'; + +interface OptimizationsBreakdownConfigurationOwnProps { + term: RecommendationItem; +} + +type OptimizationsBreakdownConfigurationProps = OptimizationsBreakdownConfigurationOwnProps; + +const OptimizationsBreakdownConfiguration: React.FC = ({ term }) => { + const [copied, setCopied] = useState(false); + const intl = useIntl(); + + const getConfig = (key: 'config' | 'current', isFormatted = true, isUnitsOnly = false) => { + const hasConfigLimitsCpu = hasRecommendationValues(term, key, 'limits', 'cpu'); + const hasConfigLimitsMemory = hasRecommendationValues(term, key, 'limits', 'memory'); + const hasConfigRequestsCpu = hasRecommendationValues(term, key, 'requests', 'cpu'); + const hasConfigRequestsMemory = hasRecommendationValues(term, key, 'requests', 'memory'); + + const cpuConfigLimitsAmount = hasConfigLimitsCpu ? term[key].limits.cpu.amount : undefined; + const cpuConfigLimitsUnits = hasConfigLimitsCpu ? term[key].limits.cpu.format : undefined; + const cpuConfigRequestsAmount = hasConfigRequestsCpu ? term[key].requests.cpu.amount : undefined; + const cpuConfigRequestsUnits = hasConfigRequestsCpu ? term[key].requests.cpu.format : undefined; + + const memConfigLimitsAmount = hasConfigLimitsMemory ? term[key].limits.memory.amount : undefined; + const memConfigLimitsUnits = hasConfigLimitsMemory ? term[key].limits.memory.format : undefined; + const memConfigRequestsAmount = hasConfigRequestsMemory ? term[key].requests.memory.amount : undefined; + const memConfigRequestsUnits = hasConfigRequestsMemory ? term[key].requests.memory.format : undefined; + + const formatValue = (value, units) => { + if (!value) { + return ''; + } + return isFormatted + ? intl.formatMessage(messages.optimizationsValue, { + value: formatOptimization(value), + units, + }) + : isUnitsOnly + ? units + : value; + }; + return { + resources: { + limits: { + cpu: formatValue(cpuConfigLimitsAmount, cpuConfigLimitsUnits), + memory: formatValue(memConfigLimitsAmount, memConfigLimitsUnits), + }, + requests: { + cpu: formatValue(cpuConfigRequestsAmount, cpuConfigRequestsUnits), + memory: formatValue(memConfigRequestsAmount, memConfigRequestsUnits), + }, + }, + }; + }; + + const getCurrentConfig = () => { + const code = getConfig('current'); + + // See https://eemeli.org/yaml/#tojs-options + return YAML.stringify(code); + }; + + const getCurrentConfigCodeBlock = () => { + const code = getCurrentConfig(); + if (code === null) { + return null; + } + return ( + + {code} + + ); + }; + + // Returns empty element to force a header + const getEmptyActions = () => { + return
; + }; + + const getPercentage = (oldNumber: number, newNumber: number) => { + if (typeof oldNumber !== 'number' || typeof newNumber !== 'number') { + return 0; + } + const changeValue = newNumber - oldNumber; + return (changeValue / oldNumber) * 100; + }; + + const getRecommendedActions = () => { + const code = getRecommendedConfig(); + + return ( + + handleClipboardCopyOnClick(e, code)} + exitDelay={copied ? 1500 : 600} + maxWidth="110px" + variant="plain" + onTooltipHidden={() => setCopied(false)} + > + {intl.formatMessage(copied ? messages.copyToClipboardSuccessfull : messages.copyToClipboard)} + + + ); + }; + + const getRecommendedConfig = () => { + let code = getConfig('config'); + if (code === null) { + return null; + } + // Add change values + code = getVariationConfig(code); + + // See https://eemeli.org/yaml/#tojs-options + return YAML.stringify(code).replace(/"/g, ''); + }; + + const getRecommendedConfigCodeBlock = () => { + const code = getRecommendedConfig(); + if (code === null) { + return null; + } + return ( + + {code} + + ); + }; + + const getVariationChange = (key1: 'limits' | 'requests', key2: 'cpu' | 'memory') => { + const currentValues = getConfig('current', false); + const currentUnits = getConfig('current', false, true); + const recommendedValues = getConfig('config', false); + const recommendedUnits = getConfig('config', false, true); + + let currentVal = currentValues.resources[key1][key2]; + let recommendedVal = recommendedValues.resources[key1][key2]; + + // Convert millicores to cores + if (currentUnits.resources[key1][key2] === 'cores' && recommendedUnits.resources[key1][key2] === 'millicores') { + recommendedVal = recommendedValues.resources[key1][key2] / 1000; + } else if ( + currentUnits.resources[key1][key2] === 'millicores' && + recommendedUnits.resources[key1][key2] === 'cores' + ) { + currentVal = currentValues.resources[key1][key2] / 1000; + } + + // Calculate percentage change + const percentage = getPercentage(currentVal, recommendedVal); + return intl.formatMessage(messages.percentPlus, { + count: percentage > 0 ? 1 : 0, + value: formatPercentage(percentage), + }); + }; + + const getVariationConfig = config => { + // Get longest formatted string containing units + const limitsCpuChars = `cpu: ${config.resources.limits.cpu}`.length; + const limitsMemoryChars = `memory: ${config.resources.limits.memory}`.length; + const requestsCpuChars = `cpu: ${config.resources.requests.cpu}`.length; + const requestsMemoryChars = `memory: ${config.resources.requests.memory}`.length; + + // Calculate max characters in order to left-align change values + const maxChars = Math.max(limitsCpuChars, limitsMemoryChars, requestsCpuChars, requestsMemoryChars); + + const cpuVariationLimitsChange = getVariationSpacing( + maxChars - limitsCpuChars, + getVariationChange('limits', 'cpu') + ); + const memoryVariationLimitsChange = getVariationSpacing( + maxChars - limitsMemoryChars, + getVariationChange('limits', 'memory') + ); + const cpuVariationRequestsChange = getVariationSpacing( + maxChars - requestsCpuChars, + getVariationChange('requests', 'cpu') + ); + const memoryVariationRequestsChange = getVariationSpacing( + maxChars - requestsMemoryChars, + getVariationChange('requests', 'memory') + ); + + const formatValue = (value, change) => { + if (!value) { + return ''; + } + return `${value}${change}`; + }; + return { + resources: { + limits: { + cpu: formatValue(config.resources.limits.cpu, cpuVariationLimitsChange), + memory: formatValue(config.resources.limits.memory, memoryVariationLimitsChange), + }, + requests: { + cpu: formatValue(config.resources.requests.cpu, cpuVariationRequestsChange), + memory: formatValue(config.resources.requests.memory, memoryVariationRequestsChange), + }, + }, + }; + }; + + const getVariationSpacing = (count: number, value) => { + if (!value) { + return ''; + } + let spacing = ' # '; + for (let i = 0; i < count; i++) { + spacing = ` ${spacing}`; + } + return `${spacing}${value}`; + }; + + const handleClipboardCopyOnClick = (event, text) => { + navigator.clipboard.writeText(text.toString()); + setCopied(true); + }; + + return ( + + + + + + {intl.formatMessage(messages.currentConfiguration)} + + + {getCurrentConfigCodeBlock()} + + + + + + + {intl.formatMessage(messages.recommendedConfiguration)} + + + {getRecommendedConfigCodeBlock()} + + + + ); +}; + +export { OptimizationsBreakdownConfiguration }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx index 29b7cc9eb..196fd41db 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx @@ -8,16 +8,11 @@ import { TitleSizes, } from '@patternfly/react-core'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; -import type { Query } from 'api/queries/query'; -import { getQueryRoute } from 'api/queries/query'; import type { RecommendationReportData } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { createMapStateToProps } from 'store/common'; +import { useIntl } from 'react-intl'; +import { Link, useLocation } from 'react-router-dom'; import { getTimeFromNow } from 'utils/dates'; import { hasWarning } from 'utils/recomendations'; @@ -25,53 +20,42 @@ import { styles } from './optimizationsBreakdownHeader.styles'; import { OptimizationsBreakdownToolbar } from './optimizationsBreakdownToolbar'; interface OptimizationsBreakdownHeaderOwnProps { + breadcrumbLabel?: string; + breadcrumbPath?: string; currentInterval?: string; isDisabled?: boolean; onSelected?: (value: string) => void; - optimizationsURL?: string; - queryFromRoute?: Query; report?: RecommendationReportData; } -interface OptimizationsBreakdownHeaderStateProps { - // TBD... -} - -interface OptimizationsBreakdownHeaderState {} - -type OptimizationsBreakdownHeaderProps = OptimizationsBreakdownHeaderOwnProps & - OptimizationsBreakdownHeaderStateProps & - WrappedComponentProps; - -class OptimizationsBreakdownHeaderBase extends React.Component< - OptimizationsBreakdownHeaderProps, - OptimizationsBreakdownHeaderState -> { - protected defaultState: OptimizationsBreakdownHeaderState = {}; - public state: OptimizationsBreakdownHeaderState = { ...this.defaultState }; - - private buildOptimizationsLink = url => { - const { queryFromRoute } = this.props; - - const newQuery = { - ...(queryFromRoute.state && { state: queryFromRoute.state }), - }; - return `${url}?${getQueryRoute(newQuery)}`; - }; - - private getBackToLink = () => { - const { optimizationsURL, intl } = this.props; - +type OptimizationsBreakdownHeaderProps = OptimizationsBreakdownHeaderOwnProps; + +const OptimizationsBreakdownHeader: React.FC = ({ + breadcrumbLabel, + breadcrumbPath, + currentInterval, + isDisabled, + onSelected, + report, +}) => { + const intl = useIntl(); + const location = useLocation(); + + const recommendations = report ? report.recommendations.duration_based : undefined; + const showWarningIcon = hasWarning(recommendations); + + const getBackToLink = () => { + if (!breadcrumbPath) { + return null; + } return ( - - {intl.formatMessage(messages.breakdownBackToOptimizations)} + + {breadcrumbLabel ? breadcrumbLabel : intl.formatMessage(messages.breakdownBackToOptimizations)} ); }; - private getDescription = () => { - const { intl, report } = this.props; - + const getDescription = () => { const clusterAlias = report && report.cluster_alias ? report.cluster_alias : undefined; const clusterUuid = report && report.cluster_uuid ? report.cluster_uuid : ''; const cluster = clusterAlias ? clusterAlias : clusterUuid; @@ -109,49 +93,30 @@ class OptimizationsBreakdownHeaderBase extends React.Component< ); }; - public render() { - const { currentInterval, isDisabled, onSelected, report } = this.props; - - const recommendations = report ? report.recommendations.duration_based : undefined; - const showWarningIcon = hasWarning(recommendations); - - return ( -
- {this.getBackToLink()} -
- - {report ? report.container : null} - - {showWarningIcon && ( - - - - )} -
-
{this.getDescription()}
-
- -
-
- ); - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps< - OptimizationsBreakdownHeaderOwnProps, - OptimizationsBreakdownHeaderStateProps ->(() => { - return { - // TBD... - }; -}); - -const OptimizationsBreakdownHeader = injectIntl(connect(mapStateToProps, {})(OptimizationsBreakdownHeaderBase)); + return ( +
+ {getBackToLink()} +
+ + {report ? report.container : null} + + {showWarningIcon && ( + + + + )} +
+
{getDescription()}
+
+ +
+
+ ); +}; export { OptimizationsBreakdownHeader }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx index 9dc7b4980..0d68d1011 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx @@ -2,9 +2,7 @@ import type { Query } from 'api/queries/query'; import type { RecommendationItems } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React from 'react'; -import { connect } from 'react-redux'; import { PerspectiveSelect } from 'routes/components/perspective/perspectiveSelect'; -import { createMapStateToProps } from 'store/common'; import { hasNotification, hasRecommendation } from 'utils/recomendations'; import { Interval } from './optimizationsBreakdown'; @@ -17,29 +15,15 @@ interface OptimizationsBreakdownToolbarOwnProps { recommendations?: RecommendationItems; } -interface OptimizationsBreakdownToolbarStateProps { - // TDB... -} - -interface OptimizationsBreakdownToolbarDispatchProps { - // TDB... -} - -interface OptimizationsBreakdownToolbarState {} - -type OptimizationsBreakdownToolbarProps = OptimizationsBreakdownToolbarOwnProps & - OptimizationsBreakdownToolbarStateProps & - OptimizationsBreakdownToolbarDispatchProps; - -export class OptimizationsBreakdownToolbarBase extends React.Component { - protected defaultState: OptimizationsBreakdownToolbarState = { - // TBD... - }; - public state: OptimizationsBreakdownToolbarState = { ...this.defaultState }; - - private getOptions = () => { - const { recommendations } = this.props; +type OptimizationsBreakdownToolbarProps = OptimizationsBreakdownToolbarOwnProps; +const OptimizationsBreakdownToolbar: React.FC = ({ + currentInterval, + isDisabled, + onSelected, + recommendations, +}) => { + const getOptions = () => { return [ { isDisabled: !hasRecommendation(recommendations?.short_term) && !hasNotification(recommendations?.short_term), @@ -59,33 +43,17 @@ export class OptimizationsBreakdownToolbarBase extends React.Component - ); - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps< - OptimizationsBreakdownToolbarOwnProps, - OptimizationsBreakdownToolbarStateProps ->(() => { - return {}; -}); - -const mapDispatchToProps: OptimizationsBreakdownToolbarDispatchProps = {}; - -const OptimizationsBreakdownToolbar = connect(mapStateToProps, mapDispatchToProps)(OptimizationsBreakdownToolbarBase); + const options = getOptions(); + + return ( + + ); +}; export { OptimizationsBreakdownToolbar }; diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx index 0c845889f..478988a9e 100644 --- a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx @@ -1,6 +1,6 @@ import { PageSection, Pagination, PaginationVariant } from '@patternfly/react-core'; import type { Query } from 'api/queries/query'; -import { getQuery, parseQuery, parseQueryState } from 'api/queries/query'; +import { getQuery, getQueryState, parseQuery } from 'api/queries/query'; import type { RosQuery } from 'api/queries/rosQuery'; import type { RosReport } from 'api/ros/ros'; import { RosPathsType, RosType } from 'api/ros/ros'; @@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { useLocation, useNavigate } from 'react-router-dom'; import type { AnyAction } from 'redux'; import type { ThunkDispatch } from 'redux-thunk'; +import { routes } from 'routes'; import { OptimizationsTable, OptimizationsToolbar } from 'routes/components/optimizations'; import { Loading } from 'routes/components/page/loading'; import { NoOptimizations } from 'routes/components/page/noOptimizations'; @@ -22,6 +23,7 @@ import * as queryUtils from 'routes/utils/query'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; import { rosActions, rosSelectors } from 'store/ros'; +import { formatPath } from 'utils/paths'; import { styles } from './optimizationsDetails.styles'; import { OptimizationsDetailsHeader } from './optimizationsDetailsHeader'; @@ -31,7 +33,7 @@ interface OptimizationsDetailsOwnProps { } export interface OptimizationsDetailsStateProps { - queryState?: Query; + groupBy?: string; report: RosReport; reportError: AxiosError; reportFetchStatus: FetchStatus; @@ -56,22 +58,21 @@ const reportType = RosType.ros as any; const reportPathsType = RosPathsType.recommendations as any; const OptimizationsDetails: React.FC = () => { - const [query, setQuery] = useState({ ...baseQuery }); - const { queryState, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ + const intl = useIntl(); + const location = useLocation(); + + const queryState = getQueryState(location, 'optimizations'); + const [query, setQuery] = useState({ ...baseQuery, ...(queryState && queryState) }); + const { groupBy, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({ query, }); - const intl = useIntl(); - const location = useLocation(); + // Clear state returned from breakdown page const navigate = useNavigate(); - - // Restore state returned from breakdown page useEffect(() => { - setQuery({ - ...query, - ...queryState, + navigate(location.pathname, { + state: { ...(location.state && location.state), optimizations: undefined }, }); - navigate(location.pathname, { replace: true }); }, [reportQueryString]); const getPagination = (isDisabled = false, isBottom = false) => { @@ -104,7 +105,10 @@ const OptimizationsDetails: React.FC = () => { const getTable = () => { return ( handleOnSort(sortType, isSortAscending)} orderBy={query.order_by} @@ -200,7 +204,6 @@ const useQueryFromRoute = () => { const useMapToProps = ({ query }: OptimizationsDetailsMapProps): OptimizationsDetailsStateProps => { const dispatch: ThunkDispatch = useDispatch(); const queryFromRoute = useQueryFromRoute(); - const queryState = parseQueryState(queryFromRoute); const groupBy = getGroupById(queryFromRoute); const groupByValue = getGroupByValue(queryFromRoute); @@ -235,7 +238,7 @@ const useMapToProps = ({ query }: OptimizationsDetailsMapProps): OptimizationsDe }, [query]); return { - queryState, + groupBy, report, reportError, reportFetchStatus, diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx index f08081988..c01889e4b 100644 --- a/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx @@ -2,10 +2,7 @@ import { Button, ButtonVariant, Popover, Title, TitleSizes } from '@patternfly/r import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon'; import messages from 'locales/messages'; import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { createMapStateToProps } from 'store/common'; +import { useIntl } from 'react-intl'; import { styles } from './optimizationsDetailsHeader.styles'; @@ -13,62 +10,34 @@ interface OptimizationsDetailsHeaderOwnProps { // TBD... } -interface OptimizationsDetailsHeaderStateProps { - // TBD... -} - -interface OptimizationsDetailsHeaderState {} - -type OptimizationsDetailsHeaderProps = OptimizationsDetailsHeaderOwnProps & - OptimizationsDetailsHeaderStateProps & - WrappedComponentProps; - -class OptimizationsDetailsHeaderBase extends React.Component< - OptimizationsDetailsHeaderProps, - OptimizationsDetailsHeaderState -> { - protected defaultState: OptimizationsDetailsHeaderState = {}; - public state: OptimizationsDetailsHeaderState = { ...this.defaultState }; - - public render() { - const { intl } = this.props; - - return ( -
-
- - {intl.formatMessage(messages.optimizations)} - <span style={styles.infoIcon}> - <Popover - aria-label={intl.formatMessage(messages.optimizationsInfoArialLabel)} - enableFlip - bodyContent={<p style={styles.infoTitle}>{intl.formatMessage(messages.optimizationsInfo)}</p>} +type OptimizationsDetailsHeaderProps = OptimizationsDetailsHeaderOwnProps; + +const OptimizationsDetailsHeader: React.FC<OptimizationsDetailsHeaderProps> = () => { + const intl = useIntl(); + + return ( + <header style={styles.header}> + <div style={styles.headerContent}> + <Title headingLevel="h1" style={styles.title} size={TitleSizes['2xl']}> + {intl.formatMessage(messages.optimizations)} + <span style={styles.infoIcon}> + <Popover + aria-label={intl.formatMessage(messages.optimizationsInfoArialLabel)} + enableFlip + bodyContent={<p style={styles.infoTitle}>{intl.formatMessage(messages.optimizationsInfo)}</p>} + > + <Button + aria-label={intl.formatMessage(messages.optimizationsInfoButtonArialLabel)} + variant={ButtonVariant.plain} > - <Button - aria-label={intl.formatMessage(messages.optimizationsInfoButtonArialLabel)} - variant={ButtonVariant.plain} - > - <OutlinedQuestionCircleIcon /> - </Button> - </Popover> - </span> - -
-
- ); - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps( - () => { - return { - // TBD... - }; - } -); - -const OptimizationsDetailsHeader = injectIntl(connect(mapStateToProps, {})(OptimizationsDetailsHeaderBase)); + + + + + +
+
+ ); +}; export { OptimizationsDetailsHeader }; -export type { OptimizationsDetailsHeaderProps }; diff --git a/src/routes/utils/paths.ts b/src/routes/utils/paths.ts index 0d902a964..53f4c9bd5 100644 --- a/src/routes/utils/paths.ts +++ b/src/routes/utils/paths.ts @@ -1,8 +1,5 @@ -import type { Query } from 'api/queries/query'; import { getQueryRoute } from 'api/queries/query'; -import { parseQuery } from 'api/queries/query'; -import { breakdownDescKey, breakdownTitleKey, orgUnitIdKey } from 'utils/props'; -import type { RouteComponentProps } from 'utils/router'; +import { breadcrumbLabelKey, breakdownDescKey, breakdownTitleKey, orgUnitIdKey } from 'utils/props'; export const getBreakdownPath = ({ basePath, @@ -12,7 +9,6 @@ export const getBreakdownPath = ({ isPlatformCosts, isOptimizationsPath, isOptimizationsTab, - router, title, }: { basePath: string; @@ -22,11 +18,8 @@ export const getBreakdownPath = ({ isPlatformCosts?: boolean; isOptimizationsPath?: boolean; isOptimizationsTab?: boolean; - router: RouteComponentProps; title: string | number; // Used to display a title in the breakdown header }) => { - const queryFromRoute = router ? parseQuery(router.location.search) : undefined; - const state = queryFromRoute ? JSON.stringify(queryFromRoute) : undefined; // Ignores query prefix const newQuery: any = { ...(description && description !== title && { [breakdownDescKey]: description }), ...(title && { [breakdownTitleKey]: title }), @@ -39,28 +32,25 @@ export const getBreakdownPath = ({ }), id, isPlatformCosts: isPlatformCosts ? true : undefined, - ...(state && { state: window.btoa(state) }), }; return `${basePath}?${getQueryRoute(newQuery)}`; }; export const getOptimizationsBreakdownPath = ({ basePath, + breadcrumbLabel, id, - query, title, }: { - basePath: string; + basePath?: string; + breadcrumbLabel?: string; // Used to display a breadcrumb in the breakdown header id: string | number; // group_by[account]= param in the breakdown page - query?: Query; title: string | number; // Used to display a title in the breakdown header }) => { - const state = query ? JSON.stringify(query) : undefined; // Ignores query prefix - const newQuery: any = { id, - ...(state && { state: window.btoa(state) }), ...(title && { [breakdownTitleKey]: title }), + ...(breadcrumbLabel && { [breadcrumbLabelKey]: breadcrumbLabel }), }; return `${basePath}?${getQueryRoute(newQuery)}`; }; @@ -71,7 +61,6 @@ export const getOrgBreakdownPath = ({ groupBy, groupByOrg, id, - router, title, type, }: { @@ -80,12 +69,9 @@ export const getOrgBreakdownPath = ({ groupBy: string | number; groupByOrg: string | number; // Used for group_by[org_unit_id]= param in the breakdown page id: string | number; // group_by[account]= param in the breakdown page - router: RouteComponentProps; title: string | number; // Used to display a title in the breakdown header type: string; // account or organizational_unit }) => { - const queryFromRoute = parseQuery(router.location.search); - const state = JSON.stringify(queryFromRoute); // Ignores query prefix const newQuery: any = { ...(description && description !== title && { [breakdownDescKey]: description }), ...(title && { [breakdownTitleKey]: title }), @@ -96,7 +82,6 @@ export const getOrgBreakdownPath = ({ group_by: { [groupBy]: id, // Group by may be overridden below }, - state: window.btoa(state), }; if (type === 'account') { newQuery.group_by = { diff --git a/src/utils/props.ts b/src/utils/props.ts index dbeaa9a34..82917e92a 100644 --- a/src/utils/props.ts +++ b/src/utils/props.ts @@ -5,6 +5,7 @@ export const noPrefix = 'No-'; // no-project, no-region, no- export const tagPrefix = 'tag:'; // Tag prefix for group_by export const awsCategoryKey = 'aws_category'; // AWS category for group_by +export const breadcrumbLabelKey = 'breadcrumb_label'; // Used to display a breadcrumb in the breakdown header export const breakdownDescKey = 'breakdown_desc'; // Used to display a description in the breakdown header export const breakdownTitleKey = 'breakdown_title'; // Used to display a title in the breakdown header export const orgUnitIdKey = 'org_unit_id'; // Org unit ID for group_by From 3010f87840b339523ced3de889ae9d548f43e20a Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Sat, 30 Sep 2023 10:24:43 -0400 Subject: [PATCH 05/33] Refactor optimizations table to initialize columns and rows once --- .../optimizations/optimizationsTable.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/routes/components/optimizations/optimizationsTable.tsx b/src/routes/components/optimizations/optimizationsTable.tsx index 6848e3bd3..836480da2 100644 --- a/src/routes/components/optimizations/optimizationsTable.tsx +++ b/src/routes/components/optimizations/optimizationsTable.tsx @@ -4,7 +4,7 @@ import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/ import type { Query } from 'api/queries/query'; import type { RecommendationReport } from 'api/ros/recommendations'; import messages from 'locales/messages'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { Link, useLocation } from 'react-router-dom'; import { DataTable } from 'routes/components/dataTable'; @@ -45,11 +45,17 @@ const OptimizationsTable: React.FC = ({ const intl = useIntl(); const location = useLocation(); + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState([]); + const initDatum = () => { + if (!report) { + return; + } const hasData = report && report.data && report.data.length > 0; - const rows = []; - const columns = [ + const newRows = []; + const newColumns = [ { name: intl.formatMessage(messages.optimizationsNames, { value: 'container' }), orderBy: 'container', @@ -95,7 +101,7 @@ const OptimizationsTable: React.FC = ({ const workloadType = item.workload_type ? item.workload_type : ''; const showWarningIcon = hasWarning(item?.recommendations?.duration_based); - rows.push({ + newRows.push({ cells: [ { value: ( @@ -144,16 +150,14 @@ const OptimizationsTable: React.FC = ({ }); }); - const filteredColumns = (columns as any[]).filter(column => !column.hidden); - const filteredRows = rows.map(({ ...row }) => { + const filteredColumns = (newColumns as any[]).filter(column => !column.hidden); + const filteredRows = newRows.map(({ ...row }) => { row.cells = row.cells.filter(cell => !cell.hidden); return row; }); - return { - columns: filteredColumns, - rows: filteredRows, - }; + setColumns(filteredColumns); + setRows(filteredRows); }; const handleOnSort = (value: string, isSortAscending: boolean) => { @@ -162,10 +166,9 @@ const OptimizationsTable: React.FC = ({ } }; - if (!report) { - return null; - } - const { columns, rows } = initDatum(); + useEffect(() => { + initDatum(); + }, [report]); return ( Date: Sat, 30 Sep 2023 12:38:11 -0400 Subject: [PATCH 06/33] Added warning icons to missing recommendation values --- .../optimizationsBreakdown.scss | 7 -- .../optimizationsBreakdown.styles.ts | 8 +++ .../optimizationsBreakdownConfiguration.tsx | 71 ++++++++++++++++++- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss index 2f87157b0..1ba39bf49 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss @@ -7,13 +7,6 @@ } } -.rightCodeBlockOverride { - .pf-c-code-block__header, - .pf-c-code-block__content { - padding-left: 0; - } -} - .optimizationsOverride { div { display: block; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts index 70f2be23a..8c9cad473 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts @@ -5,10 +5,18 @@ export const styles = { alertContainer: { marginBottom: global_spacer_lg.value, }, + codeBlock: { + display: 'flex', + }, container: { minHeight: '100vh', }, currentActions: { height: '36px', }, + rightCodeBlock: { + display: 'flex', + flexGrow: 1, + flexDirection: 'column', + }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx index b08ee10c5..9894020bf 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx @@ -13,6 +13,7 @@ import { Title, TitleSizes, } from '@patternfly/react-core'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import type { RecommendationItem } from 'api/ros/recommendations'; import messages from 'locales/messages'; import React, { useState } from 'react'; @@ -80,7 +81,7 @@ const OptimizationsBreakdownConfiguration: React.FC { @@ -138,7 +139,7 @@ const OptimizationsBreakdownConfiguration: React.FC { @@ -238,11 +239,66 @@ const OptimizationsBreakdownConfiguration: React.FC { + const config = getConfig('config', false); + + const getWarning = value => { + return !value ? : null; + }; + + const cpuLimitsWarning = getWarning(config.resources.limits.cpu); + const cpuRequestsWarning = getWarning(config.resources.requests.cpu); + const memoryLimitsWarning = getWarning(config.resources.limits.memory); + const memoryRequestsWarning = getWarning(config.resources.requests.memory); + + return ( + <> +
+
+ {cpuLimitsWarning} +
+ {memoryLimitsWarning} +
+
+ {cpuRequestsWarning} +
+ {memoryRequestsWarning !== null ? memoryRequestsWarning :
} + + ); + }; + + const getWarningCodeBlock = () => { + const code = getWarningConfig(); + if (code === null) { + return null; + } + return ( + + {code} + + ); + }; + const handleClipboardCopyOnClick = (event, text) => { navigator.clipboard.writeText(text.toString()); setCopied(true); }; + const hasMissingValue = (key: 'config' | 'current') => { + const config = getConfig(key, false); + + const isMissingValue = value => { + return !value || `${value}`.trim().length === 0; + }; + + const cpuLimitsWarning = isMissingValue(config.resources.limits.cpu); + const cpuRequestsWarning = isMissingValue(config.resources.requests.cpu); + const memoryLimitsWarning = isMissingValue(config.resources.limits.memory); + const memoryRequestsWarning = isMissingValue(config.resources.requests.memory); + + return cpuLimitsWarning || cpuRequestsWarning || memoryLimitsWarning || memoryRequestsWarning; + }; + return ( @@ -262,7 +318,16 @@ const OptimizationsBreakdownConfiguration: React.FC - {getRecommendedConfigCodeBlock()} + + {hasMissingValue('config') ? ( +
+
{getWarningCodeBlock()}
+
{getRecommendedConfigCodeBlock()}
+
+ ) : ( + getRecommendedConfigCodeBlock() + )} +
From c6a42a1c0da62991a7839c78dc3ee5092fa9739a Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Sat, 30 Sep 2023 13:46:22 -0400 Subject: [PATCH 07/33] Added warning icons to missing current config values --- .../optimizationsBreakdownConfiguration.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx index 9894020bf..9be1f5081 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx @@ -308,7 +308,16 @@ const OptimizationsBreakdownConfiguration: React.FC - {getCurrentConfigCodeBlock()} + + {hasMissingValue('current') ? ( +
+
{getWarningCodeBlock()}
+
{getCurrentConfigCodeBlock()}
+
+ ) : ( + getRecommendedConfigCodeBlock() + )} +
From dd4f639c87cf776d685dc459b5b4d7fbb96150ba Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Sat, 30 Sep 2023 14:13:15 -0400 Subject: [PATCH 08/33] Simplified code block --- .../optimizationsBreakdown.scss | 7 --- .../optimizationsBreakdown.styles.ts | 5 -- .../optimizationsBreakdownConfiguration.tsx | 62 ++++++++----------- 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss index 1ba39bf49..fea98da1d 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss @@ -1,12 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); -.leftCodeBlockOverride { - .pf-c-code-block__header, - .pf-c-code-block__content { - padding-right: 0; - } -} - .optimizationsOverride { div { display: block; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts index 8c9cad473..968d679ac 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts @@ -14,9 +14,4 @@ export const styles = { currentActions: { height: '36px', }, - rightCodeBlock: { - display: 'flex', - flexGrow: 1, - flexDirection: 'column', - }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx index 9be1f5081..2ab189805 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx @@ -91,7 +91,17 @@ const OptimizationsBreakdownConfiguration: React.FC - {code} + + {hasMissingValue('current') ? ( + + {getWarningConfig('current')} +   + {code} + + ) : ( + code + )} + ); }; @@ -149,7 +159,17 @@ const OptimizationsBreakdownConfiguration: React.FC - {code} + + {hasMissingValue('config') ? ( + + {getWarningConfig('config')} +   + {code} + + ) : ( + code + )} + ); }; @@ -239,8 +259,8 @@ const OptimizationsBreakdownConfiguration: React.FC { - const config = getConfig('config', false); + const getWarningConfig = (key: 'current' | 'config') => { + const config = getConfig(key, false); const getWarning = value => { return !value ? : null; @@ -267,18 +287,6 @@ const OptimizationsBreakdownConfiguration: React.FC { - const code = getWarningConfig(); - if (code === null) { - return null; - } - return ( - - {code} - - ); - }; - const handleClipboardCopyOnClick = (event, text) => { navigator.clipboard.writeText(text.toString()); setCopied(true); @@ -308,16 +316,7 @@ const OptimizationsBreakdownConfiguration: React.FC - - {hasMissingValue('current') ? ( -
-
{getWarningCodeBlock()}
-
{getCurrentConfigCodeBlock()}
-
- ) : ( - getRecommendedConfigCodeBlock() - )} -
+ {getCurrentConfigCodeBlock()}
@@ -327,16 +326,7 @@ const OptimizationsBreakdownConfiguration: React.FC - - {hasMissingValue('config') ? ( -
-
{getWarningCodeBlock()}
-
{getRecommendedConfigCodeBlock()}
-
- ) : ( - getRecommendedConfigCodeBlock() - )} -
+ {getRecommendedConfigCodeBlock()}
From 1c9abe6bf5ad53d4c35e7bffbd6e8e34ee5a3588 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Sat, 30 Sep 2023 20:20:06 -0400 Subject: [PATCH 09/33] Clean up --- .../optimizationsBreakdownConfiguration.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx index 2ab189805..11f545eb1 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx @@ -266,23 +266,18 @@ const OptimizationsBreakdownConfiguration: React.FC : null; }; - const cpuLimitsWarning = getWarning(config.resources.limits.cpu); - const cpuRequestsWarning = getWarning(config.resources.requests.cpu); - const memoryLimitsWarning = getWarning(config.resources.limits.memory); - const memoryRequestsWarning = getWarning(config.resources.requests.memory); - return ( <>

- {cpuLimitsWarning} + {getWarning(config.resources.limits.cpu)}
- {memoryLimitsWarning} + {getWarning(config.resources.limits.memory)}

- {cpuRequestsWarning} + {getWarning(config.resources.requests.cpu)}
- {memoryRequestsWarning !== null ? memoryRequestsWarning :
} + {getWarning(config.resources.requests.memory)} ); }; From cb951ed8d3d686ce45c93e7cc88b2c129912a034 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Sun, 1 Oct 2023 20:03:43 -0400 Subject: [PATCH 10/33] Added default "back to" link path --- .../optimizationsBreakdown/optimizationsBreakdownHeader.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx index 196fd41db..6fcefdc71 100644 --- a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx +++ b/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx @@ -13,6 +13,7 @@ import messages from 'locales/messages'; import React from 'react'; import { useIntl } from 'react-intl'; import { Link, useLocation } from 'react-router-dom'; +import { routes } from 'routes'; import { getTimeFromNow } from 'utils/dates'; import { hasWarning } from 'utils/recomendations'; @@ -32,7 +33,7 @@ type OptimizationsBreakdownHeaderProps = OptimizationsBreakdownHeaderOwnProps; const OptimizationsBreakdownHeader: React.FC = ({ breadcrumbLabel, - breadcrumbPath, + breadcrumbPath = routes.optimizationsDetails, currentInterval, isDisabled, onSelected, @@ -45,9 +46,6 @@ const OptimizationsBreakdownHeader: React.FC const showWarningIcon = hasWarning(recommendations); const getBackToLink = () => { - if (!breadcrumbPath) { - return null; - } return ( {breadcrumbLabel ? breadcrumbLabel : intl.formatMessage(messages.breakdownBackToOptimizations)} From 0ad98d34a15cef6b13f0677d7dde9579ece2af39 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 1 Jun 2023 10:52:37 -0400 Subject: [PATCH 11/33] Update Hybrid Committed Spend UI deployments --- .../drawers/optimzations/optimizations.scss | 10 +- .../optimzations/optimizationsContent.tsx | 12 +- .../optimzations/optimizationsLink.tsx | 6 +- .../components/dataToolbar/workloadType.tsx | 7 +- .eslintrc.json | 2 + fec.config.js | 8 +- package-lock.json | 1268 ++++++++++++----- package.json | 28 +- .../drawers/exports/exportActions.tsx | 4 +- .../drawers/exports/exportsContent.tsx | 2 +- .../drawers/exports/exportsLink.tsx | 2 +- .../drawers/exports/exportsTable.tsx | 24 +- .../inactiveSources/inactiveSources.scss | 8 +- .../charts/chartTheme/theme-utils.ts | 2 +- .../historicalCostChart.test.tsx.snap | 64 +- .../historicalCostChart.tsx | 3 +- .../historicalTrendChart.test.tsx.snap | 58 +- .../historicalUsageChart.test.tsx.snap | 36 +- .../historicalUsageChart.tsx | 3 +- .../__snapshots__/trendChart.test.tsx.snap | 28 +- .../__snapshots__/usageChart.test.tsx.snap | 28 +- .../costDistribution/costDistribution.scss | 4 +- .../costDistribution/costDistribution.tsx | 11 +- src/routes/components/costType/costType.scss | 4 +- src/routes/components/costType/costType.tsx | 11 +- src/routes/components/currency/currency.scss | 2 +- src/routes/components/currency/currency.tsx | 11 +- .../components/dataTable/dataTable.scss | 12 +- src/routes/components/dataTable/dataTable.tsx | 27 +- .../components/dataTable/selectableTable.tsx | 30 +- .../components/dataToolbar/basicToolbar.tsx | 4 +- .../dataToolbar/costCategoryValue.tsx | 58 +- .../components/dataToolbar/customSelect.tsx | 16 +- .../components/dataToolbar/dataKebab.tsx | 2 +- .../components/dataToolbar/dataToolbar.scss | 6 +- .../components/dataToolbar/dataToolbar.tsx | 15 +- .../components/dataToolbar/tagValue.tsx | 58 +- .../components/dataToolbar/utils/actions.tsx | 2 +- .../dataToolbar/utils/bulkSelect.tsx | 6 +- .../components/dataToolbar/utils/category.tsx | 79 +- .../dataToolbar/utils/costCategory.tsx | 15 +- .../components/dataToolbar/utils/exclude.tsx | 11 +- .../dataToolbar/utils/orgUntits.tsx | 10 +- .../components/dataToolbar/utils/tags.tsx | 13 +- src/routes/components/export/exportModal.tsx | 24 +- src/routes/components/groupBy/groupBy.tsx | 11 +- src/routes/components/groupBy/groupByOrg.tsx | 10 +- .../components/groupBy/groupBySelect.tsx | 12 +- .../components/page/noData/noDataState.tsx | 27 +- .../noOptimizations/noOptimizationsState.tsx | 14 +- .../page/noProviders/noProvidersState.tsx | 31 +- .../perspective/perspectiveSelect.tsx | 11 +- .../reportSummaryItems.test.tsx.snap | 24 +- .../reports/reportSummary/reportSummary.scss | 10 +- .../reportSummary/reportSummaryAlt.scss | 14 +- .../reportSummary/reportSummaryCost.scss | 2 +- .../reportSummary/reportSummaryDailyCost.scss | 2 +- .../reportSummaryDailyTrend.scss | 2 +- .../reportSummary/reportSummaryDetails.scss | 30 +- .../reportSummary/reportSummaryItem.scss | 2 +- .../reportSummary/reportSummaryItems.scss | 2 +- .../reportSummary/reportSummaryTrend.scss | 2 +- .../reportSummary/reportSummaryUsage.scss | 2 +- .../resourceTypeahead/resourceInput.tsx | 14 +- .../emptyFilterState/emptyFilterState.tsx | 7 +- .../emptyValueState/emptyValueState.scss | 2 +- .../state/errorState/errorState.tsx | 10 +- .../state/loadingState/loadingState.tsx | 8 +- src/routes/details/awsDetails/awsDetails.tsx | 2 +- .../details/azureDetails/azureDetails.tsx | 2 +- .../details/components/actions/actions.tsx | 4 +- .../components/breakdown/breakdownHeader.scss | 8 +- .../components/breakdown/breakdownHeader.tsx | 6 +- .../components/cluster/clusterModal.scss | 6 +- .../columnManagementModal.tsx | 2 +- .../components/summary/summaryModal.scss | 6 +- src/routes/details/gcpDetails/gcpDetails.tsx | 2 +- src/routes/details/ibmDetails/ibmDetails.tsx | 2 +- src/routes/details/ociDetails/ociDetails.tsx | 2 +- .../ocpBreakdownOptimizations.tsx | 2 +- src/routes/details/ocpDetails/ocpDetails.tsx | 2 +- .../details/rhelDetails/rhelDetails.tsx | 2 +- src/routes/explorer/explorer.tsx | 2 +- src/routes/explorer/explorerDateRange.tsx | 10 +- src/routes/explorer/explorerFilter.scss | 2 +- src/routes/explorer/explorerTable.scss | 8 +- src/routes/explorer/explorerTable.tsx | 27 +- .../optimizationsBreakdownHeader.tsx | 2 +- .../optimizationsDetails.tsx | 2 +- .../overview/components/chartComparison.tsx | 10 +- .../optimizationsSummary.scss | 4 +- src/routes/overview/overview.scss | 4 +- .../settings/costCategory/costCategory.tsx | 2 +- .../__snapshots__/rateTable.test.tsx.snap | 12 +- .../__snapshots__/warningIcon.test.tsx.snap | 5 +- .../costModels/components/errorState.tsx | 17 +- .../costModels/components/forms/form.tsx | 10 +- .../components/inputs/rateInput.tsx | 48 +- .../costModels/components/inputs/selector.tsx | 23 +- .../components/inputs/simpleInput.tsx | 24 +- .../components/paginationToolbarTemplate.tsx | 12 +- .../components/priceListToolbar.tsx | 2 +- .../components/rateForm/rateForm.tsx | 10 +- .../components/rateForm/taggingRatesForm.tsx | 6 +- .../costModels/components/rateTable.tsx | 15 +- .../components/toolbar/checkboxSelector.tsx | 2 +- .../components/toolbar/primarySelector.tsx | 2 +- .../__snapshots__/dialog.test.tsx.snap | 9 +- .../costModels/costModel/addSourceStep.tsx | 18 +- .../costModel/assignSourcesModalToolbar.tsx | 33 +- .../costModels/costModel/costModelInfo.tsx | 12 +- .../costModels/costModel/distribution.tsx | 51 +- .../settings/costModels/costModel/header.tsx | 6 +- .../settings/costModels/costModel/markup.tsx | 51 +- .../costModels/costModel/priceListTable.tsx | 32 +- .../costModels/costModel/sourcesTable.tsx | 9 +- .../costModels/costModel/sourcesToolbar.tsx | 34 +- .../settings/costModels/costModel/table.tsx | 18 +- .../costModels/costModel/updateCostModel.tsx | 4 +- .../costModel/updateDistributionDialog.tsx | 6 +- .../costModel/updateMarkupDialog.tsx | 45 +- .../costModelWizard/assignSourcesToolbar.tsx | 33 +- .../costModels/costModelWizard/context.ts | 8 +- .../costModelWizard/costModelWizard.tsx | 36 +- .../costModelWizard/generalInformation.tsx | 41 +- .../costModels/costModelWizard/markup.tsx | 41 +- .../costModelWizard/priceListTable.tsx | 22 +- .../costModels/costModelWizard/review.tsx | 25 +- .../costModels/costModelWizard/table.tsx | 12 +- .../costModelsDetails/bottomPagination.tsx | 2 +- .../costModelsDetails/emptyStateBase.tsx | 9 +- .../costModels/costModelsDetails/table.tsx | 9 +- .../costModelsDetails/utils/filters.tsx | 48 +- .../costModelsDetails/utils/table.tsx | 6 +- .../costModelsDetails/utils/toolbar.tsx | 7 +- .../platformProjects/platformProjects.tsx | 2 +- src/routes/settings/settings.scss | 4 +- src/routes/settings/tagDetails/tagDetails.tsx | 2 +- src/styles/global.css | 4 +- src/styles/revert.css | 12 +- src/typings/frontend-components.ts | 1 + 141 files changed, 1927 insertions(+), 1317 deletions(-) diff --git a/.archive/components/drawers/optimzations/optimizations.scss b/.archive/components/drawers/optimzations/optimizations.scss index fea98da1d..91ae5bbaf 100644 --- a/.archive/components/drawers/optimzations/optimizations.scss +++ b/.archive/components/drawers/optimzations/optimizations.scss @@ -4,13 +4,13 @@ div { display: block; margin-right: 0; - margin-bottom: var(--pf-global--spacer--xs); + margin-bottom: var(--pf-v5-global--spacer--xs); &.iconOverride { &.decrease { - // color: var(--pf-global--success-color--100); + // color: var(--pf-v5-global--success-color--100); } &.increase { - // color: var(--pf-global--danger-color--100); + // color: var(--pf-v5-global--danger-color--100); } .fa-equals { margin-left: 25px; @@ -27,10 +27,10 @@ top: -3px; } .fa-sort-up::before { - // color: var(--pf-global--danger-color--100); + // color: var(--pf-v5-global--danger-color--100); } .fa-sort-down::before { - // color: var(--pf-global--success-color--100); + // color: var(--pf-v5-global--success-color--100); } span { margin-right: -17px !important; diff --git a/.archive/components/drawers/optimzations/optimizationsContent.tsx b/.archive/components/drawers/optimzations/optimizationsContent.tsx index 30cc66403..fb673705f 100644 --- a/.archive/components/drawers/optimzations/optimizationsContent.tsx +++ b/.archive/components/drawers/optimzations/optimizationsContent.tsx @@ -13,7 +13,7 @@ import { TextListVariants, } from '@patternfly/react-core'; import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; -import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import type { RecommendationItem, RecommendationReportData } from 'api/ros/recommendations'; import { RosPathsType, RosType } from 'api/ros/ros'; import type { AxiosError } from 'axios'; @@ -265,10 +265,9 @@ class OptimizationsContentBase extends React.Component @@ -313,7 +312,7 @@ class OptimizationsContentBase extends React.Component{this.getChangeValue(memVariation, memVariationUnits)} - + ); }; @@ -377,10 +376,9 @@ class OptimizationsContentBase extends React.Component @@ -425,7 +423,7 @@ class OptimizationsContentBase extends React.Component{this.getChangeValue(memVariation, memVariationUnits)} - + ); }; diff --git a/.archive/components/drawers/optimzations/optimizationsLink.tsx b/.archive/components/drawers/optimzations/optimizationsLink.tsx index 4e3c4cb5f..2758ba0a4 100644 --- a/.archive/components/drawers/optimzations/optimizationsLink.tsx +++ b/.archive/components/drawers/optimzations/optimizationsLink.tsx @@ -110,11 +110,7 @@ class OptimizationsLinkBase extends React.Component ); if (isDisabled) { - return ( - - {buttonComponent} - - ); + return {buttonComponent}; } return buttonComponent; } diff --git a/.archive/routes/components/dataToolbar/workloadType.tsx b/.archive/routes/components/dataToolbar/workloadType.tsx index 8ba61f977..2d358ef92 100644 --- a/.archive/routes/components/dataToolbar/workloadType.tsx +++ b/.archive/routes/components/dataToolbar/workloadType.tsx @@ -1,7 +1,8 @@ import './dataToolbar.scss'; -import type { SelectOptionObject, ToolbarChipGroup } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; +import type { ToolbarChipGroup } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -60,8 +61,8 @@ class WorkloadTypeBase extends React.Component this.onToggle(isExpanded)} selections={selections} isOpen={isWorkloadTypeExpanded} placeholderText={intl.formatMessage(messages.chooseValuePlaceholder)} diff --git a/.eslintrc.json b/.eslintrc.json index 43002e254..714910baa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -136,6 +136,8 @@ "react/prop-types": 0, "simple-import-sort/imports": "error", "spaced-comment": "error", + "rulesdir/disallow-pf-migrated-components": "off", + "rulesdir/forbid-pf-relative-imports": "off", "testing-library/await-async-queries": "error", "testing-library/no-await-sync-queries": "error", "testing-library/no-debugging-utils": "warn", diff --git a/fec.config.js b/fec.config.js index dd95b1d9c..e2424f8bb 100644 --- a/fec.config.js +++ b/fec.config.js @@ -37,6 +37,8 @@ module.exports = { interceptChromeConfig: false, // Change to false after your app is registered in configuration files proxyVerbose: true, sassPrefix: `.${moduleName}`, + // sassPrefix: 'body', // For PF v5 testing only + // bundlePfModules: true, // For PF v5 testing only stats, standalone: process.env.LOCAL_API_PORT ? true : false, useCache: true, @@ -61,9 +63,9 @@ module.exports = { './RootApp': path.resolve(__dirname, './src/appEntry.tsx'), }, shared: [ - { 'react-redux': { requiredVersion: dependencies['react-redux'] } }, - { 'react-router-dom': { import: false, requiredVersion: '*', singleton: true } }, - { '@unleash/proxy-client-react': { requiredVersion: '*', singleton: true } }, + { 'react-redux': { version: dependencies['react-redux'] } }, + { 'react-router-dom': { version: dependencies['react-router-dom'], import: false, singleton: true } }, + { '@unleash/proxy-client-react': { version: dependencies['@unleash/proxy-client-react'], singleton: true } }, ], }, /** diff --git a/package-lock.json b/package-lock.json index e6bb40955..471788504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,17 +10,16 @@ "hasInstallScript": true, "license": "GNU AGPLv3", "dependencies": { - "@patternfly/patternfly": "4.224.2", - "@patternfly/react-charts": "6.94.19", - "@patternfly/react-core": "4.276.8", - "@patternfly/react-icons": "4.93.6", - "@patternfly/react-styles": "4.92.6", - "@patternfly/react-table": "4.113.0", - "@patternfly/react-tokens": "4.94.6", - "@redhat-cloud-services/frontend-components": "^3.11.5", - "@redhat-cloud-services/frontend-components-notifications": "^3.2.16", + "@patternfly/patternfly": "5.0.4", + "@patternfly/react-charts": "7.0.1", + "@patternfly/react-core": "5.0.1", + "@patternfly/react-icons": "5.0.1", + "@patternfly/react-table": "5.0.1", + "@patternfly/react-tokens": "5.0.1", + "@redhat-cloud-services/frontend-components": "^4.0.10", + "@redhat-cloud-services/frontend-components-notifications": "^4.0.4", "@redhat-cloud-services/frontend-components-translations": "^3.2.7", - "@redhat-cloud-services/frontend-components-utilities": "^3.7.6", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", "@redhat-cloud-services/rbac-client": "^1.2.9", "@unleash/proxy-client-react": "^3.6.0", "axios": "^1.5.1", @@ -44,8 +43,9 @@ }, "devDependencies": { "@formatjs/cli": "^6.2.0", - "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.3.0", - "@redhat-cloud-services/frontend-components-config": "^5.1.1", + "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", + "@redhat-cloud-services/frontend-components-config": "^6.0.5", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", "@types/jest": "^29.5.5", @@ -714,6 +714,19 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz", + "integrity": "sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==", + "dependencies": { + "@emotion/memoize": "0.7.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz", + "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==" + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.40.1", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.40.1.tgz", @@ -2100,95 +2113,109 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@patternfly/patternfly": { - "version": "4.224.2", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.224.2.tgz", - "integrity": "sha512-HGNV26uyHSIECuhjPg/WGn0mXbAotcs6ODfhAOkfYjIgGylddgiwElxUe1rpEHV5mQJJ2rMn4OdeJIIpzRX61g==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-5.0.4.tgz", + "integrity": "sha512-8akdWzFpG384Q6Es8lzkfuhAlzVGrNK7TJqXGecHDAg8u1JsYn3+Nw6gLRviI88z8Kjxmg5YKirILjpclGxkIA==" }, "node_modules/@patternfly/react-charts": { - "version": "6.94.19", - "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-6.94.19.tgz", - "integrity": "sha512-+yYwXAy/GBH2bTHFzMpdVKc8LUJCCNEqqS7bqovNkNLd0m3FP3q9fKJ22QxNnP9NeFHK6UFa4KfouQAb2fInfQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-7.0.1.tgz", + "integrity": "sha512-CwzRWzQhsEuWDJaCA2k7DvCaEqX1hA6UteNlYU/9Oh7z4mfWIbE89DRdxBc1AFnWRZbbZQW9TXBU3IPe/qAdqQ==", "dependencies": { - "@patternfly/react-styles": "^4.92.6", - "@patternfly/react-tokens": "^4.94.6", + "@patternfly/react-styles": "^5.0.1", + "@patternfly/react-tokens": "^5.0.1", "hoist-non-react-statics": "^3.3.0", "lodash": "^4.17.19", - "tslib": "^2.0.0", - "victory-area": "^36.6.7", - "victory-axis": "^36.6.7", - "victory-bar": "^36.6.7", - "victory-chart": "^36.6.7", - "victory-core": "^36.6.7", - "victory-create-container": "^36.6.7", - "victory-cursor-container": "^36.6.7", - "victory-group": "^36.6.7", - "victory-legend": "^36.6.7", - "victory-line": "^36.6.7", - "victory-pie": "^36.6.7", - "victory-scatter": "^36.6.7", - "victory-stack": "^36.6.7", - "victory-tooltip": "^36.6.7", - "victory-voronoi-container": "^36.6.7", - "victory-zoom-container": "^36.6.7" + "tslib": "^2.5.0", + "victory-area": "^36.6.11", + "victory-axis": "^36.6.11", + "victory-bar": "^36.6.11", + "victory-box-plot": "^36.6.11", + "victory-chart": "^36.6.11", + "victory-core": "^36.6.11", + "victory-create-container": "^36.6.11", + "victory-cursor-container": "^36.6.11", + "victory-group": "^36.6.11", + "victory-legend": "^36.6.11", + "victory-line": "^36.6.11", + "victory-pie": "^36.6.11", + "victory-scatter": "^36.6.11", + "victory-stack": "^36.6.11", + "victory-tooltip": "^36.6.11", + "victory-voronoi-container": "^36.6.11", + "victory-zoom-container": "^36.6.11" }, "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" + "react": "^17 || ^18", + "react-dom": "^17 || ^18" } }, - "node_modules/@patternfly/react-core": { - "version": "4.276.8", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.8.tgz", - "integrity": "sha512-dn322rEzBeiVztZEuCZMUUittNb8l1hk30h9ZN31FLZLLVtXGlThFNV9ieqOJYA9zrYxYZrHMkTnOxSWVacMZg==", + "node_modules/@patternfly/react-component-groups": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-1.1.0.tgz", + "integrity": "sha512-F77HZ1pAisIUfptl0dm/B3O9JGsdDP105QqDQgMxrKKM2GtFz21DrMmt1gNmun63GKFbEv5tCa25P4gqjCfPKA==", "dependencies": { - "@patternfly/react-icons": "^4.93.6", - "@patternfly/react-styles": "^4.92.6", - "@patternfly/react-tokens": "^4.94.6", - "focus-trap": "6.9.2", - "react-dropzone": "9.0.0", - "tippy.js": "5.1.2", - "tslib": "^2.0.0" + "@patternfly/react-core": "^5.0.0", + "@patternfly/react-icons": "^5.0.0", + "react-jss": "^10.9.2" }, "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, + "node_modules/@patternfly/react-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-5.0.1.tgz", + "integrity": "sha512-Eevd+8ACLFV733J+cpo4FRgNtRBObIgmUcrqLjf9H99jZ1hFpBgacFyHiALFi2cuoNVGmdEzskFl+4c7Uo0n+w==", + "dependencies": { + "@patternfly/react-icons": "^5.0.1", + "@patternfly/react-styles": "^5.0.1", + "@patternfly/react-tokens": "^5.0.1", + "focus-trap": "7.4.3", + "react-dropzone": "^14.2.3", + "tslib": "^2.5.0" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" } }, "node_modules/@patternfly/react-icons": { - "version": "4.93.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.93.6.tgz", - "integrity": "sha512-ZrXegc/81oiuTIeWvoHb3nG/eZODbB4rYmekBEsrbiysyO7m/sUFoi/RLvgFINrRoh6YCJqL5fj06Jg6d7jX1g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-5.0.1.tgz", + "integrity": "sha512-MduetDRzve3eRlKAioM/UxmVuPyFccdeBWAKhbN4SBn7RaZWS7kO7/xZzNkpeT5pqQIeAACvz3uiV2/3uAf38w==", "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" + "react": "^17 || ^18", + "react-dom": "^17 || ^18" } }, "node_modules/@patternfly/react-styles": { - "version": "4.92.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.92.6.tgz", - "integrity": "sha512-b8uQdEReMyeoMzjpMri845QxqtupY/tIFFYfVeKoB2neno8gkcW1RvDdDe62LF88q45OktCwAe/8A99ker10Iw==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.0.1.tgz", + "integrity": "sha512-kHP/lbvmhBnNfWiqJJLNwOQZnkcl6wfwAesRp22s4Lj941EWe0oFIqn925/uORIOAOz2du1121t7T4UTfLZg4w==" }, "node_modules/@patternfly/react-table": { - "version": "4.113.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.113.0.tgz", - "integrity": "sha512-qxa3NWCdYasqQQL1rqEd8DyNa8oWr6HNveNW5YJRakE7imWZhUPG2Nd6Op60+KYX8kbCSl7gwSmgAZAYMBMZkQ==", - "dependencies": { - "@patternfly/react-core": "^4.276.8", - "@patternfly/react-icons": "^4.93.6", - "@patternfly/react-styles": "^4.92.6", - "@patternfly/react-tokens": "^4.94.6", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-5.0.1.tgz", + "integrity": "sha512-2YbM6XvgG9ubJE4caPQKPMFBkcf7zYzLUFbHnkyfInpWeVNBs/+ZDAP2wnIHce7uaPLnJ1t0FVzGwaD/UuPwkg==", + "dependencies": { + "@patternfly/react-core": "^5.0.1", + "@patternfly/react-icons": "^5.0.1", + "@patternfly/react-styles": "^5.0.1", + "@patternfly/react-tokens": "^5.0.1", "lodash": "^4.17.19", - "tslib": "^2.0.0" + "tslib": "^2.5.0" }, "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" + "react": "^17 || ^18", + "react-dom": "^17 || ^18" } }, "node_modules/@patternfly/react-tokens": { - "version": "4.94.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.94.6.tgz", - "integrity": "sha512-tm7C6nat+uKMr1hrapis7hS3rN9cr41tpcCKhx6cod6FLU8KwF2Yt5KUxakhIOCEcE/M/EhXhAw/qejp8w0r7Q==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.0.1.tgz", + "integrity": "sha512-YafAGJYvxDP4GaQ0vMybalWmx7MJ+etUf1cGoaMh0wRD2eswltT/RckygtEBKR/M61qXbgG+CxKmMyY8leoiDw==" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -2300,9 +2327,9 @@ } }, "node_modules/@redhat-cloud-services/eslint-config-redhat-cloud-services": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/eslint-config-redhat-cloud-services/-/eslint-config-redhat-cloud-services-1.3.0.tgz", - "integrity": "sha512-/ceH7UeLMN+AoFdPDN0PeNgufiUKivU7wJ8ypqXINV1fKDVQf71mn48RjkdDCZ12/QHItHwaXFVpdnktBvRYHQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/eslint-config-redhat-cloud-services/-/eslint-config-redhat-cloud-services-2.0.3.tgz", + "integrity": "sha512-kYj1IzjSH8v+Me884WbOrm7PdPbd05lSqRydJ5bIj7ydNg0fLySzX2vzvCdSfS3pbhYOlnYG7VbBJ5/Aml+1tg==", "dev": true, "dependencies": { "@babel/eslint-parser": "^7.19.1", @@ -2354,41 +2381,43 @@ } }, "node_modules/@redhat-cloud-services/frontend-components": { - "version": "3.11.8", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-3.11.8.tgz", - "integrity": "sha512-zgBMLMxYOjRH66YGiRGJBhT6WatHiBWMNvZ/cStKdw6WxKBVCF5k4Bk2Bw8fi+xabc/RkR2PetqaUABOvtJuYA==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.0.10.tgz", + "integrity": "sha512-xYgHwtpkPYXXByMizbraIm+M9WXS65FDwA4wIJq/hzOAiVv4WtORVCRn3Ysul3wAKlXLB9Q+M6kzxXXcbc+v4g==", "dependencies": { - "@redhat-cloud-services/frontend-components-utilities": "^3.2.25", + "@patternfly/react-component-groups": "^1.0.17", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", "@redhat-cloud-services/types": "^0.0.24", "@scalprum/core": "^0.5.1", "@scalprum/react-core": "^0.5.1", "sanitize-html": "^2.7.2" }, "peerDependencies": { - "@patternfly/react-core": "^4.18.5", - "@patternfly/react-icons": "^4.53.16", - "@patternfly/react-table": "^4.5.7", + "@patternfly/react-core": "^5.0.0", + "@patternfly/react-icons": "^5.0.0", + "@patternfly/react-table": "^5.0.0", "classnames": "^2.2.5", "lodash": "^4.17.15", "prop-types": "^15.6.2", - "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react": "^18.2.0", "react-content-loader": "^6.2.0", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^18.2.0", "react-redux": ">=7.0.0", "react-router-dom": "^5.0.0 || ^6.0.0" } }, "node_modules/@redhat-cloud-services/frontend-components-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-5.1.1.tgz", - "integrity": "sha512-13vZTD06IO5TxSy+1+Y4flpReC45gJTOci3b1QmR5+HvWL5nY0pB7TmmGMWxr5JeVk45v3EYOlXzdLWQuy0pOw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.0.5.tgz", + "integrity": "sha512-nBJue5FfClmEwdrEZJV6Wi4tiTP/n/AxDA5IwSxOTsquyK31YofIZtEPOuqVL3tDfEN8vJR0b1JUDe6X1ZOJ9g==", "dev": true, "dependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", - "@redhat-cloud-services/frontend-components-config-utilities": "^2.0.3", + "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.4", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.3", + "@swc/core": "^1.3.76", "assert": "^2.0.0", "axios": "^0.27.2", - "babel-loader": "^8.2.5", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "chalk": "^4.1.2", @@ -2415,7 +2444,8 @@ "sass-loader": "^11.1.1", "source-map-loader": "^3.0.1", "stream-browserify": "^3.0.0", - "ts-loader": "^8.4.0", + "swc-loader": "^0.2.3", + "ts-loader": "^9.4.4", "url": "^0.11.0", "util": "^0.12.4", "webpack": "^5.88.0", @@ -2428,9 +2458,9 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config-utilities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config-utilities/-/frontend-components-config-utilities-2.1.0.tgz", - "integrity": "sha512-RQcXn+0WEVzBk98XHHOA3SIatT7qw30h/7UW/oWAYfGGceXeE5/5IjdRIZmuUaNym9v24nv1F4M9+RTdvXrWPA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config-utilities/-/frontend-components-config-utilities-3.0.4.tgz", + "integrity": "sha512-09nzlC7BOOuPF9MBHvLpd1MUYFJ8uz6FN80nbAc9sSMosrBH5R+BWQ/rbRUlEOnX3NdicQxx3Xg9EVPoTOTJmg==", "dev": true, "dependencies": { "@openshift/dynamic-plugin-sdk-webpack": "^3.0.0", @@ -2601,20 +2631,20 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-notifications": { - "version": "3.2.16", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-3.2.16.tgz", - "integrity": "sha512-MpuUip15IPuRjZK+FePnVZespKJ/NpUbIimy5BKyE0JcvXS79nCMRvf6I9Xak+5R3fnsX+TStLC6vC2s5WkxRg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-notifications/-/frontend-components-notifications-4.0.4.tgz", + "integrity": "sha512-ZIr7Z+9Q61CtRw3X5fERf/3oDlmdblffLZu+wFFrVBFdAb+vxeK4l9lM1cFhIp/0toJVmQuao06pjkInWf1LLQ==", "dependencies": { - "@redhat-cloud-services/frontend-components": "*", - "@redhat-cloud-services/frontend-components-utilities": "*", + "@redhat-cloud-services/frontend-components": "^4.0.9", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", "redux-promise-middleware": "6.1.3" }, "peerDependencies": { - "@patternfly/react-core": "^4.18.5", - "@patternfly/react-icons": "^4.3.5", + "@patternfly/react-core": "^5.0.0", + "@patternfly/react-icons": "^5.0.0", "prop-types": "^15.6.2", - "react": "^16.14.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-redux": ">=7.2.9", "redux": ">=4.2.0" } @@ -2633,10 +2663,75 @@ "react-intl": "^6.4.4" } }, - "node_modules/@redhat-cloud-services/frontend-components-utilities": { + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/@patternfly/react-core": { + "version": "4.276.12", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.12.tgz", + "integrity": "sha512-bBN2BMFhWjl/27zmTG5z3+6aH7XMO8our9nsaryxuzN5wFn6S8cIDILsw5NY7N8SLOOtmqbFJUgcM5GAn1ZAXg==", + "optional": true, + "peer": true, + "dependencies": { + "@patternfly/react-icons": "^4.93.7", + "@patternfly/react-styles": "^4.92.8", + "@patternfly/react-tokens": "^4.94.7", + "focus-trap": "6.9.2", + "react-dropzone": "9.0.0", + "tippy.js": "5.1.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/@patternfly/react-icons": { + "version": "4.93.7", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.93.7.tgz", + "integrity": "sha512-3kr35dgba7Qz5CSzmfH0rIjSvBC5xkmiknf3SvVUVxaiVA7KRowID8viYHeZlf3v/Oa3sEewaH830Q0t+nWsZQ==", + "optional": true, + "peer": true, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/@patternfly/react-styles": { + "version": "4.92.8", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.92.8.tgz", + "integrity": "sha512-K4lUU8O4HiCX9NeuNUIrPgN3wlGERCxJVio+PAjd8hpJD/PKnjFfOJ9u6/Cii3qLy/5ZviWPRNHbGiwA/+YUhg==", + "optional": true, + "peer": true + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/@patternfly/react-table": { + "version": "4.113.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.113.4.tgz", + "integrity": "sha512-WN00JBlJR8VVXpNH/IBmuTj70/Ww4vNQwB67mJ8yPzZ0refgAMqMGHCsp9yo5GImJhcBqQr4xlQNpHpeJIKphQ==", + "optional": true, + "peer": true, + "dependencies": { + "@patternfly/react-core": "^4.276.12", + "@patternfly/react-icons": "^4.93.7", + "@patternfly/react-styles": "^4.92.8", + "@patternfly/react-tokens": "^4.94.7", + "lodash": "^4.17.19", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17 || ^18", + "react-dom": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/@patternfly/react-tokens": { + "version": "4.94.7", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.94.7.tgz", + "integrity": "sha512-h+ducOLDMSxcuec3+YY3x+stM5ZUSnrl/lC/eVmjypil2El08NuE2MNEPMQWdhrod6VRRZFMNqZw/m82iv6U1A==", + "optional": true, + "peer": true + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/@redhat-cloud-services/frontend-components-utilities": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-3.7.6.tgz", "integrity": "sha512-qXevUW8Clj1AoBLwfZAmS+oFJCWKZaMp8O33cyUhkoGjrwkQaL5eJg28zjLVa1uQhdJAvAdwTNtE+Gt3hJTC1g==", + "optional": true, "dependencies": { "@redhat-cloud-services/types": "^0.0.24", "@sentry/browser": "^5.30.0", @@ -2657,6 +2752,102 @@ "react-router-dom": "^5.0.0 || ^6.0.0" } }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/attr-accept": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", + "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", + "optional": true, + "peer": true, + "dependencies": { + "core-js": "^2.5.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "optional": true, + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/file-selector": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", + "integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/focus-trap": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.2.tgz", + "integrity": "sha512-gBEuXOPNOKPrLdZpMFUSTyIo1eT2NSZRrwZ9r/0Jqw5tmT3Yvxfmu8KBHw8xW2XQkw6E/JoG+OlEq7UDtSUNgw==", + "optional": true, + "peer": true, + "dependencies": { + "tabbable": "^5.3.2" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/react-dropzone": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-9.0.0.tgz", + "integrity": "sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw==", + "optional": true, + "peer": true, + "dependencies": { + "attr-accept": "^1.1.3", + "file-selector": "^0.1.8", + "prop-types": "^15.6.2", + "prop-types-extra": "^1.1.0" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/@redhat-cloud-services/frontend-components-translations/node_modules/tabbable": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", + "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", + "optional": true, + "peer": true + }, + "node_modules/@redhat-cloud-services/frontend-components-utilities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.2.tgz", + "integrity": "sha512-LUAaJwpi8EmyrNrGum53HcSpO0rrwvXkdEmaXjfooRlvVtLz8twsjaiM2jFfqWXbZMq43gQufn/wy8nquRoq6w==", + "dependencies": { + "@redhat-cloud-services/types": "^0.0.24", + "@sentry/browser": "^5.30.0", + "awesome-debounce-promise": "^2.1.0", + "axios": "^0.27.2", + "commander": "^2.20.3", + "mkdirp": "^1.0.4", + "react-content-loader": "^6.2.0" + }, + "peerDependencies": { + "@patternfly/react-core": "^5.0.0", + "@patternfly/react-table": "^5.0.0", + "@redhat-cloud-services/rbac-client": "^1.0.100", + "cypress": ">=12.0.0 < 14.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": ">=7.0.0", + "react-router-dom": "^5.0.0 || ^6.0.0" + } + }, "node_modules/@redhat-cloud-services/frontend-components-utilities/node_modules/axios": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", @@ -2683,6 +2874,64 @@ "form-data": "^4.0.0" } }, + "node_modules/@redhat-cloud-services/tsc-transform-imports": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/tsc-transform-imports/-/tsc-transform-imports-1.0.4.tgz", + "integrity": "sha512-5qI4QYSZqH4dSTfMqxLaVHGTRqocHVaXGGUK6JXei4/sP6ZMq3liJmLmnvXMe9gSziMVz0I8ij4wuKpB0FuC8A==", + "dev": true, + "dependencies": { + "glob": "10.3.3" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@redhat-cloud-services/tsc-transform-imports/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@redhat-cloud-services/tsc-transform-imports/node_modules/glob": { + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", + "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@redhat-cloud-services/tsc-transform-imports/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@redhat-cloud-services/types": { "version": "0.0.24", "resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.24.tgz", @@ -2881,6 +3130,203 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@swc/core": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.83.tgz", + "integrity": "sha512-PccHDgGQlFjpExgJxH91qA3a4aifR+axCFJ4RieCoiI0m5gURE4nBhxzTBY5YU/YKTBmPO8Gc5Q6inE3+NquWg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/types": "^0.1.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.83", + "@swc/core-darwin-x64": "1.3.83", + "@swc/core-linux-arm-gnueabihf": "1.3.83", + "@swc/core-linux-arm64-gnu": "1.3.83", + "@swc/core-linux-arm64-musl": "1.3.83", + "@swc/core-linux-x64-gnu": "1.3.83", + "@swc/core-linux-x64-musl": "1.3.83", + "@swc/core-win32-arm64-msvc": "1.3.83", + "@swc/core-win32-ia32-msvc": "1.3.83", + "@swc/core-win32-x64-msvc": "1.3.83" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.83.tgz", + "integrity": "sha512-Plz2IKeveVLivbXTSCC3OZjD2MojyKYllhPrn9RotkDIZEFRYJZtW5/Ik1tJW/2rzu5HVKuGYrDKdScVVTbOxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.83.tgz", + "integrity": "sha512-FBGVg5IPF/8jQ6FbK60iDUHjv0H5+LwfpJHKH6wZnRaYWFtm7+pzYgreLu3NTsm3m7/1a7t0+7KURwBGUaJCCw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.83.tgz", + "integrity": "sha512-EZcsuRYhGkzofXtzwDjuuBC/suiX9s7zeg2YYXOVjWwyebb6BUhB1yad3mcykFQ20rTLO9JUyIaiaMYDHGobqw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.83.tgz", + "integrity": "sha512-khI41szLHrCD/cFOcN4p2SYvZgHjhhHlcMHz5BksRrDyteSJKu0qtWRZITVom0N/9jWoAleoFhMnFTUs0H8IWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.83.tgz", + "integrity": "sha512-zgT7yNOdbjHcGAwvys79mbfNLK65KBlPJWzeig+Yk7I8TVzmaQge7B6ZS/gwF9/p+8TiLYo/tZ5aF2lqlgdSVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.83.tgz", + "integrity": "sha512-x+mH0Y3NC/G0YNlFmGi3vGD4VOm7IPDhh+tGrx6WtJp0BsShAbOpxtfU885rp1QweZe4qYoEmGqiEjE2WrPIdA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.83.tgz", + "integrity": "sha512-s5AYhAOmetUwUZwS5g9qb92IYgNHHBGiY2mTLImtEgpAeBwe0LPDj6WrujxCBuZnaS55mKRLLOuiMZE5TpjBNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.83.tgz", + "integrity": "sha512-yw2rd/KVOGs95lRRB+killLWNaO1dy4uVa8Q3/4wb5txlLru07W1m041fZLzwOg/1Sh0TMjJgGxj0XHGR3ZXhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.83.tgz", + "integrity": "sha512-POW+rgZ6KWqBpwPGIRd2/3pcf46P+UrKBm4HLt5IwbHvekJ4avIM8ixJa9kK0muJNVJcDpaZgxaU1ELxtJ1j8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.83.tgz", + "integrity": "sha512-CiWQtkFnZElXQUalaHp+Wacw0Jd+24ncRYhqaJ9YKnEQP1H82CxIIuQqLM8IFaLpn5dpY6SgzaeubWF46hjcLA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", @@ -2889,6 +3335,12 @@ "tslib": "^2.4.0" } }, + "node_modules/@swc/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.4.tgz", + "integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==", + "dev": true + }, "node_modules/@testing-library/dom": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", @@ -4716,12 +5168,9 @@ } }, "node_modules/attr-accept": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", - "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", - "dependencies": { - "core-js": "^2.5.0" - }, + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", "engines": { "node": ">=4" } @@ -4790,9 +5239,9 @@ "peer": true }, "node_modules/axe-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.0.tgz", - "integrity": "sha512-ZtlVZobOeDQhb/y2lMK6mznDw7TJHDNcKx5/bbBkFvArIQ5CVFhSI6hWWQnMx9I8cNmNmZ30wpDyOC2E2nvgbQ==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.1.tgz", + "integrity": "sha512-9l850jDDPnKq48nbad8SiEelCv4OrUWrKab/cPj0GScVg6cb6NbCCt/Ulk26QEq5jP9NnGr04Bit1BHyV6r5CQ==", "dev": true, "engines": { "node": ">=4" @@ -4908,43 +5357,6 @@ "node": ">=8" } }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -5941,12 +6353,6 @@ "node": ">=4.0.0" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -6294,7 +6700,9 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true + "hasInstallScript": true, + "optional": true, + "peer": true }, "node_modules/core-js-pure": { "version": "3.32.2", @@ -6460,6 +6868,16 @@ "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", + "integrity": "sha512-YyMIS/LsSKEGXEaVJdjonWe18p4vXLo8CMA4FrW/kcaEyqdIGKCFXao31gbJddXEdIxSXFFURWrenBJPlKTgAA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "^10.10.0", + "jss-preset-default": "^10.10.0" + } + }, "node_modules/css-loader": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", @@ -6537,6 +6955,15 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "dependencies": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -7689,26 +8116,16 @@ } }, "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "engines": { - "node": ">=6" + "node": ">=10.13.0" } }, "node_modules/enquirer": { @@ -7747,18 +8164,6 @@ "node": ">=4" } }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9865,14 +10270,14 @@ } }, "node_modules/file-selector": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", - "integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", "dependencies": { - "tslib": "^2.0.1" + "tslib": "^2.4.0" }, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/fill-range": { @@ -9914,29 +10319,12 @@ "ms": "2.0.0" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -9989,11 +10377,11 @@ "dev": true }, "node_modules/focus-trap": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.2.tgz", - "integrity": "sha512-gBEuXOPNOKPrLdZpMFUSTyIo1eT2NSZRrwZ9r/0Jqw5tmT3Yvxfmu8KBHw8xW2XQkw6E/JoG+OlEq7UDtSUNgw==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz", + "integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==", "dependencies": { - "tabbable": "^5.3.2" + "tabbable": "^6.1.2" } }, "node_modules/follow-redirects": { @@ -11074,8 +11462,7 @@ "node_modules/hyphenate-style-name": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==", - "dev": true + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, "node_modules/iconv-lite": { "version": "0.4.24", @@ -11656,6 +12043,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -12105,48 +12497,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-report/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12159,12 +12509,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-report/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -14372,6 +14716,158 @@ "verror": "1.10.0" } }, + "node_modules/jss": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", + "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", + "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-compose": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-compose/-/jss-plugin-compose-10.10.0.tgz", + "integrity": "sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-default-unit": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", + "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-expand": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-expand/-/jss-plugin-expand-10.10.0.tgz", + "integrity": "sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-extend": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-extend/-/jss-plugin-extend-10.10.0.tgz", + "integrity": "sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", + "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-nested": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", + "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-props-sort": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", + "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "node_modules/jss-plugin-rule-value-function": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", + "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-rule-value-observable": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-observable/-/jss-plugin-rule-value-observable-10.10.0.tgz", + "integrity": "sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "symbol-observable": "^1.2.0" + } + }, + "node_modules/jss-plugin-template": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-template/-/jss-plugin-template-10.10.0.tgz", + "integrity": "sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-vendor-prefixer": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", + "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.10.0" + } + }, + "node_modules/jss-preset-default": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-10.10.0.tgz", + "integrity": "sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "jss-plugin-camel-case": "10.10.0", + "jss-plugin-compose": "10.10.0", + "jss-plugin-default-unit": "10.10.0", + "jss-plugin-expand": "10.10.0", + "jss-plugin-extend": "10.10.0", + "jss-plugin-global": "10.10.0", + "jss-plugin-nested": "10.10.0", + "jss-plugin-props-sort": "10.10.0", + "jss-plugin-rule-value-function": "10.10.0", + "jss-plugin-rule-value-observable": "10.10.0", + "jss-plugin-template": "10.10.0", + "jss-plugin-vendor-prefixer": "10.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -14938,20 +15434,53 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -15009,55 +15538,6 @@ "node": ">= 4.0.0" } }, - "node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/memory-fs/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/memory-fs/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/memory-fs/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/memory-fs/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -16323,6 +16803,8 @@ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "optional": true, + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -16639,12 +17121,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -16807,6 +17283,11 @@ "react": ">=16.0.0" } }, + "node_modules/react-display-name": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz", + "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -16820,20 +17301,19 @@ } }, "node_modules/react-dropzone": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-9.0.0.tgz", - "integrity": "sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw==", + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", "dependencies": { - "attr-accept": "^1.1.3", - "file-selector": "^0.1.8", - "prop-types": "^15.6.2", - "prop-types-extra": "^1.1.0" + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" }, "engines": { - "node": ">= 6" + "node": ">= 10.13" }, "peerDependencies": { - "react": ">=0.14.0" + "react": ">= 16.8 || 18.0.0" } }, "node_modules/react-fast-compare": { @@ -16872,6 +17352,27 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-jss": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/react-jss/-/react-jss-10.10.0.tgz", + "integrity": "sha512-WLiq84UYWqNBF6579/uprcIUnM1TSywYq6AIjKTTTG5ziJl9Uy+pwuvpN3apuyVwflMbD60PraeTKT7uWH9XEQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "@emotion/is-prop-valid": "^0.7.3", + "css-jss": "10.10.0", + "hoist-non-react-statics": "^3.2.0", + "is-in-browser": "^1.1.3", + "jss": "10.10.0", + "jss-preset-default": "10.10.0", + "prop-types": "^15.6.0", + "shallow-equal": "^1.2.0", + "theming": "^3.3.0", + "tiny-warning": "^1.0.2" + }, + "peerDependencies": { + "react": ">=16.8.6" + } + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -17869,6 +18370,11 @@ "node": ">=8" } }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -18453,6 +18959,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swc-loader": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz", + "integrity": "sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==", + "dev": true, + "peerDependencies": { + "@swc/core": "^1.2.147", + "webpack": ">=2" + } + }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -18482,9 +19006,9 @@ } }, "node_modules/tabbable": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/tapable": { "version": "2.2.1", @@ -18624,6 +19148,23 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/theming": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/theming/-/theming-3.3.0.tgz", + "integrity": "sha512-u6l4qTJRDaWZsqa8JugaNt7Xd8PPl9+gonZaIe28vAhqgHMIG/DOyFPqiKN/gQLQYj05tHv+YQdNILL4zoiAVA==", + "dependencies": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.8", + "react-display-name": "^0.2.4", + "tiny-warning": "^1.0.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.3" + } + }, "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -18646,10 +19187,17 @@ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/tippy.js": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-5.1.2.tgz", "integrity": "sha512-Qtrv2wqbRbaKMUb6bWWBQWPayvcDKNrGlvihxtsyowhT7RLGEh1STWuy6EMXC6QLkfKPB2MLnf8W2mzql9VDAw==", + "optional": true, + "peer": true, "dependencies": { "popper.js": "^1.16.0" } @@ -18799,23 +19347,22 @@ } }, "node_modules/ts-loader": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", - "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "dependencies": { "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", + "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" }, "peerDependencies": { "typescript": "*", - "webpack": "*" + "webpack": "^5.0.0" } }, "node_modules/ts-loader/node_modules/ansi-styles": { @@ -19555,6 +20102,20 @@ "react": ">=16.6.0" } }, + "node_modules/victory-box-plot": { + "version": "36.6.11", + "resolved": "https://registry.npmjs.org/victory-box-plot/-/victory-box-plot-36.6.11.tgz", + "integrity": "sha512-+J7Hb0Vf6cQe+qZyRhm6sM7V7AMS43jSTXnrFtRP7Bn+HdAb7p2S7h8abtgUhg3uVeTQa9UUqJBmC/maP8V3Nw==", + "dependencies": { + "lodash": "^4.17.19", + "prop-types": "^15.8.1", + "victory-core": "^36.6.11", + "victory-vendor": "^36.6.11" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, "node_modules/victory-brush-container": { "version": "36.6.11", "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-36.6.11.tgz", @@ -20336,19 +20897,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", diff --git a/package.json b/package.json index 10a1c132b..269c67ea4 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,15 @@ "check:milestone": "npx npm-check-updates -t newest -f '/^(@patternfly|@redhat-cloud-services)/'", "check:milestone:update": "npx npm-check-updates -t newest -f '/^(@patternfly|@redhat-cloud-services)/' -u", "clean": "rimraf dist .cache", + "codemods": "npx @patternfly/pf-codemods@latest src", "deploy": "npm-run-all build lint test", "install:pkgs": "npm install", + "install:pkgs:force": "npm install --force", "lint": "npm-run-all lint:*", "lint:ts": "eslint src", "lint:ts:fix": "eslint src --fix", "patch:hosts": "fec patch-etc-hosts", - "postinstall": "rm -rf .cache", + "postinstall": "ts-patch install && rm -rf .cache", "start": "HMR=true fec dev", "start:csc": "CLOUD_SERVICES_CONFIG_PORT=8889 npm start", "start:ephemeral": "EPHEMERAL_PORT=8000 npm start", @@ -45,17 +47,16 @@ "verify": "npm-run-all build lint test" }, "dependencies": { - "@patternfly/patternfly": "4.224.2", - "@patternfly/react-charts": "6.94.19", - "@patternfly/react-core": "4.276.8", - "@patternfly/react-icons": "4.93.6", - "@patternfly/react-styles": "4.92.6", - "@patternfly/react-table": "4.113.0", - "@patternfly/react-tokens": "4.94.6", - "@redhat-cloud-services/frontend-components": "^3.11.5", - "@redhat-cloud-services/frontend-components-notifications": "^3.2.16", + "@patternfly/patternfly": "5.0.4", + "@patternfly/react-charts": "7.0.1", + "@patternfly/react-core": "5.0.1", + "@patternfly/react-icons": "5.0.1", + "@patternfly/react-table": "5.0.1", + "@patternfly/react-tokens": "5.0.1", + "@redhat-cloud-services/frontend-components": "^4.0.10", + "@redhat-cloud-services/frontend-components-notifications": "^4.0.4", "@redhat-cloud-services/frontend-components-translations": "^3.2.7", - "@redhat-cloud-services/frontend-components-utilities": "^3.7.6", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", "@redhat-cloud-services/rbac-client": "^1.2.9", "@unleash/proxy-client-react": "^3.6.0", "axios": "^1.5.1", @@ -79,8 +80,9 @@ }, "devDependencies": { "@formatjs/cli": "^6.2.0", - "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^1.3.0", - "@redhat-cloud-services/frontend-components-config": "^5.1.1", + "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", + "@redhat-cloud-services/frontend-components-config": "^6.0.5", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", "@types/jest": "^29.5.5", diff --git a/src/components/drawers/exports/exportActions.tsx b/src/components/drawers/exports/exportActions.tsx index 308771e97..03e80819b 100644 --- a/src/components/drawers/exports/exportActions.tsx +++ b/src/components/drawers/exports/exportActions.tsx @@ -1,4 +1,4 @@ -import { Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core'; +import { Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -61,7 +61,7 @@ class ExportsActionsBase extends React.Component } + toggle={ this.handleOnToggle(isOpen)} />} isOpen={isDropdownOpen} isPlain position="right" diff --git a/src/components/drawers/exports/exportsContent.tsx b/src/components/drawers/exports/exportsContent.tsx index e6ea9c825..9df5e1bec 100644 --- a/src/components/drawers/exports/exportsContent.tsx +++ b/src/components/drawers/exports/exportsContent.tsx @@ -82,7 +82,7 @@ class ExportsContentBase extends React.Component { // @redhat-cloud-services/frontend-components-notifications does not expose PatternFly's actionLinks prop if (isActionLink) { return ( -
+
{intl.formatMessage(messages.exportsTitle)}
); diff --git a/src/components/drawers/exports/exportsTable.tsx b/src/components/drawers/exports/exportsTable.tsx index aabbba360..1c879327b 100644 --- a/src/components/drawers/exports/exportsTable.tsx +++ b/src/components/drawers/exports/exportsTable.tsx @@ -4,19 +4,20 @@ import { ButtonVariant, EmptyState, EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, EmptyStateIcon, Label, Popover, Spinner, - Title, - TitleSizes, } from '@patternfly/react-core'; import { DownloadIcon } from '@patternfly/react-icons/dist/esm/icons/download-icon'; import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; import { OutlinedClockIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-clock-icon'; import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import { SyncIcon } from '@patternfly/react-icons/dist/esm/icons/sync-icon'; -import { sortable, SortByDirection, Table, TableBody, TableHeader, TableVariant } from '@patternfly/react-table'; +import { sortable, SortByDirection, TableVariant } from '@patternfly/react-table'; +import { Table, TableBody, TableHeader } from '@patternfly/react-table/deprecated'; import type { Query } from 'api/queries/query'; import { getQuery } from 'api/queries/query'; import type { Report } from 'api/reports/report'; @@ -169,14 +170,17 @@ class ExportsTableBase extends React.Component - - - {intl.formatMessage(messages.noExportsStateTitle)} - + {intl.formatMessage(messages.noExportsStateTitle)}} + icon={} + headingLevel="h5" + /> {intl.formatMessage(messages.exportsEmptyState)} - + + + ); }; diff --git a/src/components/inactiveSources/inactiveSources.scss b/src/components/inactiveSources/inactiveSources.scss index 242012ffa..b46cf0a92 100644 --- a/src/components/inactiveSources/inactiveSources.scss +++ b/src/components/inactiveSources/inactiveSources.scss @@ -1,8 +1,8 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .alert { - background-color: var(--pf-global--BackgroundColor--light-100); - padding-left: var(--pf-global--spacer--lg); - padding-right: var(--pf-global--spacer--lg); - padding-top: var(--pf-global--spacer--lg); + background-color: var(--pf-v5-global--BackgroundColor--light-100); + padding-left: var(--pf-v5-global--spacer--lg); + padding-right: var(--pf-v5-global--spacer--lg); + padding-top: var(--pf-v5-global--spacer--lg); } diff --git a/src/routes/components/charts/chartTheme/theme-utils.ts b/src/routes/components/charts/chartTheme/theme-utils.ts index a496be99f..e8df95739 100644 --- a/src/routes/components/charts/chartTheme/theme-utils.ts +++ b/src/routes/components/charts/chartTheme/theme-utils.ts @@ -3,6 +3,6 @@ import { ChartThemeColor, getCustomTheme } from '@patternfly/react-charts'; import { default as ChartTheme } from './theme-koku'; // Applies theme color and variant to base theme -const getTheme = () => getCustomTheme(ChartThemeColor.default, null, ChartTheme); +const getTheme = () => getCustomTheme(ChartThemeColor.default, ChartTheme); export default getTheme; diff --git a/src/routes/components/charts/historicalCostChart/__snapshots__/historicalCostChart.test.tsx.snap b/src/routes/components/charts/historicalCostChart/__snapshots__/historicalCostChart.test.tsx.snap index 6401aa7c6..4bc66bb90 100644 --- a/src/routes/components/charts/historicalCostChart/__snapshots__/historicalCostChart.test.tsx.snap +++ b/src/routes/components/charts/historicalCostChart/__snapshots__/historicalCostChart.test.tsx.snap @@ -6,9 +6,9 @@ exports[`null previous and current reports are handled 1`] = ` class="chartOverride" >

@@ -22,7 +22,7 @@ exports[`null previous and current reports are handled 1`] = ` style="height: 100px; width: 0px;" >
@@ -139,7 +139,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -160,7 +160,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -181,7 +181,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -196,7 +196,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -234,7 +234,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -255,7 +255,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -276,7 +276,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -297,7 +297,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -318,7 +318,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -368,7 +368,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -385,7 +385,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -417,9 +417,9 @@ exports[`reports are formatted to datums 1`] = ` class="chartOverride" >

@@ -433,7 +433,7 @@ exports[`reports are formatted to datums 1`] = ` style="height: 100px; width: 0px;" >
@@ -550,7 +550,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -571,7 +571,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -592,7 +592,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -607,7 +607,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -645,7 +645,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -666,7 +666,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -687,7 +687,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -737,7 +737,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -754,7 +754,7 @@ exports[`reports are formatted to datums 1`] = ` diff --git a/src/routes/components/charts/historicalCostChart/historicalCostChart.tsx b/src/routes/components/charts/historicalCostChart/historicalCostChart.tsx index c0eaf7969..392697493 100644 --- a/src/routes/components/charts/historicalCostChart/historicalCostChart.tsx +++ b/src/routes/components/charts/historicalCostChart/historicalCostChart.tsx @@ -16,8 +16,7 @@ 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 { getCostRangeString, getCostRangeTooltip } from 'routes/components/charts/common/chartDatum'; -import { getDateRange } from 'routes/components/charts/common/chartDatum'; +import { getCostRangeString, getCostRangeTooltip, getDateRange } from 'routes/components/charts/common/chartDatum'; import type { ChartSeries } from 'routes/components/charts/common/chartUtils'; import { getChartNames, diff --git a/src/routes/components/charts/historicalTrendChart/__snapshots__/historicalTrendChart.test.tsx.snap b/src/routes/components/charts/historicalTrendChart/__snapshots__/historicalTrendChart.test.tsx.snap index 97fffa9ec..91c0fbe30 100644 --- a/src/routes/components/charts/historicalTrendChart/__snapshots__/historicalTrendChart.test.tsx.snap +++ b/src/routes/components/charts/historicalTrendChart/__snapshots__/historicalTrendChart.test.tsx.snap @@ -5,9 +5,9 @@ exports[`null previous and current reports are handled 1`] = ` class="chartOverride" >

@@ -21,7 +21,7 @@ exports[`null previous and current reports are handled 1`] = ` style="height: 100px; width: 0px;" >
@@ -142,7 +142,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -163,7 +163,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -178,7 +178,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -219,7 +219,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -240,7 +240,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -261,7 +261,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -282,7 +282,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -332,7 +332,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -349,7 +349,7 @@ exports[`null previous and current reports are handled 1`] = ` @@ -379,9 +379,9 @@ exports[`reports are formatted to datums 1`] = ` class="chartOverride" >

@@ -395,7 +395,7 @@ exports[`reports are formatted to datums 1`] = ` style="height: 100px; width: 0px;" >
@@ -516,7 +516,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -537,7 +537,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -552,7 +552,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -593,7 +593,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -614,7 +614,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -635,7 +635,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -685,7 +685,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -702,7 +702,7 @@ exports[`reports are formatted to datums 1`] = ` diff --git a/src/routes/components/charts/historicalUsageChart/__snapshots__/historicalUsageChart.test.tsx.snap b/src/routes/components/charts/historicalUsageChart/__snapshots__/historicalUsageChart.test.tsx.snap index 15de8cfb3..6f07f85ba 100644 --- a/src/routes/components/charts/historicalUsageChart/__snapshots__/historicalUsageChart.test.tsx.snap +++ b/src/routes/components/charts/historicalUsageChart/__snapshots__/historicalUsageChart.test.tsx.snap @@ -5,9 +5,9 @@ exports[`reports are formatted to datums 1`] = ` class="chartOverride" >

@@ -21,7 +21,7 @@ exports[`reports are formatted to datums 1`] = ` style="height: 100px; width: 0px;" >
@@ -266,7 +266,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -287,7 +287,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -302,7 +302,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -343,7 +343,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -364,7 +364,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -385,7 +385,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -495,7 +495,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -512,7 +512,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -529,7 +529,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -546,7 +546,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -563,7 +563,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -580,7 +580,7 @@ exports[`reports are formatted to datums 1`] = ` diff --git a/src/routes/components/charts/historicalUsageChart/historicalUsageChart.tsx b/src/routes/components/charts/historicalUsageChart/historicalUsageChart.tsx index f01c567ca..e89bb9243 100644 --- a/src/routes/components/charts/historicalUsageChart/historicalUsageChart.tsx +++ b/src/routes/components/charts/historicalUsageChart/historicalUsageChart.tsx @@ -16,8 +16,7 @@ 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 { getDateRange, getUsageRangeTooltip } from 'routes/components/charts/common/chartDatum'; -import { getUsageRangeString } from 'routes/components/charts/common/chartDatum'; +import { getDateRange, getUsageRangeString, getUsageRangeTooltip } from 'routes/components/charts/common/chartDatum'; import type { ChartSeries } from 'routes/components/charts/common/chartUtils'; import { getChartNames, diff --git a/src/routes/components/charts/trendChart/__snapshots__/trendChart.test.tsx.snap b/src/routes/components/charts/trendChart/__snapshots__/trendChart.test.tsx.snap index caccd3d0b..7a5992bc8 100644 --- a/src/routes/components/charts/trendChart/__snapshots__/trendChart.test.tsx.snap +++ b/src/routes/components/charts/trendChart/__snapshots__/trendChart.test.tsx.snap @@ -3,9 +3,9 @@ exports[`reports are formatted to datums 1`] = `

Example Trend Title @@ -19,7 +19,7 @@ exports[`reports are formatted to datums 1`] = ` style="height: 100px; width: 0px;" >
@@ -146,7 +146,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -167,7 +167,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -182,7 +182,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -223,7 +223,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -244,7 +244,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -265,7 +265,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -315,7 +315,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -332,7 +332,7 @@ exports[`reports are formatted to datums 1`] = ` diff --git a/src/routes/components/charts/usageChart/__snapshots__/usageChart.test.tsx.snap b/src/routes/components/charts/usageChart/__snapshots__/usageChart.test.tsx.snap index 93a9910b4..bae2eb6d2 100644 --- a/src/routes/components/charts/usageChart/__snapshots__/usageChart.test.tsx.snap +++ b/src/routes/components/charts/usageChart/__snapshots__/usageChart.test.tsx.snap @@ -6,7 +6,7 @@ exports[`reports are formatted to datums 1`] = ` style="height: 100px; width: 0px;" >
@@ -193,7 +193,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -214,7 +214,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -229,7 +229,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -270,7 +270,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -291,7 +291,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -312,7 +312,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -402,7 +402,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -419,7 +419,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -436,7 +436,7 @@ exports[`reports are formatted to datums 1`] = ` @@ -453,7 +453,7 @@ exports[`reports are formatted to datums 1`] = ` diff --git a/src/routes/components/costDistribution/costDistribution.scss b/src/routes/components/costDistribution/costDistribution.scss index 8c4757082..9a3ce9047 100644 --- a/src/routes/components/costDistribution/costDistribution.scss +++ b/src/routes/components/costDistribution/costDistribution.scss @@ -2,8 +2,8 @@ // Workaround for https://github.com/patternfly/patternfly-react/issues/6371 .selectOverride { - &.pf-c-select { - .pf-c-select__menu-item-description { + &.pf-v5-c-select { + .pf-v5-c-select__menu-item-description { width: max-content; } } diff --git a/src/routes/components/costDistribution/costDistribution.tsx b/src/routes/components/costDistribution/costDistribution.tsx index 8a8dcbd43..8c9aca685 100644 --- a/src/routes/components/costDistribution/costDistribution.tsx +++ b/src/routes/components/costDistribution/costDistribution.tsx @@ -1,8 +1,9 @@ import './costDistribution.scss'; import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, Title, TitleSizes } from '@patternfly/react-core'; +import { Title, TitleSizes } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -70,8 +71,8 @@ class CostDistributionBase extends React.Component this.handleSelect(value)} + onToggle={(_evt, isExpanded) => this.handleToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > @@ -96,7 +97,7 @@ class CostDistributionBase extends React.Component { + private handleSelect = (selection: CostDistributionOption) => { const { onSelect } = this.props; setCostDistribution(selection.value); // Set cost distribution in local storage diff --git a/src/routes/components/costType/costType.scss b/src/routes/components/costType/costType.scss index 8c4757082..9a3ce9047 100644 --- a/src/routes/components/costType/costType.scss +++ b/src/routes/components/costType/costType.scss @@ -2,8 +2,8 @@ // Workaround for https://github.com/patternfly/patternfly-react/issues/6371 .selectOverride { - &.pf-c-select { - .pf-c-select__menu-item-description { + &.pf-v5-c-select { + .pf-v5-c-select__menu-item-description { width: max-content; } } diff --git a/src/routes/components/costType/costType.tsx b/src/routes/components/costType/costType.tsx index 73341ef04..4a65e6c69 100644 --- a/src/routes/components/costType/costType.tsx +++ b/src/routes/components/costType/costType.tsx @@ -1,8 +1,9 @@ import './costType.scss'; import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, Title, TitleSizes } from '@patternfly/react-core'; +import { Title, TitleSizes } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -77,8 +78,8 @@ class CostTypeBase extends React.Component { id="costTypeSelect" isDisabled={isDisabled} isOpen={isSelectOpen} - onSelect={this.handleOnSelect} - onToggle={this.handleOnToggle} + onSelect={(_evt, value) => this.handleOnSelect(value)} + onToggle={(_evt, isExpanded) => this.handleOnToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > @@ -104,7 +105,7 @@ class CostTypeBase extends React.Component { return options; }; - private handleOnSelect = (event, selection: CostTypeOption) => { + private handleOnSelect = (selection: CostTypeOption) => { const { isLocalStorage = true, onSelect } = this.props; // Set cost type in local storage diff --git a/src/routes/components/currency/currency.scss b/src/routes/components/currency/currency.scss index 374ceb421..eafea7f5d 100644 --- a/src/routes/components/currency/currency.scss +++ b/src/routes/components/currency/currency.scss @@ -2,7 +2,7 @@ // Workaround for missing "position" property .currencyOverride { - .pf-c-select__menu { + .pf-v5-c-select__menu { right: 0; } } diff --git a/src/routes/components/currency/currency.tsx b/src/routes/components/currency/currency.tsx index 51a364d98..7788c562f 100644 --- a/src/routes/components/currency/currency.tsx +++ b/src/routes/components/currency/currency.tsx @@ -1,8 +1,9 @@ import './currency.scss'; import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, Title, TitleSizes } from '@patternfly/react-core'; +import { Title, TitleSizes } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -80,8 +81,8 @@ class CurrencyBase extends React.Component { id="currencySelect" isDisabled={isDisabled} isOpen={isSelectOpen} - onSelect={this.handleOnSelect} - onToggle={this.handleOnToggle} + onSelect={(_evt, value) => this.handleOnSelect(value)} + onToggle={(_evt, isExpanded) => this.handleOnToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > @@ -106,7 +107,7 @@ class CurrencyBase extends React.Component { return options; }; - private handleOnSelect = (event, selection: CurrencyOption) => { + private handleOnSelect = (selection: CurrencyOption) => { const { isLocalStorage = true, onSelect } = this.props; // Set currency units via local storage diff --git a/src/routes/components/dataTable/dataTable.scss b/src/routes/components/dataTable/dataTable.scss index ef5b85fd8..d97c63d36 100644 --- a/src/routes/components/dataTable/dataTable.scss +++ b/src/routes/components/dataTable/dataTable.scss @@ -4,13 +4,13 @@ div { display: block; margin-right: 0; - margin-bottom: var(--pf-global--spacer--xs); + margin-bottom: var(--pf-v5-global--spacer--xs); &.iconOverride { &.decrease { - color: var(--pf-global--success-color--100); + color: var(--pf-v5-global--success-color--100); } &.increase { - color: var(--pf-global--danger-color--100); + color: var(--pf-v5-global--danger-color--100); } .fa-sort-up { margin-left: 10px; @@ -19,10 +19,10 @@ margin-left: 10px; } .fa-sort-up::before { - color: var(--pf-global--danger-color--100); + color: var(--pf-v5-global--danger-color--100); } .fa-sort-down::before { - color: var(--pf-global--success-color--100); + color: var(--pf-v5-global--success-color--100); } span { margin-right: -17px !important; @@ -32,7 +32,7 @@ } .tableOverride { - &.pf-c-table tbody .pf-c-table__check input { + &.pf-v5-c-table tbody .pf-v5-c-table__check input { margin-top: .40rem; } } diff --git a/src/routes/components/dataTable/dataTable.tsx b/src/routes/components/dataTable/dataTable.tsx index 1f2438af5..2db3813c4 100644 --- a/src/routes/components/dataTable/dataTable.tsx +++ b/src/routes/components/dataTable/dataTable.tsx @@ -1,9 +1,16 @@ import './dataTable.scss'; -import { Bullseye, EmptyState, EmptyStateBody, EmptyStateIcon, Spinner } from '@patternfly/react-core'; +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateHeader, + EmptyStateIcon, + Spinner, +} from '@patternfly/react-core'; import { CalculatorIcon } from '@patternfly/react-icons/dist/esm/icons/calculator-icon'; import type { ThProps } from '@patternfly/react-table'; -import { SortByDirection, TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { SortByDirection, Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -49,7 +56,7 @@ class DataTable extends React.Component { } return ( - + } /> {intl.formatMessage(messages.detailsEmptyState)} ); @@ -71,12 +78,12 @@ class DataTable extends React.Component { private getSortParams = (index: number): ThProps['sort'] => { return { sortBy: this.getSortBy(index), - onSort: this.handleOnSort, + onSort: (_evt, i, direction) => this.handleOnSort(i, direction), columnIndex: index, }; }; - private handleOnSelect = (event, isSelected, rowId) => { + private handleOnSelect = (isSelected, rowId) => { const { onSelected, rows } = this.props; let newRows; @@ -98,7 +105,7 @@ class DataTable extends React.Component { }); }; - private handleOnSort = (event, index, direction) => { + private handleOnSort = (index, direction) => { const { columns, onSort } = this.props; if (onSort) { @@ -113,7 +120,7 @@ class DataTable extends React.Component { return ( <> - { key={`cell-${cellIndex}-${rowIndex}`} modifier="nowrap" select={{ - disable: row.selectionDisabled, // Disable select for "no-project" + isDisabled: row.selectionDisabled, // Disable select for "no-project" isSelected: row.selected, - onSelect: (_event, isSelected) => this.handleOnSelect(_event, isSelected, rowIndex), + onSelect: (_evt, isSelected) => this.handleOnSelect(isSelected, rowIndex), rowIndex, }} style={item.style} @@ -177,7 +184,7 @@ class DataTable extends React.Component { )) )} - + {rows.length === 0 &&
{this.getEmptyState()}
} ); diff --git a/src/routes/components/dataTable/selectableTable.tsx b/src/routes/components/dataTable/selectableTable.tsx index 83bb60d61..a9cb61074 100644 --- a/src/routes/components/dataTable/selectableTable.tsx +++ b/src/routes/components/dataTable/selectableTable.tsx @@ -1,7 +1,14 @@ -import { Bullseye, EmptyState, EmptyStateBody, EmptyStateIcon, Spinner } from '@patternfly/react-core'; +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateHeader, + EmptyStateIcon, + Spinner, +} from '@patternfly/react-core'; import { CalculatorIcon } from '@patternfly/react-icons/dist/esm/icons/calculator-icon'; import type { ThProps } from '@patternfly/react-table'; -import { SortByDirection, TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { SortByDirection, Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import messages from 'locales/messages'; import type { ReactNode } from 'react'; import React from 'react'; @@ -18,7 +25,7 @@ interface SelectableTableOwnProps { filterBy: any; isLoading?: boolean; onSort(value: string, isSortAscending: boolean); - onRowClick(event: React.KeyboardEvent | React.MouseEvent, rowIndex: number); + onRowClick(rowIndex: number); orderBy: any; rows?: any[]; } @@ -47,7 +54,7 @@ class SelectableTable extends React.Component { } return ( - + } /> {intl.formatMessage(messages.detailsEmptyState)} ); @@ -74,7 +81,7 @@ class SelectableTable extends React.Component { }; }; - private handleOnSort = (event, index, direction) => { + private handleOnSort = (index, direction) => { const { columns, onSort } = this.props; if (onSort) { @@ -84,7 +91,7 @@ class SelectableTable extends React.Component { } }; - private handleOnRowClick = (event, rowIndex) => { + private handleOnRowClick = (rowIndex: number) => { const { onRowClick, rows } = this.props; rows.map(row => (row.selected = false)); @@ -92,7 +99,7 @@ class SelectableTable extends React.Component { this.setState({ rows }, () => { if (onRowClick) { - onRowClick(event, rowIndex); + onRowClick(rowIndex); } }); }; @@ -102,10 +109,9 @@ class SelectableTable extends React.Component { return ( <> - @@ -138,9 +144,9 @@ class SelectableTable extends React.Component { this.handleOnRowClick(_event, rowIndex)} + onRowClick={() => this.handleOnRowClick(rowIndex)} key={`row-${rowIndex}`} > {row.cells.map((item, cellIndex) => @@ -169,7 +175,7 @@ class SelectableTable extends React.Component { )) )} - + {rows.length === 0 &&
{this.getEmptyState()}
} ); diff --git a/src/routes/components/dataToolbar/basicToolbar.tsx b/src/routes/components/dataToolbar/basicToolbar.tsx index 76a62274b..3ec5c4222 100644 --- a/src/routes/components/dataToolbar/basicToolbar.tsx +++ b/src/routes/components/dataToolbar/basicToolbar.tsx @@ -194,7 +194,7 @@ export class BasicToolbarBase extends React.Component { + private handleOnCategorySelect = (selection: CategoryOption) => { this.setState({ categoryInput: '', currentCategory: selection.value, @@ -361,7 +361,7 @@ export class BasicToolbarBase extends React.Component )} {actions && {actions}} - + {pagination} diff --git a/src/routes/components/dataToolbar/costCategoryValue.tsx b/src/routes/components/dataToolbar/costCategoryValue.tsx index 9b5564702..5b212bca6 100644 --- a/src/routes/components/dataToolbar/costCategoryValue.tsx +++ b/src/routes/components/dataToolbar/costCategoryValue.tsx @@ -1,13 +1,7 @@ -import type { SelectOptionObject, ToolbarChipGroup } from '@patternfly/react-core'; -import { - Button, - ButtonVariant, - InputGroup, - Select, - SelectOption, - SelectVariant, - TextInput, -} from '@patternfly/react-core'; +import type { ToolbarChipGroup } from '@patternfly/react-core'; +import { Button, ButtonVariant, InputGroup, InputGroupItem, TextInput } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; import type { Query } from 'api/queries/query'; import { getQuery, parseQuery } from 'api/queries/query'; @@ -141,8 +135,8 @@ class CostCategoryValueBase extends React.Component this.onCostCategoryValueToggle(isExpanded)} selections={selections} isOpen={isCostCategoryValueExpanded} placeholderText={intl.formatMessage(messages.chooseValuePlaceholder)} @@ -153,25 +147,29 @@ class CostCategoryValueBase extends React.Component - onCostCategoryValueInput(evt)} - /> - + + this.onCostCategoryValueChange(value)} + value={costCategoryKeyValue} + placeholder={intl.formatMessage(messages.filterByValuePlaceholder)} + onKeyDown={evt => onCostCategoryValueInput(evt)} + /> + + + + ); } diff --git a/src/routes/components/dataToolbar/customSelect.tsx b/src/routes/components/dataToolbar/customSelect.tsx index e9e3147d0..8ef166633 100644 --- a/src/routes/components/dataToolbar/customSelect.tsx +++ b/src/routes/components/dataToolbar/customSelect.tsx @@ -1,7 +1,7 @@ import './dataToolbar.scss'; -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -28,7 +28,7 @@ interface CustomSelectDispatchProps { } interface CustomSelectState { - isExpanded?: boolean; + isCustomSelectExpanded?: boolean; } export interface SelectOptionObjectExt extends SelectOptionObject { @@ -40,13 +40,13 @@ type CustomSelectProps = CustomSelectOwnProps & CustomSelectStateProps & CustomS class CustomSelectBase extends React.Component { protected defaultState: CustomSelectState = { - isExpanded: false, + isCustomSelectExpanded: false, }; public state: CustomSelectState = { ...this.defaultState }; private onCustomSelectToggle = isOpen => { this.setState({ - isExpanded: isOpen, + isCustomSelectExpanded: isOpen, }); }; @@ -66,7 +66,7 @@ class CustomSelectBase extends React.Component { @@ -79,10 +79,10 @@ class CustomSelectBase extends React.Component this.onCustomSelectToggle(isExpanded)} onSelect={onSelect} selections={selections} - isOpen={isExpanded} + isOpen={isCustomSelectExpanded} placeholderText={intl.formatMessage(messages.chooseValuePlaceholder)} > {selectOptions.map(option => ( diff --git a/src/routes/components/dataToolbar/dataKebab.tsx b/src/routes/components/dataToolbar/dataKebab.tsx index 995f4f7a0..ca31796c1 100644 --- a/src/routes/components/dataToolbar/dataKebab.tsx +++ b/src/routes/components/dataToolbar/dataKebab.tsx @@ -1,5 +1,5 @@ import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import { Dropdown, DropdownItem, DropdownPosition, KebabToggle } from '@patternfly/react-core'; +import { Dropdown, DropdownItem, DropdownPosition, KebabToggle } from '@patternfly/react-core/deprecated'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; import { injectIntl } from 'react-intl'; diff --git a/src/routes/components/dataToolbar/dataToolbar.scss b/src/routes/components/dataToolbar/dataToolbar.scss index 63c426fdb..7502b8ca3 100644 --- a/src/routes/components/dataToolbar/dataToolbar.scss +++ b/src/routes/components/dataToolbar/dataToolbar.scss @@ -3,15 +3,15 @@ // Workaround for https://github.com/patternfly/patternfly-react/issues/4477 // and https://github.com/patternfly/patternfly-react/issues/6371 .selectOverride { - &.pf-c-select { + &.pf-v5-c-select { min-width: 250px; } } .toolbarOverride { - .pf-c-button.pf-m-control::after { + .pf-v5-c-button.pf-m-control::after { border-left: none; } // Alternative workaround to overriding table sticky style - // --pf-c-toolbar--ZIndex: auto; z-index: 301; + // --pf-v5-c-toolbar--ZIndex: auto; z-index: 301; } diff --git a/src/routes/components/dataToolbar/dataToolbar.tsx b/src/routes/components/dataToolbar/dataToolbar.tsx index cc5340281..df712830d 100644 --- a/src/routes/components/dataToolbar/dataToolbar.tsx +++ b/src/routes/components/dataToolbar/dataToolbar.tsx @@ -5,8 +5,7 @@ import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import type { Org } from 'api/orgs/org'; import type { Query } from 'api/queries/query'; import type { Resource, ResourcePathsType } from 'api/resources/resource'; -import type { Tag } from 'api/tags/tag'; -import type { TagPathsType } from 'api/tags/tag'; +import type { Tag, TagPathsType } from 'api/tags/tag'; import { cloneDeep } from 'lodash'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -234,7 +233,7 @@ export class DataToolbarBase extends React.Component { + private handleOnCategorySelect = (selection: CategoryOption) => { this.setState({ categoryInput: '', currentCategory: selection.value, @@ -278,7 +277,7 @@ export class DataToolbarBase extends React.Component { + private handleOnCategoryInput = (event: React.FormEvent, key) => { const { onFilterAdded } = this.props; const { categoryInput, currentCategory, currentExclude, filters: currentFilters } = this.state; @@ -354,7 +353,7 @@ export class DataToolbarBase extends React.Component { + private handleOnCostCategoryKeySelect = selection => { this.setState({ currentCostCategoryKey: selection, isCostCategoryKeySelectExpanded: !this.state.isCostCategoryKeySelectExpanded, @@ -514,7 +513,7 @@ export class DataToolbarBase extends React.Component { + private handleOnExcludeSelect = (selection: ExcludeOption) => { this.setState({ currentExclude: selection.value, isExcludeSelectOpen: !this.state.isExcludeSelectOpen, @@ -605,7 +604,7 @@ export class DataToolbarBase extends React.Component { + private handleOnTagKeySelect = selection => { this.setState({ currentTagKey: selection, isTagKeySelectExpanded: !this.state.isTagKeySelectExpanded, @@ -836,7 +835,7 @@ export class DataToolbarBase extends React.Component )} - + {pagination} diff --git a/src/routes/components/dataToolbar/tagValue.tsx b/src/routes/components/dataToolbar/tagValue.tsx index 484c6a179..ba3e91f14 100644 --- a/src/routes/components/dataToolbar/tagValue.tsx +++ b/src/routes/components/dataToolbar/tagValue.tsx @@ -1,13 +1,7 @@ -import type { SelectOptionObject, ToolbarChipGroup } from '@patternfly/react-core'; -import { - Button, - ButtonVariant, - InputGroup, - Select, - SelectOption, - SelectVariant, - TextInput, -} from '@patternfly/react-core'; +import type { ToolbarChipGroup } from '@patternfly/react-core'; +import { Button, ButtonVariant, InputGroup, InputGroupItem, TextInput } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; import type { Query } from 'api/queries/query'; import { getQuery, parseQuery } from 'api/queries/query'; @@ -140,8 +134,8 @@ class TagValueBase extends React.Component { isDisabled={isDisabled} variant={SelectVariant.checkbox} aria-label={intl.formatMessage(messages.filterByTagValueAriaLabel)} - onToggle={this.onTagValueToggle} onSelect={onTagValueSelect} + onToggle={(_evt, isExpanded) => this.onTagValueToggle(isExpanded)} selections={selections} isOpen={isTagValueExpanded} placeholderText={intl.formatMessage(messages.chooseValuePlaceholder)} @@ -152,25 +146,29 @@ class TagValueBase extends React.Component { } return ( - onTagValueInput(evt)} - /> - + + this.onTagValueChange(value)} + value={tagKeyValue} + placeholder={intl.formatMessage(messages.filterByValuePlaceholder)} + onKeyDown={evt => onTagValueInput(evt)} + /> + + + + ); } diff --git a/src/routes/components/dataToolbar/utils/actions.tsx b/src/routes/components/dataToolbar/utils/actions.tsx index dae5af2d5..40997a9c9 100644 --- a/src/routes/components/dataToolbar/utils/actions.tsx +++ b/src/routes/components/dataToolbar/utils/actions.tsx @@ -70,7 +70,7 @@ export const getPlatformCosts = ({ label={intl.formatMessage(messages.sumPlatformCosts)} isChecked={isPlatformCostsChecked} isDisabled={isDisabled} - onChange={onPlatformCostsChanged} + onChange={(_evt, checked) => onPlatformCostsChanged(checked)} /> ); diff --git a/src/routes/components/dataToolbar/utils/bulkSelect.tsx b/src/routes/components/dataToolbar/utils/bulkSelect.tsx index ba77de447..e49c4e527 100644 --- a/src/routes/components/dataToolbar/utils/bulkSelect.tsx +++ b/src/routes/components/dataToolbar/utils/bulkSelect.tsx @@ -1,11 +1,11 @@ +import { Tooltip } from '@patternfly/react-core'; import { Dropdown, DropdownItem, DropdownPosition, DropdownToggle, DropdownToggleCheckbox, - Tooltip, -} from '@patternfly/react-core'; +} from '@patternfly/react-core/deprecated'; import { intl } from 'components/i18n'; import messages from 'locales/messages'; import React from 'react'; @@ -82,7 +82,7 @@ export const getBulkSelect = ({ }} />, ]} - onToggle={onBulkSelectToggle} + onToggle={(_evt, isOpen) => onBulkSelectToggle(isOpen)} > {numSelected !== 0 && ( {intl.formatMessage(messages.selected, { value: numSelected })} diff --git a/src/routes/components/dataToolbar/utils/category.tsx b/src/routes/components/dataToolbar/utils/category.tsx index b72ed6ce7..3e06ab593 100644 --- a/src/routes/components/dataToolbar/utils/category.tsx +++ b/src/routes/components/dataToolbar/utils/category.tsx @@ -1,19 +1,18 @@ -import type { SelectOptionObject, ToolbarChipGroup } from '@patternfly/react-core'; +import type { ToolbarChipGroup } from '@patternfly/react-core'; import { Button, ButtonVariant, InputGroup, - Select, - SelectOption, - SelectVariant, + InputGroupItem, TextInput, ToolbarFilter, ToolbarItem, } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; -import type { ResourceType } from 'api/resources/resource'; -import type { ResourcePathsType } from 'api/resources/resource'; +import type { ResourcePathsType, ResourceType } from 'api/resources/resource'; import { isResourceTypeValid } from 'api/resources/resourceUtils'; import { intl } from 'components/i18n'; import messages from 'locales/messages'; @@ -70,39 +69,41 @@ export const getCategoryInput = ({ showToolbarItem={currentCategory === categoryOption.key} > - {isResourceTypeValid(resourcePathsType, categoryOption.key as ResourceType) ? ( - onCategoryInputSelect(value, categoryOption.key)} - placeholder={intl.formatMessage(messages.filterByPlaceholder, { value: placeholderKey })} - resourcePathsType={resourcePathsType} - resourceType={categoryOption.key as ResourceType} - /> - ) : ( - <> - + {isResourceTypeValid(resourcePathsType, categoryOption.key as ResourceType) ? ( + onCategoryInputSelect(value, categoryOption.key)} placeholder={intl.formatMessage(messages.filterByPlaceholder, { value: placeholderKey })} - onKeyDown={evt => onCategoryInput(evt, categoryOption.key)} - size={intl.formatMessage(messages.filterByPlaceholder, { value: placeholderKey }).length} + resourcePathsType={resourcePathsType} + resourceType={categoryOption.key as ResourceType} /> - - - )} + ) : ( + <> + onCategoryInputChange(value)} + value={categoryInput} + placeholder={intl.formatMessage(messages.filterByPlaceholder, { value: placeholderKey })} + onKeyDown={evt => onCategoryInput(evt, categoryOption.key)} + size={intl.formatMessage(messages.filterByPlaceholder, { value: placeholderKey }).length} + /> + + + )} + ); @@ -205,7 +206,7 @@ export const getCategorySelect = ({ currentCategory?: string; filters?: Filters; isDisabled?: boolean; - onCategorySelect?: (event, selection: CategoryOption) => void; + onCategorySelect?: (selection: CategoryOption) => void; onCategoryToggle?: (isOpen: boolean) => void; isCategorySelectOpen?: boolean; }) => { @@ -222,8 +223,8 @@ export const getCategorySelect = ({ id="category-select" isDisabled={isDisabled && !hasFilters(filters)} isOpen={isCategorySelectOpen} - onSelect={onCategorySelect} - onToggle={onCategoryToggle} + onSelect={(_evt, value) => onCategorySelect(value)} + onToggle={(_evt, isExpanded) => onCategoryToggle(isExpanded)} selections={selection} toggleIcon={} variant={SelectVariant.single} diff --git a/src/routes/components/dataToolbar/utils/costCategory.tsx b/src/routes/components/dataToolbar/utils/costCategory.tsx index 7bf27aea3..e48e405b4 100644 --- a/src/routes/components/dataToolbar/utils/costCategory.tsx +++ b/src/routes/components/dataToolbar/utils/costCategory.tsx @@ -1,7 +1,8 @@ import type { ToolbarChipGroup } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, ToolbarFilter, ToolbarItem } from '@patternfly/react-core'; -import type { Resource } from 'api/resources/resource'; -import type { ResourcePathsType } from 'api/resources/resource'; +import { ToolbarFilter, ToolbarItem } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; +import type { Resource, ResourcePathsType } from 'api/resources/resource'; import { intl } from 'components/i18n'; import messages from 'locales/messages'; import { cloneDeep, uniq, uniqBy } from 'lodash'; @@ -32,7 +33,7 @@ export const getCostCategoryKeySelect = ({ isCostCategoryKeySelectExpanded?: boolean; isDisabled?: boolean; onCostCategoryKeyClear?: () => void; - onCostCategoryKeySelect?: (event, selection) => void; + onCostCategoryKeySelect?: (selection: SelectOptionObject) => void; onCostCategoryKeyToggle?: (isOpen: boolean) => void; resourceReport?: Resource; }) => { @@ -50,10 +51,10 @@ export const getCostCategoryKeySelect = ({ isDisabled={isDisabled && !hasFilters(filters)} variant={SelectVariant.typeahead} typeAheadAriaLabel={intl.formatMessage(messages.filterByCostCategoryKeyAriaLabel)} - onClear={onCostCategoryKeyClear} - onToggle={onCostCategoryKeyToggle} - onSelect={onCostCategoryKeySelect} isOpen={isCostCategoryKeySelectExpanded} + onClear={onCostCategoryKeyClear} + onSelect={(_evt, value) => onCostCategoryKeySelect(value)} + onToggle={(_evt, isExpanded) => onCostCategoryKeyToggle(isExpanded)} placeholderText={intl.formatMessage(messages.chooseKeyPlaceholder)} selections={currentCostCategoryKey} > diff --git a/src/routes/components/dataToolbar/utils/exclude.tsx b/src/routes/components/dataToolbar/utils/exclude.tsx index 2af24681b..f093e975e 100644 --- a/src/routes/components/dataToolbar/utils/exclude.tsx +++ b/src/routes/components/dataToolbar/utils/exclude.tsx @@ -1,5 +1,6 @@ -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, ToolbarItem } from '@patternfly/react-core'; +import { ToolbarItem } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import { intl } from 'components/i18n'; import messages from 'locales/messages'; import React from 'react'; @@ -32,7 +33,7 @@ export const getExcludeSelect = ({ filters?: Filters; isDisabled?: boolean; isExcludeSelectOpen?: boolean; - onExcludeSelect: (event: any, selection: ExcludeOption) => void; + onExcludeSelect: (selection: ExcludeOption) => void; onExcludeToggle: (isOpen: boolean) => void; }) => { const selectOptions = getExcludeSelectOptions(); @@ -44,8 +45,8 @@ export const getExcludeSelect = ({ id="exclude-select" isDisabled={isDisabled && !hasFilters(filters)} isOpen={isExcludeSelectOpen} - onSelect={onExcludeSelect} - onToggle={onExcludeToggle} + onSelect={(_evt, value) => onExcludeSelect(value)} + onToggle={(_evt, isExpanded) => onExcludeToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > diff --git a/src/routes/components/dataToolbar/utils/orgUntits.tsx b/src/routes/components/dataToolbar/utils/orgUntits.tsx index 04f412393..d678f2de9 100644 --- a/src/routes/components/dataToolbar/utils/orgUntits.tsx +++ b/src/routes/components/dataToolbar/utils/orgUntits.tsx @@ -1,5 +1,7 @@ -import type { SelectOptionObject, ToolbarChipGroup } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, ToolbarFilter } from '@patternfly/react-core'; +import type { ToolbarChipGroup } from '@patternfly/react-core'; +import { ToolbarFilter } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import type { Org } from 'api/orgs/org'; import { intl } from 'components/i18n'; import messages from 'locales/messages'; @@ -32,7 +34,7 @@ export const getOrgUnitSelect = ({ isDisabled?: boolean; isOrgUnitSelectExpanded?: boolean; onDelete?: (type: any, chip: any) => void; - onOrgUnitSelect?: (event, selection: string) => void; + onOrgUnitSelect?: (event: React.MouseEvent, selection: string) => void; onOrgUnitToggle?: (isOpen: boolean) => void; orgReport: Org; }) => { @@ -78,8 +80,8 @@ export const getOrgUnitSelect = ({ className="selectOverride" variant={SelectVariant.checkbox} aria-label={intl.formatMessage(messages.filterByOrgUnitAriaLabel)} - onToggle={onOrgUnitToggle} onSelect={onOrgUnitSelect} + onToggle={(_evt, isExpanded) => onOrgUnitToggle(isExpanded)} selections={selections} isOpen={isOrgUnitSelectExpanded} placeholderText={intl.formatMessage(messages.filterByOrgUnitPlaceholder)} diff --git a/src/routes/components/dataToolbar/utils/tags.tsx b/src/routes/components/dataToolbar/utils/tags.tsx index a49a1f070..b49e58632 100644 --- a/src/routes/components/dataToolbar/utils/tags.tsx +++ b/src/routes/components/dataToolbar/utils/tags.tsx @@ -1,7 +1,8 @@ import type { ToolbarChipGroup } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, ToolbarFilter, ToolbarItem } from '@patternfly/react-core'; -import type { Tag } from 'api/tags/tag'; -import type { TagPathsType } from 'api/tags/tag'; +import { ToolbarFilter, ToolbarItem } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; +import type { Tag, TagPathsType } from 'api/tags/tag'; import { intl } from 'components/i18n'; import messages from 'locales/messages'; import { cloneDeep, uniq, uniqBy } from 'lodash'; @@ -30,7 +31,7 @@ export const getTagKeySelect = ({ isDisabled?: boolean; isTagKeySelectExpanded?: boolean; onTagKeyClear?: () => void; - onTagKeySelect?: (event, selection) => void; + onTagKeySelect?: (value: SelectOptionObject) => void; onTagKeyToggle?: (isOpen: boolean) => void; tagReport?: Tag; }) => { @@ -49,8 +50,8 @@ export const getTagKeySelect = ({ variant={SelectVariant.typeahead} typeAheadAriaLabel={intl.formatMessage(messages.filterByTagKeyAriaLabel)} onClear={onTagKeyClear} - onToggle={onTagKeyToggle} - onSelect={onTagKeySelect} + onSelect={(_evt, value) => onTagKeySelect(value)} + onToggle={(_evt, isExpanded) => onTagKeyToggle(isExpanded)} isOpen={isTagKeySelectExpanded} placeholderText={intl.formatMessage(messages.chooseKeyPlaceholder)} selections={currentTagKey} diff --git a/src/routes/components/export/exportModal.tsx b/src/routes/components/export/exportModal.tsx index 96c644e81..ad3afd5e1 100644 --- a/src/routes/components/export/exportModal.tsx +++ b/src/routes/components/export/exportModal.tsx @@ -7,6 +7,8 @@ import { FormGroup, Grid, GridItem, + HelperText, + HelperTextItem, Modal, Radio, TextInput, @@ -108,19 +110,19 @@ export class ExportModalBase extends React.Component { + private handleOnMonthChange = event => { this.setState({ timeScope: event.currentTarget.value }); }; - private handleOnNameChange = (_, event) => { + private handleOnNameChange = event => { this.setState({ name: event.currentTarget.value }); }; - private handleOnResolutionChange = (_, event) => { + private handleOnResolutionChange = event => { this.setState({ resolution: event.currentTarget.value }); }; - private handleOnTypeChange = (_, event) => { + private handleOnTypeChange = event => { this.setState({ formatType: event.currentTarget.value }); }; @@ -227,13 +229,7 @@ export class ExportModalBase extends React.Component {isExportsFeatureEnabled && ( - + + {validated === 'error' && ( + + {intl.formatMessage(helpText)} + + )} )} diff --git a/src/routes/components/groupBy/groupBy.tsx b/src/routes/components/groupBy/groupBy.tsx index edc8311fb..79277857d 100644 --- a/src/routes/components/groupBy/groupBy.tsx +++ b/src/routes/components/groupBy/groupBy.tsx @@ -1,5 +1,6 @@ -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, Title } from '@patternfly/react-core'; +import { Title } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import type { Org, OrgPathsType } from 'api/orgs/org'; import { OrgType } from 'api/orgs/org'; import type { Query } from 'api/queries/query'; @@ -196,8 +197,8 @@ class GroupByBase extends React.Component { id="groupBySelect" isDisabled={isDisabled} isOpen={isGroupByOpen} - onSelect={this.handleOnSelect} - onToggle={this.handleOnToggle} + onSelect={(_evt, value) => this.handleOnSelect(value)} + onToggle={(_evt, isExpanded) => this.handleOnToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > @@ -237,7 +238,7 @@ class GroupByBase extends React.Component { }); }; - private handleOnSelect = (event, selection: GroupByOption) => { + private handleOnSelect = (selection: GroupByOption) => { const { onSelected } = this.props; if (selection.value === orgUnitIdKey || selection.value === awsCategoryKey || selection.value === tagKey) { diff --git a/src/routes/components/groupBy/groupByOrg.tsx b/src/routes/components/groupBy/groupByOrg.tsx index 65bc37431..4511f3b71 100644 --- a/src/routes/components/groupBy/groupByOrg.tsx +++ b/src/routes/components/groupBy/groupByOrg.tsx @@ -1,5 +1,5 @@ -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import type { Org } from 'api/orgs/org'; import type { Query } from 'api/queries/query'; import { parseQuery } from 'api/queries/query'; @@ -120,7 +120,7 @@ class GroupByOrgBase extends React.Component { }); }; - private handleOnSelect = (event, selection: GroupByOrgOption) => { + private handleOnSelect = (selection: GroupByOrgOption) => { const { onSelected } = this.props; this.setState({ @@ -149,8 +149,8 @@ class GroupByOrgBase extends React.Component { aria-label={intl.formatMessage(messages.filterByOrgUnitAriaLabel)} isDisabled={isDisabled} onClear={this.handleOnClear} - onToggle={this.handleOnToggle} - onSelect={this.handleOnSelect} + onSelect={(_evt, value) => this.handleOnSelect(value)} + onToggle={(_evt, isExpanded) => this.handleOnToggle(isExpanded)} isOpen={isGroupByOpen} placeholderText={intl.formatMessage(messages.filterByOrgUnitPlaceholder)} selections={selection} diff --git a/src/routes/components/groupBy/groupBySelect.tsx b/src/routes/components/groupBy/groupBySelect.tsx index 8e3d5657b..50e4720db 100644 --- a/src/routes/components/groupBy/groupBySelect.tsx +++ b/src/routes/components/groupBy/groupBySelect.tsx @@ -1,4 +1,4 @@ -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import type { Query } from 'api/queries/query'; import { parseQuery } from 'api/queries/query'; import type { Resource } from 'api/resources/resource'; @@ -120,16 +120,16 @@ class GroupBySelectBase extends React.Component { + private handleOnSelected = value => { const { onSelected } = this.props; const { prefix } = this.state; this.setState({ - currentItem: selection, + currentItem: value, isGroupByOpen: false, }); if (onSelected) { - onSelected(`${prefix}${selection}`); + onSelected(`${prefix}${value}`); } }; @@ -149,8 +149,8 @@ class GroupBySelectBase extends React.Component this.handleOnSelected(value)} + onToggle={(_evt, isExpanded) => this.handleOnToggle(isExpanded)} isOpen={isGroupByOpen} placeholderText={intl.formatMessage(messages.chooseKeyPlaceholder)} selections={currentItem} diff --git a/src/routes/components/page/noData/noDataState.tsx b/src/routes/components/page/noData/noDataState.tsx index 10560d625..6912fff87 100644 --- a/src/routes/components/page/noData/noDataState.tsx +++ b/src/routes/components/page/noData/noDataState.tsx @@ -2,10 +2,10 @@ import { Button, EmptyState, EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, - Title, - TitleSizes, } from '@patternfly/react-core'; import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; import messages from 'locales/messages'; @@ -24,17 +24,20 @@ class NoDataStateBase extends React.Component { const { intl, showReload = true } = this.props; return ( - - - - {intl.formatMessage(messages.noDataStateTitle)} - + + {intl.formatMessage(messages.noDataStateTitle)}} + icon={} + headingLevel="h5" + /> {intl.formatMessage(messages.noDataStateDesc)} - {showReload && ( - - )} + + {showReload && ( + + )} + ); } diff --git a/src/routes/components/page/noOptimizations/noOptimizationsState.tsx b/src/routes/components/page/noOptimizations/noOptimizationsState.tsx index 2cf0d3df8..771c8275f 100644 --- a/src/routes/components/page/noOptimizations/noOptimizationsState.tsx +++ b/src/routes/components/page/noOptimizations/noOptimizationsState.tsx @@ -1,10 +1,9 @@ import { EmptyState, EmptyStateBody, + EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, - Title, - TitleSizes, } from '@patternfly/react-core'; import messages from 'locales/messages'; import React from 'react'; @@ -23,11 +22,12 @@ class NoOptimizationsStateBase extends React.Component - - - {intl.formatMessage(messages.noOptimizationsTitle)} - + + {intl.formatMessage(messages.noOptimizationsTitle)}} + icon={} + headingLevel="h1" + /> {intl.formatMessage(messages.noOptimizationsDesc)} ); diff --git a/src/routes/components/page/noProviders/noProvidersState.tsx b/src/routes/components/page/noProviders/noProvidersState.tsx index 31faf92eb..175364ee1 100644 --- a/src/routes/components/page/noProviders/noProvidersState.tsx +++ b/src/routes/components/page/noProviders/noProvidersState.tsx @@ -3,10 +3,10 @@ import { Button, EmptyState, EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, - Title, - TitleSizes, } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; @@ -82,19 +82,22 @@ class NoProvidersStateBase extends React.Component { icon = CostIcon; } return ( - - - - {intl.formatMessage(titleKey)} - + + {intl.formatMessage(titleKey)}} + icon={} + headingLevel="h1" + /> {intl.formatMessage(descKey)} - {docUrlKey && textKey ? ( -
{this.getDocLink(textKey, docUrlKey)}
- ) : ( - - )} + + {docUrlKey && textKey ? ( +
{this.getDocLink(textKey, docUrlKey)}
+ ) : ( + + )} +
); } diff --git a/src/routes/components/perspective/perspectiveSelect.tsx b/src/routes/components/perspective/perspectiveSelect.tsx index a036b356a..cb7c069e6 100644 --- a/src/routes/components/perspective/perspectiveSelect.tsx +++ b/src/routes/components/perspective/perspectiveSelect.tsx @@ -1,6 +1,7 @@ import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant, Title } from '@patternfly/react-core'; +import { Title } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import messages from 'locales/messages'; import React from 'react'; import type { WrappedComponentProps } from 'react-intl'; @@ -73,8 +74,8 @@ class PerspectiveSelectBase extends React.Component this.handleSelect(value)} + onToggle={(_evt, isExpanded) => this.handleToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > @@ -85,7 +86,7 @@ class PerspectiveSelectBase extends React.Component { + private handleSelect = (selection: PerspectiveOption) => { const { onSelected } = this.props; if (onSelected) { diff --git a/src/routes/components/reports/reportSummary/__snapshots__/reportSummaryItems.test.tsx.snap b/src/routes/components/reports/reportSummary/__snapshots__/reportSummaryItems.test.tsx.snap index ff9e4a88d..71e2bd5e4 100644 --- a/src/routes/components/reports/reportSummary/__snapshots__/reportSummaryItems.test.tsx.snap +++ b/src/routes/components/reports/reportSummary/__snapshots__/reportSummaryItems.test.tsx.snap @@ -3,35 +3,35 @@ exports[`contains skeleton readers if in progress 1`] = `
diff --git a/src/routes/components/reports/reportSummary/reportSummary.scss b/src/routes/components/reports/reportSummary/reportSummary.scss index 52822992f..e55656282 100644 --- a/src/routes/components/reports/reportSummary/reportSummary.scss +++ b/src/routes/components/reports/reportSummary/reportSummary.scss @@ -2,12 +2,12 @@ .chartSkeleton { height: 125px; - margin-bottom: var(--pf-global--spacer--md); - margin-top: var(--pf-global--spacer--md); + margin-bottom: var(--pf-v5-global--spacer--md); + margin-top: var(--pf-v5-global--spacer--md); } .legendSkeleton { - margin-top: var(--pf-global--spacer--md), + margin-top: var(--pf-v5-global--spacer--md), } .reportSummary { @@ -16,7 +16,7 @@ .subtitle { display: inline-block; - font-size: var(--pf-global--FontSize--xs); - color: var(--pf-global--Color--200); + font-size: var(--pf-v5-global--FontSize--xs); + color: var(--pf-v5-global--Color--200); margin-bottom: 0; } diff --git a/src/routes/components/reports/reportSummary/reportSummaryAlt.scss b/src/routes/components/reports/reportSummary/reportSummaryAlt.scss index 6d6ca62d8..c73fe7209 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryAlt.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryAlt.scss @@ -2,18 +2,18 @@ .chartSkeleton { height: 175px; - margin-bottom: var(--pf-global--spacer--md); - margin-top: var(--pf-global--spacer--md); + margin-bottom: var(--pf-v5-global--spacer--md); + margin-top: var(--pf-v5-global--spacer--md); } .cost { flex-grow: 1; min-height: 470px; - margin-tight: var(--pf-global--spacer--md); + margin-tight: var(--pf-v5-global--spacer--md); } .legendSkeleton { - margin-top: var(--pf-global--spacer--md), + margin-top: var(--pf-v5-global--spacer--md), } .reportSummary { @@ -22,11 +22,11 @@ .subtitle { display: inline-block; - font-size: var(--pf-global--FontSize--xs); - color: var(--pf-global--Color--200); + font-size: var(--pf-v5-global--FontSize--xs); + color: var(--pf-v5-global--Color--200); margin-bottom: 0; } .tops { - margin-top: var(--pf-global--spacer--lg), + margin-top: var(--pf-v5-global--spacer--lg), } diff --git a/src/routes/components/reports/reportSummary/reportSummaryCost.scss b/src/routes/components/reports/reportSummary/reportSummaryCost.scss index c9f1e03a9..22b63d796 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryCost.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryCost.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .chart { - margin-bottom: var(--pf-global--spacer--sm); + margin-bottom: var(--pf-v5-global--spacer--sm); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryDailyCost.scss b/src/routes/components/reports/reportSummary/reportSummaryDailyCost.scss index c9f1e03a9..22b63d796 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryDailyCost.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryDailyCost.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .chart { - margin-bottom: var(--pf-global--spacer--sm); + margin-bottom: var(--pf-v5-global--spacer--sm); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryDailyTrend.scss b/src/routes/components/reports/reportSummary/reportSummaryDailyTrend.scss index 0a23f9d30..47363e31f 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryDailyTrend.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryDailyTrend.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .chart { - margin-nottom: var(--pf-global--spacer--sm); + margin-nottom: var(--pf-v5-global--spacer--sm); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryDetails.scss b/src/routes/components/reports/reportSummary/reportSummaryDetails.scss index f7bac986d..c569b949f 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryDetails.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryDetails.scss @@ -1,40 +1,40 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .reportSummaryDetails { - margin-bottom: var(--pf-global--spacer--md); + margin-bottom: var(--pf-v5-global--spacer--md); display: flex; align-Items: flex-end; } .text { - padding-bottom: var(--pf-global--spacer--sm); - line-height: var(--pf-global--LineHeight--sm); - font-size: var(--pf-global--FontSize--xs); + padding-bottom: var(--pf-v5-global--spacer--sm); + line-height: var(--pf-v5-global--LineHeight--sm); + font-size: var(--pf-v5-global--FontSize--xs); } .units { - padding-left: var(--pf-global--spacer--xs); - padding-bottom: var(--pf-global--spacer--sm); - line-height: var(--pf-global--LineHeight--sm); - font-size: var(--pf-global--FontSize--xs); + padding-left: var(--pf-v5-global--spacer--xs); + padding-bottom: var(--pf-v5-global--spacer--sm); + line-height: var(--pf-v5-global--LineHeight--sm); + font-size: var(--pf-v5-global--FontSize--xs); white-space: nowrap; } .value { - color: var(--pf-global--Color--100); - margin-right: var(--pf-global--spacer--sm); - font-size: var(--pf-global--FontSize--2xl); + color: var(--pf-v5-global--Color--100); + margin-right: var(--pf-v5-global--spacer--sm); + font-size: var(--pf-v5-global--FontSize--2xl); } .valueAlt { - color: var(--pf-global--Color--100); - margin-right: var(--pf-global--spacer--sm); - font-size: var(--pf-global--FontSize--4xl); + color: var(--pf-v5-global--Color--100); + margin-right: var(--pf-v5-global--spacer--sm); + font-size: var(--pf-v5-global--FontSize--4xl); } .valueContainer { display: inline-block; - margin-bottom: var(--pf-global--spacer--md); + margin-bottom: var(--pf-v5-global--spacer--md); width: 50%; word-Wrap: break-word; } diff --git a/src/routes/components/reports/reportSummary/reportSummaryItem.scss b/src/routes/components/reports/reportSummary/reportSummaryItem.scss index 418c0e6c9..7bcc047b7 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryItem.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryItem.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .reportSummaryItem:not(:last-child) { - margin-bottom: var(--pf-global--spacer--md); + margin-bottom: var(--pf-v5-global--spacer--md); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryItems.scss b/src/routes/components/reports/reportSummary/reportSummaryItems.scss index 63a966fc8..b8f585f23 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryItems.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryItems.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .skeleton { - margin-top: var(--pf-global--spacer--md); + margin-top: var(--pf-v5-global--spacer--md); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryTrend.scss b/src/routes/components/reports/reportSummary/reportSummaryTrend.scss index 0a23f9d30..47363e31f 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryTrend.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryTrend.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .chart { - margin-nottom: var(--pf-global--spacer--sm); + margin-nottom: var(--pf-v5-global--spacer--sm); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryUsage.scss b/src/routes/components/reports/reportSummary/reportSummaryUsage.scss index c9f1e03a9..22b63d796 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryUsage.scss +++ b/src/routes/components/reports/reportSummary/reportSummaryUsage.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .chart { - margin-bottom: var(--pf-global--spacer--sm); + margin-bottom: var(--pf-v5-global--spacer--sm); } diff --git a/src/routes/components/resourceTypeahead/resourceInput.tsx b/src/routes/components/resourceTypeahead/resourceInput.tsx index 54db5163b..f6220ee97 100644 --- a/src/routes/components/resourceTypeahead/resourceInput.tsx +++ b/src/routes/components/resourceTypeahead/resourceInput.tsx @@ -124,17 +124,13 @@ class ResourceInputBase extends React.Component { - const { search } = this.props; - return (
- {search && search.length && ( - - - {this.getMenuItems()} - - - )} + + + {this.getMenuItems()} + +
); }; diff --git a/src/routes/components/state/emptyFilterState/emptyFilterState.tsx b/src/routes/components/state/emptyFilterState/emptyFilterState.tsx index 6da659eba..bd1e3dc70 100644 --- a/src/routes/components/state/emptyFilterState/emptyFilterState.tsx +++ b/src/routes/components/state/emptyFilterState/emptyFilterState.tsx @@ -1,6 +1,5 @@ import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import { EmptyState, EmptyStateBody, EmptyStateIcon, Title, TitleSizes } from '@patternfly/react-core'; -import { Bullseye } from '@patternfly/react-core'; +import { Bullseye, EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon } from '@patternfly/react-core'; import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; import type { Query } from 'api/queries/query'; import { parseQuery } from 'api/queries/query'; @@ -115,9 +114,7 @@ const EmptyFilterStateBase: React.FC = ({ > {getItem()} - - {intl.formatMessage(title)} - + {intl.formatMessage(title)}} headingLevel="h2" /> {intl.formatMessage(subTitle)}
diff --git a/src/routes/components/state/emptyValueState/emptyValueState.scss b/src/routes/components/state/emptyValueState/emptyValueState.scss index 11de799fa..ada261561 100644 --- a/src/routes/components/state/emptyValueState/emptyValueState.scss +++ b/src/routes/components/state/emptyValueState/emptyValueState.scss @@ -1,5 +1,5 @@ @import url("~@patternfly/patternfly/base/patternfly-variables.css"); .emptyValueContainer { - font-size: var(--pf-global--FontSize--sm); + font-size: var(--pf-v5-global--FontSize--sm); } diff --git a/src/routes/components/state/errorState/errorState.tsx b/src/routes/components/state/errorState/errorState.tsx index 579bb821e..9daa76d01 100644 --- a/src/routes/components/state/errorState/errorState.tsx +++ b/src/routes/components/state/errorState/errorState.tsx @@ -1,10 +1,9 @@ import { EmptyState, EmptyStateBody, + EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, - Title, - TitleSizes, } from '@patternfly/react-core'; import { ErrorCircleOIcon } from '@patternfly/react-icons/dist/esm/icons/error-circle-o-icon'; import { LockIcon } from '@patternfly/react-icons/dist/esm/icons/lock-icon'; @@ -30,11 +29,8 @@ const ErrorStateBase: React.FC = ({ error, icon = ErrorCircleOI } return ( - - - - {title} - + + {title}} icon={} headingLevel="h5" /> {subTitle} ); diff --git a/src/routes/components/state/loadingState/loadingState.tsx b/src/routes/components/state/loadingState/loadingState.tsx index 6e2eb0ca3..eed2a95d8 100644 --- a/src/routes/components/state/loadingState/loadingState.tsx +++ b/src/routes/components/state/loadingState/loadingState.tsx @@ -1,4 +1,4 @@ -import { EmptyState, EmptyStateBody, EmptyStateVariant, Spinner, Title, TitleSizes } from '@patternfly/react-core'; +import { EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateVariant, Spinner } from '@patternfly/react-core'; import { intl as defaultIntl } from 'components/i18n'; import messages from 'locales/messages'; import React from 'react'; @@ -19,11 +19,9 @@ const LoadingStateBase: React.FC = ({ heading = intl.formatMessage(messages.loadingStateTitle), }) => { return ( - + - - {heading} - + {heading}} headingLevel="h5" /> {body} ); diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index 52c452089..5b1789297 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -206,7 +206,7 @@ class AwsDetails extends React.Component { page={page} perPage={limit} titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { title: intl.formatMessage(messages.aws), placement: isBottom ? 'bottom' : 'top', }), diff --git a/src/routes/details/azureDetails/azureDetails.tsx b/src/routes/details/azureDetails/azureDetails.tsx index 261925b25..74ecc883a 100644 --- a/src/routes/details/azureDetails/azureDetails.tsx +++ b/src/routes/details/azureDetails/azureDetails.tsx @@ -193,7 +193,7 @@ class AzureDetails extends React.Component page={page} perPage={limit} titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { title: intl.formatMessage(messages.azure), placement: isBottom ? 'bottom' : 'top', }), diff --git a/src/routes/details/components/actions/actions.tsx b/src/routes/details/components/actions/actions.tsx index 154455304..a25d1914d 100644 --- a/src/routes/details/components/actions/actions.tsx +++ b/src/routes/details/components/actions/actions.tsx @@ -1,4 +1,4 @@ -import { Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core'; +import { Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core/deprecated'; import type { ProviderType } from 'api/providers'; import type { ReportPathsType } from 'api/reports/report'; import messages from 'locales/messages'; @@ -125,7 +125,7 @@ class DetailsActionsBase extends React.Component } + toggle={ this.handleOnToggle(isOpen)} />} isOpen={this.state.isDropdownOpen} isPlain position="right" diff --git a/src/routes/details/components/breakdown/breakdownHeader.scss b/src/routes/details/components/breakdown/breakdownHeader.scss index 2c20d57eb..44af56837 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.scss +++ b/src/routes/details/components/breakdown/breakdownHeader.scss @@ -1,11 +1,11 @@ .breadcrumbOverride { - .pf-c-breadcrumb__item:not(:last-child) { - margin-left: var(--pf-c-breadcrumb__item--MarginRight); + .pf-v5-c-breadcrumb__item:not(:last-child) { + margin-left: var(--pf-v5-c-breadcrumb__item--MarginRight); margin-right: 0; } - .pf-c-breadcrumb__item-divider { + .pf-v5-c-breadcrumb__item-divider { margin-left: 0; - margin-right: var(--pf-c-breadcrumb__item-divider--MarginLeft); + margin-right: var(--pf-v5-c-breadcrumb__item-divider--MarginLeft); } } diff --git a/src/routes/details/components/breakdown/breakdownHeader.tsx b/src/routes/details/components/breakdown/breakdownHeader.tsx index 582711de0..fbeae6ede 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.tsx +++ b/src/routes/details/components/breakdown/breakdownHeader.tsx @@ -168,9 +168,9 @@ class BreakdownHeader extends React.Component {
diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx index 478988a9e..8633aca55 100644 --- a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx +++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx @@ -91,7 +91,7 @@ const OptimizationsDetails: React.FC = () => { page={page} perPage={limit} titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { title: intl.formatMessage(messages.openShift), placement: isBottom ? 'bottom' : 'top', }), diff --git a/src/routes/overview/components/chartComparison.tsx b/src/routes/overview/components/chartComparison.tsx index 45055453d..79d0d2baf 100644 --- a/src/routes/overview/components/chartComparison.tsx +++ b/src/routes/overview/components/chartComparison.tsx @@ -1,5 +1,5 @@ -import type { SelectOptionObject } from '@patternfly/react-core'; -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; +import type { SelectOptionObject } from '@patternfly/react-core/deprecated'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import React from 'react'; interface ChartComparisonOwnProps { @@ -44,8 +44,8 @@ class ChartComparisonBase extends React.Component this.handleSelect(value)} + onToggle={(_evt, isExpanded) => this.handleToggle(isExpanded)} selections={selection} variant={SelectVariant.single} > @@ -71,7 +71,7 @@ class ChartComparisonBase extends React.Component { + private handleSelect = (selection: ComparisonOption) => { const { onItemClicked } = this.props; if (onItemClicked) { diff --git a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss b/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss index 8db4e37df..7a01575bb 100644 --- a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss +++ b/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss @@ -2,8 +2,8 @@ .skeleton { height: 125px; - margin-bottom: var(--pf-global--spacer--md); - margin-top: var(--pf-global--spacer--md); + margin-bottom: var(--pf-v5-global--spacer--md); + margin-top: var(--pf-v5-global--spacer--md); } .summary { diff --git a/src/routes/overview/overview.scss b/src/routes/overview/overview.scss index 8cf029c67..eb721c917 100644 --- a/src/routes/overview/overview.scss +++ b/src/routes/overview/overview.scss @@ -1,5 +1,5 @@ .headerOverride { - &.pf-c-page__main-section { - --pf-c-page__main-section--PaddingBottom: 0; + &.pf-v5-c-page__main-section { + --pf-v5-c-page__main-section--PaddingBottom: 0; } } diff --git a/src/routes/settings/costCategory/costCategory.tsx b/src/routes/settings/costCategory/costCategory.tsx index 71549b5ee..86b373c13 100644 --- a/src/routes/settings/costCategory/costCategory.tsx +++ b/src/routes/settings/costCategory/costCategory.tsx @@ -80,7 +80,7 @@ const CostCategory: React.FC = ({ canWrite }) => { page={page} perPage={limit} titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { title: intl.formatMessage(messages.openShift), placement: isBottom ? 'bottom' : 'top', }), diff --git a/src/routes/settings/costModels/components/__snapshots__/rateTable.test.tsx.snap b/src/routes/settings/costModels/components/__snapshots__/rateTable.test.tsx.snap index 0251cde18..12b6b07f3 100644 --- a/src/routes/settings/costModels/components/__snapshots__/rateTable.test.tsx.snap +++ b/src/routes/settings/costModels/components/__snapshots__/rateTable.test.tsx.snap @@ -3,22 +3,26 @@ exports[`rate-table sort by metric & measurement 1`] = ` [ {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"Node"} , {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"CPU"} , {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"CPU"} , {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"CPU"} , diff --git a/src/routes/settings/costModels/components/__snapshots__/warningIcon.test.tsx.snap b/src/routes/settings/costModels/components/__snapshots__/warningIcon.test.tsx.snap index d0379a688..10ecfed78 100644 --- a/src/routes/settings/costModels/components/__snapshots__/warningIcon.test.tsx.snap +++ b/src/routes/settings/costModels/components/__snapshots__/warningIcon.test.tsx.snap @@ -3,10 +3,11 @@ exports[`warning icon 1`] = `
+
); }; diff --git a/src/routes/settings/costModels/components/rateForm/rateForm.tsx b/src/routes/settings/costModels/components/rateForm/rateForm.tsx index e8a540dd8..564b630a0 100644 --- a/src/routes/settings/costModels/components/rateForm/rateForm.tsx +++ b/src/routes/settings/costModels/components/rateForm/rateForm.tsx @@ -103,7 +103,7 @@ const RateFormBase: React.FC = ({ currencyUnits, intl = defaultIn value={description} validated={errors.description ? 'error' : 'default'} helperTextInvalid={errors.description} - onChange={setDescription} + onChange={(_evt, value) => setDescription(value)} /> @@ -115,7 +115,7 @@ const RateFormBase: React.FC = ({ currencyUnits, intl = defaultIn label={intl.formatMessage(messages.metric)} placeholderText={intl.formatMessage(messages.select)} value={metric} - onChange={setMetric} + onChange={(_evt, value) => setMetric(value)} options={[ ...metricOptions.map(opt => { return { @@ -142,7 +142,7 @@ const RateFormBase: React.FC = ({ currencyUnits, intl = defaultIn ? measurement : getMeasurementLabel(measurement, metricsHash[metric][measurement].label_measurement_unit) } - onChange={setMeasurement} + onChange={(_evt, value) => setMeasurement(value)} placeholderText="Select..." options={[ ...measurementOptions.map(opt => { @@ -197,7 +197,7 @@ const RateFormBase: React.FC = ({ currencyUnits, intl = defaultIn currencyUnits={currencyUnits} fieldId="regular-rate" helperTextInvalid={errors.tieredRates} - onChange={setRegular} + onChange={(_evt, value) => setRegular(value)} style={style} validated={errors.tieredRates && regularDirty ? 'error' : 'default'} value={inputValue} @@ -208,7 +208,7 @@ const RateFormBase: React.FC = ({ currencyUnits, intl = defaultIn isRequired style={style} value={tagKey} - onChange={setTagKey} + onChange={(_evt, value) => setTagKey(value)} id="tag-key" label={messages.costModelsFilterTagKey} placeholder={intl.formatMessage(messages.costModelsEnterTagKey)} diff --git a/src/routes/settings/costModels/components/rateForm/taggingRatesForm.tsx b/src/routes/settings/costModels/components/rateForm/taggingRatesForm.tsx index c4cf61ce8..95d28cf9f 100644 --- a/src/routes/settings/costModels/components/rateForm/taggingRatesForm.tsx +++ b/src/routes/settings/costModels/components/rateForm/taggingRatesForm.tsx @@ -53,7 +53,7 @@ const TaggingRatesFormBase: React.FC = ({ label={messages.costModelsTagRateTableValue} placeholder={intl.formatMessage(messages.costModelsEnterTagValue)} value={tag.tagValue} - onChange={value => updateTag({ tagValue: value }, ix)} + onChange={(_evt, value) => updateTag({ tagValue: value }, ix)} validated={tagValues[ix].isTagValueDirty && errors.tagValueValues[ix] ? 'error' : 'default'} helperTextInvalid={errors.tagValueValues[ix]} /> @@ -63,7 +63,7 @@ const TaggingRatesFormBase: React.FC = ({ currencyUnits={currencyUnits} fieldId={`rate_${ix}`} helperTextInvalid={errors.tagValues[ix]} - onChange={value => updateTag({ value }, ix)} + onChange={(_evt, value) => updateTag({ value }, ix)} style={style} validated={tagValues[ix].isDirty && errors.tagValues[ix] ? 'error' : 'default'} value={tag.inputValue} @@ -77,7 +77,7 @@ const TaggingRatesFormBase: React.FC = ({ validated={errors.tagDescription[ix] ? 'error' : 'default'} placeholder={intl.formatMessage(messages.costModelsEnterTagDesc)} value={tag.description} - onChange={value => updateTag({ description: value }, ix)} + onChange={(_evt, value) => updateTag({ description: value }, ix)} helperTextInvalid={errors.tagDescription[ix]} /> diff --git a/src/routes/settings/costModels/components/rateTable.tsx b/src/routes/settings/costModels/components/rateTable.tsx index 2bb69ada2..da0cc9f57 100644 --- a/src/routes/settings/costModels/components/rateTable.tsx +++ b/src/routes/settings/costModels/components/rateTable.tsx @@ -2,7 +2,7 @@ import type { IActions, ThProps } from '@patternfly/react-table'; import { ActionsColumn, ExpandableRowContent, - TableComposable, + Table, TableVariant, Tbody, Td, @@ -88,7 +88,7 @@ const RateTableBase: React.FC = ({ direction: activeSortDirection, defaultDirection: 'asc', }, - onSort: (_event, index, direction) => { + onSort: (_evt, index, direction) => { setActiveSortIndex(index); setActiveSortDirection(direction); sortCallback({ index, direction }); @@ -121,10 +121,7 @@ const RateTableBase: React.FC = ({ }); return ( - + {columns.map((col: { title?: string; sortable?: boolean }, i) => ( @@ -169,7 +166,7 @@ const RateTableBase: React.FC = ({ @@ -196,7 +193,7 @@ const RateTableBase: React.FC = ({ ); })} - +
- + {tagColumns.map((tag, tagIndex) => ( @@ -188,7 +185,7 @@ const RateTableBase: React.FC = ({ ))} - +
); }; diff --git a/src/routes/settings/costModels/components/toolbar/checkboxSelector.tsx b/src/routes/settings/costModels/components/toolbar/checkboxSelector.tsx index 46b96144c..cf2069bf3 100644 --- a/src/routes/settings/costModels/components/toolbar/checkboxSelector.tsx +++ b/src/routes/settings/costModels/components/toolbar/checkboxSelector.tsx @@ -1,4 +1,4 @@ -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; +import { Select, SelectOption, SelectVariant } from '@patternfly/react-core/deprecated'; import React from 'react'; import { WithStateMachine } from 'routes/settings/costModels/components/hoc/withStateMachine'; import { selectMachineState } from 'routes/settings/costModels/components/logic/selectStateMachine'; diff --git a/src/routes/settings/costModels/components/toolbar/primarySelector.tsx b/src/routes/settings/costModels/components/toolbar/primarySelector.tsx index a5c30ae2c..d5645b822 100644 --- a/src/routes/settings/costModels/components/toolbar/primarySelector.tsx +++ b/src/routes/settings/costModels/components/toolbar/primarySelector.tsx @@ -1,4 +1,4 @@ -import { Select, SelectOption } from '@patternfly/react-core'; +import { Select, SelectOption } from '@patternfly/react-core/deprecated'; import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import React from 'react'; import { WithStateMachine } from 'routes/settings/costModels/components/hoc/withStateMachine'; diff --git a/src/routes/settings/costModels/costModel/__snapshots__/dialog.test.tsx.snap b/src/routes/settings/costModels/costModel/__snapshots__/dialog.test.tsx.snap index 8b19b8da9..2db1195f9 100644 --- a/src/routes/settings/costModels/costModel/__snapshots__/dialog.test.tsx.snap +++ b/src/routes/settings/costModels/costModel/__snapshots__/dialog.test.tsx.snap @@ -2,17 +2,18 @@ exports[`dialog title renders correctly with icon and title text 1`] = `

- ) : undefined, + tooltipProps: { + content: !isWritePermission ? ( +
{intl.formatMessage(messages.readOnlyPermissions)}
+ ) : undefined, + }, onClick: (_evt, _rowIndex, rowData) => { this.setState({ deleteRate: null, @@ -329,9 +331,11 @@ class PriceListTable extends React.Component{intl.formatMessage(messages.readOnlyPermissions)}

- ) : undefined, + tooltipProps: { + content: !isWritePermission ? ( +
{intl.formatMessage(messages.readOnlyPermissions)}
+ ) : undefined, + }, onClick: (_evt, _rowIndex, rowData) => { const rowIndex = rowData.data.stateIndex; this.setState({ @@ -372,7 +376,7 @@ class PriceListTable extends React.Component = ({ canWrite, costModels, intl, const rows: (IRow | string[])[] = costModels.length > 0 ? costModels[0].sources : []; return ( - = ({ canWrite, costModels, intl,