diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss
similarity index 100%
rename from src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss
rename to .archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.scss
diff --git a/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts
new file mode 100644
index 000000000..696df518c
--- /dev/null
+++ b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.styles.ts
@@ -0,0 +1,24 @@
+import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100';
+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 type React from 'react';
+
+export const styles = {
+ alertContainer: {
+ marginBottom: global_spacer_lg.value,
+ },
+ codeBlock: {
+ display: 'flex',
+ },
+ container: {
+ minHeight: '100vh',
+ },
+ currentActions: {
+ height: '36px',
+ },
+ pagination: {
+ backgroundColor: global_BackgroundColor_light_100.value,
+ paddingBottom: global_spacer_md.value,
+ paddingTop: global_spacer_md.value,
+ },
+} as { [className: string]: React.CSSProperties };
diff --git a/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx
new file mode 100644
index 000000000..732b67acd
--- /dev/null
+++ b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdown.tsx
@@ -0,0 +1,195 @@
+import './optimizationsBreakdown.scss';
+
+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';
+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 { 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 { Loading } from 'routes/components/page/loading';
+import type { RootState } from 'store';
+import { FetchStatus } from 'store/common';
+import { rosActions, rosSelectors } from 'store/ros';
+import { breadcrumbLabelKey } from 'utils/props';
+import { getNotifications, hasRecommendation } from 'utils/recomendations';
+import type { RouterComponentProps } from 'utils/router';
+
+import { styles } from './optimizationsBreakdown.styles';
+import { OptimizationsBreakdownConfiguration } from './optimizationsBreakdownConfiguration';
+import { OptimizationsBreakdownHeader } from './optimizationsBreakdownHeader';
+
+interface OptimizationsBreakdownOwnProps extends RouterComponentProps {
+ id?: string;
+}
+
+interface OptimizationsBreakdownStateProps {
+ breadcrumbLabel?: string;
+ report?: RecommendationReportData;
+ reportError?: AxiosError;
+ reportFetchStatus?: FetchStatus;
+ reportQueryString?: string;
+}
+
+export interface OptimizationsBreakdownMapProps {
+ query?: RosQuery;
+}
+
+type OptimizationsBreakdownProps = OptimizationsBreakdownOwnProps & OptimizationsBreakdownStateProps;
+
+// 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.recommendation as any;
+
+const OptimizationsBreakdown: React.FC = () => {
+ const { breadcrumbLabel, report, reportFetchStatus } = useMapToProps();
+ const intl = useIntl();
+ const location = useLocation();
+
+ const getDefaultTerm = () => {
+ let result = Interval.short_term;
+ if (!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 getAlert = () => {
+ let notifications;
+ if (report?.recommendations?.duration_based?.[currentInterval]) {
+ notifications = getNotifications(report.recommendations.duration_based[currentInterval]);
+ }
+
+ if (!notifications) {
+ return null;
+ }
+
+ return (
+
+
+
+ {notifications.map((notification, index) => (
+ {notification.message}
+ ))}
+
+
+
+ );
+ };
+
+ const getRecommendationTerm = (): RecommendationItem => {
+ if (!report) {
+ return undefined;
+ }
+
+ 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 handleOnSelected = (value: Interval) => {
+ setCurrentInterval(value);
+ };
+
+ const isLoading = reportFetchStatus === FetchStatus.inProgress;
+
+ return (
+
+
+
+ {isLoading ? (
+
+ ) : (
+ <>
+ {getAlert()}
+
+ >
+ )}
+
+
+ );
+};
+
+const useQueryFromRoute = () => {
+ const location = useLocation();
+ return parseQuery(location.search);
+};
+
+// eslint-disable-next-line no-empty-pattern
+const useMapToProps = (): OptimizationsBreakdownStateProps => {
+ const dispatch: ThunkDispatch = useDispatch();
+ const queryFromRoute = useQueryFromRoute();
+
+ const reportQueryString = queryFromRoute ? queryFromRoute.id : '';
+ 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 {
+ breadcrumbLabel: queryFromRoute[breadcrumbLabelKey],
+ report,
+ reportError,
+ reportFetchStatus,
+ reportQueryString,
+ };
+};
+
+export default OptimizationsBreakdown;
diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx
similarity index 100%
rename from src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx
rename to .archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownConfiguration.tsx
diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts
similarity index 100%
rename from src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts
rename to .archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.styles.ts
diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx
similarity index 100%
rename from src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx
rename to .archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownHeader.tsx
diff --git a/src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx b/.archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx
similarity index 100%
rename from src/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx
rename to .archive/routes/optimizations/optimizationsBreakdown/optimizationsBreakdownToolbar.tsx
diff --git a/.archive/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx b/.archive/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx
new file mode 100644
index 000000000..dc64b0761
--- /dev/null
+++ b/.archive/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx
@@ -0,0 +1,248 @@
+import { PageSection, Pagination, PaginationVariant } from '@patternfly/react-core';
+import type { Query } from 'api/queries/query';
+import { getQuery, 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';
+import type { AxiosError } from 'axios';
+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 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';
+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 { clearQueryState, getQueryState } from 'routes/utils/queryState';
+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';
+
+interface OptimizationsDetailsOwnProps {
+ // TBD...
+}
+
+export interface OptimizationsDetailsStateProps {
+ groupBy?: string;
+ report: RosReport;
+ reportError: AxiosError;
+ reportFetchStatus: FetchStatus;
+ reportQueryString: string;
+}
+
+export interface OptimizationsDetailsMapProps {
+ query?: RosQuery;
+}
+
+type OptimizationsDetailsProps = OptimizationsDetailsOwnProps;
+
+const baseQuery: RosQuery = {
+ limit: 10,
+ offset: 0,
+ order_by: {
+ last_reported: 'desc',
+ },
+};
+
+const reportType = RosType.ros as any;
+const reportPathsType = RosPathsType.recommendations as any;
+
+const OptimizationsDetails: React.FC = () => {
+ 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,
+ });
+
+ // Clear queryState, returned from breakdown page, after query has been initialized
+ useEffect(() => {
+ clearQueryState(location, 'optimizations');
+ }, [reportQueryString]);
+
+ const getPagination = (isDisabled = false, isBottom = false) => {
+ const count = report?.meta ? report.meta.count : 0;
+ const limit = report?.meta ? report.meta.limit : baseQuery.limit;
+ const offset = 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={{
+ paginationAriaLabel: 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}
+ query={query}
+ report={report}
+ reportQueryString={reportQueryString}
+ />
+ );
+ };
+
+ const getToolbar = () => {
+ const itemsPerPage = report?.meta ? report.meta.limit : 0;
+ const itemsTotal = 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);
+ };
+
+ const handleOnFilterRemoved = filter => {
+ const newQuery = queryUtils.handleOnFilterRemoved(query, filter);
+ setQuery(newQuery);
+ };
+
+ const handleOnPerPageSelect = perPage => {
+ const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true);
+ setQuery(newQuery);
+ };
+
+ const handleOnSetPage = pageNumber => {
+ const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true);
+ setQuery(newQuery);
+ };
+
+ const handleOnSort = (sortType, isSortAscending) => {
+ const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending);
+ setQuery(newQuery);
+ };
+
+ const itemsTotal = report?.meta ? report.meta.count : 0;
+ const isDisabled = itemsTotal === 0;
+ const title = intl.formatMessage(messages.optimizations);
+ const hasOptimizations = 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 }: OptimizationsDetailsMapProps): OptimizationsDetailsStateProps => {
+ 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 {
+ groupBy,
+ report,
+ reportError,
+ reportFetchStatus,
+ reportQueryString,
+ };
+};
+
+export default OptimizationsDetails;
diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts b/.archive/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts
similarity index 100%
rename from src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts
rename to .archive/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.styles.ts
diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx b/.archive/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx
similarity index 100%
rename from src/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx
rename to .archive/routes/optimizations/optimizationsDetails/optimizationsDetailsHeader.tsx
diff --git a/jest.config.js b/jest.config.js
index fb9d7e8d2..5ef0d27ae 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -23,5 +23,5 @@ module.exports = {
'^.+\\.[jt]sx?$': '/test/transformTS.js',
'^.+\\.(jpg)$': '/test/transformFile.js',
},
- transformIgnorePatterns: ['node_modules/(?!@patternfly/react-icons/dist/esm)'],
+ transformIgnorePatterns: ['node_modules/(?!(@patternfly/react-icons/dist/esm|uuid/dist/esm-browser))'],
};
diff --git a/package-lock.json b/package-lock.json
index b24e5cb84..f2910f021 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,7 +21,7 @@
"@redhat-cloud-services/frontend-components-translations": "^3.2.7",
"@redhat-cloud-services/frontend-components-utilities": "^4.0.2",
"@redhat-cloud-services/rbac-client": "^1.2.12",
- "@unleash/proxy-client-react": "^4.1.0",
+ "@unleash/proxy-client-react": "^4.1.1",
"axios": "^1.6.2",
"date-fns": "^2.30.0",
"js-file-download": "^0.4.12",
@@ -32,7 +32,7 @@
"react-dom": "^18.2.0",
"react-intl": "^6.5.5",
"react-redux": "^8.1.3",
- "react-router-dom": "^6.19.0",
+ "react-router-dom": "^6.20.0",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"typesafe-actions": "^5.1.0",
@@ -46,18 +46,18 @@
"@formatjs/ecma402-abstract": "^1.18.0",
"@formatjs/icu-messageformat-parser": "^2.7.3",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3",
- "@redhat-cloud-services/frontend-components-config": "^6.0.5",
+ "@redhat-cloud-services/frontend-components-config": "^6.0.6",
"@redhat-cloud-services/tsc-transform-imports": "^1.0.4",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
- "@types/jest": "^29.5.8",
+ "@types/jest": "^29.5.10",
"@types/qs": "^6.9.10",
- "@types/react": "^18.2.37",
- "@types/react-dom": "^18.2.15",
- "@types/react-redux": "^7.1.30",
+ "@types/react": "^18.2.39",
+ "@types/react-dom": "^18.2.17",
+ "@types/react-redux": "^7.1.31",
"@types/react-router-dom": "^5.3.3",
- "@typescript-eslint/eslint-plugin": "^6.11.0",
- "@typescript-eslint/parser": "^6.11.0",
+ "@typescript-eslint/eslint-plugin": "^6.13.1",
+ "@typescript-eslint/parser": "^6.13.1",
"@xstate/test": "^0.5.1",
"aphrodite": "^2.4.0",
"copy-webpack-plugin": "^11.0.0",
@@ -71,7 +71,7 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
- "eslint-plugin-testing-library": "^6.1.2",
+ "eslint-plugin-testing-library": "^6.2.0",
"git-revision-webpack-plugin": "^5.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@@ -81,7 +81,7 @@
"prettier": "^3.1.0",
"rimraf": "^5.0.5",
"ts-patch": "^3.0.2",
- "typescript": "^5.2.2",
+ "typescript": "^5.3.2",
"webpack-bundle-analyzer": "^4.10.1"
},
"engines": {
@@ -2415,9 +2415,9 @@
}
},
"node_modules/@redhat-cloud-services/frontend-components-config": {
- "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==",
+ "version": "6.0.6",
+ "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.0.6.tgz",
+ "integrity": "sha512-T0rZTPVm9Y8YMqQ0KqaiGLh4jOkRmli2ZkibTgZG8S08BXwUSEv7NTPV5XdqHOpJTPdY49P3MD5OTKpltinq6w==",
"dev": true,
"dependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.8",
@@ -2946,9 +2946,9 @@
"integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg=="
},
"node_modules/@remix-run/router": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.12.0.tgz",
- "integrity": "sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.0.tgz",
+ "integrity": "sha512-5dMOnVnefRsl4uRnAdoWjtVTdh8e6aZqgM4puy9nmEADH72ck+uXwzpJLEKE9Q6F8ZljNewLgmTfkxUrBdv4WA==",
"engines": {
"node": ">=14.0.0"
}
@@ -3615,9 +3615,9 @@
}
},
"node_modules/@types/jest": {
- "version": "29.5.8",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz",
- "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==",
+ "version": "29.5.10",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.10.tgz",
+ "integrity": "sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==",
"dev": true,
"dependencies": {
"expect": "^29.0.0",
@@ -3747,9 +3747,9 @@
"dev": true
},
"node_modules/@types/react": {
- "version": "18.2.37",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz",
- "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==",
+ "version": "18.2.39",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz",
+ "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -3757,18 +3757,18 @@
}
},
"node_modules/@types/react-dom": {
- "version": "18.2.15",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz",
- "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==",
+ "version": "18.2.17",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz",
+ "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==",
"devOptional": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
- "version": "7.1.30",
- "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.30.tgz",
- "integrity": "sha512-i2kqM6YaUwFKduamV6QM/uHbb0eCP8f8ZQ/0yWf+BsAVVsZPRYJ9eeGWZ3uxLfWwwA0SrPRMTPTqsPFkY3HZdA==",
+ "version": "7.1.31",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.31.tgz",
+ "integrity": "sha512-merF9AH72krBUekQY6uObXnMsEo1xTeZy9NONNRnqSwvwVe3HtLeRvNIPaKmPDIOWPsSFE51rc2WGpPMqmuCWg==",
"dev": true,
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
@@ -4001,16 +4001,16 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
- "integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz",
+ "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.11.0",
- "@typescript-eslint/type-utils": "6.11.0",
- "@typescript-eslint/utils": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/type-utils": "6.13.1",
+ "@typescript-eslint/utils": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -4069,15 +4069,15 @@
"dev": true
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz",
- "integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz",
+ "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.11.0",
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/typescript-estree": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4"
},
"engines": {
@@ -4097,13 +4097,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz",
- "integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz",
+ "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0"
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -4114,13 +4114,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
- "integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz",
+ "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.11.0",
- "@typescript-eslint/utils": "6.11.0",
+ "@typescript-eslint/typescript-estree": "6.13.1",
+ "@typescript-eslint/utils": "6.13.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -4141,9 +4141,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz",
- "integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz",
+ "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -4154,13 +4154,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz",
- "integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz",
+ "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/visitor-keys": "6.11.0",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/visitor-keys": "6.13.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -4214,17 +4214,17 @@
"dev": true
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
- "integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz",
+ "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.11.0",
- "@typescript-eslint/types": "6.11.0",
- "@typescript-eslint/typescript-estree": "6.11.0",
+ "@typescript-eslint/scope-manager": "6.13.1",
+ "@typescript-eslint/types": "6.13.1",
+ "@typescript-eslint/typescript-estree": "6.13.1",
"semver": "^7.5.4"
},
"engines": {
@@ -4272,12 +4272,12 @@
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz",
- "integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==",
+ "version": "6.13.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz",
+ "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.11.0",
+ "@typescript-eslint/types": "6.13.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -4307,9 +4307,9 @@
"dev": true
},
"node_modules/@unleash/proxy-client-react": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@unleash/proxy-client-react/-/proxy-client-react-4.1.0.tgz",
- "integrity": "sha512-4mdHtEDgjFtz7I+fQ5V3gdrDFJVhhymyV2J6ZC9G6xFPjOZGTKdA5ZJDFiHm3N23KeND/NUmdGSKJmz27b0LoQ==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@unleash/proxy-client-react/-/proxy-client-react-4.1.1.tgz",
+ "integrity": "sha512-lbKVGfS9G5ela/8Ei5g7FD+5Q3un61ha3Mk7OWmWTBteeu6GctzFl/zYHWSHzz4ZiayhfX/Km2UprR68Db9Tcg==",
"engines": {
"node": ">=16.0.0"
},
@@ -9269,9 +9269,9 @@
}
},
"node_modules/eslint-plugin-testing-library": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-6.1.2.tgz",
- "integrity": "sha512-Ra16FeBlonfbScOIdZEta9o+OxtwDqiUt+4UCpIM42TuatyLdtfU/SbwnIzPcAszrbl58PGwyZ9YGU9dwIo/tA==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-6.2.0.tgz",
+ "integrity": "sha512-+LCYJU81WF2yQ+Xu4A135CgK8IszcFcyMF4sWkbiu6Oj+Nel0TrkZq/HvDw0/1WuO3dhDQsZA/OpEMGd0NfcUw==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^5.58.0"
@@ -17207,11 +17207,11 @@
}
},
"node_modules/react-router": {
- "version": "6.19.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.19.0.tgz",
- "integrity": "sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==",
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.0.tgz",
+ "integrity": "sha512-pVvzsSsgUxxtuNfTHC4IxjATs10UaAtvLGVSA1tbUE4GDaOSU1Esu2xF5nWLz7KPiMuW8BJWuPFdlGYJ7/rW0w==",
"dependencies": {
- "@remix-run/router": "1.12.0"
+ "@remix-run/router": "1.13.0"
},
"engines": {
"node": ">=14.0.0"
@@ -17221,12 +17221,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.19.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.19.0.tgz",
- "integrity": "sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==",
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.0.tgz",
+ "integrity": "sha512-CbcKjEyiSVpA6UtCHOIYLUYn/UJfwzp55va4yEfpk7JBN3GPqWfHrdLkAvNCcpXr8QoihcDMuk0dzWZxtlB/mQ==",
"dependencies": {
- "@remix-run/router": "1.12.0",
- "react-router": "6.19.0"
+ "@remix-run/router": "1.13.0",
+ "react-router": "6.20.0"
},
"engines": {
"node": ">=14.0.0"
@@ -19600,9 +19600,9 @@
}
},
"node_modules/typescript": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
- "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
+ "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"devOptional": true,
"bin": {
"tsc": "bin/tsc",
diff --git a/package.json b/package.json
index ec2bcfb31..bc18f2f44 100644
--- a/package.json
+++ b/package.json
@@ -30,10 +30,11 @@
"postinstall": "ts-patch install && rm -rf .cache",
"start": "fec dev",
"start:csc": "CLOUD_SERVICES_CONFIG_PORT=8000 npm start",
+ "start:csc:mfe": "FEC_STATIC_PORT=8003 npm run start:csc",
"start:ephemeral": "EPHEMERAL_PORT=8000 npm start",
"start:hmr": "HMR=true npm start",
"start:local:api": "LOCAL_API_PORT=8000 LOCAL_API_HOST=localhost KEYCLOAK_PORT=4020 npm start",
- "start:mfe": "FEC_STATIC_PORT=8003 npm run start:csc",
+ "start:mfe": "FEC_STATIC_PORT=8003 npm run start",
"stats": "npm run build:prod --profile --json > stats.json",
"test": "jest --no-cache",
"test:clean": "jest --clearCache",
@@ -60,7 +61,7 @@
"@redhat-cloud-services/frontend-components-translations": "^3.2.7",
"@redhat-cloud-services/frontend-components-utilities": "^4.0.2",
"@redhat-cloud-services/rbac-client": "^1.2.12",
- "@unleash/proxy-client-react": "^4.1.0",
+ "@unleash/proxy-client-react": "^4.1.1",
"axios": "^1.6.2",
"date-fns": "^2.30.0",
"js-file-download": "^0.4.12",
@@ -71,7 +72,7 @@
"react-dom": "^18.2.0",
"react-intl": "^6.5.5",
"react-redux": "^8.1.3",
- "react-router-dom": "^6.19.0",
+ "react-router-dom": "^6.20.0",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"typesafe-actions": "^5.1.0",
@@ -85,18 +86,18 @@
"@formatjs/ecma402-abstract": "^1.18.0",
"@formatjs/icu-messageformat-parser": "^2.7.3",
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3",
- "@redhat-cloud-services/frontend-components-config": "^6.0.5",
+ "@redhat-cloud-services/frontend-components-config": "^6.0.6",
"@redhat-cloud-services/tsc-transform-imports": "^1.0.4",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
- "@types/jest": "^29.5.8",
+ "@types/jest": "^29.5.10",
"@types/qs": "^6.9.10",
- "@types/react": "^18.2.37",
- "@types/react-dom": "^18.2.15",
- "@types/react-redux": "^7.1.30",
+ "@types/react": "^18.2.39",
+ "@types/react-dom": "^18.2.17",
+ "@types/react-redux": "^7.1.31",
"@types/react-router-dom": "^5.3.3",
- "@typescript-eslint/eslint-plugin": "^6.11.0",
- "@typescript-eslint/parser": "^6.11.0",
+ "@typescript-eslint/eslint-plugin": "^6.13.1",
+ "@typescript-eslint/parser": "^6.13.1",
"@xstate/test": "^0.5.1",
"aphrodite": "^2.4.0",
"copy-webpack-plugin": "^11.0.0",
@@ -110,7 +111,7 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
- "eslint-plugin-testing-library": "^6.1.2",
+ "eslint-plugin-testing-library": "^6.2.0",
"git-revision-webpack-plugin": "^5.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@@ -120,11 +121,10 @@
"prettier": "^3.1.0",
"rimraf": "^5.0.5",
"ts-patch": "^3.0.2",
- "typescript": "^5.2.2",
+ "typescript": "^5.3.2",
"webpack-bundle-analyzer": "^4.10.1"
},
- "overrides": {
- },
+ "overrides": {},
"insights": {
"appname": "cost-management"
}
diff --git a/src/api/queries/rosQuery.ts b/src/api/queries/rosQuery.ts
deleted file mode 100644
index cbe1edae8..000000000
--- a/src/api/queries/rosQuery.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import * as utils from './query';
-
-export interface RosFilters extends utils.Filters {
- project?: string | number;
-}
-
-type RosGroupByValue = string | string[];
-
-interface RosGroupBys {
- cluster?: RosGroupByValue;
- node?: RosGroupByValue;
- project?: RosGroupByValue;
-}
-
-export interface RosQuery extends utils.Query {
- category?: string;
- delta?: string;
- filter?: RosFilters;
- group_by?: RosGroupBys;
- limit?: number;
- offset?: number;
- order_by?: any;
-}
-
-// filter_by props are converted and returned with logical OR/AND prefix
-export function getQuery(query: RosQuery) {
- return utils.getQuery(query);
-}
-
-// filter_by props are not converted
-export function getQueryRoute(query: RosQuery) {
- return utils.getQueryRoute(query);
-}
-
-export function parseQuery(query: string): T {
- return utils.parseQuery(query);
-}
diff --git a/src/api/ros/recommendations.test.ts b/src/api/ros/recommendations.test.ts
deleted file mode 100644
index b7bb08ea1..000000000
--- a/src/api/ros/recommendations.test.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { RosType } from 'api/ros/ros';
-import axios from 'axios';
-
-import { runRosReports } from './recommendations';
-
-test('api run reports calls axios get', () => {
- const query = 'limit=10';
- runRosReports(RosType.ros, query);
- expect(axios.get).toBeCalledWith(`recommendations/openshift?${query}`);
-});
diff --git a/src/api/ros/recommendations.ts b/src/api/ros/recommendations.ts
deleted file mode 100644
index aef2f97f4..000000000
--- a/src/api/ros/recommendations.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import axios from 'axios';
-
-import type { RosData, RosMeta, RosReport } from './ros';
-import { RosType } from './ros';
-
-export interface RecommendationValue {
- amount?: number;
- format?: string;
-}
-
-export interface Notification {
- code?: number;
- message?: string;
- type?: string;
-}
-
-export interface RecommendationItem {
- config: {
- limits: {
- memory?: RecommendationValue;
- cpu?: RecommendationValue;
- };
- requests: {
- memory?: RecommendationValue;
- cpu?: RecommendationValue;
- };
- };
- current: {
- limits: {
- memory?: RecommendationValue;
- cpu?: RecommendationValue;
- };
- requests: {
- memory?: RecommendationValue;
- cpu?: RecommendationValue;
- };
- };
- variation: {
- limits: {
- memory?: RecommendationValue;
- cpu?: RecommendationValue;
- };
- requests: {
- memory?: RecommendationValue;
- cpu?: RecommendationValue;
- };
- };
- duration_in_hours?: string;
- monitoring_start_time?: string;
- monitoring_end_time?: string;
- notifications?: {
- [key: string]: Notification;
- };
-}
-
-export interface RecommendationItems {
- short_term?: RecommendationItem;
- medium_term?: RecommendationItem;
- long_term?: RecommendationItem;
-}
-
-export interface RecommendationReportData extends RosData {
- recommendations?: {
- duration_based?: RecommendationItems;
- };
-}
-
-export interface RecommendationReportMeta extends RosMeta {
- // TBD...
-}
-
-export interface RecommendationReport extends RosReport {
- meta: RecommendationReportMeta;
- data: RecommendationReportData[];
-}
-
-export const RosTypePaths: Partial> = {
- [RosType.ros]: 'recommendations/openshift',
-};
-
-// This fetches a recommendation by ID
-export function runRosReport(reportType: RosType, query: string) {
- const path = RosTypePaths[reportType];
- const queryString = query ? `/${query}` : '';
- return axios.get(`${path}${queryString}`);
-}
-
-// This fetches a recommendations list
-export function runRosReports(reportType: RosType, query: string) {
- const path = RosTypePaths[reportType];
- const queryString = query ? `?${query}` : '';
- return axios.get(`${path}${queryString}`);
-}
diff --git a/src/api/ros/ros.ts b/src/api/ros/ros.ts
deleted file mode 100644
index e11217507..000000000
--- a/src/api/ros/ros.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { PagedMetaData, PagedResponse } from 'api/api';
-
-export interface RosData {
- cluster_uuid?: string;
- cluster_alias?: string;
- container?: string;
- id?: number;
- project?: string;
- last_reported?: string;
- recommendations?: {
- duration_based?: any;
- };
- workload?: string;
- workload_type?: string;
-}
-
-export interface RosMeta extends PagedMetaData {
- count: number;
- limit?: number;
- offset?: number;
-}
-
-export interface RosReport extends PagedResponse {}
-
-// eslint-disable-next-line no-shadow
-export const enum RosType {
- ros = 'ros',
-}
-
-// eslint-disable-next-line no-shadow
-export const enum RosPathsType {
- recommendation = 'recommendation',
- recommendations = 'recommendations',
-}
diff --git a/src/api/ros/rosUtils.ts b/src/api/ros/rosUtils.ts
deleted file mode 100644
index 2f5b64c25..000000000
--- a/src/api/ros/rosUtils.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { runRosReport as runRecommendation, runRosReports as runRecommendations } from './recommendations';
-import type { RosType } from './ros';
-import { RosPathsType } from './ros';
-
-export function runRosReport(rosPathsType: RosPathsType, rosType: RosType, query: string) {
- let result;
- switch (rosPathsType) {
- case RosPathsType.recommendation:
- result = runRecommendation(rosType, query);
- break;
- case RosPathsType.recommendations:
- result = runRecommendations(rosType, query);
- break;
- }
- return result;
-}
diff --git a/src/api/tags/tag.ts b/src/api/tags/tag.ts
index 661a5a5b1..5b6c4f979 100644
--- a/src/api/tags/tag.ts
+++ b/src/api/tags/tag.ts
@@ -38,6 +38,4 @@ export const enum TagPathsType {
ocp = 'ocp',
ocpCloud = 'ocp_cloud',
rhel = 'rhel',
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- ros = 'ocp', // Todo: Remove when APIs are available
}
diff --git a/src/routes.tsx b/src/routes.tsx
index 0475b1b77..64a18df6f 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -29,7 +29,7 @@ const RhelDetails = lazy(() => import(/* webpackChunkName: "rhelDetails" */ 'rou
const RhelBreakdown = lazy(() => import(/* webpackChunkName: "rhelBreakdown" */ 'routes/details/rhelBreakdown'));
const Settings = lazy(() => import(/* webpackChunkName: "overview" */ 'routes/settings'));
-const routes = {
+export const routes = {
awsBreakdown: {
element: userAccess(AwsBreakdown),
path: '/aws/breakdown',
@@ -118,7 +118,7 @@ const routes = {
},
};
-const Routes = () => (
+export const Routes = () => (
@@ -136,5 +136,3 @@ const Routes = () => (
);
-
-export { routes, Routes };
diff --git a/src/routes/components/optimizations/index.ts b/src/routes/components/optimizations/index.ts
deleted file mode 100644
index 9d8c4f63d..000000000
--- a/src/routes/components/optimizations/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-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
deleted file mode 100644
index 24b8abf37..000000000
--- a/src/routes/components/optimizations/optimizationsBadge.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-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?.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
deleted file mode 100644
index d95bc3860..000000000
--- a/src/routes/components/optimizations/optimizationsTable.tsx
+++ /dev/null
@@ -1,190 +0,0 @@
-import 'routes/components/dataTable/dataTable.scss';
-
-import { Icon } from '@patternfly/react-core';
-import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon';
-import type { Query } from 'api/queries/query';
-import type { RecommendationReport } from 'api/ros/recommendations';
-import messages from 'locales/messages';
-import React, { useEffect, useState } from 'react';
-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 { getOptimizationsBreakdownPath } from 'routes/utils/paths';
-import { getTimeFromNow } from 'utils/dates';
-import { hasWarning } from 'utils/recomendations';
-
-interface OptimizationsTableOwnProps {
- basePath?: string;
- breadcrumbLabel?: string;
- breadcrumbPath?: string;
- filterBy?: any;
- groupBy?: string;
- isLoading?: boolean;
- onSort(value: string, isSortAscending: boolean);
- orderBy?: any;
- query?: Query;
- report: RecommendationReport;
- reportQueryString: string;
-}
-
-type OptimizationsTableProps = OptimizationsTableOwnProps;
-
-const OptimizationsTable: React.FC = ({
- basePath,
- breadcrumbLabel,
- breadcrumbPath,
- filterBy,
- groupBy,
- isLoading,
- onSort,
- orderBy,
- query,
- report,
-}) => {
- const intl = useIntl();
- const location = useLocation();
-
- const [columns, setColumns] = useState([]);
- const [rows, setRows] = useState([]);
-
- const initDatum = () => {
- if (!report) {
- return;
- }
- const hasData = report?.data && report.data.length > 0;
-
- const newRows = [];
- const newColumns = [
- {
- name: intl.formatMessage(messages.optimizationsNames, { value: 'container' }),
- orderBy: 'container',
- ...(hasData && { isSortable: true }),
- },
- {
- hidden: groupBy === 'project',
- name: intl.formatMessage(messages.optimizationsNames, { value: 'project' }),
- orderBy: 'project',
- ...(hasData && { isSortable: true }),
- },
- {
- name: intl.formatMessage(messages.optimizationsNames, { value: 'workload' }),
- orderBy: 'workload',
- ...(hasData && { isSortable: true }),
- },
- {
- name: intl.formatMessage(messages.optimizationsNames, { value: 'workload_type' }),
- orderBy: 'workload_type',
- ...(hasData && { isSortable: true }),
- },
- {
- hidden: groupBy === 'cluster',
- name: intl.formatMessage(messages.optimizationsNames, { value: 'cluster' }),
- orderBy: 'cluster',
- ...(hasData && { isSortable: true }),
- },
- {
- name: intl.formatMessage(messages.optimizationsNames, { value: 'last_reported' }),
- orderBy: 'last_reported',
- style: styles.lastItemColumn,
- ...(hasData && { isSortable: true }),
- },
- ];
-
- hasData &&
- report.data.map(item => {
- const cluster = item.cluster_alias ? item.cluster_alias : item.cluster_uuid ? item.cluster_uuid : '';
- const container = item.container ? item.container : '';
- const lastReported = getTimeFromNow(item.last_reported);
- const project = item.project ? item.project : '';
- const workload = item.workload ? item.workload : '';
- const workloadType = item.workload_type ? item.workload_type : '';
- const showWarningIcon = hasWarning(item?.recommendations?.duration_based);
-
- newRows.push({
- cells: [
- {
- value: (
-
- {container}
-
- ),
- },
- { value: project, hidden: groupBy === 'project' },
- { value: workload },
- { value: workloadType },
- {
- value: (
- <>
- {cluster}
- {showWarningIcon && (
-
-
-
-
-
- )}
- >
- ),
- hidden: groupBy === 'cluster',
- },
- { value: lastReported, style: styles.lastItem },
- ],
- optimization: {
- container: item.container,
- id: item.id,
- project,
- },
- });
- });
-
- const filteredColumns = (newColumns as any[]).filter(column => !column.hidden);
- const filteredRows = newRows.map(({ ...row }) => {
- row.cells = row.cells.filter(cell => !cell.hidden);
- return row;
- });
-
- setColumns(filteredColumns);
- setRows(filteredRows);
- };
-
- const handleOnSort = (value: string, isSortAscending: boolean) => {
- if (onSort) {
- onSort(value, isSortAscending);
- }
- };
-
- useEffect(() => {
- initDatum();
- }, [report]);
-
- return (
- }
- filterBy={filterBy}
- isLoading={isLoading}
- isSelectable={false}
- onSort={handleOnSort}
- orderBy={orderBy}
- rows={rows}
- />
- );
-};
-
-export { OptimizationsTable };
diff --git a/src/routes/components/optimizations/optimizationsToolbar.tsx b/src/routes/components/optimizations/optimizationsToolbar.tsx
deleted file mode 100644
index 37894244c..000000000
--- a/src/routes/components/optimizations/optimizationsToolbar.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import type { ToolbarChipGroup } from '@patternfly/react-core';
-import type { RosQuery } from 'api/queries/rosQuery';
-import messages from 'locales/messages';
-import React from 'react';
-import type { WrappedComponentProps } from 'react-intl';
-import { injectIntl } from 'react-intl';
-import { BasicToolbar } from 'routes/components/dataToolbar';
-import type { Filter } from 'routes/utils/filter';
-
-interface OptimizationsToolbarOwnProps {
- isDisabled?: boolean;
- isProject?: boolean;
- itemsPerPage?: number;
- itemsTotal?: number;
- onFilterAdded(filter: Filter);
- onFilterRemoved(filter: Filter);
- pagination?: React.ReactNode;
- query?: RosQuery;
-}
-
-interface OptimizationsToolbarState {
- categoryOptions?: ToolbarChipGroup[];
-}
-
-type OptimizationsToolbarProps = OptimizationsToolbarOwnProps & WrappedComponentProps;
-
-class OptimizationsToolbarBase extends React.Component {
- protected defaultState: OptimizationsToolbarState = {};
- public state: OptimizationsToolbarState = { ...this.defaultState };
-
- public componentDidMount() {
- this.setState({
- categoryOptions: this.getCategoryOptions(),
- });
- }
-
- private getCategoryOptions = (): ToolbarChipGroup[] => {
- const { intl, isProject } = this.props;
-
- const options = [
- { name: intl.formatMessage(messages.filterByValues, { value: 'container' }), key: 'container' },
- { name: intl.formatMessage(messages.filterByValues, { value: 'cluster' }), key: 'cluster' },
- { name: intl.formatMessage(messages.filterByValues, { value: 'project' }), key: 'project' },
- { name: intl.formatMessage(messages.filterByValues, { value: 'workload' }), key: 'workload' },
- {
- name: intl.formatMessage(messages.filterByValues, { value: 'workload_type' }),
- key: 'workload_type',
- selectClassName: 'selectOverride', // A selector from routes/components/dataToolbar/dataToolbar.scss
- selectOptions: [
- { name: 'daemonset', key: 'daemonset' },
- { name: 'deployment', key: 'deployment' },
- { name: 'deploymentconfig', key: 'deploymentconfig' },
- { name: 'replicaset', key: 'replicaset' },
- { name: 'replicationcontroller', key: 'replicationcontroller' },
- { name: 'statefulset', key: 'statefulset' },
- ],
- },
- ];
- return isProject ? options : options.filter(option => option.key !== 'project');
- };
-
- public render() {
- const { isDisabled, itemsPerPage, itemsTotal, onFilterAdded, onFilterRemoved, pagination, query } = this.props;
- const { categoryOptions } = this.state;
-
- return (
-
- );
- }
-}
-
-const OptimizationsToolbar = injectIntl(OptimizationsToolbarBase);
-
-export { OptimizationsToolbar };
diff --git a/src/routes/details/awsBreakdown/awsBreakdown.tsx b/src/routes/details/awsBreakdown/awsBreakdown.tsx
index be3523a86..0e222831b 100644
--- a/src/routes/details/awsBreakdown/awsBreakdown.tsx
+++ b/src/routes/details/awsBreakdown/awsBreakdown.tsx
@@ -27,7 +27,7 @@ import { withRouter } from 'utils/router';
import { CostOverview } from './costOverview';
import { HistoricalData } from './historicalData';
-interface BreakdownDispatchProps {
+interface AwsBreakdownDispatchProps {
fetchReport?: typeof reportActions.fetchReport;
}
@@ -128,10 +128,8 @@ const mapStateToProps = createMapStateToProps((state, { intl, router }) => {
+const mapStateToProps = createMapStateToProps((state, { intl, router }) => {
const queryFromRoute = parseQuery(router.location.search);
const queryState = getQueryState(router.location, 'details');
@@ -114,10 +114,8 @@ const mapStateToProps = createMapStateToProps {
}
private getAvailableTabs = () => {
- const {
- costOverviewComponent,
- historicalDataComponent,
- isRosFeatureEnabled,
- optimizationsBadgeComponent,
- optimizationsComponent,
- } = this.props;
+ const { costOverviewComponent, historicalDataComponent, isRosFeatureEnabled, optimizationsComponent } = this.props;
const availableTabs = [];
if (costOverviewComponent) {
@@ -146,15 +141,17 @@ class BreakdownBase extends React.Component {
}
if (optimizationsComponent && isRosFeatureEnabled) {
availableTabs.push({
- badge: optimizationsBadgeComponent,
contentRef: React.createRef(),
+ showBadge: true,
tab: BreakdownTab.optimizations,
});
}
return availableTabs;
};
- private getTab = (tab: BreakdownTab, contentRef, badge: React.ReactNode, index: number) => {
+ private getTab = (tab: BreakdownTab, contentRef, showBadge: boolean, index: number) => {
+ const { groupBy, groupByValue } = this.props;
+
return (
{
title={
<>
{this.getTabTitle(tab)}
- {badge && {badge}}
+ {showBadge && (
+
+ {
+
+ }
+
+ )}
>
}
/>
@@ -211,7 +220,7 @@ class BreakdownBase extends React.Component {
return (
- {availableTabs.map((val, index) => this.getTab(val.tab, val.contentRef, val.badge, index))}
+ {availableTabs.map((val, index) => this.getTab(val.tab, val.contentRef, val.showBadge, index))}
);
};
diff --git a/src/routes/details/components/pvcChart/modal/pvcToolbar.tsx b/src/routes/details/components/pvcChart/modal/pvcToolbar.tsx
index 909be1166..8d29b4262 100644
--- a/src/routes/details/components/pvcChart/modal/pvcToolbar.tsx
+++ b/src/routes/details/components/pvcChart/modal/pvcToolbar.tsx
@@ -1,5 +1,5 @@
import type { ToolbarChipGroup } from '@patternfly/react-core';
-import type { RosQuery } from 'api/queries/rosQuery';
+import type { Query } from 'api/queries/query';
import messages from 'locales/messages';
import React from 'react';
import type { WrappedComponentProps } from 'react-intl';
@@ -15,7 +15,7 @@ interface PvcToolbarOwnProps {
onFilterAdded(filter: Filter);
onFilterRemoved(filter: Filter);
pagination?: React.ReactNode;
- query?: RosQuery;
+ query?: Query;
}
interface PvcToolbarState {
diff --git a/src/routes/details/gcpBreakdown/gcpBreakdown.tsx b/src/routes/details/gcpBreakdown/gcpBreakdown.tsx
index 31cc65894..4aaff751b 100644
--- a/src/routes/details/gcpBreakdown/gcpBreakdown.tsx
+++ b/src/routes/details/gcpBreakdown/gcpBreakdown.tsx
@@ -27,7 +27,7 @@ import { withRouter } from 'utils/router';
import { CostOverview } from './costOverview';
import { HistoricalData } from './historicalData';
-interface BreakdownDispatchProps {
+interface GcpBreakdownDispatchProps {
fetchReport?: typeof reportActions.fetchReport;
}
@@ -112,10 +112,8 @@ const mapStateToProps = createMapStateToProps((state, { intl, router }) => {
+const mapStateToProps = createMapStateToProps((state, { intl, router }) => {
const queryFromRoute = parseQuery(router.location.search);
const queryState = getQueryState(router.location, 'details');
@@ -112,10 +112,8 @@ const mapStateToProps = createMapStateToProps,
isOptimizationsTab: queryFromRoute.optimizationsTab !== undefined,
isRosFeatureEnabled: featureFlagsSelectors.selectIsRosFeatureEnabled(state),
- optimizationsBadgeComponent: ,
optimizationsComponent: groupBy === 'project' && groupByValue !== '*' ? : undefined,
providers: filterProviders(providers, ProviderType.ocp),
providersFetchStatus,
@@ -135,11 +133,9 @@ const mapStateToProps = createMapStateToProps = () => {
- const intl = useIntl();
- const location = useLocation();
-
- const queryState = getQueryState(location, 'optimizations');
- const [query, setQuery] = useState({ ...baseQuery, ...(queryState && queryState) });
- const { groupBy, project, report, reportError, reportFetchStatus, reportQueryString } = useMapToProps({
- query,
- });
-
- // Clear queryState, returned from breakdown page, after query has been initialized
- useEffect(() => {
- clearQueryState(location, 'optimizations');
- }, [reportQueryString]);
-
- const getPagination = (isDisabled = false, isBottom = false) => {
- const count = report?.meta ? report.meta.count : 0;
- const limit = report?.meta ? report.meta.limit : baseQuery.limit;
- const offset = 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={{
- paginationAriaLabel: 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}
- query={query}
- report={report}
- reportQueryString={reportQueryString}
- />
- );
- };
-
- const getToolbar = () => {
- const itemsPerPage = report?.meta ? report.meta.limit : 0;
- const itemsTotal = 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);
- };
-
- const handleOnFilterRemoved = filter => {
- const newQuery = queryUtils.handleOnFilterRemoved(query, filter);
- setQuery(newQuery);
- };
-
- const handleOnPerPageSelect = perPage => {
- const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true);
- setQuery(newQuery);
- };
-
- const handleOnSetPage = pageNumber => {
- const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true);
- setQuery(newQuery);
- };
-
- const handleOnSort = (sortType, isSortAscending) => {
- const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending);
- setQuery(newQuery);
- };
-
- const itemsTotal = report?.meta ? report.meta.count : 0;
- const isDisabled = itemsTotal === 0;
- const hasOptimizations = 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 }: OcpOptimizationsBreakdownMapProps): OcpOptimizationsBreakdownStateProps => {
- const dispatch: ThunkDispatch = useDispatch();
+const OcpBreakdownOptimizations: React.FC = () => {
+ const intl = useIntl();
+ const location = useLocation();
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 otimizationsTab = location.search.indexOf('optimizationsTab') === -1 ? '&optimizationsTab=true' : '';
- 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)
+ return (
+
);
-
- useEffect(() => {
- if (!reportError && reportFetchStatus !== FetchStatus.inProgress) {
- dispatch(rosActions.fetchRosReport(reportPathsType, reportType, reportQueryString));
- }
- }, [query]);
-
- return {
- groupBy,
- project: queryFromRoute[breakdownTitleKey],
- report,
- reportError,
- reportFetchStatus,
- reportQueryString,
- };
};
export { OcpBreakdownOptimizations };
diff --git a/src/routes/details/ocpDetails/detailsOptimization.tsx b/src/routes/details/ocpDetails/detailsOptimization.tsx
deleted file mode 100644
index fe111e788..000000000
--- a/src/routes/details/ocpDetails/detailsOptimization.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-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';
-import type { AxiosError } from 'axios';
-import React from 'react';
-import { connect } from 'react-redux';
-import { Link } from 'react-router-dom';
-import { getBreakdownPath } from 'routes/utils/paths';
-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 DetailsOptimizationOwnProps {
- basePath?: string;
- breadcrumbPath?: string;
- project?: string;
- query?: Query;
-}
-
-export interface DetailsOptimizationStateProps {
- report?: RosReport;
- reportError?: AxiosError;
- reportFetchStatus?: FetchStatus;
- reportQueryString?: string;
-}
-
-interface DetailsOptimizationDispatchProps {
- fetchReport: typeof rosActions.fetchRosReport;
-}
-
-interface DetailsOptimizationState {}
-
-type DetailsOptimizationProps = DetailsOptimizationOwnProps &
- DetailsOptimizationStateProps &
- DetailsOptimizationDispatchProps &
- RouterComponentProps;
-
-const reportPathsType = RosPathsType.recommendations;
-const reportType = RosType.ros;
-
-class DetailsOptimization extends React.Component {
- protected defaultState: DetailsOptimizationState = {
- // TBD...
- };
- public state: DetailsOptimizationState = { ...this.defaultState };
-
- public componentDidMount() {
- this.updateReport();
- }
-
- private updateReport = () => {
- const { fetchReport, reportQueryString } = this.props;
- fetchReport(reportPathsType, reportType, reportQueryString);
- };
-
- private getBreakdownLink = count => {
- const { basePath, breadcrumbPath, project, query, router } = this.props;
-
- if (count === 0 || project === undefined) {
- return count;
- }
- return (
-
- {count}
-
- );
- };
-
- public render() {
- const { report } = this.props;
-
- const count = report?.meta ? report.meta.count : 0;
-
- // Todo: Add link to breakdown page
- return {this.getBreakdownLink(count)};
- }
-}
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-const mapStateToProps = createMapStateToProps(
- (state, { project }) => {
- const reportQuery = {
- project, // 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: DetailsOptimizationDispatchProps = {
- fetchReport: rosActions.fetchRosReport,
-};
-
-export default withRouter(connect(mapStateToProps, mapDispatchToProps)(DetailsOptimization));
diff --git a/src/routes/details/ocpDetails/detailsTable.tsx b/src/routes/details/ocpDetails/detailsTable.tsx
index a44db0d00..1ad176e93 100644
--- a/src/routes/details/ocpDetails/detailsTable.tsx
+++ b/src/routes/details/ocpDetails/detailsTable.tsx
@@ -1,6 +1,7 @@
import 'routes/components/dataTable/dataTable.scss';
import { Label, Tooltip } from '@patternfly/react-core';
+import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent';
import { ProviderType } from 'api/providers';
import type { Query } from 'api/queries/query';
import type { OcpReport, OcpReportItem } from 'api/reports/ocpReports';
@@ -10,7 +11,6 @@ import React from 'react';
import type { WrappedComponentProps } from 'react-intl';
import { injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
-import { routes } from 'routes';
import { ComputedReportItemValueType } from 'routes/components/charts/common';
import { DataTable } from 'routes/components/dataTable';
import { styles } from 'routes/components/dataTable/dataTable.styles';
@@ -21,13 +21,10 @@ import { getUnsortedComputedReportItems } from 'routes/utils/computedReport/getC
import { getBreakdownPath } from 'routes/utils/paths';
import { getForDateRangeString, getNoDataForDateRangeString } from 'utils/dates';
import { formatCurrency, formatPercentage } from 'utils/format';
-import { formatPath } from 'utils/paths';
import { classificationDefault, classificationPlatform, classificationUnallocated, noPrefix } from 'utils/props';
import type { RouterComponentProps } from 'utils/router';
import { withRouter } from 'utils/router';
-import DetailsOptimization from './detailsOptimization';
-
interface DetailsTableOwnProps extends RouterComponentProps, WrappedComponentProps {
basePath?: string;
breadcrumbPath?: string;
@@ -281,11 +278,28 @@ class DetailsTableBase extends React.Component
),
},
diff --git a/src/routes/details/rhelBreakdown/rhelBreakdown.tsx b/src/routes/details/rhelBreakdown/rhelBreakdown.tsx
index 6a95d1f3a..c69c6cc19 100644
--- a/src/routes/details/rhelBreakdown/rhelBreakdown.tsx
+++ b/src/routes/details/rhelBreakdown/rhelBreakdown.tsx
@@ -27,7 +27,7 @@ import { withRouter } from 'utils/router';
import { CostOverview } from './costOverview';
import { HistoricalData } from './historicalData';
-interface BreakdownDispatchProps {
+interface RhelBreakdownDispatchProps {
fetchReport?: typeof reportActions.fetchReport;
}
@@ -113,10 +113,8 @@ const mapStateToProps = createMapStateToProps {
+ const location = useLocation();
+ return parseQuery(location.search);
+};
const OptimizationsBreakdown: React.FC = () => {
- const { breadcrumbLabel, report, reportFetchStatus } = useMapToProps();
const intl = useIntl();
- const location = useLocation();
-
- const getDefaultTerm = () => {
- let result = Interval.short_term;
- if (!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 getAlert = () => {
- let notifications;
- if (report?.recommendations?.duration_based?.[currentInterval]) {
- notifications = getNotifications(report.recommendations.duration_based[currentInterval]);
- }
-
- if (!notifications) {
- return null;
- }
-
- return (
-
-
-
- {notifications.map((notification, index) => (
- {notification.message}
- ))}
-
-
-
- );
- };
-
- const getRecommendationTerm = (): RecommendationItem => {
- if (!report) {
- return undefined;
- }
-
- 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 handleOnSelected = (value: Interval) => {
- setCurrentInterval(value);
- };
-
- const isLoading = reportFetchStatus === FetchStatus.inProgress;
+ const queryFromRoute = useQueryFromRoute();
return (
-
-
- {isLoading ? (
-
- ) : (
- <>
- {getAlert()}
-
- >
- )}
-
);
};
-const useQueryFromRoute = () => {
- const location = useLocation();
- return parseQuery(location.search);
-};
-
-// eslint-disable-next-line no-empty-pattern
-const useMapToProps = (): OptimizationsBreakdownStateProps => {
- const dispatch: ThunkDispatch = useDispatch();
- const queryFromRoute = useQueryFromRoute();
-
- const reportQueryString = queryFromRoute ? queryFromRoute.id : '';
- 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 {
- breadcrumbLabel: queryFromRoute[breadcrumbLabelKey],
- report,
- reportError,
- reportFetchStatus,
- reportQueryString,
- };
-};
-
export default OptimizationsBreakdown;
diff --git a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx
index dc64b0761..1042343e0 100644
--- a/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx
+++ b/src/routes/optimizations/optimizationsDetails/optimizationsDetails.tsx
@@ -1,248 +1,38 @@
-import { PageSection, Pagination, PaginationVariant } from '@patternfly/react-core';
-import type { Query } from 'api/queries/query';
-import { getQuery, 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';
-import type { AxiosError } from 'axios';
+import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent';
import messages from 'locales/messages';
-import React, { useEffect, useState } from 'react';
+import React 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 { routes } from 'routes';
-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 { clearQueryState, getQueryState } from 'routes/utils/queryState';
-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';
interface OptimizationsDetailsOwnProps {
// TBD...
}
-export interface OptimizationsDetailsStateProps {
- groupBy?: string;
- report: RosReport;
- reportError: AxiosError;
- reportFetchStatus: FetchStatus;
- reportQueryString: string;
-}
-
-export interface OptimizationsDetailsMapProps {
- query?: RosQuery;
-}
-
type OptimizationsDetailsProps = OptimizationsDetailsOwnProps;
-const baseQuery: RosQuery = {
- limit: 10,
- offset: 0,
- order_by: {
- last_reported: 'desc',
- },
-};
-
-const reportType = RosType.ros as any;
-const reportPathsType = RosPathsType.recommendations as any;
-
const OptimizationsDetails: React.FC = () => {
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,
- });
-
- // Clear queryState, returned from breakdown page, after query has been initialized
- useEffect(() => {
- clearQueryState(location, 'optimizations');
- }, [reportQueryString]);
-
- const getPagination = (isDisabled = false, isBottom = false) => {
- const count = report?.meta ? report.meta.count : 0;
- const limit = report?.meta ? report.meta.limit : baseQuery.limit;
- const offset = 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={{
- paginationAriaLabel: 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}
- query={query}
- report={report}
- reportQueryString={reportQueryString}
- />
- );
- };
-
- const getToolbar = () => {
- const itemsPerPage = report?.meta ? report.meta.limit : 0;
- const itemsTotal = report?.meta ? report.meta.count : 0;
- const isDisabled = itemsTotal === 0;
-
- return (
- handleOnFilterAdded(filter)}
- onFilterRemoved={filter => handleOnFilterRemoved(filter)}
- pagination={getPagination(isDisabled)}
- query={query}
+ linkPath={formatPath(routes.optimizationsBreakdown.path)}
+ linkState={{
+ ...(location.state && location.state),
+ }}
/>
- );
- };
-
- const handleOnFilterAdded = filter => {
- const newQuery = queryUtils.handleOnFilterAdded(query, filter);
- setQuery(newQuery);
- };
-
- const handleOnFilterRemoved = filter => {
- const newQuery = queryUtils.handleOnFilterRemoved(query, filter);
- setQuery(newQuery);
- };
-
- const handleOnPerPageSelect = perPage => {
- const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true);
- setQuery(newQuery);
- };
-
- const handleOnSetPage = pageNumber => {
- const newQuery = queryUtils.handleOnSetPage(query, report, pageNumber, true);
- setQuery(newQuery);
- };
-
- const handleOnSort = (sortType, isSortAscending) => {
- const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending);
- setQuery(newQuery);
- };
-
- const itemsTotal = report?.meta ? report.meta.count : 0;
- const isDisabled = itemsTotal === 0;
- const title = intl.formatMessage(messages.optimizations);
- const hasOptimizations = 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 }: OptimizationsDetailsMapProps): OptimizationsDetailsStateProps => {
- 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 {
- groupBy,
- report,
- reportError,
- reportFetchStatus,
- reportQueryString,
- };
-};
-
export default OptimizationsDetails;
diff --git a/src/routes/overview/components/dashboardWidgetBase.tsx b/src/routes/overview/components/dashboardWidgetBase.tsx
index a42110913..42bbfe84a 100644
--- a/src/routes/overview/components/dashboardWidgetBase.tsx
+++ b/src/routes/overview/components/dashboardWidgetBase.tsx
@@ -1,14 +1,15 @@
import type { MessageDescriptor } from '@formatjs/intl/src/types';
import { Tab, Tabs, TabTitleText } from '@patternfly/react-core';
+import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent';
import type { Forecast } from 'api/forecasts/forecast';
import { getQuery } from 'api/queries/query';
import type { Report } from 'api/reports/report';
-import type { RosReport } 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 { Link } from 'react-router-dom';
+import { routes } from 'routes';
import { ComputedReportItemType, DatumType, transformReport } from 'routes/components/charts/common/chartDatum';
import {
getComputedForecast,
@@ -27,11 +28,11 @@ import {
ReportSummaryTrend,
ReportSummaryUsage,
} from 'routes/components/reports/reportSummary';
-import { OptimizationsSummary } from 'routes/overview/components/optimizationsSummary';
import type { DashboardWidget } from 'store/dashboard/common/dashboardCommon';
import { DashboardChartType } from 'store/dashboard/common/dashboardCommon';
import { OcpDashboardTab } from 'store/dashboard/ocpDashboard';
import { formatCurrency, formatUnits, unitsLookupKey } from 'utils/format';
+import { formatPath } from 'utils/paths';
import { ChartComparison } from './chartComparison';
import { chartStyles, styles } from './dashboardWidget.styles';
@@ -61,9 +62,6 @@ export interface DashboardWidgetStateProps extends DashboardWidget {
previousReport?: Report;
previousReportError?: AxiosError;
previousReportFetchStatus?: number;
- rosReport?: RosReport;
- rosReportError?: AxiosError;
- rosReportFetchStatus?: number;
tabsReport?: Report;
tabsReportError?: AxiosError;
tabsReportFetchStatus?: number;
@@ -77,7 +75,6 @@ export interface DashboardWidgetState {
interface DashboardWidgetDispatchProps {
fetchForecasts: (widgetId) => void;
fetchReports: (widgetId) => void;
- fetchRosReports: (widgetId) => void;
updateTab: (id, availableTabs) => void;
}
@@ -105,9 +102,6 @@ class DashboardWidgetBase extends React.Component {
- const { rosReportFetchStatus, rosReport, titleKey } = this.props;
-
- return ;
+ return (
+
+ );
};
private getTab = (tab: string, index: number) => {
@@ -573,13 +572,6 @@ class DashboardWidgetBase extends React.Component {
- const { fetchRosReports, isRosFeatureEnabled, widgetId } = this.props;
- if (fetchRosReports && isRosFeatureEnabled) {
- fetchRosReports(widgetId);
- }
- };
-
public render() {
const { details, isRosFeatureEnabled } = this.props;
if (details.showOptimizations) {
diff --git a/src/routes/overview/components/optimizationsSummary/index.ts b/src/routes/overview/components/optimizationsSummary/index.ts
deleted file mode 100644
index 0cfb40f8b..000000000
--- a/src/routes/overview/components/optimizationsSummary/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as OptimizationsSummary } from './optimizationsSummary';
diff --git a/src/routes/overview/components/optimizationsSummary/optimizations.styles.ts b/src/routes/overview/components/optimizationsSummary/optimizations.styles.ts
deleted file mode 100644
index cf85bacc2..000000000
--- a/src/routes/overview/components/optimizationsSummary/optimizations.styles.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import global_FontSize_md from '@patternfly/react-tokens/dist/js/global_FontSize_md';
-import type React from 'react';
-
-export const styles = {
- infoIcon: {
- fontSize: global_FontSize_md.value,
- },
-} as { [className: string]: React.CSSProperties };
diff --git a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss b/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss
deleted file mode 100644
index 7a01575bb..000000000
--- a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-@import url("~@patternfly/patternfly/base/patternfly-variables.css");
-
-.skeleton {
- height: 125px;
- margin-bottom: var(--pf-v5-global--spacer--md);
- margin-top: var(--pf-v5-global--spacer--md);
-}
-
-.summary {
- height: 100%;
-}
diff --git a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx b/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx
deleted file mode 100644
index edc565d61..000000000
--- a/src/routes/overview/components/optimizationsSummary/optimizationsSummary.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import './optimizationsSummary.scss';
-
-import {
- Button,
- ButtonVariant,
- Card,
- CardBody,
- CardTitle,
- Popover,
- Skeleton,
- Title,
- TitleSizes,
-} from '@patternfly/react-core';
-import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon';
-import type { RosReport } from 'api/ros/ros';
-import messages from 'locales/messages';
-import React from 'react';
-import type { WrappedComponentProps } from 'react-intl';
-import type { MessageDescriptor } from 'react-intl';
-import { injectIntl } from 'react-intl';
-import { Link } from 'react-router-dom';
-import { routes } from 'routes';
-import { skeletonWidth } from 'routes/utils/skeleton';
-import { FetchStatus } from 'store/common';
-import { formatPath } from 'utils/paths';
-
-import { styles } from './optimizations.styles';
-
-export interface OptimizationsSummaryProps extends WrappedComponentProps {
- report: RosReport;
- status: number;
- title: MessageDescriptor;
-}
-
-const OptimizationsSummaryBase: React.FC = ({ intl, report, status, title }) => {
- const count = report?.meta ? report.meta.count : 0;
- const description = intl.formatMessage(messages.optimizationsDetails, { count });
- return (
-
-
-
- {intl.formatMessage(title)}
-
- {intl.formatMessage(messages.optimizationsInfo)}
}
- >
-
-
-
-
-
-
- {status === FetchStatus.inProgress ? (
- <>
-
-
- >
- ) : count > 0 ? (
- {description}
- ) : (
- description
- )}
-
-
- );
-};
-
-const OptimizationsSummary = injectIntl(OptimizationsSummaryBase);
-
-export default OptimizationsSummary;
diff --git a/src/routes/overview/ocpDashboard/ocpDashboardWidget.tsx b/src/routes/overview/ocpDashboard/ocpDashboardWidget.tsx
index da09be703..54bdcce4d 100644
--- a/src/routes/overview/ocpDashboard/ocpDashboardWidget.tsx
+++ b/src/routes/overview/ocpDashboard/ocpDashboardWidget.tsx
@@ -8,7 +8,6 @@ import { ocpDashboardActions, ocpDashboardSelectors, OcpDashboardTab } from 'sto
import { featureFlagsSelectors } from 'store/featureFlags';
import { forecastSelectors } from 'store/forecasts';
import { reportSelectors } from 'store/reports';
-import { rosSelectors } from 'store/ros';
import { getCurrency } from 'utils/localStorage';
import { chartStyles } from './ocpDashboardWidget.styles';
@@ -16,7 +15,6 @@ import { chartStyles } from './ocpDashboardWidget.styles';
interface OcpDashboardWidgetDispatchProps {
fetchForecasts: typeof ocpDashboardActions.fetchWidgetForecasts;
fetchReports: typeof ocpDashboardActions.fetchWidgetReports;
- fetchRosReports: typeof ocpDashboardActions.fetchWidgetRosReports;
updateTab: typeof ocpDashboardActions.changeWidgetTab;
}
@@ -114,23 +112,9 @@ const mapStateToProps = createMapStateToProps {
};
};
-export const fetchWidgetRosReports = (id: number): ThunkAction => {
- return (dispatch, getState) => {
- const state = getState();
- const widget = selectWidget(state, id);
- const { optimizations } = selectWidgetQueries(state, id);
-
- if (widget.rosPathsType && widget.rosType) {
- dispatch(rosActions.fetchRosReport(widget.rosPathsType, widget.rosType, optimizations));
- }
- };
-};
-
export const setWidgetTab = createAction('ocpDashboard/widget/tab')<{
id: number;
tab: OcpDashboardTab;
diff --git a/src/store/dashboard/ocpDashboard/ocpDashboardWidgets.ts b/src/store/dashboard/ocpDashboard/ocpDashboardWidgets.ts
index b32d4d98d..31c73b23a 100644
--- a/src/store/dashboard/ocpDashboard/ocpDashboardWidgets.ts
+++ b/src/store/dashboard/ocpDashboard/ocpDashboardWidgets.ts
@@ -1,6 +1,5 @@
import { ForecastPathsType, ForecastType } from 'api/forecasts/forecast';
import { ReportPathsType, ReportType } from 'api/reports/report';
-import { RosPathsType, RosType } from 'api/ros/ros';
import messages from 'locales/messages';
import { routes } from 'routes';
import {
@@ -99,8 +98,6 @@ export const memoryWidget: OcpDashboardWidget = {
export const optimizationsWidget: OcpDashboardWidget = {
id: getId(),
titleKey: messages.optimizations,
- rosPathsType: RosPathsType.recommendations,
- rosType: RosType.ros,
details: {
showOptimizations: true,
},
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 26e0edd84..765204aba 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -39,7 +39,6 @@ import { orgReducer, orgStateKey } from 'store/orgs';
import { priceListReducer, priceListStateKey } from 'store/priceList';
import { reportReducer, reportStateKey } from 'store/reports';
import { resourceReducer, resourceStateKey } from 'store/resources';
-import { rosReducer, rosStateKey } from 'store/ros';
import { settingsReducer, settingsStateKey } from 'store/settings';
import { sourcesReducer, sourcesStateKey } from 'store/sourceSettings';
import { tagReducer, tagStateKey } from 'store/tags';
@@ -92,7 +91,6 @@ export const rootReducer = combineReducers({
[rhelCostOverviewStateKey]: rhelCostOverviewReducer,
[rhelDashboardStateKey]: rhelDashboardReducer,
[rhelHistoricalDataStateKey]: rhelHistoricalDataReducer,
- [rosStateKey]: rosReducer,
[settingsStateKey]: settingsReducer,
[sourcesStateKey]: sourcesReducer,
[tagStateKey]: tagReducer,
diff --git a/src/store/ros/__snapshots__/ros.test.ts.snap b/src/store/ros/__snapshots__/ros.test.ts.snap
deleted file mode 100644
index c40443195..000000000
--- a/src/store/ros/__snapshots__/ros.test.ts.snap
+++ /dev/null
@@ -1,9 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`default state 1`] = `
-{
- "byId": Map {},
- "errors": Map {},
- "fetchStatus": Map {},
-}
-`;
diff --git a/src/store/ros/index.ts b/src/store/ros/index.ts
deleted file mode 100644
index 80e11b672..000000000
--- a/src/store/ros/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as rosActions from './rosActions';
-import { rosStateKey } from './rosCommon';
-import type { CachedRos, RosAction, RosState } from './rosReducer';
-import { rosReducer } from './rosReducer';
-import * as rosSelectors from './rosSelectors';
-
-export type { RosAction, CachedRos, RosState };
-export { rosActions, rosReducer, rosSelectors, rosStateKey };
diff --git a/src/store/ros/ros.test.ts b/src/store/ros/ros.test.ts
deleted file mode 100644
index f9f17a6ab..000000000
--- a/src/store/ros/ros.test.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-jest.mock('api/ros/rosUtils');
-
-import { waitFor } from '@testing-library/react';
-import type { RosReport } from 'api/ros/ros';
-import { RosPathsType, RosType } from 'api/ros/ros';
-import { runRosReport } from 'api/ros/rosUtils';
-import { FetchStatus } from 'store/common';
-import { createMockStoreCreator } from 'store/mockStore';
-
-import * as actions from './rosActions';
-import { rosStateKey } from './rosCommon';
-import { rosReducer } from './rosReducer';
-import * as selectors from './rosSelectors';
-
-const createRossStore = createMockStoreCreator({
- [rosStateKey]: rosReducer,
-});
-
-const runRosMock = runRosReport as jest.Mock;
-
-const mockRos: RosReport = {
- data: [],
- total: {
- value: 100,
- units: 'USD',
- },
-} as any;
-
-const rosType = RosType.cost;
-const rosPathsType = RosPathsType.recommendations;
-const rosQueryString = 'rosQueryString';
-
-runRosMock.mockResolvedValue({ data: mockRos });
-global.Date.now = jest.fn(() => 12345);
-
-jest.spyOn(actions, 'fetchRosReport');
-jest.spyOn(selectors, 'selectRosFetchStatus');
-
-test('default state', () => {
- const store = createRossStore();
- expect(selectors.selectRosState(store.getState())).toMatchSnapshot();
-});
-
-test('fetch ros success', async () => {
- const store = createRossStore();
- store.dispatch(actions.fetchRosReport(rosPathsType, rosType, rosQueryString));
- expect(runRosMock).toBeCalled();
- expect(selectors.selectRosFetchStatus(store.getState(), rosPathsType, rosType, rosQueryString)).toBe(
- FetchStatus.inProgress
- );
- await waitFor(() => expect(selectors.selectRosFetchStatus).toHaveBeenCalled());
- const finishedState = store.getState();
- expect(selectors.selectRosFetchStatus(finishedState, rosPathsType, rosType, rosQueryString)).toBe(
- FetchStatus.complete
- );
- expect(selectors.selectRosError(finishedState, rosPathsType, rosType, rosQueryString)).toBe(null);
-});
-
-test('fetch ros failure', async () => {
- const store = createRossStore();
- const error = Symbol('ros error');
- runRosMock.mockRejectedValueOnce(error);
- store.dispatch(actions.fetchRosReport(rosPathsType, rosType, rosQueryString));
- expect(runRosReport).toBeCalled();
- expect(selectors.selectRosFetchStatus(store.getState(), rosPathsType, rosType, rosQueryString)).toBe(
- FetchStatus.inProgress
- );
- await waitFor(() => expect(selectors.selectRosFetchStatus).toHaveBeenCalled());
- const finishedState = store.getState();
- expect(selectors.selectRosFetchStatus(finishedState, rosPathsType, rosType, rosQueryString)).toBe(
- FetchStatus.complete
- );
- // Todo: Temporarily disable test while overriding the fail state to feed fake data
- // expect(selectors.selectRosError(finishedState, rosPathsType, rosType, rosQueryString)).toBe(error);
-});
-
-test('does not fetch ros if the request is in progress', () => {
- const store = createRossStore();
- store.dispatch(actions.fetchRosReport(rosPathsType, rosType, rosQueryString));
- store.dispatch(actions.fetchRosReport(rosPathsType, rosType, rosQueryString));
- expect(runRosReport).toHaveBeenCalledTimes(1);
-});
-
-test('ros is not refetched if it has not expired', async () => {
- const store = createRossStore();
- store.dispatch(actions.fetchRosReport(rosPathsType, rosType, rosQueryString));
- await waitFor(() => expect(actions.fetchRosReport).toHaveBeenCalled());
- store.dispatch(actions.fetchRosReport(rosPathsType, rosType, rosQueryString));
- expect(runRosReport).toHaveBeenCalledTimes(1);
-});
diff --git a/src/store/ros/rosActions.ts b/src/store/ros/rosActions.ts
deleted file mode 100644
index 3a056ab88..000000000
--- a/src/store/ros/rosActions.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { RosReport, RosType } from 'api/ros/ros';
-import type { RosPathsType } from 'api/ros/ros';
-import { runRosReport } from 'api/ros/rosUtils';
-import type { AxiosError } from 'axios';
-import type { ThunkAction } from 'store/common';
-import { FetchStatus } from 'store/common';
-import type { RootState } from 'store/rootReducer';
-import { createAction } from 'typesafe-actions';
-
-import { getFetchId } from './rosCommon';
-import { selectRos, selectRosError, selectRosFetchStatus } from './rosSelectors';
-
-const expirationMS = 30 * 60 * 1000; // 30 minutes
-
-interface RosActionMeta {
- fetchId: string;
-}
-
-export const fetchRosRequest = createAction('ros/request')();
-export const fetchRosSuccess = createAction('ros/success')();
-export const fetchRosFailure = createAction('ros/failure')();
-
-export function fetchRosReport(rosPathsType: RosPathsType, rosType: RosType, rosQueryString: string): ThunkAction {
- return (dispatch, getState) => {
- if (!isRosExpired(getState(), rosPathsType, rosType, rosQueryString)) {
- return;
- }
-
- const meta: RosActionMeta = {
- fetchId: getFetchId(rosPathsType, rosType, rosQueryString),
- };
-
- dispatch(fetchRosRequest(meta));
- runRosReport(rosPathsType, rosType, rosQueryString)
- .then(res => {
- dispatch(fetchRosSuccess(res.data, meta));
- })
- .catch(err => {
- dispatch(fetchRosFailure(err, meta));
- });
- };
-}
-
-function isRosExpired(state: RootState, rosPathsType: RosPathsType, rosType: RosType, rosQueryString: string) {
- const ros = selectRos(state, rosPathsType, rosType, rosQueryString);
- const fetchError = selectRosError(state, rosPathsType, rosType, rosQueryString);
- const fetchStatus = selectRosFetchStatus(state, rosPathsType, rosType, rosQueryString);
- if (fetchError || fetchStatus === FetchStatus.inProgress) {
- return false;
- }
-
- if (!ros) {
- return true;
- }
-
- const now = Date.now();
- return now > ros.timeRequested + expirationMS;
-}
diff --git a/src/store/ros/rosCommon.ts b/src/store/ros/rosCommon.ts
deleted file mode 100644
index 8c61d8d8c..000000000
--- a/src/store/ros/rosCommon.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { RosPathsType, RosType } from 'api/ros/ros';
-export const rosStateKey = 'ros';
-
-export function getFetchId(rosPathsType: RosPathsType, rosType: RosType, rosQueryString: string) {
- return `${rosPathsType}--${rosType}--${rosQueryString}`;
-}
diff --git a/src/store/ros/rosReducer.ts b/src/store/ros/rosReducer.ts
deleted file mode 100644
index 8ea30fd40..000000000
--- a/src/store/ros/rosReducer.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import type { RosReport } from 'api/ros/ros';
-import type { AxiosError } from 'axios';
-import { FetchStatus } from 'store/common';
-import { resetState } from 'store/ui/uiActions';
-import type { ActionType } from 'typesafe-actions';
-import { getType } from 'typesafe-actions';
-
-import { fetchRosFailure, fetchRosRequest, fetchRosSuccess } from './rosActions';
-
-export interface CachedRos extends RosReport {
- timeRequested: number;
-}
-
-export type RosState = Readonly<{
- byId: Map;
- fetchStatus: Map;
- errors: Map;
-}>;
-
-const defaultState: RosState = {
- byId: new Map(),
- fetchStatus: new Map(),
- errors: new Map(),
-};
-
-export type RosAction = ActionType<
- typeof fetchRosFailure | typeof fetchRosRequest | typeof fetchRosSuccess | typeof resetState
->;
-
-export function rosReducer(state = defaultState, action: RosAction): RosState {
- switch (action.type) {
- case getType(resetState):
- state = defaultState;
- return state;
-
- case getType(fetchRosRequest):
- return {
- ...state,
- fetchStatus: new Map(state.fetchStatus).set(action.payload.fetchId, FetchStatus.inProgress),
- };
-
- case getType(fetchRosSuccess):
- return {
- ...state,
- fetchStatus: new Map(state.fetchStatus).set(action.meta.fetchId, FetchStatus.complete),
- byId: new Map(state.byId).set(action.meta.fetchId, {
- ...action.payload,
- timeRequested: Date.now(),
- }),
- errors: new Map(state.errors).set(action.meta.fetchId, null),
- };
-
- case getType(fetchRosFailure):
- return {
- ...state,
- fetchStatus: new Map(state.fetchStatus).set(action.meta.fetchId, FetchStatus.complete),
- errors: new Map(state.errors).set(action.meta.fetchId, action.payload),
- };
- default:
- return state;
- }
-}
diff --git a/src/store/ros/rosSelectors.ts b/src/store/ros/rosSelectors.ts
deleted file mode 100644
index 883211d6b..000000000
--- a/src/store/ros/rosSelectors.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { RosPathsType, RosType } from 'api/ros/ros';
-import type { RootState } from 'store/rootReducer';
-
-import { getFetchId, rosStateKey } from './rosCommon';
-
-export const selectRosState = (state: RootState) => state[rosStateKey];
-
-export const selectRos = (state: RootState, rosPathsType: RosPathsType, rosType: RosType, rosQueryString: string) =>
- selectRosState(state).byId.get(getFetchId(rosPathsType, rosType, rosQueryString));
-
-export const selectRosFetchStatus = (
- state: RootState,
- rosPathsType: RosPathsType,
- rosType: RosType,
- rosQueryString: string
-) => selectRosState(state).fetchStatus.get(getFetchId(rosPathsType, rosType, rosQueryString));
-
-export const selectRosError = (
- state: RootState,
- rosPathsType: RosPathsType,
- rosType: RosType,
- rosQueryString: string
-) => selectRosState(state).errors.get(getFetchId(rosPathsType, rosType, rosQueryString));
diff --git a/src/utils/recomendations.ts b/src/utils/recomendations.ts
deleted file mode 100644
index f15929fad..000000000
--- a/src/utils/recomendations.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { Notification, RecommendationItem } from 'api/ros/recommendations';
-import type { RecommendationItems } from 'api/ros/recommendations';
-
-export const getNotifications = (term: RecommendationItem): Notification[] => {
- if (!hasNotification(term)) {
- return undefined;
- }
- return Object.keys(term.notifications).map(key => term.notifications[key]);
-};
-
-export const hasNotification = (term: RecommendationItem) => {
- if (!term?.notifications) {
- return false;
- }
- const keys = Object.keys(term.notifications);
- return keys.length > 0;
-};
-
-export const hasRecommendation = (term: RecommendationItem) => {
- if (!term) {
- return false;
- }
-
- const hasConfigLimitsCpu = hasRecommendationValues(term, 'config', 'limits', 'cpu');
- const hasConfigLimitsMemory = hasRecommendationValues(term, 'config', 'limits', 'memory');
- const hasConfigRequestsCpu = hasRecommendationValues(term, 'config', 'requests', 'cpu');
- const hasConfigRequestsMemory = hasRecommendationValues(term, 'config', 'requests', 'memory');
-
- return hasConfigLimitsCpu || hasConfigLimitsMemory || hasConfigRequestsCpu || hasConfigRequestsMemory;
-};
-
-// Helper to determine if config and variation are empty objects
-// Example: key1 = config, key2 = limits, key3 = cpu
-export const hasRecommendationValues = (term: RecommendationItem, key1: string, key2: string, key3: string) => {
- let result = false;
- if (term && term[key1] && term[key1][key2] && term[key1][key2][key3]) {
- result = Object.keys(term[key1][key2][key3]).length > 0;
- }
- return result;
-};
-
-export const hasWarning = (recommendations: RecommendationItems) => {
- if (!recommendations) {
- return false;
- }
- return (
- hasNotification(recommendations.short_term) ||
- hasNotification(recommendations.medium_term) ||
- hasNotification(recommendations.long_term)
- );
-};
diff --git a/test/transformTS.js b/test/transformTS.js
index 06f5bef80..5326b2b2f 100644
--- a/test/transformTS.js
+++ b/test/transformTS.js
@@ -13,7 +13,12 @@ delete options.sourceMap;
module.exports = {
process(src, path) {
- if (path.endsWith('.ts') || path.endsWith('.tsx') || path.includes('@patternfly/react-icons/dist/esm')) {
+ if (
+ path.endsWith('.ts') ||
+ path.endsWith('.tsx') ||
+ path.includes('@patternfly/react-icons/dist/esm') ||
+ path.includes('uuid/dist/esm-browser')
+ ) {
return { code: tsc.transpile(src, options, path, []) };
}
return { code: src };