From 35221ff21690abbea0aded20ad4277a578613309 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 31 Aug 2023 10:35:25 -0400 Subject: [PATCH 01/37] Test paused state of inactive sources https://issues.redhat.com/browse/COST-4163 --- src/api/providers.ts | 1 + src/components/inactiveSources/inactiveSources.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/providers.ts b/src/api/providers.ts index 442278ad7..fed633825 100644 --- a/src/api/providers.ts +++ b/src/api/providers.ts @@ -47,6 +47,7 @@ export interface Provider { has_data?: boolean; infrastructure?: ProviderInfrastructure; name?: string; + paused?: boolean; previous_month_data?: boolean; source_type?: string; type?: string; diff --git a/src/components/inactiveSources/inactiveSources.tsx b/src/components/inactiveSources/inactiveSources.tsx index f088d943a..ab8d45faa 100644 --- a/src/components/inactiveSources/inactiveSources.tsx +++ b/src/components/inactiveSources/inactiveSources.tsx @@ -35,7 +35,7 @@ class InactiveSourcesBase extends React.Component { if (providers && providers.data) { providers.data.map(data => { - if (data.active !== true) { + if (data.active !== true && data.paused !== true) { sources.push(data.name); } }); From 68fce851b1e310e97db4b0e195b305af9367315a Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 31 Aug 2023 11:22:03 -0400 Subject: [PATCH 02/37] Fix notification message for updated tag https://issues.redhat.com/browse/COST-3307 --- locales/data.json | 83 +++++++++++++++++++++++++++ locales/translations.json | 1 + src/locales/messages.ts | 9 +++ src/store/settings/settingsActions.ts | 25 +++++++- 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/locales/data.json b/locales/data.json index 9174a2c62..1f03ed9c1 100644 --- a/locales/data.json +++ b/locales/data.json @@ -11549,6 +11549,89 @@ "value": "Settings for Cost Management were replaced with new values" } ], + "settingsSuccessTags": [ + { + "options": { + "disable": { + "value": [ + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tag disabled" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tags disabled" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "count" + } + ] + }, + "enable": { + "value": [ + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tag enabled" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tags enabled" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "count" + } + ] + }, + "other": { + "value": [] + } + }, + "type": 5, + "value": "value" + } + ], "settingsSuccessTitle": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index 5bf49d7e5..b12f07fb9 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -519,6 +519,7 @@ "settingsSuccessChanges": "Changes will be reflected in report summarizations within 24 hours", "settingsSuccessCostCategoryKeys": "{value, select, enable {{count, plural, one {{count} cost category key enabled} other {{count} cost category keys enabled}}} disable {{count, plural, one {{count} cost category key disabled} other {{count} cost category keys disabled}}} other {}}", "settingsSuccessDesc": "Settings for Cost Management were replaced with new values", + "settingsSuccessTags": "{value, select, enable {{count, plural, one {{count} tag enabled} other {{count} tags enabled}}} disable {{count, plural, one {{count} tag disabled} other {{count} tags disabled}}} other {}}", "settingsSuccessTitle": "Application settings saved", "settingsTitle": "Cost Management Settings", "sinceDate": "{dateRange}", diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 96eaac621..9b79d4c2c 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -3195,6 +3195,15 @@ export default defineMessages({ description: 'Settings for Cost Management were replaced with new values', id: 'settingsSuccessDesc', }, + settingsSuccessTags: { + defaultMessage: + '{value, select, ' + + 'enable {{count, plural, one {{count} tag enabled} other {{count} tags enabled}}} ' + + 'disable {{count, plural, one {{count} tag disabled} other {{count} tags disabled}}} ' + + 'other {}}', + description: 'Cost category keys enabled or disabled', + id: 'settingsSuccessTags', + }, settingsSuccessTitle: { defaultMessage: 'Application settings saved', description: 'Application settings saved', diff --git a/src/store/settings/settingsActions.ts b/src/store/settings/settingsActions.ts index a81bd4205..78119631d 100644 --- a/src/store/settings/settingsActions.ts +++ b/src/store/settings/settingsActions.ts @@ -76,14 +76,35 @@ export function updateSettings(settingsType: SettingsType, payload: SettingsPayl dispatch(updateSettingsRequest(meta)); + let msg; + let status; + switch (settingsType) { + case SettingsType.awsCategoryKeysDisable: + msg = messages.settingsSuccessCostCategoryKeys; + status = 'disable'; + break; + case SettingsType.awsCategoryKeysEnable: + msg = messages.settingsSuccessCostCategoryKeys; + status = 'enable'; + break; + case SettingsType.tagsDisable: + msg = messages.settingsSuccessTags; + status = 'disable'; + break; + case SettingsType.tagsEnable: + msg = messages.settingsSuccessTags; + status = 'enable'; + break; + } + return apiUpdateSettings(settingsType, payload) .then(res => { dispatch(updateSettingsSuccess(res, meta)); dispatch( addNotification({ - title: intl.formatMessage(messages.settingsSuccessCostCategoryKeys, { - ...{ value: settingsType === SettingsType.awsCategoryKeysEnable ? 'enable' : 'disable' }, + title: intl.formatMessage(msg, { count: payload.ids.length, + value: status, }), description: intl.formatMessage(messages.settingsSuccessChanges), variant: AlertVariant.success, From 3fc856a3a129eb294843c59ce6147703a6129768 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Thu, 31 Aug 2023 14:50:56 -0400 Subject: [PATCH 03/37] Limit precision of CPU/Memory/Volume maximums https://issues.redhat.com/browse/COST-4116 --- locales/data.json | 4 ++-- locales/translations.json | 2 +- src/locales/messages.ts | 4 ++-- src/routes/details/components/usageChart/usageChart.tsx | 7 +++---- src/utils/format.ts | 3 ++- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/data.json b/locales/data.json index 1f03ed9c1..ae202868d 100644 --- a/locales/data.json +++ b/locales/data.json @@ -12186,7 +12186,7 @@ "usageSubtitle": [ { "type": 1, - "value": "count" + "value": "value" }, { "type": 0, @@ -12194,7 +12194,7 @@ }, { "type": 1, - "value": "countUnits" + "value": "units" }, { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index b12f07fb9..111985a59 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -553,7 +553,7 @@ "usage": "Usage", "usageCostDesc": "The portion of cost calculated by applying hourly and/or monthly price list rates to metrics.", "usageCostTitle": "Usage cost", - "usageSubtitle": "{count} {countUnits} maximum", + "usageSubtitle": "{value} {units} maximum", "various": "Various", "volumeTitle": "Volume", "workerUnallocated": "Worker unallocated", diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 9b79d4c2c..02905ae59 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -3407,8 +3407,8 @@ export default defineMessages({ id: 'usageCostTitle', }, usageSubtitle: { - defaultMessage: '{count} {countUnits} maximum', - description: '{count} {countUnits} maximum', + defaultMessage: '{value} {units} maximum', + description: '{value} {units} maximum', id: 'usageSubtitle', }, various: { diff --git a/src/routes/details/components/usageChart/usageChart.tsx b/src/routes/details/components/usageChart/usageChart.tsx index b68d64cc2..0f7318a2c 100644 --- a/src/routes/details/components/usageChart/usageChart.tsx +++ b/src/routes/details/components/usageChart/usageChart.tsx @@ -415,14 +415,13 @@ class UsageChartBase extends React.Component { } const { hasCapacityCount, hasCapacityCountUnits } = this.getHasData(); - - const count = hasCapacityCount ? report.meta.total.capacity.count : 0; - const countUnits = intl.formatMessage(messages.units, { + const units = intl.formatMessage(messages.units, { units: unitsLookupKey(hasCapacityCountUnits ? report.meta.total.capacity.count_units : undefined), }); + const value = formatUnits(hasCapacityCount ? report.meta.total.capacity.count : 0, units); if (hasCapacityCount && hasCapacityCountUnits) { - return
{intl.formatMessage(messages.usageSubtitle, { count, countUnits })}
; + return
{intl.formatMessage(messages.usageSubtitle, { value, units })}
; } return null; } diff --git a/src/utils/format.ts b/src/utils/format.ts index 8599d5ec7..303191ca7 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -127,6 +127,7 @@ export const formatUnits: Formatter = (value, units, options) => { switch (lookup) { case 'byte_ms': + case 'core': case 'core_hours': case 'hour': case 'hrs': @@ -181,7 +182,7 @@ const formatUsage: UnitsFormatter = ( value, options: FormatOptions = { minimumFractionDigits: 0, - maximumFractionDigits: 0, + maximumFractionDigits: 2, } ) => { return value.toLocaleString(getLocale(), options); From ec7751be4423259c76a79677ec466a52583184c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:37:28 +0000 Subject: [PATCH 04/37] (chore): Bump webpack-bundle-analyzer from 4.9.0 to 4.9.1 Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 4.9.0 to 4.9.1. - [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases) - [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v4.9.0...v4.9.1) --- updated-dependencies: - dependency-name: webpack-bundle-analyzer dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 141 +++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 64 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..6a70907de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "rimraf": "^5.0.1", "ts-patch": "^3.0.2", "typescript": "^5.2.2", - "webpack-bundle-analyzer": "^4.9.0" + "webpack-bundle-analyzer": "^4.9.1" }, "engines": { "node": ">=18.15.0", @@ -14623,6 +14623,30 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.invokemap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", + "integrity": "sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -14635,6 +14659,18 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "peer": true }, + "node_modules/lodash.pullall": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", + "integrity": "sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==", + "dev": true + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -17811,14 +17847,14 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.20", "mrmime": "^1.0.0", - "totalist": "^1.0.0" + "totalist": "^3.0.0" }, "engines": { "node": ">= 10" @@ -18630,9 +18666,9 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" }, "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "engines": { "node": ">=6" @@ -19830,20 +19866,27 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz", - "integrity": "sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz", + "integrity": "sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", "commander": "^7.2.0", + "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", - "lodash": "^4.17.20", + "is-plain-object": "^5.0.0", + "lodash.debounce": "^4.0.8", + "lodash.escape": "^4.0.1", + "lodash.flatten": "^4.4.0", + "lodash.invokemap": "^4.6.0", + "lodash.pullall": "^4.2.0", + "lodash.uniqby": "^4.7.0", "opener": "^1.5.2", - "sirv": "^1.0.7", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { @@ -19853,55 +19896,6 @@ "node": ">= 10.13.0" } }, - "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/webpack-bundle-analyzer/node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -19911,25 +19905,16 @@ "node": ">= 10" } }, - "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "node": ">=10" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { diff --git a/package.json b/package.json index 92784b2e5..81c2234cb 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "rimraf": "^5.0.1", "ts-patch": "^3.0.2", "typescript": "^5.2.2", - "webpack-bundle-analyzer": "^4.9.0" + "webpack-bundle-analyzer": "^4.9.1" }, "overrides": { "eslint": "^8.48.0", From 76bc7bc0132849b30dfd1fe5c128ed9e03579ac7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:37:58 +0000 Subject: [PATCH 05/37] (chore): Bump eslint-plugin-jsdoc from 46.5.0 to 46.5.1 Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 46.5.0 to 46.5.1. - [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases) - [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc) - [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v46.5.0...v46.5.1) --- updated-dependencies: - dependency-name: eslint-plugin-jsdoc dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..116dd5e1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "eslint": "^8.48.0", "eslint-plugin-formatjs": "^4.10.3", "eslint-plugin-jest-dom": "^5.1.0", - "eslint-plugin-jsdoc": "^46.5.0", + "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-markdown": "^3.0.1", "eslint-plugin-patternfly-react": "^5.0.0", "eslint-plugin-prettier": "^5.0.0", @@ -8521,9 +8521,9 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc": { - "version": "46.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.5.0.tgz", - "integrity": "sha512-aulXdA4I1dyWpzyS1Nh/GNoS6PavzeucxEapnMR4JUERowWvaEk2Y4A5irpHAcdXtBBHLVe8WIhdXNjoAlGQgA==", + "version": "46.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.5.1.tgz", + "integrity": "sha512-CPbvKprmEuJYoxMj5g8gXfPqUGgcqMM6jpH06Kp4pn5Uy5MrPkFKzoD7UFp2E4RBzfXbJz1+TeuEivwFVMkXBg==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.40.1", diff --git a/package.json b/package.json index 92784b2e5..b52a62611 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "eslint": "^8.48.0", "eslint-plugin-formatjs": "^4.10.3", "eslint-plugin-jest-dom": "^5.1.0", - "eslint-plugin-jsdoc": "^46.5.0", + "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-markdown": "^3.0.1", "eslint-plugin-patternfly-react": "^5.0.0", "eslint-plugin-prettier": "^5.0.0", From dcafcf30ff24fdc968a3eb286f331df3625e5cb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:38:10 +0000 Subject: [PATCH 06/37] (chore): Bump prettier from 3.0.2 to 3.0.3 Bumps [prettier](https://github.com/prettier/prettier) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..8f9e85b71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "jest-mock-axios": "^4.7.2", "jws": "^4.0.0", "npm-run-all": "^4.1.5", - "prettier": "^3.0.2", + "prettier": "^3.0.3", "rimraf": "^5.0.1", "ts-patch": "^3.0.2", "typescript": "^5.2.2", @@ -16382,9 +16382,9 @@ } }, "node_modules/prettier": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", - "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/package.json b/package.json index 92784b2e5..0fd5eafca 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "jest-mock-axios": "^4.7.2", "jws": "^4.0.0", "npm-run-all": "^4.1.5", - "prettier": "^3.0.2", + "prettier": "^3.0.3", "rimraf": "^5.0.1", "ts-patch": "^3.0.2", "typescript": "^5.2.2", From b300502f176a1aba1f761c951747f7dbe2d497d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:38:33 +0000 Subject: [PATCH 07/37] (chore): Bump @typescript-eslint/parser from 6.4.1 to 6.5.0 Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 6.4.1 to 6.5.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.5.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 135 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..57f93f364 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "@types/react-redux": "^7.1.26", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", + "@typescript-eslint/parser": "^6.5.0", "@xstate/test": "^0.5.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^11.0.0", @@ -3745,15 +3745,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.1.tgz", - "integrity": "sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", + "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4" }, "engines": { @@ -3772,6 +3772,125 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", + "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", + "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", + "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz", diff --git a/package.json b/package.json index 92784b2e5..52d485043 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@types/react-redux": "^7.1.26", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", + "@typescript-eslint/parser": "^6.5.0", "@xstate/test": "^0.5.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^11.0.0", From 7a64d363561c1365333597321e5d85dab4cd4e3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:38:50 +0000 Subject: [PATCH 08/37] (chore): Bump @types/qs from 6.9.7 to 6.9.8 Bumps [@types/qs](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/qs) from 6.9.7 to 6.9.8. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/qs) --- updated-dependencies: - dependency-name: "@types/qs" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..f08989c05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.5.4", - "@types/qs": "^6.9.7", + "@types/qs": "^6.9.8", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/react-redux": "^7.1.26", @@ -3411,9 +3411,9 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", "dev": true }, "node_modules/@types/range-parser": { diff --git a/package.json b/package.json index 92784b2e5..06c22451e 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.5.4", - "@types/qs": "^6.9.7", + "@types/qs": "^6.9.8", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", "@types/react-redux": "^7.1.26", From 1950edf2dc56da7c4a6166cac7e86bd30d359eb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:39:04 +0000 Subject: [PATCH 09/37] (chore): Bump @redhat-cloud-services/frontend-components-translations Bumps [@redhat-cloud-services/frontend-components-translations](https://github.com/RedHatInsights/frontend-components) from 3.2.6 to 3.2.7. - [Commits](https://github.com/RedHatInsights/frontend-components/commits) --- updated-dependencies: - dependency-name: "@redhat-cloud-services/frontend-components-translations" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..946248d24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@patternfly/react-tokens": "4.94.6", "@redhat-cloud-services/frontend-components": "^3.11.5", "@redhat-cloud-services/frontend-components-notifications": "^3.2.16", - "@redhat-cloud-services/frontend-components-translations": "^3.2.6", + "@redhat-cloud-services/frontend-components-translations": "^3.2.7", "@redhat-cloud-services/frontend-components-utilities": "^3.7.6", "@redhat-cloud-services/rbac-client": "^1.2.6", "@unleash/proxy-client-react": "^3.6.0", @@ -2619,17 +2619,17 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-translations": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-translations/-/frontend-components-translations-3.2.6.tgz", - "integrity": "sha512-swndL4HO6ZnJ3BylGCyk8BUhtx8U5iic1RiQ1OrrW03TudfJlyR43Q0M2IjUpbs8hPaahBk2qjj8TZUafazUYA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-translations/-/frontend-components-translations-3.2.7.tgz", + "integrity": "sha512-g068CmZ8ff9wGHWyegO/acOVrRd6PGa9KsT1Hb/gl9Ax3LAa/UxlX7Ng7d/HL/KW0VZQB2yavWB8wbS7xsngVQ==", "optionalDependencies": { "@redhat-cloud-services/frontend-components-utilities": "^3.2.25" }, "peerDependencies": { "prop-types": "^15.6.2", - "react": "^16.14.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0", - "react-intl": "^5.17.4" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-intl": "^6.4.4" } }, "node_modules/@redhat-cloud-services/frontend-components-utilities": { diff --git a/package.json b/package.json index 92784b2e5..3485b9595 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@patternfly/react-tokens": "4.94.6", "@redhat-cloud-services/frontend-components": "^3.11.5", "@redhat-cloud-services/frontend-components-notifications": "^3.2.16", - "@redhat-cloud-services/frontend-components-translations": "^3.2.6", + "@redhat-cloud-services/frontend-components-translations": "^3.2.7", "@redhat-cloud-services/frontend-components-utilities": "^3.7.6", "@redhat-cloud-services/rbac-client": "^1.2.6", "@unleash/proxy-client-react": "^3.6.0", From 9c5a54b4bc737b5044fa05255b752019c57fe8bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:40:29 +0000 Subject: [PATCH 10/37] (chore): Bump @redhat-cloud-services/rbac-client from 1.2.6 to 1.2.7 Bumps [@redhat-cloud-services/rbac-client](https://github.com/RedHatInsights/javascript-clients) from 1.2.6 to 1.2.7. - [Release notes](https://github.com/RedHatInsights/javascript-clients/releases) - [Commits](https://github.com/RedHatInsights/javascript-clients/commits) --- updated-dependencies: - dependency-name: "@redhat-cloud-services/rbac-client" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641f42bd5..f8234074b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@redhat-cloud-services/frontend-components-notifications": "^3.2.16", "@redhat-cloud-services/frontend-components-translations": "^3.2.6", "@redhat-cloud-services/frontend-components-utilities": "^3.7.6", - "@redhat-cloud-services/rbac-client": "^1.2.6", + "@redhat-cloud-services/rbac-client": "^1.2.7", "@unleash/proxy-client-react": "^3.6.0", "axios": "^1.5.0", "date-fns": "^2.30.0", @@ -2666,9 +2666,9 @@ } }, "node_modules/@redhat-cloud-services/rbac-client": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/rbac-client/-/rbac-client-1.2.6.tgz", - "integrity": "sha512-aPmRdZIGPswQIFGIEXwKMw+NC65udBHqfcW1JN4vBglvlUiM35ak9pJQl5AQIHyboH1JaqURbzl/GzJXS952tg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/rbac-client/-/rbac-client-1.2.7.tgz", + "integrity": "sha512-ji0KbcxI6BgQQztgBzyr8qsbmRqOWs/IQCQghunk8ZwpMpCpDYBCxs/11xsDubWsl93+cdJ1S5JS87MS+fBJDA==", "dependencies": { "axios": "^0.27.2" } diff --git a/package.json b/package.json index 92784b2e5..7280619e4 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@redhat-cloud-services/frontend-components-notifications": "^3.2.16", "@redhat-cloud-services/frontend-components-translations": "^3.2.6", "@redhat-cloud-services/frontend-components-utilities": "^3.7.6", - "@redhat-cloud-services/rbac-client": "^1.2.6", + "@redhat-cloud-services/rbac-client": "^1.2.7", "@unleash/proxy-client-react": "^3.6.0", "axios": "^1.5.0", "date-fns": "^2.30.0", From ac9bb7b75d4434fcf389f049adbdee048a3fecd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 00:32:46 +0000 Subject: [PATCH 11/37] (chore): Bump @typescript-eslint/eslint-plugin from 6.4.1 to 6.6.0 Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.4.1 to 6.6.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.6.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 72 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce01a691d..69ae06487 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,7 @@ "@types/react-dom": "^18.2.7", "@types/react-redux": "^7.1.26", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^6.4.1", + "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.5.0", "@xstate/test": "^0.5.1", "aphrodite": "^2.4.0", @@ -3677,16 +3677,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz", - "integrity": "sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", + "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/type-utils": "6.4.1", - "@typescript-eslint/utils": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/type-utils": "6.6.0", + "@typescript-eslint/utils": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3892,13 +3892,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz", - "integrity": "sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", + "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1" + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3909,13 +3909,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz", - "integrity": "sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz", + "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/utils": "6.4.1", + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/utils": "6.6.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3936,9 +3936,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.1.tgz", - "integrity": "sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz", + "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3949,13 +3949,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz", - "integrity": "sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz", + "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4009,17 +4009,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.1.tgz", - "integrity": "sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz", + "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==", "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.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", "semver": "^7.5.4" }, "engines": { @@ -4067,12 +4067,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz", - "integrity": "sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz", + "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/types": "6.6.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { diff --git a/package.json b/package.json index 079690525..2e5fd5425 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "@types/react-dom": "^18.2.7", "@types/react-redux": "^7.1.26", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^6.4.1", + "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.5.0", "@xstate/test": "^0.5.1", "aphrodite": "^2.4.0", From 872f00b07183a191e2216ae4b1f67c6222ba21e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 00:40:24 +0000 Subject: [PATCH 12/37] (chore): Bump @typescript-eslint/parser from 6.5.0 to 6.6.0 Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 6.5.0 to 6.6.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.6.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 135 +++------------------------------------------- package.json | 2 +- 2 files changed, 9 insertions(+), 128 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69ae06487..94b38757d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "@types/react-redux": "^7.1.26", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.5.0", + "@typescript-eslint/parser": "^6.6.0", "@xstate/test": "^0.5.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^11.0.0", @@ -3745,15 +3745,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", - "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz", + "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/typescript-estree": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", "debug": "^4.3.4" }, "engines": { @@ -3772,125 +3772,6 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", - "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", - "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", - "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", - "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.5.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", diff --git a/package.json b/package.json index 2e5fd5425..5a66a50fe 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@types/react-redux": "^7.1.26", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.5.0", + "@typescript-eslint/parser": "^6.6.0", "@xstate/test": "^0.5.1", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^11.0.0", From 1ee69024d60443bf4b2d259cd7b445df5e415f85 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Wed, 6 Sep 2023 02:34:39 -0400 Subject: [PATCH 13/37] release script update --- scripts/release-branch.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/release-branch.sh b/scripts/release-branch.sh index 05467ac77..8f59d8871 100755 --- a/scripts/release-branch.sh +++ b/scripts/release-branch.sh @@ -17,6 +17,8 @@ default() UI_DIR="$TMP_DIR/koku-ui" UI_REPO="git@github.com:project-koku/koku-ui.git" + + BODY_FILE="$UI_DIR/body" } usage() @@ -49,6 +51,14 @@ clone() git clone $UI_REPO } +createPullRequestBody() +{ +cat <<- EEOOFF > $BODY_FILE +Merged $REMOTE_BRANCH branch to $BRANCH. + +Use latest commit to update namespace \`ref\` in app-interface repo. Don't use merge commit, SHAs must be unique when images are created for each branch. +EEOOFF +} merge() { @@ -67,7 +77,7 @@ merge() # Use gh in a non-interactive way -- see https://github.com/cli/cli/issues/1718 pullRequest() { - NEW_BRANCH="release/${BRANCH}.$$" + NEW_BRANCH="release_${BRANCH}.$$" git branch -m $NEW_BRANCH @@ -75,7 +85,7 @@ pullRequest() git push -u origin HEAD TITLE="Deployment commit for $BRANCH" - BODY="Merged $REMOTE_BRANCH branch to $BRANCH. Use latest commit to update namespace \`ref\` in app-interface repo. Don't use merge commit, SHAs must be unique when images are created for each branch." + BODY=`cat $BODY_FILE` gh pr create -t "$TITLE" -b "$BODY" -B $BRANCH } @@ -124,6 +134,7 @@ push() if [ -n "$PUSH" ]; then push else + createPullRequestBody pullRequest fi else From cf201734c46a6f1a31bbce568b665650c92f46f6 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Wed, 6 Sep 2023 14:47:55 -0400 Subject: [PATCH 14/37] Workaround for Consoledot's pf4-v5.css --- src/components/drawers/commonDrawer/commonDrawer.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/drawers/commonDrawer/commonDrawer.scss b/src/components/drawers/commonDrawer/commonDrawer.scss index 9c7ecdcba..79bab74f8 100644 --- a/src/components/drawers/commonDrawer/commonDrawer.scss +++ b/src/components/drawers/commonDrawer/commonDrawer.scss @@ -2,4 +2,9 @@ .pf-c-drawer__content { background-color: unset; } + /* Todo: Remove when Optimizations drawer is replaced */ + /* Workaround for Consoledot's pf4-v5.css */ + .pf-c-drawer__body { + background-color: var(--pf-v5-global--BackgroundColor--200); + } } From c57d1704404f31b20e90a4be4e8464e9a075b321 Mon Sep 17 00:00:00 2001 From: Dan Labrecque Date: Fri, 8 Sep 2023 11:12:19 -0400 Subject: [PATCH 15/37] Remove obsolete Unleash flags https://issues.redhat.com/browse/COST-4180 --- locales/data.json | 6 + locales/translations.json | 1 + src/components/featureFlags/featureFlags.tsx | 6 - src/components/pageTitle/pageTitle.tsx | 6 +- src/components/permissions/permissions.tsx | 6 +- src/locales/messages.ts | 5 + src/routes.tsx | 13 - src/routes/components/groupBy/groupBy.tsx | 10 +- .../page/notAuthorized/notAuthorizedState.tsx | 3 +- .../__snapshots__/rateTable.test.tsx.snap | 26 - .../__snapshots__/warningIcon.test.tsx.snap | 17 - .../components/addPriceList.test.tsx | 322 ---------- .../costModels/components/addPriceList.tsx | 74 --- .../costModels/components/errorState.tsx | 141 ----- .../costModels/components/filterLogic.test.ts | 69 --- .../costModels/components/filterLogic.ts | 44 -- .../costModels/components/forms/form.tsx | 13 - .../hoc/withPriceListSearch.test.tsx | 67 --- .../components/hoc/withPriceListSearch.tsx | 80 --- .../components/hoc/withStateMachine.test.tsx | 38 -- .../components/hoc/withStateMachine.tsx | 41 -- .../components/inputs/rateInput.styles.ts | 8 - .../components/inputs/rateInput.tsx | 74 --- .../costModels/components/inputs/selector.tsx | 120 ---- .../components/inputs/simpleInput.tsx | 59 -- .../selectStateMachine.test.tsx.snap | 23 - .../components/logic/selectCheckbox.test.ts | 14 - .../components/logic/selectCheckbox.ts | 7 - .../logic/selectStateMachine.test.tsx | 42 -- .../components/logic/selectStateMachine.ts | 40 -- .../costModels/components/logic/types.ts | 4 - .../components/paginationToolbarTemplate.tsx | 46 -- .../components/priceListToolbar.styles.ts | 7 - .../components/priceListToolbar.test.tsx | 37 -- .../components/priceListToolbar.tsx | 64 -- .../components/rateForm/canSubmit.tsx | 19 - .../components/rateForm/constants.ts | 11 - .../costModels/components/rateForm/hasDiff.ts | 53 -- .../costModels/components/rateForm/index.ts | 6 - .../components/rateForm/rateForm.tsx | 243 -------- .../components/rateForm/taggingRatesForm.tsx | 109 ---- .../components/rateForm/useRateForm.test.tsx | 82 --- .../components/rateForm/useRateForm.tsx | 321 ---------- .../costModels/components/rateForm/utils.tsx | 297 --------- .../costModels/components/rateTable.test.tsx | 177 ------ .../costModels/components/rateTable.tsx | 205 ------- .../components/readOnlyTooltip.test.tsx | 24 - .../costModels/components/readOnlyTooltip.tsx | 22 - .../toolbar/checkboxSelector.test.tsx | 33 - .../components/toolbar/checkboxSelector.tsx | 55 -- .../toolbar/primarySelector.test.tsx | 28 - .../components/toolbar/primarySelector.tsx | 48 -- .../components/warningIcon.test.tsx | 9 - .../costModels/components/warningIcon.tsx | 15 - .../__snapshots__/dialog.test.tsx.snap | 26 - .../costModel/addRateModal.styles.ts | 7 - .../costModels/costModel/addRateModal.tsx | 121 ---- .../costModels/costModel/addSourceStep.tsx | 276 --------- .../costModels/costModel/addSourceWizard.tsx | 198 ------ .../costModel/assignSourcesModalToolbar.tsx | 100 --- .../costModels/costModel/costCalc.styles.ts | 38 -- .../costModel/costModelInfo.styles.ts | 35 -- .../costModels/costModel/costModelInfo.tsx | 199 ------ .../costModel/costModelsDetails.styles.ts | 71 --- .../costModels/costModel/dialog.test.tsx | 40 -- src/routes/costModels/costModel/dialog.tsx | 68 --- .../costModels/costModel/distribution.tsx | 112 ---- src/routes/costModels/costModel/header.tsx | 229 ------- src/routes/costModels/costModel/index.ts | 1 - src/routes/costModels/costModel/markup.tsx | 102 ---- .../costModels/costModel/priceListTable.tsx | 413 ------------- .../costModels/costModel/sourceTable.tsx | 112 ---- .../costModels/costModel/sourcesTable.tsx | 89 --- .../costModels/costModel/sourcesToolbar.tsx | 101 ---- .../costModels/costModel/table.styles.ts | 14 - src/routes/costModels/costModel/table.tsx | 143 ----- .../costModels/costModel/updateCostModel.tsx | 130 ---- .../costModel/updateDistributionDialog.tsx | 243 -------- .../costModel/updateMarkupDialog.tsx | 258 -------- .../costModel/updateRateModel.test.tsx | 393 ------------ .../costModels/costModel/updateRateModel.tsx | 170 ------ src/routes/costModels/costModelWizard/api.ts | 21 - .../costModelWizard/assignSourcesToolbar.tsx | 100 --- .../costModels/costModelWizard/context.ts | 61 -- .../costModelWizard/costModelWizard.tsx | 567 ------------------ .../costModelWizard/distribution.tsx | 159 ----- .../costModelWizard/generalInformation.tsx | 175 ------ .../costModels/costModelWizard/index.ts | 1 - .../costModels/costModelWizard/markup.tsx | 180 ------ .../costModelWizard/parseError.test.ts | 30 - .../costModels/costModelWizard/parseError.ts | 13 - .../costModels/costModelWizard/priceList.tsx | 57 -- .../costModelWizard/priceListTable.tsx | 299 --------- .../costModels/costModelWizard/review.tsx | 201 ------- .../costModels/costModelWizard/sources.tsx | 45 -- .../costModels/costModelWizard/steps.tsx | 65 -- .../costModels/costModelWizard/table.tsx | 154 ----- .../costModelWizard/wizard.styles.ts | 18 - .../bottomPagination.test.tsx.snap | 21 - .../bottomPagination.test.tsx | 63 -- .../costModelsDetails/bottomPagination.tsx | 102 ---- .../costModelsDetails/costModelsDetails.tsx | 73 --- .../createCostModelButton.tsx | 82 --- .../costModelsDetails/dialog.test.tsx | 119 ---- .../costModels/costModelsDetails/dialog.tsx | 74 --- .../costModelsDetails/emptyStateBase.tsx | 24 - .../costModelsDetails/header.styles.ts | 8 - .../costModels/costModelsDetails/header.tsx | 59 -- .../costModels/costModelsDetails/index.ts | 1 - .../costModelsDetails/noCostModels.tsx | 31 - .../costModelsDetails/table.test.tsx | 87 --- .../costModels/costModelsDetails/table.tsx | 179 ------ .../costModelsDetails/toolbar.test.tsx | 66 -- .../costModels/costModelsDetails/toolbar.tsx | 42 -- .../costModelsDetails/utils/dialog.tsx | 119 ---- .../costModelsDetails/utils/filters.tsx | 359 ----------- .../costModelsDetails/utils/query.tsx | 68 --- .../costModelsDetails/utils/table.tsx | 100 --- .../costModelsDetails/utils/toolbar.tsx | 141 ----- .../costModelsDetails/utils/types.ts | 9 - .../details/awsDetails/detailsToolbar.tsx | 21 +- .../components/breakdown/breakdownHeader.tsx | 6 +- .../costOverview/costOverviewBase.tsx | 5 +- .../details/ocpBreakdown/ocpBreakdown.tsx | 5 +- .../details/ocpDetails/detailsHeader.tsx | 5 +- src/routes/details/ocpDetails/ocpDetails.tsx | 4 +- src/routes/explorer/explorer.tsx | 5 +- src/routes/explorer/explorerChart.tsx | 6 +- src/routes/explorer/explorerHeader.tsx | 10 +- src/store/costModels/actions.ts | 9 +- .../__snapshots__/featureFlags.test.ts.snap | 3 - src/store/featureFlags/featureFlags.test.ts | 18 - src/store/featureFlags/featureFlagsActions.ts | 3 - src/store/featureFlags/featureFlagsReducer.ts | 9 - .../featureFlags/featureFlagsSelectors.ts | 6 - src/utils/paths.ts | 7 +- 136 files changed, 36 insertions(+), 11038 deletions(-) delete mode 100644 src/routes/costModels/components/__snapshots__/rateTable.test.tsx.snap delete mode 100644 src/routes/costModels/components/__snapshots__/warningIcon.test.tsx.snap delete mode 100644 src/routes/costModels/components/addPriceList.test.tsx delete mode 100644 src/routes/costModels/components/addPriceList.tsx delete mode 100644 src/routes/costModels/components/errorState.tsx delete mode 100644 src/routes/costModels/components/filterLogic.test.ts delete mode 100644 src/routes/costModels/components/filterLogic.ts delete mode 100644 src/routes/costModels/components/forms/form.tsx delete mode 100644 src/routes/costModels/components/hoc/withPriceListSearch.test.tsx delete mode 100644 src/routes/costModels/components/hoc/withPriceListSearch.tsx delete mode 100644 src/routes/costModels/components/hoc/withStateMachine.test.tsx delete mode 100644 src/routes/costModels/components/hoc/withStateMachine.tsx delete mode 100644 src/routes/costModels/components/inputs/rateInput.styles.ts delete mode 100644 src/routes/costModels/components/inputs/rateInput.tsx delete mode 100644 src/routes/costModels/components/inputs/selector.tsx delete mode 100644 src/routes/costModels/components/inputs/simpleInput.tsx delete mode 100644 src/routes/costModels/components/logic/__snapshots__/selectStateMachine.test.tsx.snap delete mode 100644 src/routes/costModels/components/logic/selectCheckbox.test.ts delete mode 100644 src/routes/costModels/components/logic/selectCheckbox.ts delete mode 100644 src/routes/costModels/components/logic/selectStateMachine.test.tsx delete mode 100644 src/routes/costModels/components/logic/selectStateMachine.ts delete mode 100644 src/routes/costModels/components/logic/types.ts delete mode 100644 src/routes/costModels/components/paginationToolbarTemplate.tsx delete mode 100644 src/routes/costModels/components/priceListToolbar.styles.ts delete mode 100644 src/routes/costModels/components/priceListToolbar.test.tsx delete mode 100644 src/routes/costModels/components/priceListToolbar.tsx delete mode 100644 src/routes/costModels/components/rateForm/canSubmit.tsx delete mode 100644 src/routes/costModels/components/rateForm/constants.ts delete mode 100644 src/routes/costModels/components/rateForm/hasDiff.ts delete mode 100644 src/routes/costModels/components/rateForm/index.ts delete mode 100644 src/routes/costModels/components/rateForm/rateForm.tsx delete mode 100644 src/routes/costModels/components/rateForm/taggingRatesForm.tsx delete mode 100644 src/routes/costModels/components/rateForm/useRateForm.test.tsx delete mode 100644 src/routes/costModels/components/rateForm/useRateForm.tsx delete mode 100644 src/routes/costModels/components/rateForm/utils.tsx delete mode 100644 src/routes/costModels/components/rateTable.test.tsx delete mode 100644 src/routes/costModels/components/rateTable.tsx delete mode 100644 src/routes/costModels/components/readOnlyTooltip.test.tsx delete mode 100644 src/routes/costModels/components/readOnlyTooltip.tsx delete mode 100644 src/routes/costModels/components/toolbar/checkboxSelector.test.tsx delete mode 100644 src/routes/costModels/components/toolbar/checkboxSelector.tsx delete mode 100644 src/routes/costModels/components/toolbar/primarySelector.test.tsx delete mode 100644 src/routes/costModels/components/toolbar/primarySelector.tsx delete mode 100644 src/routes/costModels/components/warningIcon.test.tsx delete mode 100644 src/routes/costModels/components/warningIcon.tsx delete mode 100644 src/routes/costModels/costModel/__snapshots__/dialog.test.tsx.snap delete mode 100644 src/routes/costModels/costModel/addRateModal.styles.ts delete mode 100644 src/routes/costModels/costModel/addRateModal.tsx delete mode 100644 src/routes/costModels/costModel/addSourceStep.tsx delete mode 100644 src/routes/costModels/costModel/addSourceWizard.tsx delete mode 100644 src/routes/costModels/costModel/assignSourcesModalToolbar.tsx delete mode 100644 src/routes/costModels/costModel/costCalc.styles.ts delete mode 100644 src/routes/costModels/costModel/costModelInfo.styles.ts delete mode 100644 src/routes/costModels/costModel/costModelInfo.tsx delete mode 100644 src/routes/costModels/costModel/costModelsDetails.styles.ts delete mode 100644 src/routes/costModels/costModel/dialog.test.tsx delete mode 100644 src/routes/costModels/costModel/dialog.tsx delete mode 100644 src/routes/costModels/costModel/distribution.tsx delete mode 100644 src/routes/costModels/costModel/header.tsx delete mode 100644 src/routes/costModels/costModel/index.ts delete mode 100644 src/routes/costModels/costModel/markup.tsx delete mode 100644 src/routes/costModels/costModel/priceListTable.tsx delete mode 100644 src/routes/costModels/costModel/sourceTable.tsx delete mode 100644 src/routes/costModels/costModel/sourcesTable.tsx delete mode 100644 src/routes/costModels/costModel/sourcesToolbar.tsx delete mode 100644 src/routes/costModels/costModel/table.styles.ts delete mode 100644 src/routes/costModels/costModel/table.tsx delete mode 100644 src/routes/costModels/costModel/updateCostModel.tsx delete mode 100644 src/routes/costModels/costModel/updateDistributionDialog.tsx delete mode 100644 src/routes/costModels/costModel/updateMarkupDialog.tsx delete mode 100644 src/routes/costModels/costModel/updateRateModel.test.tsx delete mode 100644 src/routes/costModels/costModel/updateRateModel.tsx delete mode 100644 src/routes/costModels/costModelWizard/api.ts delete mode 100644 src/routes/costModels/costModelWizard/assignSourcesToolbar.tsx delete mode 100644 src/routes/costModels/costModelWizard/context.ts delete mode 100644 src/routes/costModels/costModelWizard/costModelWizard.tsx delete mode 100644 src/routes/costModels/costModelWizard/distribution.tsx delete mode 100644 src/routes/costModels/costModelWizard/generalInformation.tsx delete mode 100644 src/routes/costModels/costModelWizard/index.ts delete mode 100644 src/routes/costModels/costModelWizard/markup.tsx delete mode 100644 src/routes/costModels/costModelWizard/parseError.test.ts delete mode 100644 src/routes/costModels/costModelWizard/parseError.ts delete mode 100644 src/routes/costModels/costModelWizard/priceList.tsx delete mode 100644 src/routes/costModels/costModelWizard/priceListTable.tsx delete mode 100644 src/routes/costModels/costModelWizard/review.tsx delete mode 100644 src/routes/costModels/costModelWizard/sources.tsx delete mode 100644 src/routes/costModels/costModelWizard/steps.tsx delete mode 100644 src/routes/costModels/costModelWizard/table.tsx delete mode 100644 src/routes/costModels/costModelWizard/wizard.styles.ts delete mode 100644 src/routes/costModels/costModelsDetails/__snapshots__/bottomPagination.test.tsx.snap delete mode 100644 src/routes/costModels/costModelsDetails/bottomPagination.test.tsx delete mode 100644 src/routes/costModels/costModelsDetails/bottomPagination.tsx delete mode 100644 src/routes/costModels/costModelsDetails/costModelsDetails.tsx delete mode 100644 src/routes/costModels/costModelsDetails/createCostModelButton.tsx delete mode 100644 src/routes/costModels/costModelsDetails/dialog.test.tsx delete mode 100644 src/routes/costModels/costModelsDetails/dialog.tsx delete mode 100644 src/routes/costModels/costModelsDetails/emptyStateBase.tsx delete mode 100644 src/routes/costModels/costModelsDetails/header.styles.ts delete mode 100644 src/routes/costModels/costModelsDetails/header.tsx delete mode 100644 src/routes/costModels/costModelsDetails/index.ts delete mode 100644 src/routes/costModels/costModelsDetails/noCostModels.tsx delete mode 100644 src/routes/costModels/costModelsDetails/table.test.tsx delete mode 100644 src/routes/costModels/costModelsDetails/table.tsx delete mode 100644 src/routes/costModels/costModelsDetails/toolbar.test.tsx delete mode 100644 src/routes/costModels/costModelsDetails/toolbar.tsx delete mode 100644 src/routes/costModels/costModelsDetails/utils/dialog.tsx delete mode 100644 src/routes/costModels/costModelsDetails/utils/filters.tsx delete mode 100644 src/routes/costModels/costModelsDetails/utils/query.tsx delete mode 100644 src/routes/costModels/costModelsDetails/utils/table.tsx delete mode 100644 src/routes/costModels/costModelsDetails/utils/toolbar.tsx delete mode 100644 src/routes/costModels/costModelsDetails/utils/types.ts diff --git a/locales/data.json b/locales/data.json index ae202868d..267ff38be 100644 --- a/locales/data.json +++ b/locales/data.json @@ -10855,6 +10855,12 @@ "value": "RHEL - Cost Management | OpenShift" } ], + "pageTitleSettings": [ + { + "type": 0, + "value": "Settings - Cost Management | OpenShift" + } + ], "paginationTitle": [ { "options": { diff --git a/locales/translations.json b/locales/translations.json index 111985a59..858c3ab84 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -458,6 +458,7 @@ "pageTitleOptimizations": "Optimizations - Cost Management | OpenShift", "pageTitleOverview": "Overview - Cost Management | OpenShift", "pageTitleRhel": "RHEL - Cost Management | OpenShift", + "pageTitleSettings": "Settings - Cost Management | OpenShift", "paginationTitle": "{placement, select, top {{title} top pagination} bottom {{title} bottom pagination} other {{title} pagination}}", "percent": "{value} %", "percentOfCost": "{value} % of cost", diff --git a/src/components/featureFlags/featureFlags.tsx b/src/components/featureFlags/featureFlags.tsx index d005deb5f..193c42b4a 100644 --- a/src/components/featureFlags/featureFlags.tsx +++ b/src/components/featureFlags/featureFlags.tsx @@ -6,14 +6,11 @@ import { featureFlagsActions } from 'store/featureFlags'; // eslint-disable-next-line no-shadow export const enum FeatureToggle { - costCategories = 'cost-management.ui.cost-categories', // AWS cost categories https://issues.redhat.com/browse/COST-3611 - costDistribution = 'cost-management.ui.cost-distribution', // OCP distributed overhead costs https://issues.redhat.com/browse/COST-3681 exports = 'cost-management.ui.exports', // Async exports https://issues.redhat.com/browse/COST-2223 finsights = 'cost-management.ui.finsights', // RHEL support for FINsights https://issues.redhat.com/browse/COST-3306 ibm = 'cost-management.ui.ibm', // IBM https://issues.redhat.com/browse/COST-935 ros = 'cost-management.ui.ros', // ROS support https://issues.redhat.com/browse/COST-3477 rosBeta = 'cost-management.ui.ros-beta', // ROS support https://issues.redhat.com/browse/COST-3477 - settings = 'cost-management.ui.settings', // Settings page https://issues.redhat.com/browse/COST-3307 settingsPlatform = 'cost-management.ui.settings.platform', // Platform projects https://issues.redhat.com/browse/COST-3818 } @@ -55,15 +52,12 @@ const useFeatureFlags = () => { await updateContext({ userId }).then(() => { dispatch( featureFlagsActions.setFeatureFlags({ - isCostCategoriesFeatureEnabled: client.isEnabled(FeatureToggle.costCategories), - isCostDistributionFeatureEnabled: client.isEnabled(FeatureToggle.costDistribution), isExportsFeatureEnabled: client.isEnabled(FeatureToggle.exports), isFinsightsFeatureEnabled: client.isEnabled(FeatureToggle.finsights), isIbmFeatureEnabled: client.isEnabled(FeatureToggle.ibm), isRosFeatureEnabled: client.isEnabled(FeatureToggle.ros) || (client.isEnabled(FeatureToggle.rosBeta) && insights && insights.chrome && insights.chrome.isBeta()), - isSettingsFeatureEnabled: client.isEnabled(FeatureToggle.settings), isSettingsPlatformFeatureEnabled: client.isEnabled(FeatureToggle.settingsPlatform), }) ); diff --git a/src/components/pageTitle/pageTitle.tsx b/src/components/pageTitle/pageTitle.tsx index 9a9d0a732..9fd7f88a5 100644 --- a/src/components/pageTitle/pageTitle.tsx +++ b/src/components/pageTitle/pageTitle.tsx @@ -21,9 +21,7 @@ const PageTitleBase: React.FC = ({ children = null, intl }) => { case formatPath(routes.azureDetails.path): case formatPath(routes.azureDetailsBreakdown.path): return messages.pageTitleAzure; - case formatPath(routes.costModel.path): - case formatPath(routes.costModelsDetails.path): - case formatPath(routes.costModels.path): + case formatPath(routes.costModel.basePath): return messages.pageTitleCostModels; case formatPath(routes.explorer.path): return messages.pageTitleExplorer; @@ -46,6 +44,8 @@ const PageTitleBase: React.FC = ({ children = null, intl }) => { case formatPath(routes.rhelDetails.path): case formatPath(routes.rhelDetailsBreakdown.path): return messages.pageTitleRhel; + case formatPath(routes.settings.path): + return messages.pageTitleSettings; default: return messages.pageTitleDefault; } diff --git a/src/components/permissions/permissions.tsx b/src/components/permissions/permissions.tsx index 6c43db379..ffb88f655 100644 --- a/src/components/permissions/permissions.tsx +++ b/src/components/permissions/permissions.tsx @@ -35,7 +35,6 @@ interface PermissionsStateProps { isFinsightsFeatureEnabled?: boolean; isIbmFeatureEnabled?: boolean; isRosFeatureEnabled?: boolean; - isSettingsFeatureEnabled?: boolean; userAccess: UserAccess; userAccessError: AxiosError; userAccessFetchStatus: FetchStatus; @@ -50,7 +49,6 @@ const PermissionsBase: React.FC = ({ isFinsightsFeatureEnabled, isIbmFeatureEnabled, isRosFeatureEnabled, - isSettingsFeatureEnabled, userAccess, userAccessError, userAccessFetchStatus, @@ -69,7 +67,7 @@ const PermissionsBase: React.FC = ({ const ocp = hasOcpAccess(userAccess); const rhel = isFinsightsFeatureEnabled && hasRhelAccess(userAccess); const ros = isRosFeatureEnabled && hasRosAccess(userAccess); - const settings = (costModel || hasSettingsAccess(userAccess)) && isSettingsFeatureEnabled; + const settings = costModel || hasSettingsAccess(userAccess); switch (pathname) { case formatPath(routes.explorer.path): @@ -82,7 +80,6 @@ const PermissionsBase: React.FC = ({ case formatPath(routes.azureDetailsBreakdown.path): return azure; case formatPath(routes.costModel.basePath): - case formatPath(routes.costModelsDetails.path): return costModel; case formatPath(routes.gcpDetails.path): case formatPath(routes.gcpDetailsBreakdown.path): @@ -137,7 +134,6 @@ const mapStateToProps = createMapStateToProps import(/* webpackChunkName: "awsBreakdown" */ 'r const AwsDetails = lazy(() => import(/* webpackChunkName: "awsDetails" */ 'routes/details/awsDetails')); const AzureBreakdown = lazy(() => import(/* webpackChunkName: "azureBreakdown" */ 'routes/details/azureBreakdown')); const AzureDetails = lazy(() => import(/* webpackChunkName: "azureDetails" */ 'routes/details/azureDetails')); -const CostModelOld = lazy(() => import(/* webpackChunkName: "costModel" */ 'routes/costModels/costModel')); -const CostModelsDetailsOld = lazy(() => import(/* lazy: "costModelsDetails" */ 'routes/costModels/costModelsDetails')); const CostModel = lazy(() => import(/* webpackChunkName: "costModel" */ 'routes/settings/costModels/costModel')); const Explorer = lazy(() => import(/* webpackChunkName: "explorer" */ 'routes/explorer')); const GcpBreakdown = lazy(() => import(/* webpackChunkName: "gcpBreakdown" */ 'routes/details/gcpBreakdown')); @@ -49,17 +47,6 @@ const routes = { element: userAccess(CostModel), path: `/settings/cost-model/:uuid`, }, - // Todo: Remove when settings page is enabled - costModels: { - // Note: Order matters here (i.e., dynamic segment must be defined after costModelsDetails) - element: userAccess(CostModelOld), - path: `/cost-models/:uuid`, - }, - // Todo: Remove when settings page is enabled - costModelsDetails: { - element: userAccess(CostModelsDetailsOld), - path: '/cost-models', - }, explorer: { element: userAccess(Explorer), path: '/explorer', diff --git a/src/routes/components/groupBy/groupBy.tsx b/src/routes/components/groupBy/groupBy.tsx index 60a85d8be..edc8311fb 100644 --- a/src/routes/components/groupBy/groupBy.tsx +++ b/src/routes/components/groupBy/groupBy.tsx @@ -17,7 +17,6 @@ import type { PerspectiveType } from 'routes/explorer/explorerUtils'; import { getDateRangeFromQuery } from 'routes/utils/dateRange'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; import { orgActions, orgSelectors } from 'store/orgs'; import { resourceActions, resourceSelectors } from 'store/resources'; import { tagActions, tagSelectors } from 'store/tags'; @@ -48,7 +47,6 @@ interface GroupByOwnProps extends RouterComponentProps, WrappedComponentProps { } interface GroupByStateProps { - isCostCategoriesFeatureEnabled?: boolean; orgReport?: Org; orgReportFetchStatus?: FetchStatus; orgQueryString?: string; @@ -211,7 +209,7 @@ class GroupByBase extends React.Component { }; private getGroupByOptions = (): GroupByOption[] => { - const { isCostCategoriesFeatureEnabled, options, orgReport, resourceReport, tagReport, intl } = this.props; + const { options, orgReport, resourceReport, tagReport, intl } = this.props; const allOptions = [...options]; if (orgReport && orgReport.data && orgReport.data.length > 0) { @@ -220,7 +218,7 @@ class GroupByBase extends React.Component { if (tagReport && tagReport.data && tagReport.data.length > 0) { allOptions.push(...groupByTagOptions); } - if (resourceReport && resourceReport.data && resourceReport.data.length > 0 && isCostCategoriesFeatureEnabled) { + if (resourceReport && resourceReport.data && resourceReport.data.length > 0) { allOptions.push(...groupByCostCategoryOptions); } return allOptions @@ -279,7 +277,6 @@ class GroupByBase extends React.Component { fetchOrg, fetchResource, fetchTag, - isCostCategoriesFeatureEnabled, orgPathsType, orgQueryString, showCostCategories, @@ -291,7 +288,7 @@ class GroupByBase extends React.Component { tagQueryString, } = this.props; - if (showCostCategories && isCostCategoriesFeatureEnabled) { + if (showCostCategories) { fetchResource(resourcePathsType, resourceType, resourceQueryString); } if (showOrgs) { @@ -415,7 +412,6 @@ const mapStateToProps = createMapStateToProps - {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"Node"} - , - - {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"CPU"} - , - - {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"CPU"} - , - - {value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}{"value":"CPU"} - , -] -`; diff --git a/src/routes/costModels/components/__snapshots__/warningIcon.test.tsx.snap b/src/routes/costModels/components/__snapshots__/warningIcon.test.tsx.snap deleted file mode 100644 index d0379a688..000000000 --- a/src/routes/costModels/components/__snapshots__/warningIcon.test.tsx.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`warning icon 1`] = ` - -`; diff --git a/src/routes/costModels/components/addPriceList.test.tsx b/src/routes/costModels/components/addPriceList.test.tsx deleted file mode 100644 index fc19d58bf..000000000 --- a/src/routes/costModels/components/addPriceList.test.tsx +++ /dev/null @@ -1,322 +0,0 @@ -import { act, configure, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import type { Rate } from 'api/rates'; -import messages from 'locales/messages'; -import React from 'react'; -import { CostModelContext, defaultCostModelContext } from 'routes/costModels/costModelWizard/context'; - -import AddPriceList from './addPriceList'; - -const metricsHash = { - CPU: { - Request: { - source_type: 'Openshift Container Platform', - metric: 'cpu_core_request_per_hour', - label_metric: 'CPU', - label_measurement: 'Request', - label_measurement_unit: 'core-hours', - default_cost_type: 'Infrastructure', - }, - Usage: { - source_type: 'Openshift Container Platform', - metric: 'cpu_core_usage_per_hour', - label_metric: 'CPU', - label_measurement: 'Usage', - label_measurement_unit: 'core-hours', - default_cost_type: 'Infrastructure', - }, - }, - Memory: { - Request: { - source_type: 'Openshift Container Platform', - metric: 'memory_gb_request_per_hour', - label_metric: 'Memory', - label_measurement: 'Request', - label_measurement_unit: 'GB-hours', - default_cost_type: 'Supplementary', - }, - Usage: { - source_type: 'Openshift Container Platform', - metric: 'memory_gb_usage_per_hour', - label_metric: 'Memory', - label_measurement: 'Usage', - label_measurement_unit: 'GB-hours', - default_cost_type: 'Supplementary', - }, - }, - Cluster: { - Currency: { - source_type: 'Openshift Container Platform', - metric: 'cluster_cost_per_month', - label_metric: 'Cluster', - label_measurement: 'Currency', - label_measurement_unit: 'pvc-months', - default_cost_type: 'Infrastructure', - }, - }, -}; - -const qr = { - metric: '[data-ouia-component-id="metric"] button', - measurement: '[data-ouia-component-id="measurement"] button', - description: '#description', - infraradio: /infrastructure/i, - supplradio: /supplementary/i, - regular: '#regular-rate', - regularError: '#regular-rate-helper', - switch: 'Enter rate by tag', - tagKeyPlaceHolder: 'Enter a tag key', - descriptionNth: (id: number) => `#desc_${id}`, - defaultNth: (id: number) => `#isDefault_${id}`, - tagValueNth: (id: number) => `#tagValue_${id}`, - rateNth: (id: number) => `#rate_${id}`, -}; - -function RenderFormDataUI({ cancel, submit }) { - const memoryRate = { - metric: { - name: 'memory_gb_usage_per_hour', - label_metric: 'Memory', - label_measurement: 'Request', - label_measurement_unit: 'GB-hours', - }, - description: '', - tag_rates: { - tag_key: 'app', - tag_values: [ - { - unit: 'USD', - value: 1, - default: false, - tag_value: 'app1', - description: '', - }, - { - unit: 'USD', - value: 2.31, - default: false, - tag_value: 'app2', - description: '', - }, - ], - }, - cost_type: 'Supplementary', - }; - return ( - - - - ); -} - -function regExp(msg) { - return new RegExp(msg.defaultMessage); -} - -// Update testId accessor since data-testid is not passed to the parent component of Select -configure({ testIdAttribute: 'data-ouia-component-id' }); - -describe('add-a-new-rate', () => { - test('regular rate', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - const submit = jest.fn(); - const cancel = jest.fn(); - let options = null; - render(); - - await user.type(screen.getByLabelText('Description'), 'regular rate test'); - - // select first option for metric - await act(async () => user.click(screen.getByLabelText('Select Metric'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - // select first option for measurement - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - // make sure the default cost type is selected - expect(screen.getByLabelText(qr.infraradio)).toHaveProperty('checked', true); - - // selecting a different measurement does not reset cost type to default - await user.click(screen.getByLabelText(qr.supplradio)); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[1]); - - expect(screen.getByLabelText(qr.supplradio)).toHaveProperty('checked', true); - - // selecting metric will reset both measurement and cost type - await user.click(screen.getByLabelText(qr.infraradio)); - - await act(async () => user.click(screen.getByLabelText('Select Metric'))); - options = await screen.findAllByRole('option'); - await user.click(options[1]); - - expect(screen.getByText(regExp(messages.costModelsRequiredField))).not.toBeNull(); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - expect(screen.getByLabelText(qr.supplradio)).toHaveProperty('checked', true); - await user.click(screen.getByLabelText(qr.infraradio)); - - const rateInput = screen.getByLabelText('Assign rate'); - - // setting rate to anything but a number - expect(screen.queryByText(regExp(messages.priceListNumberRate))).toBeNull(); - await user.type(rateInput, 'A'); - expect(screen.getByText(regExp(messages.priceListNumberRate))).not.toBeNull(); - - // setting rate to a negative number - validation is done on blur - await user.clear(rateInput); - await user.type(rateInput, '-12'); - expect(screen.getByText(regExp(messages.priceListPosNumberRate))).not.toBeNull(); - - // setting rate to a valid number - await user.clear(rateInput); - await user.type(rateInput, '0.2'); - expect(screen.queryByText(regExp(messages.priceListNumberRate))).toBeNull(); - - // making sure button is enabled - const createButton = screen.getByText(regExp(messages.createRate)); - expect(createButton.getAttribute('aria-disabled')).toBe('false'); - await user.click(createButton); - expect(submit).toHaveBeenCalled(); - }); - - test('tag rates', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - const submit = jest.fn(); - const cancel = jest.fn(); - let options = null; - render(); - - await user.type(screen.getByLabelText('Description'), 'tag rate test'); - - await act(async () => user.click(screen.getByLabelText('Select Metric'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - await user.click(screen.getByLabelText(regExp(messages.costModelsEnterTagRate))); - - // tag key is required validation - const tagKeyInput = screen.getByPlaceholderText(qr.tagKeyPlaceHolder); - await user.type(tagKeyInput, 'test'); - expect(screen.queryByText(regExp(messages.costModelsRequiredField))).toBeNull(); - await user.clear(tagKeyInput); - expect(screen.getByText(regExp(messages.costModelsRequiredField))).not.toBeNull(); - await user.type(tagKeyInput, 'openshift'); - expect(screen.queryByText(regExp(messages.costModelsRequiredField))).toBeNull(); - - // tag value is required validation - const tagValueInput = screen.getByPlaceholderText('Enter a tag value'); - await user.type(tagValueInput, 'test'); - expect(screen.queryByText(regExp(messages.costModelsRequiredField))).toBeNull(); - await user.clear(tagValueInput); - expect(screen.getByText(regExp(messages.costModelsRequiredField))).not.toBeNull(); - await user.type(tagValueInput, 'openshift'); - expect(screen.queryByText(regExp(messages.costModelsRequiredField))).toBeNull(); - - // rate must be a number - const tagRateInput = screen.getByLabelText('Assign rate'); - await user.type(tagRateInput, 'test'); - expect(screen.getByText(regExp(messages.priceListNumberRate))).not.toBeNull(); - - // rate is required - await user.clear(tagRateInput); - expect(screen.getByText(regExp(messages.costModelsRequiredField))).not.toBeNull(); - - // rate must be positive - await user.type(tagRateInput, '-0.23'); - expect(screen.getByText(regExp(messages.priceListPosNumberRate))).not.toBeNull(); - - // setting a valid rate - now form is valid and can be submitted - const createButton = screen.getByText(regExp(messages.createRate)); - expect(createButton.getAttribute('aria-disabled')).toBe('true'); - await user.clear(tagRateInput); - - await user.type(tagRateInput, '0.23'); - await user.type(screen.getByPlaceholderText('Enter a tag description'), 'default worker'); - expect(createButton.getAttribute('aria-disabled')).toBe('false'); - - // set tag to default - await user.click(screen.getByLabelText('Default')); - - // add a new rate disables the submit button - await user.click(screen.getByText(/add more tag values/i)); - expect(createButton.getAttribute('aria-disabled')).toBe('true'); - - await user.click(screen.getAllByRole('button', { name: /remove tag value/i })[1]); - expect(createButton.getAttribute('aria-disabled')).toBe('false'); - await user.click(createButton); - expect(submit).toHaveBeenCalled(); - }); - - test('tag rates duplicate tag key', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - const submit = jest.fn(); - const cancel = jest.fn(); - let options = null; - render(); - - await act(async () => user.click(screen.getByLabelText('Select Metric'))); - options = await screen.findAllByRole('option'); - await user.click(options[1]); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - await user.click(screen.getByLabelText(regExp(messages.costModelsEnterTagRate))); - - // tag key is duplicated - const tagKeyInput = screen.getByPlaceholderText(qr.tagKeyPlaceHolder); - await user.type(tagKeyInput, 'app'); - expect(screen.getByText(regExp(messages.priceListDuplicate))).not.toBeNull(); - - await user.type(tagKeyInput, '1'); - expect(screen.queryByText(regExp(messages.priceListDuplicate))).toBeNull(); - - // change measurement will set tag key as not duplicate - await user.type(tagKeyInput, '{backspace}'); - expect(screen.getByText(regExp(messages.priceListDuplicate))).not.toBeNull(); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[1]); - - expect(screen.queryByText(regExp(messages.priceListDuplicate))).toBeNull(); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - - expect(screen.getByText(regExp(messages.priceListDuplicate))).not.toBeNull(); - }); - - test('hide "enter tag rates" switch on Cluster metric', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - const submit = jest.fn(); - const cancel = jest.fn(); - let options = null; - - await render(); - - await act(async () => user.click(screen.getByLabelText('Select Metric'))); - options = await screen.findAllByRole('option'); - await user.click(options[2]); - - await act(async () => user.click(screen.getByLabelText('Select Measurement'))); - options = await screen.findAllByRole('option'); - await user.click(options[0]); - expect(screen.queryAllByLabelText(regExp(messages.costModelsEnterTagRate))).toHaveLength(0); - }); -}); diff --git a/src/routes/costModels/components/addPriceList.tsx b/src/routes/costModels/components/addPriceList.tsx deleted file mode 100644 index 18e80d6b0..000000000 --- a/src/routes/costModels/components/addPriceList.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - ActionGroup, - Button, - ButtonVariant, - Form, - Stack, - StackItem, - Text, - TextContent, - TextVariants, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import type { MetricHash } from 'api/metrics'; -import { intl as defaultIntl } from 'components/i18n'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import type { RateFormData } from 'routes/costModels/components/rateForm'; -import { canSubmit as isReadyForSubmit, RateForm, useRateData } from 'routes/costModels/components/rateForm'; -import { CostModelContext } from 'routes/costModels/costModelWizard/context'; - -interface AddPriceListOwnProps { - cancel: () => void; - currencyUnits?: string; - metricsHash: MetricHash; - submitRate: (data: RateFormData) => void; -} - -type AddPriceListProps = AddPriceListOwnProps & WrappedComponentProps; - -const AddPriceList: React.FC = ({ - cancel, - currencyUnits, - intl = defaultIntl, // Default required for testing - metricsHash, - submitRate, -}) => { - const { tiers } = React.useContext(CostModelContext); - const rateFormData: any = useRateData(metricsHash, undefined, tiers); - const canSubmit = React.useMemo(() => isReadyForSubmit(rateFormData), [rateFormData.errors, rateFormData.rateKind]); - return ( - - - - {intl.formatMessage(messages.costModelsWizardCreatePriceList)} - - - - - {intl.formatMessage(messages.costModelsWizardPriceListMetric)} - - - -
- - -
- - - - - - -
- ); -}; - -export default injectIntl(AddPriceList); diff --git a/src/routes/costModels/components/errorState.tsx b/src/routes/costModels/components/errorState.tsx deleted file mode 100644 index def15a71b..000000000 --- a/src/routes/costModels/components/errorState.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import type { EmptyStateProps } from '@patternfly/react-core'; -import { - Button, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - EmptyStateVariant, - Stack, - StackItem, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; -import global_DangerColor_100 from '@patternfly/react-tokens/dist/js/global_danger_color_100'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { createMapStateToProps } from 'store/common'; -import { sourcesSelectors } from 'store/sourceSettings'; - -interface OwnProps { - actionButton: React.ReactNode; - title: string; - description: React.ReactNode; -} -type ErrorEmptyProps = Pick; -type ErrorStateProps = ErrorEmptyProps & OwnProps; - -export const ErrorState: React.FC = ({ variant, actionButton, title, description }) => { - return ( - - - - {title} - - - - {description} - - - {actionButton} - - ); -}; - -interface SourcesErrorStateProps extends WrappedComponentProps { - onRefresh: () => void; -} - -export const SourceStepErrorStateBase: React.FC = ({ intl, onRefresh }) => { - const title = intl.formatMessage(messages.costModelsWizardSourceErrorTitle); - const description = intl.formatMessage(messages.costModelsWizardSourceErrorDesc, { - url: ( - - "Status Page" - - ), - }); - const actionButton = ; - return ( - - - - {intl.formatMessage(messages.costModelsWizardSourceTitle)} - - - - - - - ); -}; -const SourceStepErrorState = injectIntl(SourceStepErrorStateBase); -export { SourceStepErrorState }; - -interface SourcesModalErrorOwnProps { - // TBD... -} - -interface SourcesModalErrorStateProps { - query: any; -} - -interface SourcesModalErrorDispatchProps { - onRefresh: () => void; -} - -type SourcesModalErrorProps = SourcesModalErrorOwnProps & - SourcesModalErrorStateProps & - SourcesModalErrorDispatchProps & - WrappedComponentProps; - -export const SourcesModalErrorStateBase: React.FC = ({ intl, onRefresh }) => { - const title = intl.formatMessage(messages.costModelsAssignSourcesErrorTitle); - const description = intl.formatMessage(messages.costModelsAssignSourcesErrorDesc, { - url: ( - - "Status Page" - - ), - }); - const actionButton = ; - return ( - - ); -}; - -const mapStateToProps = createMapStateToProps(state => { - return { - query: sourcesSelectors.query(state), - }; -}); - -const mapDispatchToProps = (stateProps, dispatchProps): SourcesModalErrorDispatchProps => { - const { query } = stateProps; - const { fetch } = dispatchProps; - const searchTerm = Object.keys(query).reduce((acc, curr) => { - if (query[curr] === null) { - return acc; - } - if (acc === '') { - return `${curr}=${query[curr]}`; - } - return `${acc}&${curr}=${query[curr]}`; - }, ''); - - return { - onRefresh: () => fetch(searchTerm), - }; -}; - -export const SourcesModalErrorState = injectIntl( - connect(mapStateToProps, mapDispatchToProps)(SourcesModalErrorStateBase) -); diff --git a/src/routes/costModels/components/filterLogic.test.ts b/src/routes/costModels/components/filterLogic.test.ts deleted file mode 100644 index b4d844ab5..000000000 --- a/src/routes/costModels/components/filterLogic.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { addMultiValueQuery, addSingleValueQuery, removeMultiValueQuery, removeSingleValueQuery } from './filterLogic'; - -describe('add multi value query', () => { - it('add new query key', () => { - const q = {}; - expect(addMultiValueQuery(q)('name', 'ocp-cost')).toEqual({ - name: ['ocp-cost'], - }); - }); - it('add new value to query key', () => { - const q = { name: ['ocp-cost'] }; - expect(addMultiValueQuery(q)('name', 'aws-cost')).toEqual({ - name: ['ocp-cost', 'aws-cost'], - }); - }); - it('add new value to query key even if it exists', () => { - const q = { name: ['ocp-cost'] }; - expect(addMultiValueQuery(q)('name', 'ocp-cost')).toEqual({ - name: ['ocp-cost', 'ocp-cost'], - }); - }); -}); - -describe('add single value query', () => { - it('add new query key', () => { - const q = {}; - expect(addSingleValueQuery(q)('name', 'ocp-cost')).toEqual({ - name: 'ocp-cost', - }); - }); - it('update value to query key', () => { - const q = { name: 'ocp-cost' }; - expect(addSingleValueQuery(q)('name', 'aws-cost')).toEqual({ - name: 'aws-cost', - }); - }); -}); - -describe('remove single value query', () => { - it('remove single value for not existed key', () => { - const q = { name: 'ocp-cost' }; - expect(removeSingleValueQuery(q)('source_type', 'AWS')).toEqual({ - name: 'ocp-cost', - }); - }); - it('remove value from query key', () => { - const q = { name: 'ocp-cost' }; - expect(removeSingleValueQuery(q)('name', 'aws-cost')).toEqual({}); - }); -}); - -describe('remove multi value query', () => { - it('remove multi value for not existed key', () => { - const q = { name: 'ocp-cost' }; - expect(removeMultiValueQuery(q)('source_type', 'AWS')).toEqual({ - name: 'ocp-cost', - }); - }); - it('remove value from query key that has one value', () => { - const q = { name: ['ocp-cost'] }; - expect(removeMultiValueQuery(q)('name', 'ocp-cost')).toEqual({}); - }); - it('remove value from query key that has more than one value', () => { - const q = { name: ['ocp-cost', 'aws-cost'] }; - expect(removeMultiValueQuery(q)('name', 'ocp-cost')).toEqual({ - name: ['aws-cost'], - }); - }); -}); diff --git a/src/routes/costModels/components/filterLogic.ts b/src/routes/costModels/components/filterLogic.ts deleted file mode 100644 index 22364812a..000000000 --- a/src/routes/costModels/components/filterLogic.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const addMultiValueQuery = query => (key, value) => ({ - ...query, - [key]: query[key] ? [...query[key], value] : [value], -}); - -export const addSingleValueQuery = query => (key, value) => ({ - ...query, - [key]: value, -}); - -export const removeMultiValueQuery = query => (key, value) => { - if (query[key] === undefined) { - return query; - } - const newSubQuery = query[key].filter(qval => qval !== value); - if (newSubQuery.length === 0) { - return Object.keys(query).reduce((acc, cur) => { - if (cur === key) { - return acc; - } - return { ...acc, [cur]: query[cur] }; - }, {}); - } - return { - ...query, - [key]: newSubQuery, - }; -}; - -export const removeSingleValueQuery = query => key => { - return Object.keys(query).reduce((acc, cur) => { - if (cur === key) { - return acc; - } - return { ...acc, [cur]: query[cur] }; - }, {}); -}; - -export const flatQueryValue = (name: string, value: string | string[]) => { - if (typeof value === 'string') { - return [{ name, value }]; - } - return value.map(vl => ({ name, value: vl })); -}; diff --git a/src/routes/costModels/components/forms/form.tsx b/src/routes/costModels/components/forms/form.tsx deleted file mode 100644 index 3efbaa7b2..000000000 --- a/src/routes/costModels/components/forms/form.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { FormProps as FormPF4Props } from '@patternfly/react-core'; -import { Form as FormPF4 } from '@patternfly/react-core'; -import React from 'react'; - -type FormProps = Omit; - -export const Form: React.ComponentType = ({ children, ...props }) => { - return ( - ) => event.preventDefault()} {...props}> - {children} - - ); -}; diff --git a/src/routes/costModels/components/hoc/withPriceListSearch.test.tsx b/src/routes/costModels/components/hoc/withPriceListSearch.test.tsx deleted file mode 100644 index a5e614325..000000000 --- a/src/routes/costModels/components/hoc/withPriceListSearch.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { WithPriceListSearch } from './withPriceListSearch'; - -test('with price list search', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - render( - - {({ onClearAll, onRemove, onSelect, setSearch, search }) => { - return ( -
- - - - - - -
-
Primary: {search.primary || 'None'}
-
Metrics: {search.metrics[0] || 'None'}
-
Measurements: {search.measurements[0] || 'None'}
-
-
- ); - }} -
- ); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: metrics
Metrics: None
Measurements: None
` - ); - await user.click(screen.getByText('Select CPU')); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: metrics
Metrics: CPU
Measurements: None
` - ); - await user.click(screen.getByText('Remove CPU')); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: metrics
Metrics: None
Measurements: None
` - ); - await user.click(screen.getByText('Select request')); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: metrics
Metrics: None
Measurements: Request
` - ); - await user.click(screen.getByText('Remove request')); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: metrics
Metrics: None
Measurements: None
` - ); - await user.click(screen.getByText('Set search')); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: measurements
Metrics: Memory
Measurements: Usage
` - ); - await user.click(screen.getByText('Clear all')); - expect(screen.getByRole('main').outerHTML).toEqual( - `
Primary: measurements
Metrics: None
Measurements: None
` - ); -}); diff --git a/src/routes/costModels/components/hoc/withPriceListSearch.tsx b/src/routes/costModels/components/hoc/withPriceListSearch.tsx deleted file mode 100644 index 9da2477a3..000000000 --- a/src/routes/costModels/components/hoc/withPriceListSearch.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { checkBoxLogic, deleteChip } from 'routes/costModels/components/logic/selectCheckbox'; - -export interface PriceListSearchQuery { - primary: string; - metrics?: string[]; - measurements?: string[]; -} - -export interface WithPriceListSearchPropsRender { - search: PriceListSearchQuery; - setSearch: (newSearch: PriceListSearchQuery) => void; - onRemove: (category: string, chip: string) => void; - onSelect: (key: string, value: string) => void; - onClearAll: () => void; -} - -export interface WithPriceListSearchProps { - initialFilters?: PriceListSearchQuery; - children: (props: WithPriceListSearchPropsRender) => JSX.Element; -} - -interface WithPriceListSearchState { - filters: PriceListSearchQuery; -} - -export class WithPriceListSearch extends React.Component { - constructor(props) { - super(props); - this.state = { filters: this.props.initialFilters }; - this.handleChange = this.handleChange.bind(this); - this.onRemove = this.onRemove.bind(this); - this.onSelect = this.onSelect.bind(this); - this.onClearAll = this.onClearAll.bind(this); - } - - public handleChange(newSearch: PriceListSearchQuery) { - this.setState({ - filters: { ...this.state.filters, ...newSearch }, - }); - } - - public onClearAll() { - this.setState({ - filters: { - ...this.state.filters, - metrics: [], - measurements: [], - }, - }); - } - - public onRemove(category: string, chip: string) { - this.setState({ - filters: { - ...this.state.filters, - [category]: deleteChip(this.state.filters[category], chip), - }, - }); - } - - public onSelect(key: string, value: string) { - this.setState({ - filters: { - ...this.state.filters, - [key]: checkBoxLogic(this.state.filters[key], value), - }, - }); - } - - public render() { - return this.props.children({ - onClearAll: this.onClearAll, - onRemove: this.onRemove, - onSelect: this.onSelect, - setSearch: this.handleChange, - search: this.state.filters, - }); - } -} diff --git a/src/routes/costModels/components/hoc/withStateMachine.test.tsx b/src/routes/costModels/components/hoc/withStateMachine.test.tsx deleted file mode 100644 index fbfd7bfce..000000000 --- a/src/routes/costModels/components/hoc/withStateMachine.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { Machine } from 'xstate'; - -import { WithStateMachine } from './withStateMachine'; - -test('with state machine', async () => { - const toggleMachine = Machine({ - initial: 'off', - states: { - off: { - on: { - TOGGLE: 'on', - }, - }, - on: { - on: { - TOGGLE: 'off', - }, - }, - }, - }); - - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - - render( - - {({ current, send }) => { - return ; - }} - - ); - const buttonNode = screen.getByRole('button'); - expect(buttonNode.outerHTML).toBe(''); - await user.click(buttonNode); - expect(buttonNode.outerHTML).toBe(''); -}); diff --git a/src/routes/costModels/components/hoc/withStateMachine.tsx b/src/routes/costModels/components/hoc/withStateMachine.tsx deleted file mode 100644 index 49117f21b..000000000 --- a/src/routes/costModels/components/hoc/withStateMachine.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { interpret } from 'xstate'; - -export interface WithStateMachineState { - current: any; -} - -export interface WithStateMachinePropsRender { - current: any; - send: (event: any) => void; -} - -export interface WithStateMachineProps { - machine: any; - children: (args: WithStateMachinePropsRender) => JSX.Element; -} - -export class WithStateMachine extends React.Component { - public service = null; - constructor(props) { - super(props); - this.service = interpret(props.machine).onTransition(current => this.setState({ current })); - this.state = { - current: props.machine.initialState, - }; - } - - public componentDidMount() { - this.service.start(); - } - - public componentWillUnmount() { - this.service.stop(); - } - - public render() { - const { current } = this.state; - const { send } = this.service; - return this.props.children({ current, send }); - } -} diff --git a/src/routes/costModels/components/inputs/rateInput.styles.ts b/src/routes/costModels/components/inputs/rateInput.styles.ts deleted file mode 100644 index 8cb06777c..000000000 --- a/src/routes/costModels/components/inputs/rateInput.styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import global_FontWeight_bold from '@patternfly/react-tokens/dist/js/global_FontWeight_bold'; -import type React from 'react'; - -export const styles = { - currency: { - fontWeight: global_FontWeight_bold.value as any, - }, -} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/costModels/components/inputs/rateInput.tsx b/src/routes/costModels/components/inputs/rateInput.tsx deleted file mode 100644 index b64e695a9..000000000 --- a/src/routes/costModels/components/inputs/rateInput.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { FormGroupProps, TextInputProps } from '@patternfly/react-core'; -import { FormGroup, InputGroup, InputGroupText, TextInput } from '@patternfly/react-core'; -import { intl as defaultIntl } from 'components/i18n'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { formatCurrencyRaw } from 'utils/format'; - -import { styles } from './rateInput.styles'; - -interface UniqueProps { - currencyUnits?: string; - label?: MessageDescriptor | string; - helperTextInvalid?: MessageDescriptor | string; -} - -type RateFormGroup = Pick; -type RateTextInput = Pick; -type RateInputBaseProps = RateFormGroup & RateTextInput & UniqueProps & WrappedComponentProps; - -const RateInputBase: React.FC = ({ - currencyUnits = 'USD', - fieldId, - helperTextInvalid: helpText = messages.priceListPosNumberRate, - intl = defaultIntl, // Default required for testing - label = messages.rate, - onBlur, - onChange, - style, - validated, - value, -}) => { - const handleOnKeyDown = event => { - // Prevent 'enter' and '+' - if (event.keyCode === 13 || event.keyCode === 187) { - event.preventDefault(); - } - }; - return ( - - - - {intl.formatMessage(messages.currencyUnits, { units: currencyUnits })} - - - - - ); -}; - -const RateInput = injectIntl(RateInputBase); -export { RateInput }; diff --git a/src/routes/costModels/components/inputs/selector.tsx b/src/routes/costModels/components/inputs/selector.tsx deleted file mode 100644 index 7129c8a60..000000000 --- a/src/routes/costModels/components/inputs/selector.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { FormGroupProps, FormSelectProps, SelectOptionObject } from '@patternfly/react-core'; -import { FormGroup, Select, SelectDirection, SelectOption, SelectVariant } from '@patternfly/react-core'; -import { intl as defaultIntl } from 'components/i18n'; -import React, { useEffect, useState } from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; - -interface SelectorFormGroupOwnProps { - helperTextInvalid?: MessageDescriptor | string; - isInvalid?: boolean; - label?: MessageDescriptor | string; - appendMenuTo?: HTMLElement | 'parent' | 'inline' | (() => HTMLElement); - toggleAriaLabel?: string; - maxHeight?: string | number; - placeholderText?: string; - direction?: SelectDirection.up | SelectDirection.down; - options: { - label: MessageDescriptor | string; - value: any; - description?: string; - }[]; -} - -interface SelectorOption extends SelectOptionObject { - toString(): string; // label - value?: string; - description?: string; -} - -type SelectorFormGroupProps = Pick; -type SelectorFormSelectProps = Pick< - FormSelectProps, - 'isDisabled' | 'value' | 'onChange' | 'aria-label' | 'id' | 'isRequired' ->; - -type SelectorProps = SelectorFormGroupOwnProps & - SelectorFormGroupProps & - SelectorFormSelectProps & - WrappedComponentProps; - -const SelectorBase: React.FC = ({ - 'aria-label': ariaLabel, - helperTextInvalid: helpText, - id, - intl = defaultIntl, // Default required for testing - toggleAriaLabel, - maxHeight, - placeholderText, - direction = SelectDirection.down, - isInvalid = false, - isRequired = false, - appendMenuTo = 'parent', - label, - value, - onChange, - options, - style, -}) => { - const [isOpen, setIsOpen] = useState(false); - const [selection, setSelection] = useState(null); - - useEffect(() => { - if (!value) { - setSelection(null); - } else { - setSelection(value); - } - }, [value]); - - const getSelectorOptions = (): SelectorOption[] => { - const ret = options.map(option => { - return { - toString: () => (typeof option.label === 'object' ? intl.formatMessage(option.label) : option.label), - value: option.value, - description: option.description, - } as SelectorOption; - }); - return ret; - }; - return ( - - - - ); -}; - -const Selector = injectIntl(SelectorBase); -export { Selector }; diff --git a/src/routes/costModels/components/inputs/simpleInput.tsx b/src/routes/costModels/components/inputs/simpleInput.tsx deleted file mode 100644 index 172e84822..000000000 --- a/src/routes/costModels/components/inputs/simpleInput.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { MessageDescriptor } from '@formatjs/intl/src/types'; -import type { FormGroupProps, TextInputProps } from '@patternfly/react-core'; -import { FormGroup, TextInput } from '@patternfly/react-core'; -import { intl as defaultIntl } from 'components/i18n'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; - -interface SimpleInputOwnProps { - helperTextInvalid?: MessageDescriptor | string; - label?: MessageDescriptor | string; -} - -type SimpleInputFormGroupProps = Pick; -type SimpleInputTextInputProps = Pick; -type SimpleInputProps = SimpleInputOwnProps & - SimpleInputTextInputProps & - SimpleInputFormGroupProps & - WrappedComponentProps; - -const SimpleInputBase: React.FC = ({ - id, - intl = defaultIntl, // Default required for testing - label, - isRequired, - helperTextInvalid: helpText, - onChange, - onBlur, - placeholder, - style, - validated, - value, -}) => { - return ( - - - - ); -}; - -const SimpleInput = injectIntl(SimpleInputBase); -export { SimpleInput }; diff --git a/src/routes/costModels/components/logic/__snapshots__/selectStateMachine.test.tsx.snap b/src/routes/costModels/components/logic/__snapshots__/selectStateMachine.test.tsx.snap deleted file mode 100644 index b869cdd84..000000000 --- a/src/routes/costModels/components/logic/__snapshots__/selectStateMachine.test.tsx.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` 1`] = ` -[ - { - "selection": [], - }, - [ - "collapsed", - ], -] -`; - -exports[`selectmachine via TOGGLE: reaches state: "#(machine).expanded" ({"selection":[]}) 1`] = ` -[ - { - "selection": [], - }, - [ - "expanded", - ], -] -`; diff --git a/src/routes/costModels/components/logic/selectCheckbox.test.ts b/src/routes/costModels/components/logic/selectCheckbox.test.ts deleted file mode 100644 index 1c1a659c7..000000000 --- a/src/routes/costModels/components/logic/selectCheckbox.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { checkBoxLogic, deleteChip } from './selectCheckbox'; - -describe('select checkbox', () => { - it('deleteChip', () => { - expect(deleteChip(['user1', 'user2'], 'user1')).toEqual(['user2']); - expect(deleteChip(['user2'], 'user1')).toEqual(['user2']); - expect(deleteChip(['user2'], 'user2')).toEqual([]); - }); - it('checkbox logic', () => { - expect(checkBoxLogic([], 'user1')).toEqual(['user1']); - expect(checkBoxLogic(['user1'], 'user1')).toEqual([]); - expect(checkBoxLogic(['user1'], 'user2')).toEqual(['user1', 'user2']); - }); -}); diff --git a/src/routes/costModels/components/logic/selectCheckbox.ts b/src/routes/costModels/components/logic/selectCheckbox.ts deleted file mode 100644 index 0eda60828..000000000 --- a/src/routes/costModels/components/logic/selectCheckbox.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const checkBoxLogic = (current: string[], selection: string): string[] => { - return current.includes(selection) ? deleteChip(current, selection) : [...current, selection]; -}; - -export const deleteChip = (current: string[], selection: string): string[] => { - return current.filter(chip => chip !== selection); -}; diff --git a/src/routes/costModels/components/logic/selectStateMachine.test.tsx b/src/routes/costModels/components/logic/selectStateMachine.test.tsx deleted file mode 100644 index 9f8ed4634..000000000 --- a/src/routes/costModels/components/logic/selectStateMachine.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { createModel } from '@xstate/test'; -import type { Interpreter } from 'xstate'; -import { assign, interpret } from 'xstate'; - -import type { SelectMachineContext, SelectMachineEvents, SelectMachineStates } from './selectStateMachine'; -import { selectMachineState } from './selectStateMachine'; - -describe('selectmachine', () => { - const newMachine = selectMachineState.withConfig({ - actions: { - assignSelection: assign({ - selection: (_ctx, evt) => [evt.selection], - }), - }, - }); - - const s = interpret(newMachine).onTransition(stt => { - expect([stt.context, stt.toStrings()]).toMatchSnapshot(); - }) as Interpreter; - s.start(); - - const tModel = createModel(newMachine).withEvents({ - TOGGLE: { - exec: () => { - s.send({ type: 'TOGGLE' }); - }, - }, - SELECT: { - exec: () => { - s.send({ type: 'SELECT', selection: '23' }); - }, - }, - }); - - tModel.getShortestPathPlansTo('expanded').forEach(plan => { - plan.paths.forEach(path => { - it(`${path.description}: ${plan.description}`, () => { - path.test({}); - }); - }); - }); -}); diff --git a/src/routes/costModels/components/logic/selectStateMachine.ts b/src/routes/costModels/components/logic/selectStateMachine.ts deleted file mode 100644 index e2dddf128..000000000 --- a/src/routes/costModels/components/logic/selectStateMachine.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { MachineConfig } from 'xstate'; -import { Machine } from 'xstate'; - -export interface SelectMachineContext { - selection?: string[]; -} - -export interface SelectMachineStates { - states: { - collapsed: any; - expanded: any; - }; -} - -export type SelectMachineEvents = { type: 'TOGGLE'; selection?: string } | { type: 'SELECT'; selection: string }; - -export const selectMachineConfig: MachineConfig = { - context: { - selection: [], - }, - initial: 'collapsed', - states: { - collapsed: { - on: { - TOGGLE: 'expanded', - }, - }, - expanded: { - on: { - TOGGLE: 'collapsed', - SELECT: { - target: 'collapsed', - actions: ['assignSelection'], - }, - }, - }, - }, -}; - -export const selectMachineState = Machine(selectMachineConfig); diff --git a/src/routes/costModels/components/logic/types.ts b/src/routes/costModels/components/logic/types.ts deleted file mode 100644 index d92ad48c3..000000000 --- a/src/routes/costModels/components/logic/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Option { - label: string; - value: string; -} diff --git a/src/routes/costModels/components/paginationToolbarTemplate.tsx b/src/routes/costModels/components/paginationToolbarTemplate.tsx deleted file mode 100644 index 495054063..000000000 --- a/src/routes/costModels/components/paginationToolbarTemplate.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { PaginationProps } from '@patternfly/react-core'; -import { Pagination, Toolbar, ToolbarContent, ToolbarItem } from '@patternfly/react-core'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; - -interface PaginationToolbarTemplateProps extends PaginationProps, WrappedComponentProps { - id?: string; -} - -export const PaginationToolbarTemplateBase: React.FC = ({ - id, - intl, - itemCount, - perPage, - page, - variant, - onPerPageSelect, - onSetPage, -}) => { - return ( - - - - - - - - ); -}; - -export const PaginationToolbarTemplate = injectIntl(PaginationToolbarTemplateBase); diff --git a/src/routes/costModels/components/priceListToolbar.styles.ts b/src/routes/costModels/components/priceListToolbar.styles.ts deleted file mode 100644 index 449d6e958..000000000 --- a/src/routes/costModels/components/priceListToolbar.styles.ts +++ /dev/null @@ -1,7 +0,0 @@ -import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; - -export const styles = { - toolbar: { - gridGap: global_spacer_md.value, - }, -}; diff --git a/src/routes/costModels/components/priceListToolbar.test.tsx b/src/routes/costModels/components/priceListToolbar.test.tsx deleted file mode 100644 index e6de2707c..000000000 --- a/src/routes/costModels/components/priceListToolbar.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { PriceListToolbar } from './priceListToolbar'; - -test('price list toolbar', () => { - render( - Add rate} - primary={
Primary selector
} - selected={'sec2'} - secondaries={[ - { - component:
Secondary 1
, - name: 'sec1', - onRemove: jest.fn(), - filters: ['item1', 'item2'], - }, - { - component:
Secondary 2
, - name: 'sec2', - onRemove: jest.fn(), - filters: ['version3'], - }, - ]} - onClear={jest.fn()} - pagination={
Pagination
} - /> - ); - expect(screen.queryAllByText('Primary selector').length).toBe(1); - expect(screen.queryAllByText('Secondary 2').length).toBe(1); - expect(screen.queryAllByText('Secondary 1').length).toBe(0); - expect(screen.queryAllByText('Pagination').length).toBe(1); - expect(screen.queryAllByText(/sec\d/).length).toBe(2); - expect(screen.queryAllByText(/item\d/).length).toBe(2); - expect(screen.queryAllByText(/version\d/).length).toBe(1); -}); diff --git a/src/routes/costModels/components/priceListToolbar.tsx b/src/routes/costModels/components/priceListToolbar.tsx deleted file mode 100644 index 0a56d92cd..000000000 --- a/src/routes/costModels/components/priceListToolbar.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - Toolbar, - ToolbarContent, - ToolbarFilter, - ToolbarGroup, - ToolbarItem, - ToolbarItemVariant, - ToolbarToggleGroup, -} from '@patternfly/react-core'; -import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; -import React from 'react'; - -import { styles } from './priceListToolbar.styles'; - -interface PriceListToolbarProps { - primary: React.ReactNode; - selected: string; - secondaries: { - component: React.ReactNode; - name: string; - onRemove: (category: string, chip: string) => void; - filters: string[]; - }[]; - onClear: () => void; - pagination: React.ReactNode; - button: React.ReactNode; -} - -export const PriceListToolbar: React.FC = ({ - primary, - secondaries, - pagination, - button, - onClear, - selected, -}) => { - return ( - - - }> - - {primary} - {secondaries.map(secondary => { - return ( - - - {selected === secondary.name ? secondary.component : ''} - - - ); - })} - - - {button} - {pagination} - -
-
- ); -}; diff --git a/src/routes/costModels/components/rateForm/canSubmit.tsx b/src/routes/costModels/components/rateForm/canSubmit.tsx deleted file mode 100644 index 5f391ac0a..000000000 --- a/src/routes/costModels/components/rateForm/canSubmit.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { RateFormData } from './utils'; - -export function canSubmit(rateFormData: RateFormData): boolean { - if (rateFormData.rateKind === 'tagging') { - return ( - rateFormData.errors.description === null && - rateFormData.errors.measurement === null && - rateFormData.errors.tagValues.every(err => err === null) && - rateFormData.errors.tagValueValues.every(err => err === null) && - rateFormData.errors.tagDescription.every(err => err === null) && - rateFormData.errors.tagKey === null - ); - } - return ( - rateFormData.errors.description === null && - rateFormData.errors.measurement === null && - rateFormData.errors.tieredRates === null - ); -} diff --git a/src/routes/costModels/components/rateForm/constants.ts b/src/routes/costModels/components/rateForm/constants.ts deleted file mode 100644 index aeffb5d08..000000000 --- a/src/routes/costModels/components/rateForm/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -import messages from 'locales/messages'; - -export const textHelpers = { - description_too_long: messages.costModelsDescTooLong, - duplicate: messages.priceListDuplicate, - not_number: messages.priceListNumberRate, - not_positive: messages.priceListPosNumberRate, - rate_too_long: messages.costModelsRateTooLong, - required: messages.costModelsRequiredField, - tag_too_long: messages.costModelsInfoTooLong, -}; diff --git a/src/routes/costModels/components/rateForm/hasDiff.ts b/src/routes/costModels/components/rateForm/hasDiff.ts deleted file mode 100644 index 681581e22..000000000 --- a/src/routes/costModels/components/rateForm/hasDiff.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Rate } from 'api/rates'; - -import type { RateFormData } from './utils'; - -export function hasDiff(rate: Rate, rateFormData: RateFormData): boolean { - if (!rate) { - return true; - } - if (rate.description !== rateFormData.description) { - return true; - } - if (rate.metric.label_metric !== rateFormData.metric) { - return true; - } - if (rate.metric.label_measurement !== rateFormData.measurement.value) { - return true; - } - if (rate.cost_type !== rateFormData.calculation) { - return true; - } - const rateKind = rate.tiered_rates ? 'regular' : 'tagging'; - if (rateKind !== rateFormData.rateKind) { - return true; - } - if (rateKind === 'regular') { - if (Number(rate.tiered_rates[0].value) !== Number(rateFormData.tieredRates[0].value)) { - return true; - } - } - if (rateKind === 'tagging') { - const tr = rate.tag_rates; - if (tr.tag_key !== rateFormData.taggingRates.tagKey.value) { - return true; - } - if (tr.tag_values.length !== rateFormData.taggingRates.tagValues.length) { - return true; - } - const hasTagValuesDiff = tr.tag_values.some((tvalue, ix) => { - const cur = rateFormData.taggingRates.tagValues[ix]; - const isCurDefault = rateFormData.taggingRates.defaultTag === ix; - return ( - tvalue.tag_value !== cur.tagValue || - Number(tvalue.value) !== Number(cur.inputValue) || - tvalue.description !== cur.description || - tvalue.default !== isCurDefault - ); - }); - if (hasTagValuesDiff) { - return true; - } - } - return false; -} diff --git a/src/routes/costModels/components/rateForm/index.ts b/src/routes/costModels/components/rateForm/index.ts deleted file mode 100644 index 9e9ed9529..000000000 --- a/src/routes/costModels/components/rateForm/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { transformFormDataToRequest, mergeToRequest, genFormDataFromRate } from './utils'; -export { useRateData } from './useRateForm'; -export { RateForm } from './rateForm'; -export { canSubmit } from './canSubmit'; -export { hasDiff } from './hasDiff'; -export type { RateFormData } from './utils'; diff --git a/src/routes/costModels/components/rateForm/rateForm.tsx b/src/routes/costModels/components/rateForm/rateForm.tsx deleted file mode 100644 index 47220e2e7..000000000 --- a/src/routes/costModels/components/rateForm/rateForm.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import { Button, ButtonVariant, FormGroup, Grid, GridItem, Radio, Switch } from '@patternfly/react-core'; -import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; -import type { MetricHash } from 'api/metrics'; -import { intl as defaultIntl } from 'components/i18n'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { RateInput } from 'routes/costModels/components/inputs/rateInput'; -import { Selector } from 'routes/costModels/components/inputs/selector'; -import { SimpleInput } from 'routes/costModels/components/inputs/simpleInput'; -import { unitsLookupKey } from 'utils/format'; - -import { TaggingRatesForm } from './taggingRatesForm'; -import type { UseRateData } from './useRateForm'; - -interface RateFormOwnProps { - currencyUnits?: string; - rateFormData: UseRateData; - metricsHash: MetricHash; -} - -type RateFormProps = RateFormOwnProps & WrappedComponentProps; - -// defaultIntl required for testing -const RateFormBase: React.FC = ({ currencyUnits, intl = defaultIntl, metricsHash, rateFormData }) => { - const { - addTag, - calculation, - description, - errors, - measurement: { value: measurement, isDirty: measurementDirty }, - metric, - rateKind, - removeTag, - setCalculation, - setDescription, - setMeasurement, - setMetric, - setRegular, - setTagKey, - step, - taggingRates: { - tagKey: { value: tagKey, isDirty: isTagKeyDirty }, - defaultTag, - tagValues, - }, - tieredRates: { - 0: { inputValue, isDirty: regularDirty }, - }, - toggleTaggingRate, - updateDefaultTag, - updateTag, - } = rateFormData; - const getMetricLabel = m => { - // Match message descriptor or default to API string - const value = m.replace(/ /g, '_').toLowerCase(); - return intl.formatMessage(messages.metricValues, { value }) || m; - }; - const getMeasurementLabel = (m, u) => { - // Match message descriptor or default to API string - const units = intl.formatMessage(messages.units, { units: unitsLookupKey(u) }) || u; - return ( - intl.formatMessage(messages.measurementValues, { - value: m.toLowerCase().replace('-', '_'), - units, - count: 2, - }) || m - ); - }; - const getMeasurementDescription = (o, u) => { - // Match message descriptor or default to API string - // units only works with Node, Cluster, and PVC. it does not need to be translated - // if the metric is CPU, Memory, or Storage, units will be like `core_hours` or `gb_hours` and must be translated - const units = u.toLowerCase().replace('-', '_'); - const desc = intl.formatMessage(messages.measurementValuesDesc, { - value: o.toLowerCase().replace('-', '_'), - units: units ? units : u, - }); - return desc ? desc : o; - }; - const metricOptions = React.useMemo(() => { - return Object.keys(metricsHash); - }, [metricsHash]); - const measurementOptions = React.useMemo(() => { - if (!metricOptions.includes(metric)) { - return []; - } - return Object.keys(metricsHash[metric]); - }, [metricOptions, metric]); - const style = { width: '360px' }; - const addStyle = { - paddingLeft: '0', - textAlign: 'left', - } as React.CSSProperties; - - return ( - <> - - - - { - return { - label: getMetricLabel(opt), - value: opt, - isDisabled: false, - }; - }), - ]} - /> - - {step === 'initial' ? null : ( - - { - const unit = metricsHash[metric][opt].label_measurement_unit; - return { - label: getMeasurementLabel(opt, unit), - value: opt, - isDisabled: false, - description: getMeasurementDescription(opt, unit), - }; - }), - ]} - /> - - )} - - {step === 'set_rate' ? ( - <> - <> - - setCalculation('Infrastructure')} - /> - setCalculation('Supplementary')} - /> - - {metric !== 'Cluster' ? ( - - ) : null} - - {rateKind === 'regular' ? ( - - ) : ( - <> - - - - - )} - - ) : null} - - ); -}; - -const RateForm = injectIntl(RateFormBase); -export { RateForm }; diff --git a/src/routes/costModels/components/rateForm/taggingRatesForm.tsx b/src/routes/costModels/components/rateForm/taggingRatesForm.tsx deleted file mode 100644 index 83fe85487..000000000 --- a/src/routes/costModels/components/rateForm/taggingRatesForm.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Button, ButtonVariant, Checkbox, FormGroup, Split, SplitItem } from '@patternfly/react-core'; -import { MinusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/minus-circle-icon'; -import { intl as defaultIntl } from 'components/i18n'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { RateInput } from 'routes/costModels/components/inputs/rateInput'; -import { SimpleInput } from 'routes/costModels/components/inputs/simpleInput'; - -import type { UseRateData } from './useRateForm'; -import type { RateFormErrors, RateFormTagValue } from './utils'; - -interface TaggingRatesFormOwnProps { - currencyUnits?: string; - defaultTag: UseRateData['taggingRates']['defaultTag']; - errors: Pick; - removeTag: UseRateData['removeTag']; - tagValues: RateFormTagValue[]; - updateDefaultTag: UseRateData['updateDefaultTag']; - updateTag: UseRateData['updateTag']; -} - -type TaggingRatesFormProps = TaggingRatesFormOwnProps & WrappedComponentProps; - -const TaggingRatesFormBase: React.FC = ({ - currencyUnits, - defaultTag, - errors, - intl = defaultIntl, // Default required for testing - tagValues, - updateDefaultTag, - removeTag, - updateTag, -}) => { - const style = { width: '200px' }; - const elementStyle = { - height: '100%', - position: 'relative', - top: '50%', - } as React.CSSProperties; - return ( - <> - {tagValues.map((tag, ix: number) => { - return ( - - {intl.formatMessage(messages.equalsSymbol)} - - updateTag({ tagValue: value }, ix)} - validated={tagValues[ix].isTagValueDirty && errors.tagValueValues[ix] ? 'error' : 'default'} - helperTextInvalid={errors.tagValueValues[ix]} - /> - - - updateTag({ value }, ix)} - style={style} - validated={tagValues[ix].isDirty && errors.tagValues[ix] ? 'error' : 'default'} - value={tag.inputValue} - /> - - - updateTag({ description: value }, ix)} - helperTextInvalid={errors.tagDescription[ix]} - /> - - - - updateDefaultTag(ix)} /> - - - -  }> - - - - - ); - })} - - ); -}; - -const TaggingRatesForm = injectIntl(TaggingRatesFormBase); -export { TaggingRatesForm }; diff --git a/src/routes/costModels/components/rateForm/useRateForm.test.tsx b/src/routes/costModels/components/rateForm/useRateForm.test.tsx deleted file mode 100644 index 83bf18888..000000000 --- a/src/routes/costModels/components/rateForm/useRateForm.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { rateFormReducer } from './useRateForm'; -import { initialRateFormData } from './utils'; - -describe('do not update state scenarios', () => { - test('in the initial step discard UPDATE_MEASUREMENT', () => { - const state = rateFormReducer(undefined, { type: 'UPDATE_MEASUREMENT', value: 'Usage' }); - expect(state.measurement).toEqual(initialRateFormData.measurement); - }); - test('in the initial step discard UPDATE_CALCULATION', () => { - const state = rateFormReducer(undefined, { type: 'UPDATE_CALCULATION', value: 'Infrastructure' }); - expect(state.calculation).toEqual(initialRateFormData.calculation); - }); - test('unless step is set_rate discard TOGGLE_RATE_KIND', () => { - let state = rateFormReducer(undefined, { type: 'TOGGLE_RATE_KIND' }); - expect(state.rateKind).toEqual(initialRateFormData.rateKind); - state = rateFormReducer({ ...initialRateFormData, step: 'set_metric' }, { type: 'TOGGLE_RATE_KIND' }); - expect(state.rateKind).toEqual(initialRateFormData.rateKind); - }); - test('unless step is set_rate and rate kind is regular discard BLUR_REGULAR', () => { - let state = rateFormReducer(undefined, { type: 'BLUR_REGULAR' }); - expect(state.errors.tieredRates).toEqual(initialRateFormData.errors.tieredRates); - state = rateFormReducer( - { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }, - { type: 'TOGGLE_RATE_KIND' } - ); - expect(state.errors.tieredRates).toEqual(initialRateFormData.errors.tieredRates); - }); - test('unless step is set_rate and rate kind is tagging discard UPDATE_TAG_KEY', () => { - let state = rateFormReducer(undefined, { type: 'UPDATE_TAG_KEY', value: 'value!' }); - expect(state.taggingRates.tagKey).toEqual(initialRateFormData.taggingRates.tagKey); - state = rateFormReducer( - { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }, - { type: 'UPDATE_TAG_KEY', value: 'value!' } - ); - expect(state.taggingRates.tagKey).toEqual(initialRateFormData.taggingRates.tagKey); - }); - test('unless step is set_rate and rate kind is tagging discard UPDATE_TAG_DEFAULT', () => { - let state = rateFormReducer(undefined, { type: 'UPDATE_TAG_DEFAULT', index: 0 }); - expect(state.taggingRates.defaultTag).toEqual(initialRateFormData.taggingRates.defaultTag); - state = rateFormReducer( - { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }, - { type: 'UPDATE_TAG_DEFAULT', index: 0 } - ); - expect(state.taggingRates.tagKey).toEqual(initialRateFormData.taggingRates.tagKey); - }); - test('unless step is set_rate and rate kind is tagging discard BLUR_TAG_RATE', () => { - let state = rateFormReducer(undefined, { type: 'BLUR_TAG_RATE', index: 0 }); - expect(state.errors).toEqual(initialRateFormData.errors); - state = rateFormReducer( - { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }, - { type: 'BLUR_TAG_RATE', index: 0 } - ); - expect(state.errors).toEqual(initialRateFormData.errors); - }); - test('unless step is set_rate and rate kind is tagging discard UPDATE_TAG', () => { - let state = rateFormReducer(undefined, { type: 'UPDATE_TAG', index: 0, payload: { value: '20' } }); - expect(state.taggingRates).toEqual(initialRateFormData.taggingRates); - state = rateFormReducer( - { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }, - { type: 'UPDATE_TAG', index: 0, payload: { value: '20' } } - ); - expect(state.taggingRates).toEqual(initialRateFormData.taggingRates); - }); - test('unless step is set_rate and rate kind is tagging discard REMOVE_TAG', () => { - let state = rateFormReducer(undefined, { type: 'REMOVE_TAG', index: 1 }); - expect(state).toEqual(initialRateFormData); - const initial = { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }; - state = rateFormReducer(initial, { type: 'REMOVE_TAG', index: 1 }); - expect(state).toEqual(initial); - }); - test('unless step is set_rate and rate kind is tagging discard ADD_TAG', () => { - let state = rateFormReducer(undefined, { type: 'ADD_TAG' }); - expect(state).toEqual(initialRateFormData); - const initial = { ...initialRateFormData, rateKind: 'regular', step: 'set_rate' }; - state = rateFormReducer(initial, { type: 'ADD_TAG' }); - expect(state).toEqual(initial); - }); - test('discard any action that is not a valid type', () => { - const state = rateFormReducer(undefined, { type: 'BLAAAAA' }); - expect(state).toEqual(initialRateFormData); - }); -}); diff --git a/src/routes/costModels/components/rateForm/useRateForm.tsx b/src/routes/costModels/components/rateForm/useRateForm.tsx deleted file mode 100644 index dd74d0953..000000000 --- a/src/routes/costModels/components/rateForm/useRateForm.tsx +++ /dev/null @@ -1,321 +0,0 @@ -import type { MetricHash } from 'api/metrics'; -import type { Rate } from 'api/rates'; -import React from 'react'; -import { unFormat } from 'utils/format'; - -import { textHelpers } from './constants'; -import type { RateFormData, RateFormTagValue } from './utils'; -import { - descriptionErrors, - initialtaggingRates, - isDuplicateTagRate, - OtherTierFromRate, - OtherTierFromRateForm, - tagKeyValueErrors, -} from './utils'; -import { checkRateOnChange, genFormDataFromRate, getDefaultCalculation, initialRateFormData } from './utils'; - -type Actions = - | { type: 'ADD_TAG' } - | { type: 'REMOVE_TAG'; index: number } - | { - type: 'UPDATE_TAG'; - index: number; - payload: Partial; - } - | { type: 'UPDATE_DESCRIPTION'; value: string } - | { type: 'UPDATE_METRIC'; value: string; defaultCalculation: string } - | { type: 'UPDATE_MEASUREMENT'; value: string } - | { type: 'UPDATE_CALCULATION'; value: string } - | { type: 'UPDATE_REGULAR'; value: string } - | { type: 'UPDATE_TAG_KEY'; value: string } - | { type: 'UPDATE_TAG_DEFAULT'; index: number } - | { type: 'TOGGLE_RATE_KIND' } - | { type: 'RESET_FORM'; payload: RateFormData }; - -export function rateFormReducer(state = initialRateFormData, action: Actions) { - switch (action.type) { - case 'UPDATE_DESCRIPTION': - return { - ...state, - description: action.value, - errors: { - ...state.errors, - description: descriptionErrors(action.value), - }, - }; - case 'UPDATE_METRIC': { - const errors = state.errors; - const newMeasurement = state.measurement; - if (newMeasurement.isDirty) { - newMeasurement.value = ''; - // Past discussions, we've agreed this required error should show on measurement when metric updates - errors.measurement = textHelpers.required; - } - let step = state.step; - if (step === 'initial') { - step = 'set_metric'; - } - const newState = { - ...state, - metric: action.value, - measurement: newMeasurement, - errors, - step, - calculation: action.defaultCalculation, - rateKind: action.value === 'Cluster' ? 'regular' : state.rateKind, - }; - const cur = OtherTierFromRateForm(newState); - const duplicate = newState.otherTiers.find(val => isDuplicateTagRate(OtherTierFromRate(val), cur)); - return { - ...newState, - errors: { ...newState.errors, tagKey: duplicate ? textHelpers.duplicate : null }, - }; - } - case 'UPDATE_MEASUREMENT': { - if (state.step === 'initial') { - return state; - } - let step: string = state.step; - if (step === 'set_metric') { - step = 'set_rate'; - } - const newState = { - ...state, - measurement: { value: action.value, isDirty: true }, - errors: { ...state.errors, measurement: null }, - step, - }; - const cur = OtherTierFromRateForm(newState); - const duplicate = newState.otherTiers.find(val => isDuplicateTagRate(OtherTierFromRate(val), cur)); - return { - ...newState, - errors: { ...newState.errors, tagKey: duplicate ? textHelpers.duplicate : null }, - }; - } - case 'UPDATE_CALCULATION': { - if (state.step !== 'set_rate') { - return state; - } - const newState = { - ...state, - calculation: action.value, - }; - const cur = OtherTierFromRateForm(newState); - const duplicate = newState.otherTiers.find(val => isDuplicateTagRate(OtherTierFromRate(val), cur)); - return { - ...newState, - errors: { ...newState.errors, tagKey: duplicate ? textHelpers.duplicate : null }, - }; - } - case 'TOGGLE_RATE_KIND': { - if (state.step !== 'set_rate') { - return state; - } - return { - ...state, - rateKind: state.rateKind === 'regular' ? 'tagging' : 'regular', - }; - } - case 'UPDATE_REGULAR': { - return { - ...state, - tieredRates: [ - { - isDirty: true, - inputValue: action.value, - value: unFormat(action.value), // Normalize for API requests where USD decimal format is expected - }, - ], - errors: { - ...state.errors, - tieredRates: checkRateOnChange(action.value), - }, - }; - } - case 'UPDATE_TAG_KEY': { - if (state.step !== 'set_rate' || state.rateKind !== 'tagging') { - return state; - } - const newState = { - ...state, - taggingRates: { - ...state.taggingRates, - tagKey: { value: action.value, isDirty: true }, - }, - errors: { - ...state.errors, - tagKey: tagKeyValueErrors(action.value), - }, - }; - const cur = OtherTierFromRateForm(newState); - const duplicate = newState.otherTiers.find(val => isDuplicateTagRate(OtherTierFromRate(val), cur)); - return { - ...newState, - errors: { ...newState.errors, tagKey: duplicate ? textHelpers.duplicate : newState.errors.tagKey }, - }; - } - case 'UPDATE_TAG_DEFAULT': { - if (state.step !== 'set_rate' && state.rateKind !== 'tagging') { - return state; - } - return { - ...state, - taggingRates: { - ...state.taggingRates, - defaultTag: state.taggingRates.defaultTag === action.index ? null : action.index, - }, - }; - } - case 'UPDATE_TAG': { - if (state.step !== 'set_rate' || state.rateKind !== 'tagging') { - return state; - } - let error = state.errors.tagValues[action.index]; - let tagValueError = state.errors.tagValueValues[action.index]; - let descriptionError = state.errors.tagDescription[action.index]; - let isDirty = state.taggingRates.tagValues[action.index].isDirty; - let isTagValueDirty = state.taggingRates.tagValues[action.index].isTagValueDirty; - - if (action.payload.value !== undefined) { - const { value: rate } = action.payload; - error = checkRateOnChange(rate); - isDirty = true; - } - if (action.payload.tagValue !== undefined) { - tagValueError = tagKeyValueErrors(action.payload.tagValue); - isTagValueDirty = true; - } - if (action.payload.description !== undefined) { - descriptionError = descriptionErrors(action.payload.description); - } - return { - ...state, - taggingRates: { - ...state.taggingRates, - tagValues: [ - ...state.taggingRates.tagValues.slice(0, action.index), - { - ...state.taggingRates.tagValues[action.index], - ...action.payload, - ...(action.payload.value !== undefined && { - inputValue: action.payload.value, // Original user input - value: unFormat(action.payload.value), // Normalize for API requests where USD decimal format is expected - }), - isDirty, - isTagValueDirty, - }, - ...state.taggingRates.tagValues.slice(action.index + 1), - ], - }, - errors: { - ...state.errors, - tagDescription: [ - ...state.errors.tagDescription.slice(0, action.index), - descriptionError, - ...state.errors.tagDescription.slice(action.index + 1), - ], - tagValueValues: [ - ...state.errors.tagValueValues.slice(0, action.index), - tagValueError, - ...state.errors.tagValueValues.slice(action.index + 1), - ], - tagValues: [ - ...state.errors.tagValues.slice(0, action.index), - error, - ...state.errors.tagValues.slice(action.index + 1), - ], - // "Create rate" button must remain disabled if tag key not set -- see https://issues.redhat.com/browse/COST-3977 - tagKey: tagKeyValueErrors(state.taggingRates.tagKey.value), - }, - }; - } - case 'REMOVE_TAG': { - if (state.step !== 'set_rate' || state.rateKind !== 'tagging') { - return state; - } - return { - ...state, - errors: { - ...state.errors, - tagValues: [ - ...state.errors.tagValues.slice(0, action.index), - ...state.errors.tagValues.slice(action.index + 1), - ], - tagValueValues: [ - ...state.errors.tagValueValues.slice(0, action.index), - ...state.errors.tagValueValues.slice(action.index + 1), - ], - }, - taggingRates: { - ...state.taggingRates, - defaultTag: - state.taggingRates.defaultTag === action.index - ? null - : state.taggingRates.defaultTag > action.index - ? state.taggingRates.defaultTag - 1 - : state.taggingRates.defaultTag, - tagValues: [ - ...state.taggingRates.tagValues.slice(0, action.index), - ...state.taggingRates.tagValues.slice(action.index + 1), - ], - }, - }; - } - case 'ADD_TAG': { - if (state.step !== 'set_rate' || state.rateKind !== 'tagging') { - return state; - } - return { - ...state, - errors: { - ...state.errors, - tagValues: [...state.errors.tagValues, textHelpers.required], - tagDescription: [...state.errors.tagDescription, null], - }, - taggingRates: { - ...state.taggingRates, - tagValues: [...state.taggingRates.tagValues, { ...initialtaggingRates.tagValues[0] }], - }, - }; - } - case 'RESET_FORM': { - return action.payload; - } - default: { - return state; - } - } -} - -export type UseRateData = ReturnType; - -export function useRateData(metricsHash: MetricHash, rate: Rate = undefined, tiers: Rate[] = []) { - const initial = genFormDataFromRate(rate, undefined, tiers); - const [state, dispatch] = React.useReducer(rateFormReducer, initial); - return { - ...state, - reset: (payload: RateFormData) => dispatch({ type: 'RESET_FORM', payload }), - setDescription: (value: string) => dispatch({ type: 'UPDATE_DESCRIPTION', value }), - setMetric: (value: string) => - dispatch({ - type: 'UPDATE_METRIC', - value, - defaultCalculation: getDefaultCalculation(metricsHash, value), - }), - setMeasurement: (value: string) => - dispatch({ - type: 'UPDATE_MEASUREMENT', - value, - }), - setCalculation: (value: string) => dispatch({ type: 'UPDATE_CALCULATION', value }), - setRegular: (value: string) => dispatch({ type: 'UPDATE_REGULAR', value }), - toggleTaggingRate: () => dispatch({ type: 'TOGGLE_RATE_KIND' }), - setTagKey: (value: string) => dispatch({ type: 'UPDATE_TAG_KEY', value }), - removeTag: (index: number) => dispatch({ type: 'REMOVE_TAG', index }), - addTag: () => dispatch({ type: 'ADD_TAG' }), - updateTag: (payload: Partial<(typeof initialRateFormData)['taggingRates']['tagValues'][0]>, index: number) => - dispatch({ type: 'UPDATE_TAG', index, payload }), - updateDefaultTag: (index: number) => dispatch({ type: 'UPDATE_TAG_DEFAULT', index }), - }; -} diff --git a/src/routes/costModels/components/rateForm/utils.tsx b/src/routes/costModels/components/rateForm/utils.tsx deleted file mode 100644 index a4f7ad582..000000000 --- a/src/routes/costModels/components/rateForm/utils.tsx +++ /dev/null @@ -1,297 +0,0 @@ -import { SortByDirection } from '@patternfly/react-table'; -import type { CostModel, CostModelRequest } from 'api/costModels'; -import type { MetricHash } from 'api/metrics'; -import type { Rate, RateRequest, TagRates } from 'api/rates'; -import { countDecimals, formatCurrencyRateRaw, isCurrencyFormatValid, unFormat } from 'utils/format'; - -import { textHelpers } from './constants'; - -export const initialtaggingRates = { - tagKey: { - value: '', - isDirty: false, - }, - defaultTag: null, - tagValues: [ - { - tagValue: '', - description: '', - isDirty: false, - isTagValueDirty: false, - inputValue: '', - value: '', - }, - ], -}; - -export const initialRateFormData = { - otherTiers: [] as Rate[], - step: 'initial', - description: '', - metric: '', - measurement: { - value: '', - isDirty: false, - }, - calculation: '', - rateKind: 'regular', - tieredRates: [ - { - isDirty: false, - inputValue: '', - value: '', - }, - ], - taggingRates: { ...initialtaggingRates }, - errors: { - description: null, - measurement: textHelpers.required, - tieredRates: textHelpers.required, - tagValues: [textHelpers.required], - tagDescription: [null], - tagKey: textHelpers.required, - tagValueValues: [textHelpers.required], - }, -}; - -export type RateFormData = typeof initialRateFormData; -export type RateFormTagValue = (typeof initialRateFormData)['taggingRates']['tagValues'][0]; -export type taggingRates = (typeof initialRateFormData)['taggingRates']; -export type RateFormErrors = (typeof initialRateFormData)['errors']; - -export const checkRateOnChange = (inputValue: string) => { - if (inputValue.length === 0) { - return textHelpers.required; - } - if (!isCurrencyFormatValid(inputValue)) { - return textHelpers.not_number; - } - if (Number(unFormat(inputValue)) < 0) { - return textHelpers.not_positive; - } - // Test number of decimals - const decimals = countDecimals(inputValue); - if (decimals > 10) { - return textHelpers.rate_too_long; - } - return null; -}; - -export function getDefaultCalculation(metricsHash: MetricHash, metric: string) { - let options = Object.keys(metricsHash); - if (!options.includes(metric)) { - return ''; - } - options = Object.keys(metricsHash[metric]); - if (options.length === 0) { - return ''; - } - return metricsHash[metric][options[0]].default_cost_type; -} - -export function genFormDataFromRate(rate: Rate, defaultValue = initialRateFormData, tiers: Rate[]): RateFormData { - const otherTiers = tiers || defaultValue.otherTiers; - if (!rate) { - return { ...defaultValue, otherTiers }; - } - const rateKind = rate.tiered_rates ? 'regular' : 'tagging'; - let tieredRates = [{ inputValue: '', value: '', isDirty: true }]; - const tagRates = { ...initialtaggingRates }; - const errors = { - description: null, - measurement: null, - tieredRates: null, - tagValues: [null], - tagKey: null, - tagValueValues: [null], - tagDescription: [null], - }; - if (rateKind === 'tagging') { - const item = rate.tag_rates as TagRates; - tagRates.tagKey = { value: item.tag_key, isDirty: true }; - const defaultIndex = item.tag_values.findIndex(tvalue => tvalue.default); - tagRates.defaultTag = defaultIndex === -1 ? null : defaultIndex; - tagRates.tagValues = item.tag_values.map(tvalue => { - const value = formatCurrencyRateRaw(tvalue.value, tvalue.unit); - return { - description: tvalue.description, - inputValue: value, - isDirty: false, - isTagValueDirty: false, - tagValue: tvalue.tag_value, - value, - }; - }); - errors.tieredRates = textHelpers.required; - errors.tagValueValues = new Array(item.tag_values.length).fill(null); - errors.tagValues = new Array(item.tag_values.length).fill(null); - errors.tagDescription = new Array(item.tag_values.length).fill(null); - } - if (rateKind === 'regular') { - tieredRates = rate.tiered_rates.map(tieredRate => { - const value = formatCurrencyRateRaw(tieredRate.value, tieredRate.unit); - return { - inputValue: value, - isDirty: true, - value, - }; - }); - errors.tagValues = [textHelpers.required]; - errors.tagValueValues = [textHelpers.required]; - } - return { - otherTiers, - step: 'set_rate', - description: rate.description, - metric: rate.metric.label_metric, - measurement: { - value: rate.metric.label_measurement, - isDirty: true, - }, - calculation: rate.cost_type, - rateKind, - tieredRates, - taggingRates: tagRates, - errors, - }; -} - -export const mergeToRequest = ( - metricsHash: MetricHash, - costModel: CostModel, - rateFormData: RateFormData, - index: number = -1 -): CostModelRequest => { - if (index < 0) { - index = costModel.rates.length; - } - const rate = transformFormDataToRequest(rateFormData, metricsHash, costModel.currency) as RateRequest; - return { - currency: costModel.currency, - name: costModel.name, - source_type: 'OCP', - description: costModel.description, - distribution_info: { - distribution_type: costModel.distribution_info ? costModel.distribution_info.distribution_type : undefined, - platform_cost: costModel.distribution_info ? costModel.distribution_info.platform_cost : undefined, - worker_cost: costModel.distribution_info ? costModel.distribution_info.worker_cost : undefined, - }, - source_uuids: costModel.sources.map(src => src.uuid), - markup: { value: costModel.markup.value, unit: 'percent' }, - rates: [...costModel.rates.slice(0, index), rate, ...costModel.rates.slice(index + 1)], - }; -}; - -export const transformFormDataToRequest = ( - rateFormData: RateFormData, - metricsHash: MetricHash, - currencyUnits: string = 'USD' -): Rate => { - const ratesKey = rateFormData.rateKind === 'tagging' ? 'tag_rates' : 'tiered_rates'; - const ratesBody = - rateFormData.rateKind === 'tagging' - ? { - tag_key: rateFormData.taggingRates.tagKey.value, - tag_values: rateFormData.taggingRates.tagValues.map((tvalue, ix) => { - return { - tag_value: tvalue.tagValue, - unit: currencyUnits, - value: tvalue.value, - description: tvalue.description, - default: ix === rateFormData.taggingRates.defaultTag, - }; - }), - } - : rateFormData.tieredRates.map(tiered => { - return { - value: tiered.value, - unit: currencyUnits, - usage: { unit: currencyUnits }, - }; - }); - const metricData = metricsHash[rateFormData.metric][rateFormData.measurement.value]; - return { - description: rateFormData.description, - metric: { - metric: metricData.metric, - name: metricData.metric, - label_metric: metricData.label_metric, - label_measurement: metricData.label_measurement, - label_measurement_unit: metricData.label_measurement_unit, - source_type: 'OpenShift Cluster Platform', - default_cost_type: metricData.default_cost_type, - }, - cost_type: rateFormData.calculation, - [ratesKey]: ratesBody, - }; -}; - -export interface OtherTier { - metric: RateFormData['metric']; - measurement: RateFormData['measurement']['value']; - costType: RateFormData['calculation']; - tagKey: RateFormData['taggingRates']['tagKey']['value']; -} - -export const OtherTierFromRate = (rate: Rate): OtherTier => { - const tagKey = rate.tag_rates && rate.tag_rates.tag_key ? rate.tag_rates.tag_key : null; - return { - metric: rate.metric.label_metric, - measurement: rate.metric.label_measurement, - tagKey, - costType: rate.cost_type, - }; -}; - -export const OtherTierFromRateForm = (rateData: RateFormData): OtherTier => { - const tagKey = rateData.taggingRates && rateData.taggingRates.tagKey ? rateData.taggingRates.tagKey.value : null; - const res = { - metric: rateData.metric, - measurement: rateData.measurement ? rateData.measurement.value : null, - tagKey, - costType: rateData.calculation, - }; - return res; -}; - -export const isDuplicateTagRate = (rate: OtherTier, current: OtherTier) => { - return ( - rate.metric === current.metric && - rate.measurement === current.measurement && - rate.costType === current.costType && - rate.tagKey === current.tagKey - ); -}; - -export type CompareResult = 1 | -1 | 0; - -export function compareBy( - r1: Rate, - r2: Rate, - direction: keyof typeof SortByDirection, - projection: (r: Rate) => string -): CompareResult { - const m1 = projection(r1); - const m2 = projection(r2); - if (direction === SortByDirection.asc) { - return m1 > m2 ? 1 : m1 < m2 ? -1 : 0; - } - return m1 > m2 ? -1 : m1 < m2 ? 1 : 0; -} - -export const descriptionErrors = (value: string) => { - if (value.length > 500) { - return textHelpers.description_too_long; - } - return null; -}; - -export const tagKeyValueErrors = (value: string) => { - if (value.length === 0) { - return textHelpers.required; - } - if (value.length > 100) { - return textHelpers.tag_too_long; - } - return null; -}; diff --git a/src/routes/costModels/components/rateTable.test.tsx b/src/routes/costModels/components/rateTable.test.tsx deleted file mode 100644 index 1cadc0964..000000000 --- a/src/routes/costModels/components/rateTable.test.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import type { Rate } from 'api/rates'; -import React from 'react'; - -import { RateTable } from './rateTable'; - -describe('rate-table', () => { - test('smoke-test', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - const tiers: Rate[] = [ - { - description: 'rate 1', - metric: { - name: 'cpu_core_request_per_hour', - metric: 'cpu_core_request_per_hour', - source_type: 'openshift container platform', - default_cost_type: 'Supplementary', - label_metric: 'CPU', - label_measurement: 'Request', - label_measurement_unit: 'core-hour', - }, - cost_type: 'Infrastructure', - tiered_rates: [ - { - unit: 'USD', - value: 130.32, - usage: { - unit: 'USD', - }, - }, - ], - }, - { - description: 'rate 2', - metric: { - name: 'cpu_core_request_per_hour', - metric: 'cpu_core_request_per_hour', - source_type: 'openshift container platform', - label_metric: 'CPU', - label_measurement: 'Usage', - label_measurement_unit: 'core-hour', - default_cost_type: 'Supplementary', - }, - cost_type: 'Supplementary', - tag_rates: { - tag_key: 'openshift', - tag_values: [ - { - unit: 'USD', - value: 0.43, - tag_value: 'worker', - description: 'default', - default: true, - }, - { - unit: 'USD', - value: 1.5, - tag_value: 'grafana', - description: 'grafana containers', - default: false, - }, - ], - }, - }, - ]; - render(); - expect(screen.getByText('rate 1')).toBeTruthy(); - expect(screen.getByText('rate 2')).toBeTruthy(); - expect(screen.queryByText('grafana')).toBeNull(); - await user.click(screen.getByRole('button', { name: 'Various' })); - expect(screen.getByText('grafana')).toBeTruthy(); - }); - test('sort by metric & measurement', () => { - // eslint-disable-next-line no-console - console.error = jest.fn(); - const tiers: Rate[] = [ - { - description: '', - metric: { - name: 'node_cost_per_month', - metric: 'node_cost_per_month', - source_type: 'openshift container platform', - label_metric: 'Node', - label_measurement: 'Currency', - label_measurement_unit: 'node-month', - default_cost_type: 'Supplementary', - }, - cost_type: 'Supplementary', - tiered_rates: [ - { - unit: 'USD', - value: 125.12, - usage: { - unit: 'USD', - }, - }, - ], - }, - { - description: '', - metric: { - name: 'cpu_core_request_per_hour', - metric: 'cpu_core_request_per_hour', - source_type: 'openshift container platform', - default_cost_type: 'Supplementary', - label_metric: 'CPU', - label_measurement: 'Request', - label_measurement_unit: 'core-hour', - }, - cost_type: 'Infrastructure', - tiered_rates: [ - { - unit: 'USD', - value: 5.5, - usage: { - unit: 'USD', - }, - }, - ], - }, - { - description: '', - metric: { - name: 'cpu_core_request_per_hour', - metric: 'cpu_core_request_per_hour', - source_type: 'openshift container platform', - default_cost_type: 'Supplementary', - label_metric: 'CPU', - label_measurement: 'Usage', - label_measurement_unit: 'core-hour', - }, - cost_type: 'Infrastructure', - tiered_rates: [ - { - unit: 'USD', - value: 7.2, - usage: { - unit: 'USD', - }, - }, - ], - }, - { - description: '', - metric: { - name: 'cpu_core_request_per_hour', - metric: 'cpu_core_request_per_hour', - source_type: 'openshift container platform', - default_cost_type: 'Supplementary', - label_metric: 'CPU', - label_measurement: 'Request', - label_measurement_unit: 'core-hour', - }, - cost_type: 'Supplementary', - tiered_rates: [ - { - unit: 'USD', - value: 124.6, - usage: { - unit: 'USD', - }, - }, - ], - }, - ]; - render(); - const metrics = screen.getAllByRole('cell', { name: /"value":"(CPU|Node)"/ }); - expect(metrics).toMatchSnapshot(); - userEvent.click(screen.getByRole('button', { name: /metric/i })).then(() => { - expect(metrics).toMatchSnapshot(); - }); - userEvent.click(screen.getByRole('button', { name: /measurement/i })).then(() => { - expect(metrics).toMatchSnapshot(); - }); - }); -}); diff --git a/src/routes/costModels/components/rateTable.tsx b/src/routes/costModels/components/rateTable.tsx deleted file mode 100644 index f922eb295..000000000 --- a/src/routes/costModels/components/rateTable.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import type { IActions, ThProps } from '@patternfly/react-table'; -import { - ActionsColumn, - ExpandableRowContent, - TableComposable, - TableVariant, - Tbody, - Td, - Th, - Thead, - Tr, -} from '@patternfly/react-table'; -import type { Rate } from 'api/rates'; -import { intl as defaultIntl } from 'components/i18n'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { formatCurrencyRate, unitsLookupKey } from 'utils/format'; - -interface RateTableProps extends WrappedComponentProps { - actions?: IActions; - isCompact?: boolean; - tiers: Rate[]; - sortCallback?: ({ index, direction }) => void; -} - -// defaultIntl required for testing -const RateTableBase: React.FC = ({ - actions = [], - intl = defaultIntl, - tiers, - sortCallback = () => {}, -}) => { - const [activeSortIndex, setActiveSortIndex] = React.useState(null); - const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null); - const [expanded, setExpanded] = React.useState([]); - - const getMetric = value => intl.formatMessage(messages.metricValues, { value }) || value; - const getMeasurement = (measurement, units) => { - units = intl.formatMessage(messages.units, { units: unitsLookupKey(units) }) || units; - return intl.formatMessage(messages.measurementValues, { - value: measurement.toLowerCase().replace('-', '_'), - units, - count: 2, - }); - }; - - const rows = tiers.reduce((acc, tier, ix) => { - const isTagRates = !tier.tiered_rates; - const tierRate = tier.tiered_rates ? tier.tiered_rates[0].value : 0; - return [ - ...acc, - { - data: { index: ix, hasChildren: isTagRates, tag_rates: tier.tag_rates, stateIndex: tier.stateIndex }, - cells: [ - tier.description || '', - getMetric(tier.metric.label_metric), - getMeasurement(tier.metric.label_measurement, tier.metric.label_measurement_unit), - tier.cost_type, - { - title: isTagRates - ? intl.formatMessage(messages.various) - : formatCurrencyRate(tierRate, tier.tiered_rates[0].unit), - expandToggle: isTagRates, - }, - ], - }, - ]; - }, []); - const columns = [ - { title: intl.formatMessage(messages.description) }, - { title: intl.formatMessage(messages.metric), sortable: true, sortIndex: 1 }, - { title: intl.formatMessage(messages.measurement), sortable: true, sortIndex: 2 }, - { title: intl.formatMessage(messages.calculationType) }, - { title: intl.formatMessage(messages.rate) }, - ]; - const tagColumns = [ - intl.formatMessage(messages.costModelsTagRateTableKey), - intl.formatMessage(messages.costModelsTagRateTableValue), - intl.formatMessage(messages.rate), - intl.formatMessage(messages.description), - intl.formatMessage(messages.default), - ]; - - const getSortParams = (columnIndex: number): ThProps['sort'] => ({ - sortBy: { - index: activeSortIndex, - direction: activeSortDirection, - defaultDirection: 'asc', - }, - onSort: (_event, index, direction) => { - setActiveSortIndex(index); - setActiveSortDirection(direction); - sortCallback({ index, direction }); - }, - columnIndex, - }); - const setRowExpanded = rowIndex => { - expanded.includes(rowIndex) - ? setExpanded(expanded.filter(ex => ex !== rowIndex)) - : setExpanded(expanded.concat([rowIndex])); - }; - const compoundExpandParams = rowIndex => ({ - isExpanded: expanded.includes(rowIndex), - onToggle: () => setRowExpanded(rowIndex), - expandId: 'expand-' + rowIndex, - rowIndex, - columnIndex: 4, - }); - - const sortedRows = - activeSortIndex === null - ? rows - : rows.sort((a, b) => { - const aValue = a.cells[activeSortIndex]; - const bValue = b.cells[activeSortIndex]; - if (activeSortDirection === 'asc') { - return (aValue as string).localeCompare(bValue as string); - } - return (bValue as string).localeCompare(aValue as string); - }); - - return ( - - - - {columns.map((col: { title?: string; sortable?: boolean }, i) => ( - - {col.title} - - ))} - {!!actions.length && } - - - {sortedRows.map((row, rowIndex) => { - const isExpanded = row.data.hasChildren && expanded.includes(rowIndex); - return ( - - - {row.cells.map((cell, i) => ( - - {cell.title ? cell.title : cell} - - ))} - {!!actions.length && ( - - { - return { - ...a, - onClick: () => { - a.onClick(null, rowIndex, row, null); - }, - }; - })} - /> - - )} - - {row.data.hasChildren && isExpanded && ( - - - - - - - {tagColumns.map((tag, tagIndex) => ( - {tag} - ))} - - - - {row.data.tag_rates.tag_values.map((v, index) => ( - - {index === 0 ? row.data.tag_rates.tag_key : ''} - {v.tag_value} - {formatCurrencyRate(v.value, v.unit)} - {v.description} - {v.default ? intl.formatMessage(messages.yes) : intl.formatMessage(messages.no)} - - ))} - - - - - - )} - - ); - })} - - ); -}; - -const RateTable = injectIntl(RateTableBase); -export { RateTable }; diff --git a/src/routes/costModels/components/readOnlyTooltip.test.tsx b/src/routes/costModels/components/readOnlyTooltip.test.tsx deleted file mode 100644 index c455f5d4b..000000000 --- a/src/routes/costModels/components/readOnlyTooltip.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { ReadOnlyTooltip } from './readOnlyTooltip'; - -test('read only tooltip is disabled', () => { - render( - - - - ); - // eslint-disable-next-line testing-library/no-node-access - expect(screen.getByRole('button').closest('div').getAttribute('aria-label')).toBe('Read only'); -}); - -test('read only tooltip is enabled', () => { - render( - - - - ); - // eslint-disable-next-line testing-library/prefer-presence-queries, testing-library/no-node-access - expect(screen.getByRole('button').closest('div').getAttribute('aria-label')).toBeNull(); -}); diff --git a/src/routes/costModels/components/readOnlyTooltip.tsx b/src/routes/costModels/components/readOnlyTooltip.tsx deleted file mode 100644 index 25a82f7ca..000000000 --- a/src/routes/costModels/components/readOnlyTooltip.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Tooltip } from '@patternfly/react-core'; -import React from 'react'; - -interface ReadOnlyTooltipBase { - tooltip?: string; - children: JSX.Element; - isDisabled: boolean; -} - -export const ReadOnlyTooltip: React.FC = ({ - children, - tooltip = 'You have read only permissions', - isDisabled, -}) => { - return isDisabled ? ( - {tooltip}}> -
{children}
-
- ) : ( - children - ); -}; diff --git a/src/routes/costModels/components/toolbar/checkboxSelector.test.tsx b/src/routes/costModels/components/toolbar/checkboxSelector.test.tsx deleted file mode 100644 index 6c6734e8e..000000000 --- a/src/routes/costModels/components/toolbar/checkboxSelector.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { CheckboxSelector } from './checkboxSelector'; - -test('checkbox selector', async () => { - const setSelections = jest.fn(); - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - render( - - ); - expect(screen.queryAllByText('Resources').length).toBe(1); - expect(screen.queryAllByText('CPU').length).toBe(0); - expect(screen.queryAllByText('Memory').length).toBe(0); - expect(screen.queryAllByText('Storage').length).toBe(0); - await user.click(screen.getByRole('button')); - expect(screen.queryAllByText('CPU').length).toBe(1); - expect(screen.queryAllByText('Memory').length).toBe(1); - expect(screen.queryAllByText('Storage').length).toBe(1); - expect(setSelections.mock.calls.length).toBe(0); - await user.click(screen.getAllByRole('checkbox')[0]); - expect(setSelections.mock.calls).toEqual([['cpu']]); -}); diff --git a/src/routes/costModels/components/toolbar/checkboxSelector.tsx b/src/routes/costModels/components/toolbar/checkboxSelector.tsx deleted file mode 100644 index 4518c2e48..000000000 --- a/src/routes/costModels/components/toolbar/checkboxSelector.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'; -import React from 'react'; -import { WithStateMachine } from 'routes/costModels/components/hoc/withStateMachine'; -import { selectMachineState } from 'routes/costModels/components/logic/selectStateMachine'; -import type { Option } from 'routes/costModels/components/logic/types'; - -interface CheckboxSelectorProps { - setSelections: (selection: string) => void; - selections: string[]; - placeholderText: string; - options: Option[]; - isDisabled?: boolean; -} - -export const CheckboxSelector: React.FC = ({ - options, - placeholderText, - setSelections, - selections, - isDisabled, -}) => { - return ( - { - setSelections(evt.selection); - }, - }, - })} - > - {({ send, current }) => { - return ( - - ); - }} - - ); -}; diff --git a/src/routes/costModels/components/toolbar/primarySelector.test.tsx b/src/routes/costModels/components/toolbar/primarySelector.test.tsx deleted file mode 100644 index b8de0d2bc..000000000 --- a/src/routes/costModels/components/toolbar/primarySelector.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { PrimarySelector } from './primarySelector'; - -test('primary selector', async () => { - const setPrimary = jest.fn(); - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - render( - - ); - expect(screen.queryAllByText('Metrics').length).toBe(1); - expect(screen.queryAllByText('Measurements').length).toBe(0); - const button = screen.getByRole('button'); - await user.click(button); - const options = screen.getAllByRole('option'); - expect(options.length).toBe(2); - await user.click(options[1]); - expect(setPrimary.mock.calls).toEqual([['measurements']]); -}); diff --git a/src/routes/costModels/components/toolbar/primarySelector.tsx b/src/routes/costModels/components/toolbar/primarySelector.tsx deleted file mode 100644 index a9485bfb0..000000000 --- a/src/routes/costModels/components/toolbar/primarySelector.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Select, SelectOption } from '@patternfly/react-core'; -import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; -import React from 'react'; -import { WithStateMachine } from 'routes/costModels/components/hoc/withStateMachine'; -import { selectMachineState } from 'routes/costModels/components/logic/selectStateMachine'; -import type { Option } from 'routes/costModels/components/logic/types'; - -export interface PrimarySelectorProps { - setPrimary: (primary: string) => void; - primary: string; - options: Option[]; - isDisabled?: boolean; -} - -export const PrimarySelector: React.FC = ({ setPrimary, primary, options, isDisabled }) => { - return ( - { - setPrimary(evt.selection); - }, - }, - })} - > - {({ current, send }) => { - return ( - - ); - }} - - ); -}; diff --git a/src/routes/costModels/components/warningIcon.test.tsx b/src/routes/costModels/components/warningIcon.test.tsx deleted file mode 100644 index dfb5234e5..000000000 --- a/src/routes/costModels/components/warningIcon.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { WarningIcon } from './warningIcon'; - -test('warning icon', () => { - render(); - expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot(); -}); diff --git a/src/routes/costModels/components/warningIcon.tsx b/src/routes/costModels/components/warningIcon.tsx deleted file mode 100644 index 5ef1a315d..000000000 --- a/src/routes/costModels/components/warningIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Tooltip } from '@patternfly/react-core'; -import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; -import React from 'react'; - -interface WarningIconProps { - text: string; -} - -export const WarningIcon: React.FC = ({ text }) => { - return ( - - - - ); -}; diff --git a/src/routes/costModels/costModel/__snapshots__/dialog.test.tsx.snap b/src/routes/costModels/costModel/__snapshots__/dialog.test.tsx.snap deleted file mode 100644 index 8b19b8da9..000000000 --- a/src/routes/costModels/costModel/__snapshots__/dialog.test.tsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`dialog title renders correctly with icon and title text 1`] = ` -

- - - Test dialog -

-`; diff --git a/src/routes/costModels/costModel/addRateModal.styles.ts b/src/routes/costModels/costModel/addRateModal.styles.ts deleted file mode 100644 index 4206f289c..000000000 --- a/src/routes/costModels/costModel/addRateModal.styles.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type React from 'react'; - -export const styles = { - form: { - width: '350px', - }, -} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/costModels/costModel/addRateModal.tsx b/src/routes/costModels/costModel/addRateModal.tsx deleted file mode 100644 index d15d639ba..000000000 --- a/src/routes/costModels/costModel/addRateModal.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Alert, Button, ButtonVariant, Form, Modal } from '@patternfly/react-core'; -import type { CostModelRequest } from 'api/costModels'; -import type { CostModel } from 'api/costModels'; -import type { MetricHash } from 'api/metrics'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { - canSubmit as isReadyForSubmit, - mergeToRequest, - RateForm, - useRateData, -} from 'routes/costModels/components/rateForm'; -import { initialRateFormData } from 'routes/costModels/components/rateForm/utils'; -import { createMapStateToProps } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; -import { metricsSelectors } from 'store/metrics'; - -interface AddRateModalOwnProps extends WrappedComponentProps { - // TBD... -} - -interface AddRateModalStateProps { - costModel?: CostModel; - isOpen?: boolean; - isProcessing?: boolean; - metricsHash?: MetricHash; - updateError?: string; -} - -interface AddRateModalDispatchProps { - onClose?: () => void; - updateCostModel?: (uuid: string, request: CostModelRequest) => void; -} - -type AddRateModalProps = AddRateModalOwnProps & AddRateModalStateProps & AddRateModalDispatchProps; - -export const AddRateModalBase: React.FC = ({ - costModel, - intl, - isOpen, - isProcessing, - metricsHash, - onClose, - updateCostModel, - updateError, -}) => { - const rateFormData: any = useRateData(metricsHash); - const canSubmit = React.useMemo(() => isReadyForSubmit(rateFormData), [rateFormData.errors, rateFormData.rateKind]); - const onProceed = () => { - const costModelReq = mergeToRequest(metricsHash, costModel, rateFormData); - updateCostModel(costModel.uuid, costModelReq); - }; - - React.useEffect(() => { - rateFormData.reset({ ...initialRateFormData, otherTiers: costModel.rates }); - }, [isOpen]); - - return ( - - {intl.formatMessage(messages.priceListAddRate)} - , - , - ]} - > -
- {updateError && } - - -
- ); -}; - -const mapStateToProps = createMapStateToProps(state => { - const costModels = costModelsSelectors.costModels(state); - let costModel = null; - if (costModels.length > 0) { - costModel = costModels[0]; - } - return { - costModel, - isOpen: (costModelsSelectors.isDialogOpen(state)('rate') as any).addRate, - updateError: costModelsSelectors.updateError(state), - isProcessing: costModelsSelectors.updateProcessing(state), - metricsHash: metricsSelectors.metrics(state), - }; -}); - -const mapDispatchToProps = (dispatch): AddRateModalDispatchProps => { - return { - onClose: () => { - dispatch( - costModelsActions.setCostModelDialog({ - name: 'addRate', - isOpen: false, - }) - ); - }, - updateCostModel: (uuid: string, request: CostModelRequest) => - costModelsActions.updateCostModel(uuid, request, 'addRate')(dispatch), - }; -}; - -const AddRateModal = injectIntl(connect(mapStateToProps, mapDispatchToProps)(AddRateModalBase)); - -export default AddRateModal; diff --git a/src/routes/costModels/costModel/addSourceStep.tsx b/src/routes/costModels/costModel/addSourceStep.tsx deleted file mode 100644 index 16639064d..000000000 --- a/src/routes/costModels/costModel/addSourceStep.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { Pagination, Toolbar, ToolbarContent, ToolbarItem } from '@patternfly/react-core'; -import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; -import type { CostModel } from 'api/costModels'; -import type { Provider } from 'api/providers'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { EmptyFilterState } from 'routes/components/state/emptyFilterState'; -import { LoadingState } from 'routes/components/state/loadingState'; -import { SourcesModalErrorState } from 'routes/costModels/components/errorState'; -import { addMultiValueQuery, removeMultiValueQuery } from 'routes/costModels/components/filterLogic'; -import { WarningIcon } from 'routes/costModels/components/warningIcon'; -import { createMapStateToProps } from 'store/common'; -import { sourcesActions, sourcesSelectors } from 'store/sourceSettings'; - -import { AssignSourcesToolbar } from './assignSourcesModalToolbar'; - -interface AddSourcesStepOwnProps extends WrappedComponentProps { - checked: { [uuid: string]: { selected: boolean; meta: Provider } }; - costModel: CostModel; - fetch: typeof sourcesActions.fetchSources; - fetchingSourcesError: string; - isLoadingSources: boolean; - providers: Provider[]; - pagination: { page: number; perPage: number; count: number }; - query: { name: string; type: string; offset: string; limit: string }; - setState: (newState: { [uuid: string]: { selected: boolean; meta: Provider } }) => void; -} - -interface AddSourcesStepStateProps { - currentFilter: { - name: string; - value: string; - }; - filter: string; -} - -interface AddSourcesStepDispatchProps { - updateFilter: typeof sourcesActions.updateFilterToolbar; -} - -interface AddSourcesStepState { - // TBD... -} - -type AddSourcesStepProps = AddSourcesStepOwnProps & AddSourcesStepStateProps & AddSourcesStepDispatchProps; - -class AddSourcesStepBase extends React.Component { - public render() { - const { costModel, intl } = this.props; - - if (this.props.isLoadingSources) { - return ; - } - if (this.props.fetchingSourcesError) { - return ; - } - - const onSelect = (_evt, isSelected, rowId) => { - if (rowId === -1) { - const pageSelections = this.props.providers.reduce((acc, cur) => { - // If assigned to another cost model, maintain original selection - const isAssigned = - cur.cost_models.length && cur.cost_models.find(cm => cm.name === costModel.name) === undefined; - const selected = this.props.checked[cur.uuid] ? this.props.checked[cur.uuid].selected : false; - return { - ...acc, - [cur.uuid]: { selected: isAssigned ? selected : isSelected, meta: cur, isAssigned }, - }; - }, {}); - const newState = { - ...this.props.checked, - ...pageSelections, - }; - this.props.setState( - newState as { - [uuid: string]: { selected: boolean; meta: Provider }; - } - ); - return; - } - this.props.setState({ - ...this.props.checked, - [this.props.providers[rowId].uuid]: { - selected: isSelected, - meta: this.props.providers[rowId], - }, - }); - }; - - const sources = this.props.providers.map(providerData => { - const isSelected = this.props.checked[providerData.uuid] ? this.props.checked[providerData.uuid].selected : false; - const provCostModels = - providerData.cost_models === undefined - ? intl.formatMessage(messages.costModelsWizardSourceTableDefaultCostModel) - : providerData.cost_models.map(cm => cm.name).join(','); - const isAssigned = - providerData.cost_models.length && - providerData.cost_models.find(cm => cm.name === costModel.name) === undefined; - // If assigned to another cost model, show warning - const warningIcon = isAssigned ? ( - - ) : null; - const cellName = ( -
- {providerData.name} {warningIcon} -
- ); - return { - cells: [cellName, provCostModels || ''], - selected: isSelected, - disableSelection: isAssigned, - }; - }); - - const sourceTypeMap = { - 'OpenShift Container Platform': 'OCP', - 'Microsoft Azure': 'Azure', - 'Amazon Web Services': 'AWS', - }; - - const source_type = sourceTypeMap[costModel.source_type]; - return ( - <> - this.props.fetch(`source_type=${source_type}&limit=${this.props.pagination.perPage}`), - onRemove: (category, chip) => { - const newQuery = removeMultiValueQuery({ - name: this.props.query.name ? this.props.query.name.split(',') : [], - })(category, chip); - this.props.fetch( - `source_type=${source_type}${newQuery.name ? `&name=${newQuery.name.join(',')}` : ''}&offset=0&limit=${ - this.props.pagination.perPage - }` - ); - }, - query: { - name: this.props.query.name ? this.props.query.name.split(',') : [], - }, - }} - filterInputProps={{ - id: 'assign-sources-modal-toolbar-input', - onChange: value => - this.props.updateFilter({ - currentFilterType: 'name', - currentFilterValue: value, - }), - value: this.props.currentFilter.value, - onSearch: () => { - const curQuery = this.props.query.name ? this.props.query.name.split(',') : []; - const newQuery = addMultiValueQuery({ name: curQuery })('name', this.props.currentFilter.value); - this.props.fetch( - `source_type=${source_type}&name=${newQuery.name.join(',')}&limit=${ - this.props.pagination.perPage - }&offset=0` - ); - }, - }} - paginationProps={{ - isCompact: true, - itemCount: this.props.pagination.count, - perPage: this.props.pagination.perPage, - page: this.props.pagination.page, - onPerPageSelect: (_evt, newPerPage) => { - this.props.fetch( - `source_type=${source_type}&limit=${newPerPage}&offset=0&${ - this.props.query.name ? `name=${this.props.query.name}` : '' - }` - ); - }, - onSetPage: (_evt, newPage) => { - this.props.fetch( - `source_type=${source_type}&limit=${this.props.pagination.perPage}&offset=${ - this.props.pagination.perPage * (newPage - 1) - }&${this.props.query.name ? `name=${this.props.query.name}` : ''}` - ); - }, - }} - /> - {sources.length > 0 && ( - - - - onSelect(_evt, isSelecting, -1), - isSelected: sources.filter(s => s.disableSelection || s.selected).length === sources.length, - }} - > - {intl.formatMessage(messages.names, { count: 1 })} - {intl.formatMessage(messages.costModelsWizardSourceTableCostModel)} - - - - {sources.map((s, rowIndex) => ( - - onSelect(_evt, !s.selected, rowIndex), - isSelected: s.selected, - rowIndex, - }} - > - {s.cells.map((c, cellIndex) => ( - {c} - ))} - - ))} - - - )} - {sources.length === 0 && ( - - )} - - - - { - this.props.fetch( - `limit=${newPerPage}&offset=0&${this.props.query.name ? `name=${this.props.query.name}` : ''}` - ); - }} - onSetPage={(_evt, newPage) => { - this.props.fetch( - `limit=${this.props.pagination.perPage}&offset=${this.props.pagination.perPage * (newPage - 1)}&${ - this.props.query.name ? `name=${this.props.query.name}` : '' - }` - ); - }} - /> - - - - - ); - } -} - -const mapStateToProps = createMapStateToProps(state => { - return { - currentFilter: { - name: sourcesSelectors.currentFilterType(state), - value: sourcesSelectors.currentFilterValue(state), - }, - filter: sourcesSelectors.filter(state), - }; -}); - -const mapDispatchToProps: AddSourcesStepDispatchProps = { - updateFilter: sourcesActions.updateFilterToolbar, -}; - -const AddSourcesStep = injectIntl(connect(mapStateToProps, mapDispatchToProps)(AddSourcesStepBase)); - -export default AddSourcesStep; diff --git a/src/routes/costModels/costModel/addSourceWizard.tsx b/src/routes/costModels/costModel/addSourceWizard.tsx deleted file mode 100644 index 07e0ca088..000000000 --- a/src/routes/costModels/costModel/addSourceWizard.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { - Alert, - Button, - Grid, - GridItem, - Modal, - Stack, - StackItem, - Text, - TextContent, - TextVariants, -} from '@patternfly/react-core'; -import type { CostModel } from 'api/costModels'; -import type { Provider } from 'api/providers'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { parseApiError } from 'routes/costModels/costModelWizard/parseError'; -import { FetchStatus } from 'store/common'; -import { createMapStateToProps } from 'store/common'; -import { costModelsSelectors } from 'store/costModels'; -import { sourcesActions, sourcesSelectors } from 'store/sourceSettings'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; - -import AddSourceStep from './addSourceStep'; - -interface AddSourceWizardOwnProps extends RouterComponentProps { - assigned?: Provider[]; - costModel?: CostModel; - isOpen?: boolean; - onClose?: () => void; - onSave?: (sources_uuid: string[]) => void; -} - -interface AddSourceWizardStateProps { - fetchingSourcesError?: string; - pagination?: { page: number; perPage: number; count: number }; - isLoadingSources?: boolean; - isUpdateInProgress?: boolean; - providers?: Provider[]; - query?: any; - updateApiError?: string; -} - -interface AddSourceWizardDispatchProps { - fetch: typeof sourcesActions.fetchSources; -} - -interface AddSourcesStepState { - checked: { [uuid: string]: { disabled?: boolean; selected: boolean; meta: Provider } }; -} - -type AddSourceWizardProps = AddSourceWizardOwnProps & - AddSourceWizardStateProps & - AddSourceWizardDispatchProps & - WrappedComponentProps; - -const sourceTypeMap = { - 'OpenShift Container Platform': 'OCP', - 'Microsoft Azure': 'Azure', - 'Amazon Web Services': 'AWS', -}; - -class AddSourceWizardBase extends React.Component { - protected defaultState: AddSourcesStepState = { - checked: {}, - }; - public state: AddSourcesStepState = { ...this.defaultState }; - - public componentDidMount() { - const { assigned } = this.props; - - const { - costModel: { source_type }, - fetch, - } = this.props; - const sourceType = sourceTypeMap[source_type]; - fetch(`type=${sourceType}&limit=10&offset=0`); - - const checked = {}; - for (const cur of assigned) { - checked[cur.uuid] = { selected: true, meta: cur, disabled: false }; - } - this.setState({ checked }); - } - - private hasSelections = () => { - const { checked } = this.state; - let result = false; - - for (const item of Object.keys(checked)) { - if (checked[item].selected && !checked[item].disabled) { - result = true; - break; - } - } - return result; - }; - - public render() { - const { intl, isUpdateInProgress, onClose, isOpen, onSave, costModel, updateApiError } = this.props; - - return ( - { - onSave(Object.keys(this.state.checked).filter(uuid => this.state.checked[uuid].selected)); - }} - > - {intl.formatMessage(messages.costModelsAssignSourcesParen)} - , - , - ]} - > - - {updateApiError && } - - - - - {intl.formatMessage(messages.names, { count: 1 })} - - - - - {this.props.costModel.name} - - - - - {intl.formatMessage(messages.sourceType)} - - - - - {this.props.costModel.source_type} - - - - - - { - this.setState({ checked: newState }); - }} - /> - - - - ); - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps(state => { - return { - fetchingSourcesError: sourcesSelectors.error(state) ? parseApiError(sourcesSelectors.error(state)) : null, - isLoadingSources: sourcesSelectors.status(state) === FetchStatus.inProgress, - isUpdateInProgress: costModelsSelectors.updateProcessing(state), - pagination: sourcesSelectors.pagination(state), - providers: sourcesSelectors.sources(state), - query: sourcesSelectors.query(state), - updateApiError: costModelsSelectors.updateError(state), - }; -}); - -const mapDispatchToProps: AddSourceWizardDispatchProps = { - fetch: sourcesActions.fetchSources, -}; - -const AddSourceWizard = injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(AddSourceWizardBase))); - -export default AddSourceWizard; diff --git a/src/routes/costModels/costModel/assignSourcesModalToolbar.tsx b/src/routes/costModels/costModel/assignSourcesModalToolbar.tsx deleted file mode 100644 index 887c2e78e..000000000 --- a/src/routes/costModels/costModel/assignSourcesModalToolbar.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import type { PaginationProps } from '@patternfly/react-core'; -import { - InputGroup, - InputGroupText, - Pagination, - TextInput, - Toolbar, - ToolbarContent, - ToolbarFilter, - ToolbarItem, - ToolbarToggleGroup, -} from '@patternfly/react-core'; -import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; -import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; - -interface FilterInputProps { - id: string; - value: string; - onChange: (value: string, event: React.FormEvent) => void; - onSearch: (evt: React.KeyboardEvent) => void; - placeholder?: string; -} - -const FilterInput: React.FC = ({ id, placeholder = '', value, onChange, onSearch }) => { - return ( - - ) => { - if (evt.key !== 'Enter' || value === '') { - return; - } - onSearch(evt); - }} - /> - - - - - ); -}; - -interface AssignSourcesToolbarBaseProps extends WrappedComponentProps { - paginationProps: PaginationProps; - filterInputProps: Omit; - filter: { - onRemove: (category: string, chip: string) => void; - onClearAll: () => void; - query: { name?: string[] }; - }; -} - -export const AssignSourcesToolbarBase: React.FC = ({ - filterInputProps, - intl, - paginationProps, - filter, -}) => { - return ( - - - }> - - - - - - - - - - - - ); -}; - -export const AssignSourcesToolbar = injectIntl(AssignSourcesToolbarBase); diff --git a/src/routes/costModels/costModel/costCalc.styles.ts b/src/routes/costModels/costModel/costCalc.styles.ts deleted file mode 100644 index 16a1c9aa1..000000000 --- a/src/routes/costModels/costModel/costCalc.styles.ts +++ /dev/null @@ -1,38 +0,0 @@ -import global_FontSize_md from '@patternfly/react-tokens/dist/js/global_FontSize_md'; -import global_FontSize_xl from '@patternfly/react-tokens/dist/js/global_FontSize_xl'; -import type React from 'react'; - -export const styles = { - card: { - minHeight: 250, - }, - cardDescription: { - fontSize: global_FontSize_md.value, - }, - cardBody: { - fontSize: global_FontSize_xl.value, - textAlign: 'center', - }, - exampleMargin: { - marginLeft: 30, - }, - inputField: { - borderLeft: 0, - width: 175, - }, - markupRadio: { - marginBottom: 6, - }, - markupRadioContainer: { - marginTop: 6, - }, - rateContainer: { - marginLeft: 20, - }, - percent: { - borderLeft: 0, - }, - sign: { - borderRight: 0, - }, -} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/costModels/costModel/costModelInfo.styles.ts b/src/routes/costModels/costModel/costModelInfo.styles.ts deleted file mode 100644 index fbc86cd2c..000000000 --- a/src/routes/costModels/costModel/costModelInfo.styles.ts +++ /dev/null @@ -1,35 +0,0 @@ -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 global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm'; -import type React from 'react'; - -export const styles = { - headerDescription: { - width: '97%', - wordWrap: 'break-word', - }, - content: { - paddingTop: global_spacer_lg.value, - height: '182vh', - }, - costCalculation: { - marginLeft: global_spacer_lg.value, - marginRight: global_spacer_lg.value, - }, - costmodelsContainer: { - marginLeft: global_spacer_lg.value, - marginRight: global_spacer_lg.value, - backgroundColor: global_BackgroundColor_light_100.value, - paddingBottom: global_spacer_md.value, - paddingTop: global_spacer_md.value, - }, - headerCostModel: { - padding: global_spacer_lg.var, - paddingBottom: 0, - backgroundColor: global_BackgroundColor_light_100.value, - }, - title: { - paddingBottom: global_spacer_sm.var, - }, -} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/costModels/costModel/costModelInfo.tsx b/src/routes/costModels/costModel/costModelInfo.tsx deleted file mode 100644 index 3f511d4f9..000000000 --- a/src/routes/costModels/costModel/costModelInfo.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Grid, - GridItem, - PageSection, - TabContent, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import { ErrorCircleOIcon } from '@patternfly/react-icons/dist/esm/icons/error-circle-o-icon'; -import { PageHeader, PageHeaderTitle } from '@redhat-cloud-services/frontend-components/PageHeader'; -import type { CostModel } from 'api/costModels'; -import type { AxiosError } from 'axios'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { Loading } from 'routes/components/page/loading'; -import { NotAvailable } from 'routes/components/page/notAvailable'; -import DistributionCard from 'routes/costModels/costModel/distribution'; -import MarkupCard from 'routes/costModels/costModel/markup'; -import PriceListTable from 'routes/costModels/costModel/priceListTable'; -import SourceTable from 'routes/costModels/costModel/sourceTable'; -import { parseApiError } from 'routes/costModels/costModelWizard/parseError'; -import { createMapStateToProps, FetchStatus } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; -import { metricsActions, metricsSelectors } from 'store/metrics'; -import { rbacActions, rbacSelectors } from 'store/rbac'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; - -import { styles } from './costModelInfo.styles'; -import Header from './header'; - -interface CostModelInfoOwnProps { - costModels: CostModel[]; - costModelStatus: FetchStatus; - costModelError: AxiosError; - distribution: { value: string }; - fetchMetrics: typeof metricsActions.fetchMetrics; - markup: { value: string }; - metricsStatus: FetchStatus; - metricsError: AxiosError; - fetchRbac: typeof rbacActions.fetchRbac; - rbacStatus: FetchStatus; - rbacError: Error; - fetchCostModels: typeof costModelsActions.fetchCostModels; -} - -type CostModelInfoProps = CostModelInfoOwnProps & RouterComponentProps & WrappedComponentProps; - -interface CostModelInfoState { - tabIndex: number; -} - -class CostModelInfo extends React.Component { - public tabRefs = [React.createRef(), React.createRef(), React.createRef()]; - constructor(props) { - super(props); - this.state = { tabIndex: 0 }; - } - - public componentDidMount() { - this.props.fetchRbac(); - this.props.fetchMetrics(); - this.props.fetchCostModels(`uuid=${this.props.router.params.uuid}`); - } - - public render() { - const { costModels, costModelError, costModelStatus, intl, metricsError, metricsStatus, rbacError, rbacStatus } = - this.props; - - if ( - metricsStatus !== FetchStatus.complete || - rbacStatus !== FetchStatus.complete || - costModelStatus !== FetchStatus.complete - ) { - return ; - } - - const fetchError = metricsError || rbacError || costModelError; - if (fetchError) { - if (costModelError !== null) { - const costModelErrMessage = parseApiError(costModelError); - if (costModelErrMessage === 'detail: Invalid provider uuid') { - return ( - <> - - - - - - - - {intl.formatMessage(messages.costModelsUUIDEmptyState)} - - - {intl.formatMessage(messages.costModelsUUIDEmptyStateDesc, { - uuid: this.props.router.params.uuid, - })} - - - - - ); - } - } - return ; - } - - const current = costModels[0]; - const sources = current.sources; - return ( -
-
this.setState({ tabIndex })} - /> -
- {current.source_type === 'OpenShift Container Platform' ? ( - <> - - - - - ) : ( - <> - - - - )} -
-
- ); - } -} - -export default injectIntl( - withRouter( - connect( - createMapStateToProps(store => { - return { - costModels: costModelsSelectors.costModels(store), - costModelError: costModelsSelectors.error(store), - costModelStatus: costModelsSelectors.status(store), - metricsHash: metricsSelectors.metrics(store), - maxRate: metricsSelectors.maxRate(store), - costTypes: metricsSelectors.costTypes(store), - metricsError: metricsSelectors.metricsState(store).error, - metricsStatus: metricsSelectors.status(store), - rbacError: rbacSelectors.selectRbacState(store).error, - rbacStatus: rbacSelectors.selectRbacState(store).status, - }; - }), - { - fetchMetrics: metricsActions.fetchMetrics, - fetchRbac: rbacActions.fetchRbac, - fetchCostModels: costModelsActions.fetchCostModels, - } - )(CostModelInfo) - ) -); diff --git a/src/routes/costModels/costModel/costModelsDetails.styles.ts b/src/routes/costModels/costModel/costModelsDetails.styles.ts deleted file mode 100644 index efd594557..000000000 --- a/src/routes/costModels/costModel/costModelsDetails.styles.ts +++ /dev/null @@ -1,71 +0,0 @@ -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 global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm'; -import type React from 'react'; - -export const styles = { - headerDescription: { - width: '97%', - wordWrap: 'break-word', - }, - content: { - paddingTop: global_spacer_lg.value, - height: '182vh', - }, - costmodelsContainer: { - marginLeft: global_spacer_lg.value, - marginRight: global_spacer_lg.value, - paddingBottom: global_spacer_md.value, - paddingTop: global_spacer_md.value, - paddingLeft: global_spacer_lg.value, - paddingRight: global_spacer_lg.value, - }, - currency: { - paddingBottom: global_spacer_md.value, - paddingTop: global_spacer_lg.value, - }, - tableContainer: { - marginLeft: global_spacer_lg.value, - marginRight: global_spacer_lg.value, - }, - paginationContainer: { - paddingBottom: global_spacer_md.value, - paddingTop: global_spacer_md.value, - paddingLeft: global_spacer_lg.value, - paddingRight: global_spacer_lg.value, - marginLeft: global_spacer_lg.value, - marginRight: global_spacer_lg.value, - marginBottom: global_spacer_lg.value, - backgroundColor: global_BackgroundColor_light_100.value, - }, - toolbarContainer: { - paddingBottom: global_spacer_md.value, - paddingTop: global_spacer_md.value, - marginLeft: global_spacer_lg.value, - marginRight: global_spacer_lg.value, - backgroundColor: global_BackgroundColor_light_100.value, - }, - header: { - padding: global_spacer_lg.var, - backgroundColor: global_BackgroundColor_light_100.var, - }, - headerContent: { - display: 'flex', - justifyContent: 'space-between', - }, - headerCostModel: { - padding: global_spacer_lg.value, - paddingBottom: 0, - backgroundColor: global_BackgroundColor_light_100.var, - }, - breadcrumb: { - paddingBottom: global_spacer_md.var, - }, - title: { - paddingBottom: global_spacer_sm.var, - }, - sourceTypeTitle: { - paddingBottom: global_spacer_md.var, - }, -} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/costModels/costModel/dialog.test.tsx b/src/routes/costModels/costModel/dialog.test.tsx deleted file mode 100644 index c29b65c35..000000000 --- a/src/routes/costModels/costModel/dialog.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import Dialog from './dialog'; - -const defaultProps = { - onClose: jest.fn(), - onProceed: jest.fn(), - isOpen: true, - title: 'Test dialog', - body:
This is a test dialog body
, - t: (text: string) => text, -}; - -test('dialog title renders correctly with icon and title text', () => { - render(); - expect(screen.getByText('Test dialog')).toMatchSnapshot(); -}); - -test('dialog with a delete action', () => { - render(); - expect(screen.getByRole('button', { name: 'Delete!' })).not.toBeNull(); -}); - -test('dialog with no action', () => { - render(); - // should only be "x" and "close", so 2 buttons - expect(screen.getAllByRole('button').length).toBe(2); -}); - -test('dialog with error', () => { - render(); - expect(screen.getByText(/danger alert/i)).not.toBeNull(); - expect(screen.getByText(/opps!/i)).not.toBeNull(); -}); - -test('dialog is small', () => { - render(); - expect(screen.getByLabelText(/test dialog/i).getAttribute('class')).toContain('pf-m-sm'); -}); diff --git a/src/routes/costModels/costModel/dialog.tsx b/src/routes/costModels/costModel/dialog.tsx deleted file mode 100644 index 46f2fe090..000000000 --- a/src/routes/costModels/costModel/dialog.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Alert, Button, Modal, Title, TitleSizes } from '@patternfly/react-core'; -import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; -import { intl as defaultIntl } from 'components/i18n'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; - -interface Props extends WrappedComponentProps { - isSmall?: boolean; - onClose: () => void; - onProceed: () => void; - title: string; - body: React.ReactElement; - actionText?: string; - isOpen?: boolean; - isProcessing?: boolean; - error?: string; -} - -const DialogBase: React.FC = ({ - intl = defaultIntl, // Default required for testing - onClose, - onProceed, - title, - body, - actionText, - isProcessing = false, - isOpen = false, - isSmall = false, - error = '', -}) => { - const CancelButtonSecondary = ( - - ); - const ProceedButton = ( - - ); - const CloseButtonPrimary = ( - - ); - const actions = actionText !== '' ? [ProceedButton, CancelButtonSecondary] : [CloseButtonPrimary]; - return ( - - {title} - - } - isOpen={isOpen} - onClose={onClose} - actions={actions} - variant={isSmall ? 'small' : 'default'} - > - {error && } - {body} - - ); -}; - -export default injectIntl(DialogBase); diff --git a/src/routes/costModels/costModel/distribution.tsx b/src/routes/costModels/costModel/distribution.tsx deleted file mode 100644 index 2924d7f3a..000000000 --- a/src/routes/costModels/costModel/distribution.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { - Card, - CardActions, - CardBody, - CardHeader, - CardHeaderMain, - Dropdown, - DropdownItem, - DropdownPosition, - KebabToggle, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import type { CostModel } from 'api/costModels'; -import messages from 'locales/messages'; -import React from 'react'; -import { useIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { ReadOnlyTooltip } from 'routes/costModels/components/readOnlyTooltip'; -import { createMapStateToProps } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; -import { rbacSelectors } from 'store/rbac'; - -import { styles } from './costCalc.styles'; -import UpdateDistributionDialog from './updateDistributionDialog'; - -interface Props { - isWritePermission: boolean; - isUpdateDialogOpen: boolean; - current: CostModel; - setCostModelDialog: typeof costModelsActions.setCostModelDialog; -} - -const DistributionCardBase: React.FC = ({ - isWritePermission, - setCostModelDialog, - current, - isUpdateDialogOpen, -}) => { - const intl = useIntl(); - const [dropdownIsOpen, setDropdownIsOpen] = React.useState(false); - - return ( - <> - {isUpdateDialogOpen && } - - - - - {intl.formatMessage(messages.costDistribution)} - - - - } - isOpen={dropdownIsOpen} - onSelect={() => setDropdownIsOpen(false)} - position={DropdownPosition.right} - isPlain - dropdownItems={[ - - setCostModelDialog({ isOpen: true, name: 'updateDistribution' })} - component="button" - > - {intl.formatMessage(messages.costModelsDistributionEdit)} - - , - ]} - /> - - - {intl.formatMessage(messages.costModelsDistributionDesc)} - - -
- {intl.formatMessage(messages.distributionTypeDesc, { - type: current.distribution_info.distribution_type, - })} -
-
- {intl.formatMessage(messages.distributeCosts, { - value: current.distribution_info.platform_cost, - type: 'platform', - })} -
-
- {intl.formatMessage(messages.distributeCosts, { - value: current.distribution_info.worker_cost, - type: 'worker', - })} -
-
-
- - ); -}; - -export default connect( - createMapStateToProps(state => { - const { updateDistribution } = costModelsSelectors.isDialogOpen(state)('distribution'); - return { - isUpdateDialogOpen: updateDistribution, - costModelDialog: costModelsSelectors.isDialogOpen(state)('distribution'), - isWritePermission: rbacSelectors.isCostModelWritePermission(state), - }; - }), - { - setCostModelDialog: costModelsActions.setCostModelDialog, - } -)(DistributionCardBase); diff --git a/src/routes/costModels/costModel/header.tsx b/src/routes/costModels/costModel/header.tsx deleted file mode 100644 index 5e1a869c3..000000000 --- a/src/routes/costModels/costModel/header.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { - Breadcrumb, - BreadcrumbItem, - Dropdown, - DropdownItem, - KebabToggle, - List, - ListItem, - Split, - SplitItem, - Tab, - Tabs, - TabTitleText, - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import type { CostModel } from 'api/costModels'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import { routes } from 'routes'; -import { ReadOnlyTooltip } from 'routes/costModels/components/readOnlyTooltip'; -import { styles } from 'routes/costModels/costModel/costModelsDetails.styles'; -import Dialog from 'routes/costModels/costModel/dialog'; -import UpdateCostModelModal from 'routes/costModels/costModel/updateCostModel'; -import { createMapStateToProps } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; -import { rbacSelectors } from 'store/rbac'; -import { formatPath } from 'utils/paths'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; -interface Props extends RouterComponentProps, WrappedComponentProps { - tabRefs: any[]; - tabIndex: number; - onSelectTab: (index: number) => void; - setDialogOpen: typeof costModelsActions.setCostModelDialog; - current: CostModel; - isDialogOpen: { deleteCostModel: boolean; updateCostModel: boolean; createWizard: boolean }; - isDeleteProcessing: boolean; - deleteError: string; - deleteCostModel: typeof costModelsActions.deleteCostModel; - isWritePermission: boolean; -} - -const Header: React.FC = ({ - current, - deleteCostModel, - deleteError, - intl, - isDeleteProcessing, - isDialogOpen, - isWritePermission, - onSelectTab, - router, - setDialogOpen, - tabRefs, - tabIndex, -}) => { - const [dropdownIsOpen, setDropdownIsOpen] = React.useState(false); - - return ( - <> - {isDialogOpen.updateCostModel && } - setDialogOpen({ name: 'deleteCostModel', isOpen: false })} - error={deleteError} - isProcessing={isDeleteProcessing} - onProceed={() => { - deleteCostModel(current.uuid, 'deleteCostModel', router); - }} - body={ - <> - {current.sources.length === 0 && - intl.formatMessage(messages.costModelsDeleteDesc, { - costModel: current.name, - })} - {current.sources.length > 0 && ( - <> - {intl.formatMessage(messages.costModelsDeleteSource)} -
-
- {intl.formatMessage(messages.costModelsAvailableSources)} -
- - {current.sources.map(provider => ( - {provider.name} - ))} - - - )} - - } - actionText={current.sources.length === 0 ? intl.formatMessage(messages.costModelsDelete) : ''} - /> -
-
- - ( - - {intl.formatMessage(messages.costModels)} - - )} - /> - {current.name} - -
- - - - {current.name} - - {current.description} - - - } - isOpen={dropdownIsOpen} - onSelect={() => setDropdownIsOpen(false)} - isPlain - position="right" - dropdownItems={[ - - - setDialogOpen({ - isOpen: true, - name: 'updateCostModel', - }) - } - > - {intl.formatMessage(messages.edit)} - - , - - - setDialogOpen({ - isOpen: true, - name: 'deleteCostModel', - }) - } - > - {intl.formatMessage(messages.delete)} - - , - ]} - /> - - - - - {intl.formatMessage(messages.currency)} - - {intl.formatMessage(messages.currencyOptions, { units: current.currency || 'USD' })} - - - - {current.source_type === 'OpenShift Container Platform' ? ( - onSelectTab(index)}> - {intl.formatMessage(messages.priceList)}} - tabContentId="refPriceList" - tabContentRef={tabRefs[0]} - /> - {intl.formatMessage(messages.costCalculations)}} - tabContentId="refMarkup" - tabContentRef={tabRefs[1]} - /> - {intl.formatMessage(messages.sources)}} - tabContentId="refSources" - tabContentRef={tabRefs[2]} - /> - - ) : ( - onSelectTab(index)}> - {intl.formatMessage(messages.costCalculations)}} - tabContentId="refMarkup" - tabContentRef={tabRefs[0]} - /> - {intl.formatMessage(messages.sources)}} - tabContentId="refSources" - tabContentRef={tabRefs[1]} - /> - - )} -
- - ); -}; - -export default injectIntl( - withRouter( - connect( - createMapStateToProps(state => ({ - isDialogOpen: costModelsSelectors.isDialogOpen(state)('costmodel'), - isDeleteProcessing: costModelsSelectors.deleteProcessing(state), - deleteError: costModelsSelectors.deleteError(state), - isWritePermission: rbacSelectors.isCostModelWritePermission(state), - })), - { - setDialogOpen: costModelsActions.setCostModelDialog, - deleteCostModel: costModelsActions.deleteCostModel, - } - )(Header) - ) -); diff --git a/src/routes/costModels/costModel/index.ts b/src/routes/costModels/costModel/index.ts deleted file mode 100644 index 2c4812ad8..000000000 --- a/src/routes/costModels/costModel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './costModelInfo'; diff --git a/src/routes/costModels/costModel/markup.tsx b/src/routes/costModels/costModel/markup.tsx deleted file mode 100644 index fc59dea0f..000000000 --- a/src/routes/costModels/costModel/markup.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { - Card, - CardActions, - CardBody, - CardHeader, - CardHeaderMain, - Dropdown, - DropdownItem, - DropdownPosition, - KebabToggle, - Title, - TitleSizes, -} from '@patternfly/react-core'; -import type { CostModel } from 'api/costModels'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { ReadOnlyTooltip } from 'routes/costModels/components/readOnlyTooltip'; -import { createMapStateToProps } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; -import { rbacSelectors } from 'store/rbac'; -import { formatPercentageMarkup } from 'utils/format'; - -import { styles } from './costCalc.styles'; -import UpdateMarkupDialog from './updateMarkupDialog'; - -interface Props extends WrappedComponentProps { - isWritePermission: boolean; - isUpdateDialogOpen: boolean; - current: CostModel; - setCostModelDialog: typeof costModelsActions.setCostModelDialog; -} - -const MarkupCardBase: React.FC = ({ - intl, - isWritePermission, - setCostModelDialog, - current, - isUpdateDialogOpen, -}) => { - const [dropdownIsOpen, setDropdownIsOpen] = React.useState(false); - const markupValue = formatPercentageMarkup( - current && current.markup && current.markup.value ? Number(current.markup.value) : 0 - ); - - return ( - <> - {isUpdateDialogOpen && } - - - - - {intl.formatMessage(messages.markupOrDiscount)} - - - - } - isOpen={dropdownIsOpen} - onSelect={() => setDropdownIsOpen(false)} - position={DropdownPosition.right} - isPlain - dropdownItems={[ - - setCostModelDialog({ isOpen: true, name: 'updateMarkup' })} - component="button" - > - {intl.formatMessage(messages.editMarkup)} - - , - ]} - /> - - - {intl.formatMessage(messages.markupOrDiscountDesc)} - - {intl.formatMessage(messages.percent, { value: markupValue })} - - - - ); -}; - -export default injectIntl( - connect( - createMapStateToProps(state => { - const { updateMarkup } = costModelsSelectors.isDialogOpen(state)('markup'); - return { - isUpdateDialogOpen: updateMarkup, - costModelDialog: costModelsSelectors.isDialogOpen(state)('markup'), - isWritePermission: rbacSelectors.isCostModelWritePermission(state), - }; - }), - { - setCostModelDialog: costModelsActions.setCostModelDialog, - } - )(MarkupCardBase) -); diff --git a/src/routes/costModels/costModel/priceListTable.tsx b/src/routes/costModels/costModel/priceListTable.tsx deleted file mode 100644 index 53f3de499..000000000 --- a/src/routes/costModels/costModel/priceListTable.tsx +++ /dev/null @@ -1,413 +0,0 @@ -import { - Bullseye, - Button, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - List, - ListItem, - Pagination, - Title, - TitleSizes, - Toolbar, - ToolbarContent, - ToolbarItem, - ToolbarItemVariant, -} from '@patternfly/react-core'; -import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; -import { SortByDirection } from '@patternfly/react-table'; -import { Unavailable } from '@redhat-cloud-services/frontend-components/Unavailable'; -import type { CostModel } from 'api/costModels'; -import type { MetricHash } from 'api/metrics'; -import type { Rate } from 'api/rates'; -import type { AxiosError } from 'axios'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { EmptyFilterState } from 'routes/components/state/emptyFilterState'; -import { LoadingState } from 'routes/components/state/loadingState'; -import { WithPriceListSearch } from 'routes/costModels/components/hoc/withPriceListSearch'; -import { PriceListToolbar } from 'routes/costModels/components/priceListToolbar'; -import { compareBy } from 'routes/costModels/components/rateForm/utils'; -import { RateTable } from 'routes/costModels/components/rateTable'; -import { CheckboxSelector } from 'routes/costModels/components/toolbar/checkboxSelector'; -import { PrimarySelector } from 'routes/costModels/components/toolbar/primarySelector'; -import { FetchStatus } from 'store/common'; -import { createMapStateToProps } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; -import { metricsSelectors } from 'store/metrics'; -import { rbacSelectors } from 'store/rbac'; -import { unitsLookupKey } from 'utils/format'; - -import AddRateModal from './addRateModal'; -import Dialog from './dialog'; -import UpdateRateModal from './updateRateModel'; - -interface PriceListTableProps extends WrappedComponentProps { - assignees?: string[]; - costModel?: string; - current: CostModel; - costTypes: string[]; - error: string; - isDialogOpen: { deleteRate: boolean; updateRate: boolean; addRate: boolean }; - isLoading: boolean; - isWritePermission: boolean; - fetchError: AxiosError; - fetchStatus: FetchStatus; - metricsHash: MetricHash; - updateCostModel: typeof costModelsActions.updateCostModel; - setDialogOpen: typeof costModelsActions.setCostModelDialog; -} - -interface PriceListTableState { - deleteRate: any; - index: number; - sortBy: { - index: number; - direction: SortByDirection; - }; - pagination: { - perPage: number; - page: number; - }; -} - -class PriceListTable extends React.Component { - protected defaultState: PriceListTableState = { - deleteRate: null, - index: -1, - sortBy: { - index: 0, - direction: SortByDirection.asc, - }, - pagination: { - perPage: 10, - page: 1, - }, - }; - public state: PriceListTableState = { ...this.defaultState }; - - public render() { - const { fetchStatus, fetchError, intl, isDialogOpen, isWritePermission, metricsHash } = this.props; - - const getMetricLabel = m => { - // Match message descriptor or default to API string - const value = m.replace(/ /g, '_').toLowerCase(); - const label = intl.formatMessage(messages.metricValues, { value }); - return label ? label : m; - }; - const getMeasurementLabel = m => { - // Match message descriptor or default to API string - const label = intl.formatMessage(messages.measurementValues, { - value: m.toLowerCase().replace('-', '_'), - count: 1, - }); - return label ? label : m; - }; - const metricOpts = Object.keys(metricsHash).map(m => ({ - label: getMetricLabel(m), // metric - value: m, - })); - const measurementOpts = metricOpts.reduce((acc, curr) => { - const measurs = Object.keys(metricsHash[curr.value]) - .filter(m => !acc.map(i => i.value).includes(m)) - .map(m => ({ label: getMeasurementLabel(m), value: m })); - return [...acc, ...measurs]; - }, []); - - const showAssignees = this.props.assignees && this.props.assignees.length > 0; - const cm = this.props.costModel; - const metric = this.state.deleteRate - ? `${this.state.deleteRate.metric.label_metric}-${this.state.deleteRate.metric.label_measurement} (${this.state.deleteRate.metric.label_measurement_unit})` - : ''; - return ( - <> - - - { - this.props.setDialogOpen({ name: 'deleteRate', isOpen: false }); - this.setState({ deleteRate: null }); - }} - isProcessing={this.props.isLoading} - onProceed={() => { - const { index } = this.state; - const { current } = this.props; - const newState = { - ...current, - source_uuids: current.sources.map(provider => provider.uuid), - source_type: 'OCP', - rates: [...current.rates.slice(0, index), ...current.rates.slice(index + 1)], - }; - this.props.updateCostModel(current.uuid, newState, 'deleteRate'); - }} - body={ - <> - {intl.formatMessage(messages.priceListDeleteRateDesc, { - metric: {metric}, - costModel: {cm}, - count: showAssignees ? 2 : 1, - })} - {showAssignees && ( - - {this.props.assignees.map(p => ( - {p} - ))} - - )} - - } - actionText={intl.formatMessage(messages.priceListDeleteRate)} - /> - - {({ search, setSearch, onRemove, onSelect, onClearAll }) => { - const getMetric = value => intl.formatMessage(messages.metricValues, { value }) || value; - const getMeasurement = (measurement, units) => { - units = intl.formatMessage(messages.units, { units: unitsLookupKey(units) }) || units; - return intl.formatMessage(messages.measurementValues, { - value: measurement.toLowerCase().replace('-', '_'), - units, - count: 2, - }); - }; - const from = (this.state.pagination.page - 1) * this.state.pagination.perPage; - const to = this.state.pagination.page * this.state.pagination.perPage; - - const res = this.props.current.rates - .map((r, i) => { - return { ...r, stateIndex: i }; - }) - .filter(rate => search.metrics.length === 0 || search.metrics.includes(rate.metric.label_metric)) - .filter( - rate => search.measurements.length === 0 || search.measurements.includes(rate.metric.label_measurement) - ) - .sort((r1, r2) => { - const projection = - this.state.sortBy.index === 1 - ? (r: Rate) => getMetric(r.metric.label_metric) - : this.state.sortBy.index === 2 - ? (r: Rate) => getMeasurement(r.metric.label_measurement, r.metric.label_measurement_unit) - : () => ''; - return compareBy(r1, r2, this.state.sortBy.direction, projection); - }); - const filtered = res.slice(from, to); - return ( - <> - setSearch({ primary })} - options={[ - { - label: intl.formatMessage(messages.metric), - value: 'metrics', - }, - { - label: intl.formatMessage(messages.measurement), - value: 'measurements', - }, - ]} - /> - } - selected={search.primary} - secondaries={[ - { - component: ( - onSelect('measurements', selection)} - options={measurementOpts} - /> - ), - name: 'measurements', - onRemove, - filters: search.measurements, - }, - { - component: ( - onSelect('metrics', selection)} - options={metricOpts} - /> - ), - name: 'metrics', - onRemove, - filters: search.metrics, - }, - ]} - button={ - - } - onClear={onClearAll} - pagination={ - - this.setState({ - pagination: { ...this.state.pagination, page }, - }) - } - onPerPageSelect={(_evt, perPage) => this.setState({ pagination: { page: 1, perPage } })} - titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { - title: intl.formatMessage(messages.priceList), - placement: 'top', - }), - }} - /> - } - /> - {fetchStatus !== FetchStatus.complete && } - {fetchStatus === FetchStatus.complete && fetchError && } - {fetchStatus === FetchStatus.complete && - filtered.length === 0 && - (search.metrics.length !== 0 || search.measurements.length !== 0) && } - {fetchStatus === FetchStatus.complete && - filtered.length === 0 && - search.measurements.length === 0 && - search.metrics.length === 0 && ( - - - - - {intl.formatMessage(messages.priceListEmptyRate)} - - {intl.formatMessage(messages.priceListEmptyRateDesc)} - - - )} - {fetchStatus === FetchStatus.complete && filtered.length > 0 && ( - <> - {intl.formatMessage(messages.readOnlyPermissions)} - ) : undefined, - onClick: (_evt, _rowIndex, rowData) => { - this.setState({ - deleteRate: null, - index: rowData.data.stateIndex, - }); - this.props.setDialogOpen({ - name: 'updateRate', - isOpen: true, - }); - }, - }, - { - title: intl.formatMessage(messages.delete), - isDisabled: !isWritePermission, - // HACK: to display tooltip on disable - style: !isWritePermission ? { pointerEvents: 'auto' } : {}, - tooltip: !isWritePermission ? ( -
{intl.formatMessage(messages.readOnlyPermissions)}
- ) : undefined, - onClick: (_evt, _rowIndex, rowData) => { - const rowIndex = rowData.data.stateIndex; - this.setState({ - deleteRate: filtered[rowIndex], - index: rowIndex + from, - }); - this.props.setDialogOpen({ - name: 'deleteRate', - isOpen: true, - }); - }, - }, - ]} - tiers={filtered} - sortCallback={e => { - this.setState({ - ...this.state, - sortBy: { ...e }, - }); - }} - /> - - - - - - this.setState({ - pagination: { ...this.state.pagination, page }, - }) - } - onPerPageSelect={(_evt, perPage) => - this.setState({ - pagination: { page: 1, perPage }, - }) - } - titles={{ - paginationTitle: intl.formatMessage(messages.paginationTitle, { - title: intl.formatMessage(messages.priceList), - placement: 'bottom', - }), - }} - variant="bottom" - /> - - - - - )} - - ); - }} -
- - ); - } -} - -export default injectIntl( - connect( - createMapStateToProps(state => ({ - isLoading: costModelsSelectors.updateProcessing(state), - error: costModelsSelectors.updateError(state), - isDialogOpen: costModelsSelectors.isDialogOpen(state)('rate'), - fetchError: costModelsSelectors.error(state), - fetchStatus: costModelsSelectors.status(state), - metricsHash: metricsSelectors.metrics(state), - costTypes: metricsSelectors.costTypes(state), - isWritePermission: rbacSelectors.isCostModelWritePermission(state), - })), - { - updateCostModel: costModelsActions.updateCostModel, - setDialogOpen: costModelsActions.setCostModelDialog, - } - )(PriceListTable) -); diff --git a/src/routes/costModels/costModel/sourceTable.tsx b/src/routes/costModels/costModel/sourceTable.tsx deleted file mode 100644 index f6d5b7531..000000000 --- a/src/routes/costModels/costModel/sourceTable.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import type { CostModel, CostModelProvider, CostModelRequest } from 'api/costModels'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { createMapStateToProps } from 'store/common'; -import { costModelsActions, costModelsSelectors } from 'store/costModels'; - -import AddSourceWizard from './addSourceWizard'; -import Dialog from './dialog'; -import Table from './table'; - -interface SourceTableProps extends WrappedComponentProps { - updateCostModel: typeof costModelsActions.updateCostModel; - costModel: CostModel; - sources: CostModelProvider[]; - isLoading: boolean; - setDialogOpen: typeof costModelsActions.setCostModelDialog; - isDialogOpen: { deleteSource: boolean; addSource: boolean }; -} - -interface SourceTableState { - dialogSource: string; -} - -class SourceTableBase extends React.Component { - protected defaultState: SourceTableState = { - dialogSource: null, - }; - public state: SourceTableState = { ...this.defaultState }; - - public render() { - const { intl, isDialogOpen, isLoading, setDialogOpen, sources, costModel } = this.props; - - return ( - <> - {isDialogOpen.addSource && ( - setDialogOpen({ name: 'addSource', isOpen: false })} - onSave={(source_uuids: string[]) => { - this.props.updateCostModel( - costModel.uuid, - { - ...costModel, - source_type: costModel.source_type === 'OpenShift Container Platform' ? 'OCP' : 'AWS', - source_uuids, - } as CostModelRequest, - 'addSource' - ); - }} - /> - )} - { - setDialogOpen({ name: 'deleteSource', isOpen: false }); - this.setState({ dialogSource: null }); - }} - isProcessing={isLoading} - onProceed={() => { - const newState = { - ...costModel, - source_type: costModel.source_type === 'OpenShift Container Platform' ? 'OCP' : 'AWS', - source_uuids: sources - .filter(provider => provider.name !== this.state.dialogSource) - .map(provider => provider.uuid), - }; - this.props.updateCostModel(costModel.uuid, newState, 'deleteSource'); - }} - body={ - <> - {intl.formatMessage(messages.costModelsSourceDeleteSourceDesc, { - source: this.state.dialogSource, - costModel: costModel.name, - })} - - } - actionText={intl.formatMessage(messages.costModelsSourceDeleteSource)} - /> - { - this.setState({ dialogSource: item[0] }); - setDialogOpen({ name: 'deleteSource', isOpen: true }); - }} - onAdd={() => setDialogOpen({ name: 'addSource', isOpen: true })} - rows={sources.map(p => p.name)} - current={costModel} - /> - - ); - } -} - -export default injectIntl( - connect( - createMapStateToProps(state => ({ - isLoading: costModelsSelectors.updateProcessing(state), - isDialogOpen: costModelsSelectors.isDialogOpen(state)('sources'), - })), - { - setDialogOpen: costModelsActions.setCostModelDialog, - updateCostModel: costModelsActions.updateCostModel, - } - )(SourceTableBase) -); diff --git a/src/routes/costModels/costModel/sourcesTable.tsx b/src/routes/costModels/costModel/sourcesTable.tsx deleted file mode 100644 index e8563ccb5..000000000 --- a/src/routes/costModels/costModel/sourcesTable.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { IAction, IRow } from '@patternfly/react-table'; -import { ActionsColumn, TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; -import { TableGridBreakpoint } from '@patternfly/react-table'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { createMapStateToProps } from 'store/common'; -import { costModelsSelectors } from 'store/costModels'; -import { rbacSelectors } from 'store/rbac'; - -interface SourcesTableOwnProps { - showDeleteDialog: (rowId: number) => void; -} - -interface SourcesTableStateProps { - canWrite: boolean; - costModels: any[]; -} - -type SourcesTableProps = SourcesTableOwnProps & SourcesTableStateProps & WrappedComponentProps; - -const SourcesTable: React.FC = ({ canWrite, costModels, intl, showDeleteDialog }) => { - const getActions = (): IAction[] => { - if (canWrite) { - return [ - { - title: intl.formatMessage(messages.costModelsSourceDelete), - onClick: (_evt, rowIndex: number) => showDeleteDialog(rowIndex), - }, - ]; - } - return [ - { - style: { pointerEvents: 'auto' }, - tooltip: intl.formatMessage(messages.readOnlyPermissions), - isDisabled: true, - title: intl.formatMessage(messages.costModelsSourceDelete), - }, - ]; - }; - - const actions = getActions(); - const rows: (IRow | string[])[] = costModels.length > 0 ? costModels[0].sources.map(source => [source.name]) : []; - - return ( - - - - - - - - - {rows.map((r: any, rowIndex) => ( - - - - - ))} - - - ); -}; - -export default injectIntl( - connect( - createMapStateToProps(state => { - return { - canWrite: rbacSelectors.isCostModelWritePermission(state), - costModels: costModelsSelectors.costModels(state), - }; - }) - )(SourcesTable) -); diff --git a/src/routes/costModels/costModel/sourcesToolbar.tsx b/src/routes/costModels/costModel/sourcesToolbar.tsx deleted file mode 100644 index 44076f5f3..000000000 --- a/src/routes/costModels/costModel/sourcesToolbar.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import type { ButtonProps, PaginationProps } from '@patternfly/react-core'; -import { - Button, - InputGroup, - InputGroupText, - Pagination, - TextInput, - Toolbar, - ToolbarContent, - ToolbarFilter, - ToolbarItem, - ToolbarToggleGroup, -} from '@patternfly/react-core'; -import { FilterIcon } from '@patternfly/react-icons/dist/esm/icons/filter-icon'; -import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon'; -import React from 'react'; -import { ReadOnlyTooltip } from 'routes/costModels/components/readOnlyTooltip'; - -interface FilterInputProps { - id: string; - value: string; - onChange: (value: string, event: React.FormEvent) => void; - onSearch: (evt: React.KeyboardEvent) => void; - placeholder?: string; -} - -const FilterInput: React.FC = ({ id, placeholder = '', value, onChange, onSearch }) => { - return ( - - ) => { - if (evt.key !== 'Enter' || value === '') { - return; - } - onSearch(evt); - }} - /> - - - - - ); -}; - -interface SourcesToolbarProps { - actionButtonProps: ButtonProps; - paginationProps?: PaginationProps; - filterInputProps: FilterInputProps; - filter: { - onRemove: (category: string, chip: string) => void; - onClearAll: () => void; - query: { name?: string[] }; - categoryNames: { name?: string }; - }; -} - -export const SourcesToolbar: React.FC = ({ - filterInputProps, - paginationProps, - filter, - actionButtonProps, -}) => { - return ( - - - }> - - - - - - - - - , - , - ]} - > - <> - {updateError && } -
- - this.setState({ name: value })} - /> - - -
{intl.formatMessage(messages.names, { count: 1 })}
{r} - { - return { - ...a, - onClick: () => a.onClick(null, rowIndex, r, null), - }; - })} - > -