diff --git a/.env b/.env
index f1e89d173..989fbc3c7 100644
--- a/.env
+++ b/.env
@@ -26,9 +26,13 @@ REACT_APP_UI_WINDOW_ID=curiosity
REACT_APP_AJAX_TIMEOUT=60000
REACT_APP_AJAX_CACHE=15000
-REACT_APP_AJAX_POLL_INTERVAL=15000
+REACT_APP_AJAX_POLL_INTERVAL=5000
REACT_APP_SELECTOR_CACHE=120000
+REACT_APP_CONFIG_EXPORT_EXPIRE=86400000
+REACT_APP_CONFIG_EXPORT_FILENAME=swatch_report_{0}
+REACT_APP_CONFIG_EXPORT_SERVICE_NAME_PREFIX=swatch
+
REACT_APP_CONFIG_SERVICE_LOCALES_COOKIE=rh_locale
REACT_APP_CONFIG_SERVICE_LOCALES_DEFAULT_LNG=en-US
REACT_APP_CONFIG_SERVICE_LOCALES_DEFAULT_LNG_DESC=English
diff --git a/.env.test b/.env.test
index f771e5ada..c9e7aa68a 100644
--- a/.env.test
+++ b/.env.test
@@ -2,5 +2,7 @@ REACT_APP_ENV=test
REACT_APP_UI_VERSION=0.0.0.0000000
+REACT_APP_AJAX_POLL_INTERVAL=1
+
REACT_APP_CONFIG_SERVICE_LOCALES=./locales/locales.json
REACT_APP_CONFIG_SERVICE_LOCALES_PATH=./locales/{{lng}}.json
diff --git a/public/locales/en-US.json b/public/locales/en-US.json
index e970ea8f3..69010deb8 100644
--- a/public/locales/en-US.json
+++ b/public/locales/en-US.json
@@ -297,6 +297,8 @@
},
"curiosity-toolbar": {
"button_displayName": "Search button for name",
+ "button_yes": "Yes",
+ "button_no": "No",
"clearFilters": "Reset filters",
"label": "",
"label_billing_provider": "",
@@ -360,9 +362,32 @@
"label_usage_Development/Test": "Development/Test",
"label_usage_Disaster Recovery": "Disaster Recovery",
"label_usage_Production": "Production",
+ "notifications_export_completed_description": "Downloading file {{fileName}}",
+ "notifications_export_completed_description_existing_completed": "{{completed}} completed report(s) are available. Would you like to continue and download?",
+ "notifications_export_completed_description_existing_completed_one": "{{completed}} completed report is available. Would you like to continue and download?",
+ "notifications_export_completed_description_existing_completed_other": "{{completed}} completed reports are available. Would you like to continue and download?",
+ "notifications_export_completed_description_existing_completed_pending": "{{completed}} completed and {{pending}} pending reports are available. Would you like to continue and download?",
+ "notifications_export_completed_description_existing_pending": "{{pending}} pending report(s) are available. Would you like to continue and download?",
+ "notifications_export_completed_description_existing_pending_one": "{{pending}} pending report is available. Would you like to continue and download?",
+ "notifications_export_completed_description_existing_pending_other": "{{pending}} pending reports are available. Would you like to continue and download?",
+ "notifications_export_completed_descriptionGlobal": "Download in progress",
+ "notifications_export_completed_descriptionGlobal_one": "{{count}} file download in progress",
+ "notifications_export_completed_descriptionGlobal_other": "{{count}} file downloads in progress",
+ "notifications_export_completed_title": "Report ready",
+ "notifications_export_completed_title_existing": "{{count}} existing report(s) are available",
+ "notifications_export_completed_title_existing_one": "{{count}} existing report is available",
+ "notifications_export_completed_title_existing_other": "{{count}} existing reports are available",
+ "notifications_export_completed_titleGlobal": "Existing report(s) ready",
+ "notifications_export_completed_titleGlobal_one": "Existing report ready",
+ "notifications_export_completed_titleGlobal_other": "Existing reports ready",
+ "notifications_export_error_description": "Closing report generator",
+ "notifications_export_error_title": "Report failed",
+ "notifications_export_pending_title": "Generating report in progress",
+ "notifications_export_pending_titleGlobal": "Continuing reports download",
"placeholder": "Select",
"placeholder_billing_provider": "Select purchased through",
- "placeholder_export": "Export",
+ "placeholder_export": "Export data",
+ "placeholder_export_loading": "Export data is loading",
"placeholder_granularity": "Select date range",
"placeholder_groupVariant": "Select a variant",
"placeholder_rangedMonthly": "Select a month",
diff --git a/src/common/README.md b/src/common/README.md
index af0d3149e..33c1170b6 100644
--- a/src/common/README.md
+++ b/src/common/README.md
@@ -306,6 +306,9 @@ Download the debug log file.
* [~PROD_MODE](#Helpers.module_General..PROD_MODE) : boolean
* [~REVIEW_MODE](#Helpers.module_General..REVIEW_MODE) : boolean
* [~TEST_MODE](#Helpers.module_General..TEST_MODE) : boolean
+ * [~CONFIG_EXPORT_EXPIRE](#Helpers.module_General..CONFIG_EXPORT_EXPIRE) : string
+ * [~CONFIG_EXPORT_FILENAME](#Helpers.module_General..CONFIG_EXPORT_FILENAME) : string
+ * [~CONFIG_EXPORT_SERVICE_NAME_PREFIX](#Helpers.module_General..CONFIG_EXPORT_SERVICE_NAME_PREFIX) : string
* [~UI_DEPLOY_PATH_PREFIX](#Helpers.module_General..UI_DEPLOY_PATH_PREFIX) : string
* [~UI_DEPLOY_PATH_LINK_PREFIX](#Helpers.module_General..UI_DEPLOY_PATH_LINK_PREFIX) : string
* [~UI_DISABLED](#Helpers.module_General..UI_DISABLED) : boolean
@@ -423,6 +426,24 @@ Associated with using the NPM script "start:proxy". See dotenv config files for
Is test mode active.
Associated with running unit tests. See dotenv config files for activation.
+**Kind**: inner constant of [General
](#Helpers.module_General)
+
+
+### General~CONFIG\_EXPORT\_EXPIRE : string
+CONFIG export download file expiration.
+
+**Kind**: inner constant of [General
](#Helpers.module_General)
+
+
+### General~CONFIG\_EXPORT\_FILENAME : string
+CONFIG export download file name. Extension is handled at the service level.
+
+**Kind**: inner constant of [General
](#Helpers.module_General)
+
+
+### General~CONFIG\_EXPORT\_SERVICE\_NAME\_PREFIX : string
+CONFIG export "post" download name prefix, for consistency.
+
**Kind**: inner constant of [General
](#Helpers.module_General)
diff --git a/src/common/__tests__/__snapshots__/helpers.test.js.snap b/src/common/__tests__/__snapshots__/helpers.test.js.snap
index 566a9e93e..538390107 100644
--- a/src/common/__tests__/__snapshots__/helpers.test.js.snap
+++ b/src/common/__tests__/__snapshots__/helpers.test.js.snap
@@ -8,6 +8,9 @@ G {
exports[`Helpers should expose a window object: limited window object 1`] = `
{
+ "CONFIG_EXPORT_EXPIRE": "86400000",
+ "CONFIG_EXPORT_FILENAME": "swatch_report_{0}",
+ "CONFIG_EXPORT_SERVICE_NAME_PREFIX": "swatch",
"DEV_MODE": false,
"PROD_MODE": false,
"REVIEW_MODE": false,
@@ -55,6 +58,9 @@ exports[`Helpers should expose a window object: limited window object 1`] = `
exports[`Helpers should expose a window object: window object 1`] = `
{
+ "CONFIG_EXPORT_EXPIRE": "86400000",
+ "CONFIG_EXPORT_FILENAME": "swatch_report_{0}",
+ "CONFIG_EXPORT_SERVICE_NAME_PREFIX": "swatch",
"DEV_MODE": false,
"PROD_MODE": false,
"REVIEW_MODE": false,
@@ -114,6 +120,9 @@ exports[`Helpers should handle use aggregate error, or fallback: emulated aggreg
exports[`Helpers should have specific functions: helpers 1`] = `
{
+ "CONFIG_EXPORT_EXPIRE": "86400000",
+ "CONFIG_EXPORT_FILENAME": "swatch_report_{0}",
+ "CONFIG_EXPORT_SERVICE_NAME_PREFIX": "swatch",
"DEV_MODE": false,
"PROD_MODE": false,
"REVIEW_MODE": false,
diff --git a/src/common/helpers.js b/src/common/helpers.js
index 066d1ddfa..a9a303709 100644
--- a/src/common/helpers.js
+++ b/src/common/helpers.js
@@ -238,6 +238,27 @@ const REVIEW_MODE = process.env.REACT_APP_ENV === 'review';
*/
const TEST_MODE = process.env.REACT_APP_ENV === 'test';
+/**
+ * CONFIG export download file expiration.
+ *
+ * @type {string}
+ */
+const CONFIG_EXPORT_EXPIRE = process.env.REACT_APP_CONFIG_EXPORT_EXPIRE;
+
+/**
+ * CONFIG export download file name. Extension is handled at the service level.
+ *
+ * @type {string}
+ */
+const CONFIG_EXPORT_FILENAME = process.env.REACT_APP_CONFIG_EXPORT_FILENAME;
+
+/**
+ * CONFIG export "post" download name prefix, for consistency.
+ *
+ * @type {string}
+ */
+const CONFIG_EXPORT_SERVICE_NAME_PREFIX = process.env.REACT_APP_CONFIG_EXPORT_SERVICE_NAME_PREFIX;
+
/**
* Apply a path prefix for routing.
* Typically associated with applying a "beta" path prefix. See dotenv config files for updating.
@@ -463,6 +484,9 @@ const helpers = {
PROD_MODE,
REVIEW_MODE,
TEST_MODE,
+ CONFIG_EXPORT_EXPIRE,
+ CONFIG_EXPORT_FILENAME,
+ CONFIG_EXPORT_SERVICE_NAME_PREFIX,
UI_DEPLOY_PATH_PREFIX,
UI_DEPLOY_PATH_LINK_PREFIX,
UI_DISABLED,
diff --git a/src/components/README.md b/src/components/README.md
index 37c6b191c..c514eebc8 100644
--- a/src/components/README.md
+++ b/src/components/README.md
@@ -161,8 +161,10 @@ recreate the core component.
A standalone Display Name input filter.
ToolbarFieldExport
-A standalone export select/dropdown filter.
+A standalone export select/dropdown filter and download hooks.
+ToolbarFieldExportContext
+
ToolbarFieldGranularity
A standalone Granularity select filter.
@@ -4743,6 +4745,7 @@ Default props.
* [~useProductInventoryHostsConfig(options)](#ProductView.module_ProductViewContext..useProductInventoryHostsConfig) ⇒ Object
* [~useProductInventorySubscriptionsConfig(options)](#ProductView.module_ProductViewContext..useProductInventorySubscriptionsConfig) ⇒ Object
* [~useProductToolbarConfig(options)](#ProductView.module_ProductViewContext..useProductToolbarConfig) ⇒ Object
+ * [~useProductExportQuery(options)](#ProductView.module_ProductViewContext..useProductExportQuery) ⇒ Object
@@ -5088,6 +5091,32 @@ Return primary toolbar configuration.
+
+
+### ProductViewContext~useProductExportQuery(options) ⇒ Object
+Return an export query for subscriptions.
+
+**Kind**: inner method of [ProductViewContext
](#ProductView.module_ProductViewContext)
+
+
+
+ Param Type
+
+
+
+
+ options object
+
+ options.useProduct function
+
+ options.schemaCheck object
+
+ options.useProductToolbarQuery function
+
+ options.options object
+
+
+
## ProductViewMissing
@@ -6591,14 +6620,11 @@ On enter submit value, on type submit value, and on esc ignore (clear value at c
## ToolbarFieldExport
-A standalone export select/dropdown filter.
+A standalone export select/dropdown filter and download hooks.
* [ToolbarFieldExport](#Toolbar.module_ToolbarFieldExport)
* [~toolbarFieldOptions](#Toolbar.module_ToolbarFieldExport..toolbarFieldOptions) : Array.<{title: React.ReactNode, value: string, selected: boolean}>
- * [~useExportStatus(options)](#Toolbar.module_ToolbarFieldExport..useExportStatus) ⇒ Object
- * [~useExport(options)](#Toolbar.module_ToolbarFieldExport..useExport) ⇒ function
- * [~validate](#Toolbar.module_ToolbarFieldExport..useExport..validate) : function
* [~useOnSelect(options)](#Toolbar.module_ToolbarFieldExport..useOnSelect) ⇒ function
* [~ToolbarFieldExport(props)](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport) ⇒ React.ReactNode
* [.propTypes](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport.propTypes) : Object
@@ -6610,10 +6636,10 @@ A standalone export select/dropdown filter.
Select field options.
**Kind**: inner constant of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport)
-
+
-### ToolbarFieldExport~useExportStatus(options) ⇒ Object
-Aggregated export status
+### ToolbarFieldExport~useOnSelect(options) ⇒ function
+On select create/post an export.
**Kind**: inner method of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport)
@@ -6626,18 +6652,21 @@ Aggregated export status
options object
+ options.useExport function
+
options.useProduct function
- options.useSelector function
+ options.useProductExportQuery function
-
+
-### ToolbarFieldExport~useExport(options) ⇒ function
-Apply a centralized export hook for, post/put, polling status, and download.
+### ToolbarFieldExport~ToolbarFieldExport(props) ⇒ React.ReactNode
+Display an export/download field with options. Check and download available exports.
**Kind**: inner method of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport)
+**Emits**: [onSelect
](#event_onSelect)
@@ -6646,32 +6675,56 @@ Apply a centralized export hook for, post/put, polling status, and download.
- options object
+ props object
- options.createExport function
+ props.options Array
- options.getExport function
+ props.position string
- options.getExportStatus function
+ props.t function
- options.useDispatch function
+ props.useExistingExports function
+
+ props.useExportStatus function
- options.useExportStatus function
+ props.useOnSelect function
-
-#### useExport~validate : function
-A polling response validator
+* [~ToolbarFieldExport(props)](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport) ⇒ React.ReactNode
+ * [.propTypes](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport.propTypes) : Object
+ * [.defaultProps](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport.defaultProps) : Object
-**Kind**: inner constant of [useExport
](#Toolbar.module_ToolbarFieldExport..useExport)
-
+
-### ToolbarFieldExport~useOnSelect(options) ⇒ function
-On select update export.
+#### ToolbarFieldExport.propTypes : Object
+Prop types.
-**Kind**: inner method of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport)
+**Kind**: static property of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport)
+
+
+#### ToolbarFieldExport.defaultProps : Object
+Default props.
+
+**Kind**: static property of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport)
+
+
+## ToolbarFieldExportContext
+
+* [ToolbarFieldExportContext](#ToolbarFieldExport.module_ToolbarFieldExportContext)
+ * [~useExportConfirmation(options)](#ToolbarFieldExport.module_ToolbarFieldExportContext..useExportConfirmation) ⇒ function
+ * [~useExport(options)](#ToolbarFieldExport.module_ToolbarFieldExportContext..useExport) ⇒ function
+ * [~useExistingExportsConfirmation(options)](#ToolbarFieldExport.module_ToolbarFieldExportContext..useExistingExportsConfirmation) ⇒ function
+ * [~useExistingExports(options)](#ToolbarFieldExport.module_ToolbarFieldExportContext..useExistingExports)
+ * [~useExportStatus(options)](#ToolbarFieldExport.module_ToolbarFieldExportContext..useExportStatus) ⇒ Object
+
+
+
+### ToolbarFieldExportContext~useExportConfirmation(options) ⇒ function
+Return a polling status callback. Used when creating an export.
+
+**Kind**: inner method of [ToolbarFieldExportContext
](#ToolbarFieldExport.module_ToolbarFieldExportContext)
@@ -6682,21 +6735,22 @@ On select update export.
options object
- options.useExport function
+ options.addNotification function
- options.useProduct function
+ options.t function
- options.useProductInventoryQuery function
+ options.useDispatch function
+
+ options.useProduct function
-
+
-### ToolbarFieldExport~ToolbarFieldExport(props) ⇒ React.ReactNode
-Display an export/download field with options.
+### ToolbarFieldExportContext~useExport(options) ⇒ function
+Apply an export hook for an export post. The service automatically sets up polling, then force downloads the file.
-**Kind**: inner method of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport)
-**Emits**: [onSelect
](#event_onSelect)
+**Kind**: inner method of [ToolbarFieldExportContext
](#ToolbarFieldExport.module_ToolbarFieldExportContext)
@@ -6705,39 +6759,98 @@ Display an export/download field with options.
- props object
+ options object
- props.options Array
+ options.createExport function
- props.position string
+ options.t function
- props.t function
+ options.useDispatch function
+
+ options.useExportConfirmation function
+
+
+
+
+
+### ToolbarFieldExportContext~useExistingExportsConfirmation(options) ⇒ function
+User confirmation results when existing exports are detected.
+
+**Kind**: inner method of [ToolbarFieldExportContext
](#ToolbarFieldExport.module_ToolbarFieldExportContext)
+
+
+
+ Param Type
+
+
+
+
+ options object
- props.useExport function
+ options.deleteExistingExports function
- props.useExportStatus function
+ options.getExistingExports function
- props.useOnSelect function
+ options.removeNotification function
+
+ options.t function
+
+ options.useDispatch function
+
-* [~ToolbarFieldExport(props)](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport) ⇒ React.ReactNode
- * [.propTypes](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport.propTypes) : Object
- * [.defaultProps](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport.defaultProps) : Object
+### ToolbarFieldExportContext~useExistingExports(options)
+Apply an existing exports hook for user abandoned reports. Allow bulk polling status with download.
-
+**Kind**: inner method of [ToolbarFieldExportContext
](#ToolbarFieldExport.module_ToolbarFieldExportContext)
+
+
+
+ Param Type
+
+
+
+
+ options object
+
+ options.addNotification function
+
+ options.getExistingExportsStatus function
+
+ options.t function
+
+ options.useDispatch function
+
+ options.useExistingExportsConfirmation function
+
+ options.useSelectorsResponse function
+
+
-#### ToolbarFieldExport.propTypes : Object
-Prop types.
+
-**Kind**: static property of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport)
-
+### ToolbarFieldExportContext~useExportStatus(options) ⇒ Object
+Aggregated export status
-#### ToolbarFieldExport.defaultProps : Object
-Default props.
+**Kind**: inner method of [ToolbarFieldExportContext
](#ToolbarFieldExport.module_ToolbarFieldExportContext)
+
+
+
+ Param Type
+
+
+
+
+ options object
+
+ options.useProduct function
+
+ options.useSelector function
+
+
-**Kind**: static property of [ToolbarFieldExport
](#Toolbar.module_ToolbarFieldExport..ToolbarFieldExport)
## ToolbarFieldGranularity
diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
index 9a11dd273..34576c85b 100644
--- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
+++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap
@@ -352,6 +352,87 @@ exports[`I18n Component should generate a predictable locale key output snapshot
"key": "curiosity-toolbar.placeholder",
"match": "t('curiosity-toolbar.placeholder', { context: 'export' })",
},
+ {
+ "key": "curiosity-toolbar.placeholder",
+ "match": "t('curiosity-toolbar.placeholder', { context: 'export' })",
+ },
+ ],
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "keys": [
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'completed', 'title'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'completed', 'description'], fileName: completed?.[0]?.fileName })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'pending', 'title', id] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'pending', 'titleGlobal'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'completed', 'titleGlobal'], count: allResults.length })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'completed', 'descriptionGlobal'], count: allResults.length })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: ['export', 'completed', 'title', 'existing'], count: totalResults })",
+ },
+ {
+ "key": "curiosity-toolbar.notifications",
+ "match": "t('curiosity-toolbar.notifications', { context: [ 'export', 'completed', 'description', 'existing', completed.length && 'completed', pending.length && 'pending' ], count: totalResults, completed: completed.length, pending: pending.length })",
+ },
+ {
+ "key": "curiosity-toolbar.button",
+ "match": "t('curiosity-toolbar.button', { context: 'yes' })",
+ },
+ {
+ "key": "curiosity-toolbar.button",
+ "match": "t('curiosity-toolbar.button', { context: 'no' })",
+ },
],
},
{
@@ -436,6 +517,10 @@ exports[`I18n Component should generate a predictable locale key output snapshot
"key": "curiosity-toolbar.label",
"match": "translate('curiosity-toolbar.label', { context: ['filter', RHSM_API_QUERY_SET_TYPES.DISPLAY_NAME] })",
},
+ {
+ "key": "curiosity-toolbar.label",
+ "match": "translate('curiosity-toolbar.label', { context: ['filter', 'export'] })",
+ },
{
"key": "curiosity-toolbar.placeholder",
"match": "t('curiosity-toolbar.placeholder', { context: ['filter'] })",
@@ -975,6 +1060,78 @@ exports[`I18n Component should have locale keys that exist in the default langua
"file": "src/components/toolbar/toolbarFieldExport.js",
"key": "curiosity-toolbar.label",
},
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.notifications",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.button",
+ },
+ {
+ "file": "src/components/toolbar/toolbarFieldExportContext.js",
+ "key": "curiosity-toolbar.button",
+ },
{
"file": "src/components/toolbar/toolbarFieldGranularity.js",
"key": "curiosity-toolbar.label",
@@ -1015,6 +1172,10 @@ exports[`I18n Component should have locale keys that exist in the default langua
"file": "src/components/toolbar/toolbarFieldSelectCategory.js",
"key": "curiosity-toolbar.label",
},
+ {
+ "file": "src/components/toolbar/toolbarFieldSelectCategory.js",
+ "key": "curiosity-toolbar.label",
+ },
{
"file": "src/components/toolbar/toolbarFieldSla.js",
"key": "curiosity-toolbar.label",
diff --git a/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap b/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap
index 3e67dd64b..23a7a47de 100644
--- a/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap
+++ b/src/components/productView/__tests__/__snapshots__/productViewContext.test.js.snap
@@ -19,6 +19,13 @@ exports[`ProductViewContext should apply a hook for retrieving product context:
}
`;
+exports[`ProductViewContext should apply hooks for retrieving specific api queries: exportQuery 1`] = `
+{
+ "product_id": undefined,
+ "sla": "testSla",
+}
+`;
+
exports[`ProductViewContext should apply hooks for retrieving specific api queries: graphTallyQuery 1`] = `
{
"granularity": "testGranularity",
@@ -177,6 +184,7 @@ exports[`ProductViewContext should return specific properties: specific properti
"useInventorySubscriptionsQuery": [Function],
"useProduct": [Function],
"useProductContext": [Function],
+ "useProductExportQuery": [Function],
"useQuery": [Function],
"useQueryFactory": [Function],
"useToolbarConfig": [Function],
diff --git a/src/components/productView/__tests__/productViewContext.test.js b/src/components/productView/__tests__/productViewContext.test.js
index eb1ca956f..35afc610b 100644
--- a/src/components/productView/__tests__/productViewContext.test.js
+++ b/src/components/productView/__tests__/productViewContext.test.js
@@ -2,6 +2,7 @@ import {
context,
useProductQueryFactory,
useProductQuery,
+ useProductExportQuery,
useProductGraphTallyQuery,
useProductInventoryGuestsQuery,
useProductInventoryHostsQuery,
@@ -76,6 +77,11 @@ describe('ProductViewContext', () => {
useProductToolbarQuery({ options: { useProductViewContext: () => mockContextValue } })
);
expect(toolbarQuery).toMatchSnapshot('toolbarQuery');
+
+ const { result: exportQuery } = await renderHook(() =>
+ useProductExportQuery({ options: { useProductViewContext: () => mockContextValue } })
+ );
+ expect(exportQuery).toMatchSnapshot('exportQuery');
});
it('should apply a hook for retrieving product context', async () => {
diff --git a/src/components/productView/productViewContext.js b/src/components/productView/productViewContext.js
index 48b50be60..4c596de98 100644
--- a/src/components/productView/productViewContext.js
+++ b/src/components/productView/productViewContext.js
@@ -2,6 +2,7 @@ import React, { useContext } from 'react';
import { reduxHelpers } from '../../redux/common';
import { storeHooks } from '../../redux/hooks';
import { rhsmConstants } from '../../services/rhsm/rhsmConstants';
+import { platformConstants } from '../../services/platform/platformConstants';
import { helpers } from '../../common/helpers';
/**
@@ -308,6 +309,32 @@ const useProductToolbarConfig = ({ useProductContext: useAliasProductContext = u
};
};
+/**
+ * Return an export query for subscriptions.
+ *
+ * @param {object} options
+ * @param {Function} options.useProduct
+ * @param {object} options.schemaCheck
+ * @param {Function} options.useProductToolbarQuery
+ * @param {object} options.options
+ * @returns {{}}
+ */
+const useProductExportQuery = ({
+ useProduct: useAliasProduct = useProduct,
+ schemaCheck = platformConstants.PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES,
+ useProductToolbarQuery: useAliasProductToolbarQuery = useProductToolbarQuery,
+ options
+} = {}) => {
+ const { productId } = useAliasProduct();
+ return reduxHelpers.setApiQuery(
+ {
+ ...useAliasProductToolbarQuery({ options }),
+ [platformConstants.PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES.PRODUCT_ID]: productId
+ },
+ schemaCheck
+ );
+};
+
const context = {
ProductViewContext,
DEFAULT_CONTEXT,
@@ -319,6 +346,7 @@ const context = {
useInventoryHostsQuery: useProductInventoryHostsQuery,
useInventorySubscriptionsQuery: useProductInventorySubscriptionsQuery,
useProduct,
+ useProductExportQuery,
useGraphConfig: useProductGraphConfig,
useInventoryGuestsConfig: useProductInventoryGuestsConfig,
useInventoryHostsConfig: useProductInventoryHostsConfig,
@@ -340,6 +368,7 @@ export {
useProductInventoryHostsQuery,
useProductInventorySubscriptionsQuery,
useProduct,
+ useProductExportQuery,
useProductGraphConfig,
useProductInventoryGuestsConfig,
useProductInventoryHostsConfig,
diff --git a/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExport.test.js.snap b/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExport.test.js.snap
index 9f17bced2..723c29a9e 100644
--- a/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExport.test.js.snap
+++ b/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExport.test.js.snap
@@ -1,90 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`ToolbarFieldExport Component should aggregate export service calls: createExport 1`] = `
-[
- [
- {
- "lorem": "ipsum",
- },
- {
- "poll": {
- "validate": [Function],
- },
- },
- ],
-]
-`;
-
-exports[`ToolbarFieldExport Component should aggregate export service calls: getExport 1`] = `
+exports[`ToolbarFieldExport Component should export select options: toolbarFieldOptions 1`] = `
[
- [
- "dolorSit",
- ],
+ {
+ "selected": false,
+ "title": "t(curiosity-toolbar.label_export, {"context":"json"})",
+ "value": "json",
+ },
]
`;
-exports[`ToolbarFieldExport Component should aggregate export service calls: getStatus 1`] = `
+exports[`ToolbarFieldExport Component should handle updating export through redux action with hook: dispatch, hook 1`] = `
[
[
+ undefined,
{
- "poll": {
- "validate": [Function],
- },
+ "expires_at": "2019-07-21T00:00:00.000Z",
+ "format": "dolor sit",
+ "name": "swatch-undefined",
+ "sources": [
+ {
+ "application": "subscriptions",
+ "filters": {},
+ "resource": "subscriptions",
+ },
+ ],
},
],
]
`;
-exports[`ToolbarFieldExport Component should aggregate export service calls: getStatus, existing polling 1`] = `[]`;
-
-exports[`ToolbarFieldExport Component should aggregate export status, polling status with a hook: status, basic 1`] = `
-{
- "isCompleted": undefined,
- "isPolling": undefined,
- "isProductPolling": false,
- "productPollingFormats": [],
-}
-`;
-
-exports[`ToolbarFieldExport Component should aggregate export status, polling status with a hook: status, completed 1`] = `
-{
- "isCompleted": undefined,
- "isPolling": undefined,
- "isProductPolling": false,
- "productPollingFormats": [],
-}
-`;
-
-exports[`ToolbarFieldExport Component should aggregate export status, polling status with a hook: status, polling 1`] = `
-{
- "isCompleted": undefined,
- "isPolling": true,
- "isProductPolling": true,
- "productPollingFormats": [
- "dolorSit",
- ],
-}
-`;
-
-exports[`ToolbarFieldExport Component should export select options: toolbarFieldOptions 1`] = `
-[
- {
- "selected": false,
- "title": "t(curiosity-toolbar.label_export, {"context":"csv"})",
- "value": "csv",
- },
- {
- "selected": false,
- "title": "t(curiosity-toolbar.label_export, {"context":"json"})",
- "value": "json",
- },
-]
-`;
-
-exports[`ToolbarFieldExport Component should handle updating export through redux state with component: dispatch, component 1`] = `[]`;
-
-exports[`ToolbarFieldExport Component should handle updating export through redux state with hook: dispatch, hook 1`] = `[]`;
-
exports[`ToolbarFieldExport Component should render a basic component: basic 1`] = `
}
variant="single"
/>
diff --git a/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExportContext.test.js.snap b/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExportContext.test.js.snap
new file mode 100644
index 000000000..c85506f43
--- /dev/null
+++ b/src/components/toolbar/__tests__/__snapshots__/toolbarFieldExportContext.test.js.snap
@@ -0,0 +1,187 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ToolbarFieldExport Component should aggregate export status, polling status with a hook: status, basic 1`] = `
+{
+ "isProductPending": false,
+ "pendingProductFormats": [],
+}
+`;
+
+exports[`ToolbarFieldExport Component should aggregate export status, polling status with a hook: status, completed 1`] = `
+{
+ "isProductPending": false,
+ "pendingProductFormats": [],
+}
+`;
+
+exports[`ToolbarFieldExport Component should aggregate export status, polling status with a hook: status, polling 1`] = `
+{
+ "isProductPending": true,
+ "pendingProductFormats": [
+ "dolorSit",
+ ],
+}
+`;
+
+exports[`ToolbarFieldExport Component should allow export service calls on existing exports: existingExports 1`] = `
+[
+ {
+ "type": "return",
+ "value": {
+ "rejected": {
+ "description": "t(curiosity-toolbar.notifications_export_error, {"context":"description"})",
+ "dismissable": true,
+ "title": "t(curiosity-toolbar.notifications_export_error, {"context":"title"})",
+ "variant": "warning",
+ },
+ },
+ },
+ {
+ "type": "return",
+ "value": {
+ "payload": "swatch-exports-status",
+ "type": "@@INSIGHTS-CORE/NOTIFICATIONS/REMOVE_NOTIFICATION",
+ },
+ },
+ {
+ "type": "return",
+ "value": {
+ "payload": {
+ "autoDismiss": false,
+ "description":
+ t(curiosity-toolbar.notifications_export_completed_description_existing, {"context":"pending","count":1,"completed":0,"pending":1})
+
+
+ t(curiosity-toolbar.button, {"context":"yes"})
+
+
+
+ t(curiosity-toolbar.button, {"context":"no"})
+
+
+
,
+ "dismissable": false,
+ "id": "swatch-exports-status",
+ "title": "t(curiosity-toolbar.notifications_export_completed_title, {"context":"existing","count":1})",
+ },
+ "type": "@@INSIGHTS-CORE/NOTIFICATIONS/ADD_NOTIFICATION",
+ },
+ },
+]
+`;
+
+exports[`ToolbarFieldExport Component should allow export service calls: createExport 1`] = `
+[
+ {
+ "type": "return",
+ "value": {
+ "id": "mock-product-id",
+ "isPending": true,
+ "type": "SET_PLATFORM_EXPORT_STATUS",
+ },
+ },
+ {
+ "type": "return",
+ "value": "mock-product-id",
+ },
+]
+`;
+
+exports[`ToolbarFieldExport Component should allow service calls on user confirmation: confirmation 1`] = `
+[
+ [
+ [
+ "lorem",
+ "ipsum",
+ "dolor",
+ "sit",
+ ],
+ {
+ "rejected": {
+ "description": "t(curiosity-toolbar.notifications_export_error, {"context":"description"})",
+ "dismissable": true,
+ "title": "t(curiosity-toolbar.notifications_export_error, {"context":"title"})",
+ "variant": "warning",
+ },
+ },
+ ],
+ [
+ [
+ "lorem",
+ "ipsum",
+ "dolor",
+ "sit",
+ ],
+ {
+ "fulfilled": {
+ "description": "t(curiosity-toolbar.notifications_export_completed, {"context":"descriptionGlobal","count":4})",
+ "dismissable": true,
+ "title": "t(curiosity-toolbar.notifications_export_completed, {"context":"titleGlobal","count":4})",
+ "variant": "success",
+ },
+ "pending": {
+ "dismissable": true,
+ "title": "t(curiosity-toolbar.notifications_export_pending, {"context":"titleGlobal"})",
+ "variant": "info",
+ },
+ "rejected": {
+ "description": "t(curiosity-toolbar.notifications_export_error, {"context":"description"})",
+ "dismissable": true,
+ "title": "t(curiosity-toolbar.notifications_export_error, {"context":"title"})",
+ "variant": "warning",
+ },
+ },
+ ],
+]
+`;
+
+exports[`ToolbarFieldExport Component should expose an export polling status confirmation: statusConfirmation 1`] = `
+[
+ {
+ "type": "return",
+ "value": {
+ "payload": "swatch-create-export-loremIpsum",
+ "type": "@@INSIGHTS-CORE/NOTIFICATIONS/REMOVE_NOTIFICATION",
+ },
+ },
+ {
+ "type": "return",
+ "value": {
+ "payload": {
+ "description": "t(curiosity-toolbar.notifications_export_completed, {"context":"description","fileName":"helloWorldFileName"})",
+ "dismissable": true,
+ "id": "swatch-create-export-loremIpsum",
+ "title": "t(curiosity-toolbar.notifications_export_completed, {"context":"title"})",
+ "variant": "success",
+ },
+ "type": "@@INSIGHTS-CORE/NOTIFICATIONS/ADD_NOTIFICATION",
+ },
+ },
+ {
+ "type": "return",
+ "value": {
+ "id": "loremIpsum",
+ "isPending": false,
+ "pending": [
+ {
+ "fileName": "dolorSitFileName",
+ "id": "dolorSit",
+ },
+ ],
+ "type": "SET_PLATFORM_EXPORT_STATUS",
+ },
+ },
+]
+`;
diff --git a/src/components/toolbar/__tests__/__snapshots__/toolbarFieldSelectCategory.test.js.snap b/src/components/toolbar/__tests__/__snapshots__/toolbarFieldSelectCategory.test.js.snap
index b7751c1ed..8f2927cd7 100644
--- a/src/components/toolbar/__tests__/__snapshots__/toolbarFieldSelectCategory.test.js.snap
+++ b/src/components/toolbar/__tests__/__snapshots__/toolbarFieldSelectCategory.test.js.snap
@@ -247,6 +247,14 @@ exports[`ToolbarFieldSelectCategory Component should export select options: tool
"title": "t(curiosity-toolbar.label_filter_display_name, {"context":"contains"})",
"value": "display_name_contains",
},
+ {
+ "component": [Function],
+ "isClearable": false,
+ "options": [],
+ "selected": false,
+ "title": "t(curiosity-toolbar.label_filter, {"context":"export"})",
+ "value": "export",
+ },
]
`;
diff --git a/src/components/toolbar/__tests__/toolbarFieldExport.test.js b/src/components/toolbar/__tests__/toolbarFieldExport.test.js
index 2b1f3c2cc..3f85c05e5 100644
--- a/src/components/toolbar/__tests__/toolbarFieldExport.test.js
+++ b/src/components/toolbar/__tests__/toolbarFieldExport.test.js
@@ -1,28 +1,10 @@
import React from 'react';
-import {
- ToolbarFieldExport,
- toolbarFieldOptions,
- useExport,
- useExportStatus,
- useOnSelect
-} from '../toolbarFieldExport';
-import { store } from '../../../redux/store';
-import { PLATFORM_API_EXPORT_STATUS_TYPES } from '../../../services/platform/platformConstants';
+import { ToolbarFieldExport, toolbarFieldOptions, useOnSelect } from '../toolbarFieldExport';
describe('ToolbarFieldExport Component', () => {
- let mockDispatch;
-
- beforeEach(() => {
- mockDispatch = jest.spyOn(store, 'dispatch').mockImplementation((type, data) => ({ type, data }));
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
it('should render a basic component', async () => {
const props = {
- useExport: () => jest.fn()
+ useExistingExports: () => jest.fn()
};
const component = await shallowComponent( );
@@ -33,10 +15,11 @@ describe('ToolbarFieldExport Component', () => {
expect(toolbarFieldOptions).toMatchSnapshot('toolbarFieldOptions');
});
- it('should handle updating export through redux state with component', () => {
+ it('should handle updating export through redux action with component', () => {
+ const mockOnSelect = jest.fn();
const props = {
- useOnSelect: () => jest.fn(),
- useExport: () => jest.fn()
+ useOnSelect: () => mockOnSelect,
+ useExistingExports: () => jest.fn()
};
const component = renderComponent( );
@@ -46,14 +29,15 @@ describe('ToolbarFieldExport Component', () => {
const inputMenuItem = component.find('a.pf-v5-c-dropdown__menu-item');
component.fireEvent.click(inputMenuItem);
- expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch, component');
+ expect(mockOnSelect).toHaveBeenCalledTimes(1);
});
- it('should handle updating export through redux state with hook', () => {
+ it('should handle updating export through redux action with hook', () => {
+ const mockExport = jest.fn();
const options = {
- useExport: () => jest.fn(),
+ useExport: () => mockExport,
useProduct: () => ({ viewId: 'loremIpsum' }),
- useProductInventoryQuery: () => ({})
+ useProductExportQuery: () => ({})
};
const onSelect = useOnSelect(options);
@@ -61,133 +45,6 @@ describe('ToolbarFieldExport Component', () => {
onSelect({
value: 'dolor sit'
});
- expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch, hook');
- });
-
- it('should aggregate export status, polling status with a hook', async () => {
- const { result: basic, unmount: unmountBasic } = await renderHook(() =>
- useExportStatus({
- useProduct: () => ({
- productId: 'loremIpsum'
- })
- })
- );
- await unmountBasic();
- expect(basic).toMatchSnapshot('status, basic');
-
- const { result: polling, unmount: unmountPolling } = await renderHook(() =>
- useExportStatus({
- useProduct: () => ({
- productId: 'loremIpsum'
- }),
- useSelector: () => ({
- data: {
- data: {
- isAnythingPending: true,
- loremIpsum: [
- {
- status: PLATFORM_API_EXPORT_STATUS_TYPES.PENDING,
- format: 'dolorSit'
- }
- ]
- }
- }
- })
- })
- );
- await unmountPolling();
- expect(polling).toMatchSnapshot('status, polling');
-
- const { result: completed, unmount: unmountCompleted } = await renderHook(() =>
- useExportStatus({
- useProduct: () => ({
- productId: 'loremIpsum'
- }),
- useSelector: () => ({
- app: {
- exports: {
- data: {
- data: {
- isAnythingPending: false,
- loremIpsum: {
- status: PLATFORM_API_EXPORT_STATUS_TYPES.COMPLETED,
- format: 'dolorSit'
- }
- }
- }
- }
- }
- })
- })
- );
- await unmountCompleted();
- expect(completed).toMatchSnapshot('status, completed');
- });
-
- it('should aggregate export service calls', async () => {
- // confirm attempt at creating an export
- const createExport = jest.fn().mockImplementation(
- (...args) =>
- dispatch =>
- dispatch(...args)
- );
- const { result: create, unmount: unmountCreate } = await renderHook(() =>
- useExport({
- createExport
- })
- );
- create({ data: { lorem: 'ipsum' } });
- await unmountCreate();
- expect(createExport).toHaveBeenCalledTimes(1);
- expect(createExport.mock.calls).toMatchSnapshot('createExport');
-
- // confirm attempt at getting an export
- const getExport = jest.fn().mockImplementation(
- (...args) =>
- dispatch =>
- dispatch(...args)
- );
- const { result: get, unmount: unmountGet } = await renderHook(() =>
- useExport({
- getExport
- })
- );
- get({ id: 'dolorSit' });
- await unmountGet();
- expect(getExport).toHaveBeenCalledTimes(1);
- expect(getExport.mock.calls).toMatchSnapshot('getExport');
-
- // confirm attempt at getting an export status
- const getExportStatus = jest.fn().mockImplementation(
- (...args) =>
- dispatch =>
- dispatch(...args)
- );
- const { result: status, unmount: unmountStatus } = await renderHook(() =>
- useExport({
- getExportStatus
- })
- );
- status();
- await unmountStatus();
- expect(getExportStatus).toHaveBeenCalledTimes(1);
- expect(getExportStatus.mock.calls).toMatchSnapshot('getStatus');
-
- // confirm attempt at getting an export status when there is already polling
- const getExportStatusAgain = jest.fn().mockImplementation(
- (...args) =>
- dispatch =>
- dispatch(...args)
- );
- const { result: statusAgain, unmount: unmountStatusAgain } = await renderHook(() =>
- useExport({
- getExportStatus: getExportStatusAgain,
- useExportStatus: () => ({ isPolling: true })
- })
- );
- statusAgain();
- await unmountStatusAgain();
- expect(getExportStatusAgain).toHaveBeenCalledTimes(0);
- expect(getExportStatusAgain.mock.calls).toMatchSnapshot('getStatus, existing polling');
+ expect(mockExport.mock.calls).toMatchSnapshot('dispatch, hook');
});
});
diff --git a/src/components/toolbar/__tests__/toolbarFieldExportContext.test.js b/src/components/toolbar/__tests__/toolbarFieldExportContext.test.js
new file mode 100644
index 000000000..f3e522324
--- /dev/null
+++ b/src/components/toolbar/__tests__/toolbarFieldExportContext.test.js
@@ -0,0 +1,143 @@
+import {
+ useExport,
+ useExportConfirmation,
+ useExportStatus,
+ useExistingExports,
+ useExistingExportsConfirmation
+} from '../toolbarFieldExportContext';
+import { store } from '../../../redux/store';
+import { PLATFORM_API_EXPORT_STATUS_TYPES } from '../../../services/platform/platformConstants';
+
+describe('ToolbarFieldExport Component', () => {
+ let mockDispatch;
+ let mockService;
+
+ beforeEach(() => {
+ mockDispatch = jest
+ .spyOn(store, 'dispatch')
+ .mockImplementation(
+ type =>
+ (Array.isArray(type) && type.map(value => (typeof value === 'function' && value.toString()) || value)) || type
+ );
+
+ mockService = jest.fn().mockImplementation(
+ (...args) =>
+ dispatch =>
+ dispatch(...args)
+ );
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should expose an export polling status confirmation', async () => {
+ const { result: statusConfirmation, unmount } = await renderHook(() =>
+ useExportConfirmation({
+ useProduct: () => ({ productId: 'loremIpsum' })
+ })
+ );
+
+ statusConfirmation({
+ data: {
+ data: {
+ products: {
+ loremIpsum: {
+ completed: [{ id: 'helloWorld', fileName: 'helloWorldFileName' }],
+ isCompleted: true,
+ pending: [{ id: 'dolorSit', fileName: 'dolorSitFileName' }]
+ }
+ }
+ }
+ }
+ });
+
+ await unmount();
+ expect(mockDispatch.mock.results).toMatchSnapshot('statusConfirmation');
+ });
+
+ it('should allow export service calls', async () => {
+ const { result: createExport, unmount } = await renderHook(() => useExport({ createExport: mockService }));
+ createExport('mock-product-id', { data: { lorem: 'ipsum' } });
+
+ await unmount();
+ expect(mockService).toHaveBeenCalledTimes(1);
+ expect(mockDispatch.mock.results).toMatchSnapshot('createExport');
+ });
+
+ it('should allow export service calls on existing exports', async () => {
+ const { unmount } = await renderHook((...args) => {
+ useExistingExports({
+ getExistingExports: mockService,
+ getExistingExportsStatus: mockService,
+ deleteExistingExports: mockService,
+ useSelectorsResponse: () => ({
+ data: [{ data: { isAnythingPending: true, pending: [{ lorem: 'ipsum' }] } }],
+ fulfilled: true
+ }),
+ ...args?.[0]
+ });
+ });
+
+ await unmount();
+ expect(mockDispatch.mock.results).toMatchSnapshot('existingExports');
+ });
+
+ it('should allow service calls on user confirmation', async () => {
+ const { result: onConfirmation, unmount } = await renderHook(() =>
+ useExistingExportsConfirmation({
+ deleteExistingExports: mockService,
+ getExistingExports: mockService
+ })
+ );
+ onConfirmation('no', ['lorem', 'ipsum', 'dolor', 'sit']);
+ onConfirmation('yes', ['lorem', 'ipsum', 'dolor', 'sit']);
+ await unmount();
+ expect(mockService.mock.calls).toMatchSnapshot('confirmation');
+ });
+
+ it('should aggregate export status, polling status with a hook', async () => {
+ const { result: basic, unmount: unmountBasic } = await renderHook(() =>
+ useExportStatus({
+ useProduct: () => ({
+ productId: 'loremIpsum'
+ })
+ })
+ );
+ await unmountBasic();
+ expect(basic).toMatchSnapshot('status, basic');
+
+ const { result: polling, unmount: unmountPolling } = await renderHook(() =>
+ useExportStatus({
+ useProduct: () => ({
+ productId: 'loremIpsum'
+ }),
+ useSelector: () => ({
+ isPending: true,
+ pending: [
+ {
+ status: PLATFORM_API_EXPORT_STATUS_TYPES.PENDING,
+ format: 'dolorSit'
+ }
+ ]
+ })
+ })
+ );
+ await unmountPolling();
+ expect(polling).toMatchSnapshot('status, polling');
+
+ const { result: completed, unmount: unmountCompleted } = await renderHook(() =>
+ useExportStatus({
+ useProduct: () => ({
+ productId: 'loremIpsum'
+ }),
+ useSelector: () => ({
+ isPending: false,
+ pending: []
+ })
+ })
+ );
+ await unmountCompleted();
+ expect(completed).toMatchSnapshot('status, completed');
+ });
+});
diff --git a/src/components/toolbar/toolbarFieldExport.js b/src/components/toolbar/toolbarFieldExport.js
index f48f91860..093e3ccee 100644
--- a/src/components/toolbar/toolbarFieldExport.js
+++ b/src/components/toolbar/toolbarFieldExport.js
@@ -1,21 +1,21 @@
-import React, { useCallback } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import { ExportIcon } from '@patternfly/react-icons';
-import { useMount } from 'react-use';
-import { reduxActions, storeHooks } from '../../redux';
-import { useProduct, useProductInventoryHostsQuery } from '../productView/productViewContext';
+import { useProduct, useProductExportQuery } from '../productView/productViewContext';
+import { useExport, useExistingExports, useExportStatus } from './toolbarFieldExportContext';
import { Select, SelectPosition, SelectButtonVariant } from '../form/select';
import {
PLATFORM_API_EXPORT_APPLICATION_TYPES as APP_TYPES,
PLATFORM_API_EXPORT_CONTENT_TYPES as FIELD_TYPES,
- PLATFORM_API_EXPORT_FILENAME_PREFIX as EXPORT_PREFIX,
PLATFORM_API_EXPORT_RESOURCE_TYPES as RESOURCE_TYPES,
- PLATFORM_API_EXPORT_STATUS_TYPES
+ PLATFORM_API_EXPORT_POST_TYPES as POST_TYPES,
+ PLATFORM_API_EXPORT_SOURCE_TYPES as SOURCE_TYPES
} from '../../services/platform/platformConstants';
import { translate } from '../i18n/i18n';
+import { dateHelpers, helpers } from '../../common';
/**
- * A standalone export select/dropdown filter.
+ * A standalone export select/dropdown filter and download hooks.
*
* @memberof Toolbar
* @module ToolbarFieldExport
@@ -33,149 +33,56 @@ const toolbarFieldOptions = Object.values(FIELD_TYPES).map(type => ({
}));
/**
- * Aggregated export status
- *
- * @param {object} options
- * @param {Function} options.useProduct
- * @param {Function} options.useSelector
- * @returns {{isPolling: boolean, isProductPolling: boolean, productPollingFormats: Array,
- * isCompleted: boolean}}
- */
-const useExportStatus = ({
- useProduct: useAliasProduct = useProduct,
- useSelector: useAliasSelector = storeHooks.reactRedux.useSelector
-} = {}) => {
- const { productId } = useAliasProduct();
- const { data = {} } = useAliasSelector(({ app }) => app?.exports, {});
-
- const isPolling = data?.data?.isAnythingPending === true || undefined;
- const isCompleted = data?.data?.isAnythingPending === false || undefined;
- const productPollingFormats = [];
- let isProductPolling = false;
-
- if (isPolling && Array.isArray(data?.data?.[productId])) {
- const pollingResults = data?.data?.[productId]
- .filter(({ status: productStatus }) => productStatus === PLATFORM_API_EXPORT_STATUS_TYPES.PENDING)
- .map(({ format: productFormat }) => productFormat);
-
- productPollingFormats.push(...pollingResults);
-
- if (pollingResults.length) {
- isProductPolling = true;
- }
- }
-
- return {
- isCompleted,
- isPolling,
- isProductPolling,
- productPollingFormats
- };
-};
-
-/**
- * Apply a centralized export hook for, post/put, polling status, and download.
- *
- * @param {object} options
- * @param {Function} options.createExport
- * @param {Function} options.getExport
- * @param {Function} options.getExportStatus
- * @param {Function} options.useDispatch
- * @param {Function} options.useExportStatus
- * @returns {Function}
- */
-const useExport = ({
- createExport = reduxActions.platform.createExport,
- getExport = reduxActions.platform.getExport,
- getExportStatus = reduxActions.platform.getExportStatus,
- useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch,
- useExportStatus: useAliasExportStatus = useExportStatus
-} = {}) => {
- const dispatch = useAliasDispatch();
- const { isPolling } = useAliasExportStatus();
-
- /**
- * A polling response validator
- *
- * @type {Function}
- */
- const validate = useCallback(response => response?.data?.data?.isAnythingPending === false, []);
-
- /**
- * Create, or get, an export while detecting, or setting up, polling
- */
- return useCallback(
- ({ id, data } = {}) => {
- const updatedOptions = {};
-
- if (!isPolling) {
- updatedOptions.poll = {
- validate
- };
- }
-
- if (data) {
- return createExport(data, updatedOptions)(dispatch);
- }
-
- if (id) {
- return getExport(id)(dispatch);
- }
-
- if (isPolling === undefined) {
- return getExportStatus(updatedOptions)(dispatch);
- }
-
- return undefined;
- },
- [createExport, dispatch, getExport, getExportStatus, isPolling, validate]
- );
-};
-
-/**
- * On select update export.
+ * On select create/post an export.
*
* @param {object} options
* @param {Function} options.useExport
* @param {Function} options.useProduct
- * @param {Function} options.useProductInventoryQuery
+ * @param {Function} options.useProductExportQuery
* @returns {Function}
*/
const useOnSelect = ({
useExport: useAliasExport = useExport,
useProduct: useAliasProduct = useProduct,
- useProductInventoryQuery: useAliasProductInventoryQuery = useProductInventoryHostsQuery
+ useProductExportQuery: useAliasProductExportQuery = useProductExportQuery
} = {}) => {
- const createExport = useAliasExport();
const { productId } = useAliasProduct();
- const inventoryQuery = useAliasProductInventoryQuery();
+ const exportQuery = useAliasProductExportQuery();
+ const createExport = useAliasExport();
return ({ value = null } = {}) => {
const sources = [
{
- application: APP_TYPES.SUBSCRIPTIONS,
- resource: RESOURCE_TYPES.SUBSCRIPTIONS,
- filters: {
- ...inventoryQuery,
- productId
+ [SOURCE_TYPES.APPLICATION]: APP_TYPES.SUBSCRIPTIONS,
+ [SOURCE_TYPES.RESOURCE]: RESOURCE_TYPES.SUBSCRIPTIONS,
+ [SOURCE_TYPES.FILTERS]: {
+ ...exportQuery
}
}
];
- const data = { format: value, name: `${EXPORT_PREFIX}-${productId}`, sources };
- createExport({ data });
+ createExport(productId, {
+ [POST_TYPES.EXPIRES_AT]: dateHelpers
+ .setMillisecondsFromDate({
+ ms: helpers.CONFIG_EXPORT_EXPIRE
+ })
+ .toISOString(),
+ [POST_TYPES.FORMAT]: value,
+ [POST_TYPES.NAME]: `${helpers.CONFIG_EXPORT_SERVICE_NAME_PREFIX}-${productId}`,
+ [POST_TYPES.SOURCES]: sources
+ });
};
};
/**
- * Display an export/download field with options.
+ * Display an export/download field with options. Check and download available exports.
*
* @fires onSelect
* @param {object} props
* @param {Array} props.options
* @param {string} props.position
* @param {Function} props.t
- * @param {Function} props.useExport
+ * @param {Function} props.useExistingExports
* @param {Function} props.useExportStatus
* @param {Function} props.useOnSelect
* @returns {React.ReactNode}
@@ -184,30 +91,32 @@ const ToolbarFieldExport = ({
options,
position,
t,
- useExport: useAliasExport,
+ useExistingExports: useAliasExistingExports,
useExportStatus: useAliasExportStatus,
useOnSelect: useAliasOnSelect
}) => {
- const { isProductPolling, productPollingFormats = [] } = useAliasExportStatus();
- const checkExport = useAliasExport();
+ const { isProductPending, pendingProductFormats = [] } = useAliasExportStatus();
const onSelect = useAliasOnSelect();
const updatedOptions = options.map(option => ({
...option,
title:
- (isProductPolling &&
- productPollingFormats?.includes(option.value) &&
+ (((isProductPending && !pendingProductFormats?.length) ||
+ (isProductPending && pendingProductFormats?.includes(option.value))) &&
t('curiosity-toolbar.label', { context: ['export', 'loading'] })) ||
option.title,
- selected: isProductPolling && productPollingFormats?.includes(option.value),
- isDisabled: isProductPolling && productPollingFormats?.includes(option.value)
+ selected:
+ (isProductPending && !pendingProductFormats?.length) ||
+ (isProductPending && pendingProductFormats?.includes(option.value)),
+ isDisabled:
+ (isProductPending && !pendingProductFormats?.length) ||
+ (isProductPending && pendingProductFormats?.includes(option.value))
}));
- useMount(() => {
- checkExport();
- });
+ useAliasExistingExports();
return (
{
+ const { productId } = useAliasProduct();
+ const dispatch = useAliasDispatch();
+
+ return useCallback(
+ successResponse => {
+ const { completed = [], isCompleted, pending = [] } = successResponse?.data?.data?.products?.[productId] || {};
+ const isPending = !isCompleted;
+
+ if (isCompleted) {
+ addAliasNotification({
+ variant: 'success',
+ id: `swatch-create-export-${productId}`,
+ title: t('curiosity-toolbar.notifications', {
+ context: ['export', 'completed', 'title']
+ }),
+ description: t('curiosity-toolbar.notifications', {
+ context: ['export', 'completed', 'description'],
+ fileName: completed?.[0]?.fileName
+ }),
+ dismissable: true
+ })(dispatch);
+ }
+
+ dispatch({
+ type: reduxTypes.platform.SET_PLATFORM_EXPORT_STATUS,
+ id: productId,
+ isPending,
+ pending
+ });
+ },
+ [addAliasNotification, dispatch, productId, t]
+ );
+};
+
+/**
+ * Apply an export hook for an export post. The service automatically sets up polling, then force downloads the file.
+ *
+ * @param {object} options
+ * @param {Function} options.createExport
+ * @param {Function} options.t
+ * @param {Function} options.useDispatch
+ * @param {Function} options.useExportConfirmation
+ * @returns {Function}
+ */
+const useExport = ({
+ createExport: createAliasExport = reduxActions.platform.createExport,
+ t = translate,
+ useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch,
+ useExportConfirmation: useAliasExportConfirmation = useExportConfirmation
+} = {}) => {
+ const statusConfirmation = useAliasExportConfirmation();
+ const dispatch = useAliasDispatch();
+
+ return useCallback(
+ (id, data) => {
+ dispatch({
+ type: reduxTypes.platform.SET_PLATFORM_EXPORT_STATUS,
+ id,
+ isPending: true
+ });
+
+ createAliasExport(
+ id,
+ data,
+ { poll: { status: statusConfirmation } },
+ {
+ rejected: {
+ variant: 'warning',
+ title: t('curiosity-toolbar.notifications', {
+ context: ['export', 'error', 'title']
+ }),
+ description: t('curiosity-toolbar.notifications', {
+ context: ['export', 'error', 'description']
+ }),
+ dismissable: true
+ },
+ pending: {
+ variant: 'info',
+ id: `swatch-create-export-${id}`,
+ title: t('curiosity-toolbar.notifications', {
+ context: ['export', 'pending', 'title', id]
+ }),
+ dismissable: true
+ }
+ }
+ )(dispatch);
+ },
+ [createAliasExport, dispatch, statusConfirmation, t]
+ );
+};
+
+/**
+ * User confirmation results when existing exports are detected.
+ *
+ * @param {object} options
+ * @param {Function} options.deleteExistingExports
+ * @param {Function} options.getExistingExports
+ * @param {Function} options.removeNotification
+ * @param {Function} options.t
+ * @param {Function} options.useDispatch
+ * @returns {Function}
+ */
+const useExistingExportsConfirmation = ({
+ deleteExistingExports: deleteAliasExistingExports = reduxActions.platform.deleteExistingExports,
+ getExistingExports: getAliasExistingExports = reduxActions.platform.getExistingExports,
+ removeNotification: removeAliasNotification = reduxActions.platform.removeNotification,
+ t = translate,
+ useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch
+} = {}) => {
+ const dispatch = useAliasDispatch();
+
+ return useCallback(
+ (confirmation, allResults) => {
+ dispatch(removeAliasNotification('swatch-exports-status'));
+
+ if (confirmation === 'no') {
+ return deleteAliasExistingExports(allResults, {
+ rejected: {
+ variant: 'warning',
+ title: t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] }),
+ description: t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] }),
+ dismissable: true
+ }
+ })(dispatch);
+ }
+
+ return getAliasExistingExports(allResults, {
+ rejected: {
+ variant: 'warning',
+ title: t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] }),
+ description: t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] }),
+ dismissable: true
+ },
+ pending: {
+ variant: 'info',
+ title: t('curiosity-toolbar.notifications', { context: ['export', 'pending', 'titleGlobal'] }),
+ dismissable: true
+ },
+ fulfilled: {
+ variant: 'success',
+ title: t('curiosity-toolbar.notifications', {
+ context: ['export', 'completed', 'titleGlobal'],
+ count: allResults.length
+ }),
+ description: t('curiosity-toolbar.notifications', {
+ context: ['export', 'completed', 'descriptionGlobal'],
+ count: allResults.length
+ }),
+ dismissable: true
+ }
+ })(dispatch);
+ },
+ [dispatch, deleteAliasExistingExports, getAliasExistingExports, removeAliasNotification, t]
+ );
+};
+
+/**
+ * Apply an existing exports hook for user abandoned reports. Allow bulk polling status with download.
+ *
+ * @param {object} options
+ * @param {Function} options.addNotification
+ * @param {Function} options.getExistingExportsStatus
+ * @param {Function} options.t
+ * @param {Function} options.useDispatch
+ * @param {Function} options.useExistingExportsConfirmation
+ * @param {Function} options.useSelectorsResponse
+ */
+const useExistingExports = ({
+ addNotification: addAliasNotification = reduxActions.platform.addNotification,
+ getExistingExportsStatus: getAliasExistingExportsStatus = reduxActions.platform.getExistingExportsStatus,
+ t = translate,
+ useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch,
+ useExistingExportsConfirmation: useAliasExistingExportsConfirmation = useExistingExportsConfirmation,
+ useSelectorsResponse: useAliasSelectorsResponse = storeHooks.reactRedux.useSelectorsResponse
+} = {}) => {
+ const [isConfirmation, setIsConfirmation] = useState(false);
+ const dispatch = useAliasDispatch();
+ const onConfirmation = useAliasExistingExportsConfirmation();
+ const { data, fulfilled } = useAliasSelectorsResponse(({ app }) => app?.exportsExisting);
+ const { completed = [], isAnythingPending, isAnythingCompleted, pending = [] } = data?.[0]?.data || {};
+
+ useMount(() => {
+ if (!isConfirmation) {
+ getAliasExistingExportsStatus({
+ rejected: {
+ variant: 'warning',
+ title: t('curiosity-toolbar.notifications', { context: ['export', 'error', 'title'] }),
+ description: t('curiosity-toolbar.notifications', { context: ['export', 'error', 'description'] }),
+ dismissable: true
+ }
+ })(dispatch);
+ }
+ });
+
+ useEffect(() => {
+ if (!fulfilled || isConfirmation) {
+ return;
+ }
+
+ const isAnythingAvailable = isAnythingPending || isAnythingCompleted || false;
+ const totalResults = completed.length + pending.length;
+
+ if (isAnythingAvailable && totalResults) {
+ addAliasNotification({
+ id: `swatch-exports-status`,
+ title: t('curiosity-toolbar.notifications', {
+ context: ['export', 'completed', 'title', 'existing'],
+ count: totalResults
+ }),
+ description: (
+
+ {t('curiosity-toolbar.notifications', {
+ context: [
+ 'export',
+ 'completed',
+ 'description',
+ 'existing',
+ completed.length && 'completed',
+ pending.length && 'pending'
+ ],
+ count: totalResults,
+ completed: completed.length,
+ pending: pending.length
+ })}
+
+ onConfirmation('yes', [...completed, ...pending])}
+ autoFocus
+ >
+ {t('curiosity-toolbar.button', { context: 'yes' })}
+ {' '}
+ onConfirmation('no', [...completed, ...pending])}
+ >
+ {t('curiosity-toolbar.button', { context: 'no' })}
+
+
+
+ ),
+ autoDismiss: false,
+ dismissable: false
+ })(dispatch);
+
+ setIsConfirmation(true);
+ }
+ }, [
+ addAliasNotification,
+ completed,
+ dispatch,
+ fulfilled,
+ isAnythingCompleted,
+ isAnythingPending,
+ isConfirmation,
+ onConfirmation,
+ pending,
+ t
+ ]);
+};
+
+/**
+ * Aggregated export status
+ *
+ * @param {object} options
+ * @param {Function} options.useProduct
+ * @param {Function} options.useSelector
+ * @returns {{isProductPending: boolean, productPendingFormats: Array}}
+ */
+const useExportStatus = ({
+ useProduct: useAliasProduct = useProduct,
+ useSelector: useAliasSelector = storeHooks.reactRedux.useSelector
+} = {}) => {
+ const { productId } = useAliasProduct();
+ const { isPending, pending } = useAliasSelector(({ app }) => app?.exports?.[productId], {});
+
+ const pendingProductFormats = [];
+ const isProductPending = isPending || false;
+
+ if (isProductPending && Array.isArray(pending)) {
+ pendingProductFormats.push(...pending.map(({ format: productFormat }) => productFormat));
+ }
+
+ return {
+ isProductPending,
+ pendingProductFormats
+ };
+};
+
+const context = {
+ useExport,
+ useExportConfirmation,
+ useExportStatus,
+ useExistingExports,
+ useExistingExportsConfirmation
+};
+
+export {
+ context as default,
+ context,
+ useExport,
+ useExportConfirmation,
+ useExportStatus,
+ useExistingExports,
+ useExistingExportsConfirmation
+};
diff --git a/src/components/toolbar/toolbarFieldSelectCategory.js b/src/components/toolbar/toolbarFieldSelectCategory.js
index 79ac4e1a5..7e94f812b 100644
--- a/src/components/toolbar/toolbarFieldSelectCategory.js
+++ b/src/components/toolbar/toolbarFieldSelectCategory.js
@@ -13,6 +13,7 @@ import {
} from './toolbarFieldBillingProvider';
import { ToolbarFieldCategory } from './toolbarFieldCategory';
import { ToolbarFieldDisplayName } from './toolbarFieldDisplayName';
+import { ToolbarFieldExport } from './toolbarFieldExport';
import { ToolbarFieldGranularity, toolbarFieldOptions as granularityOptions } from './toolbarFieldGranularity';
import { ToolbarFieldRangedMonthly, toolbarFieldOptions as rangedMonthlyOptions } from './toolbarFieldRangedMonthly';
import { ToolbarFieldSla, toolbarFieldOptions as slaOptions } from './toolbarFieldSla';
@@ -92,6 +93,15 @@ const toolbarFieldOptions = [
},
options: null,
isClearable: true
+ },
+ {
+ title: translate('curiosity-toolbar.label', { context: ['filter', 'export'] }),
+ value: 'export',
+ component: function Export(props) {
+ return ;
+ },
+ options: [],
+ isClearable: false
}
].map(option => ({
...option,
diff --git a/src/redux/README.md b/src/redux/README.md
index 94df37d08..67f7c1ca9 100644
--- a/src/redux/README.md
+++ b/src/redux/README.md
@@ -65,10 +65,10 @@ Platform service wrappers for dispatch, state update.
* [~removeNotification(id)](#Actions.module_PlatformActions..removeNotification) ⇒ \*
* [~clearNotifications()](#Actions.module_PlatformActions..clearNotifications) ⇒ \*
* [~authorizeUser(appName)](#Actions.module_PlatformActions..authorizeUser) ⇒ function
- * [~getExport(id)](#Actions.module_PlatformActions..getExport) ⇒ function
- * [~setExportStatus(dispatch)](#Actions.module_PlatformActions..setExportStatus) ⇒ function
- * [~getExportStatus(options)](#Actions.module_PlatformActions..getExportStatus) ⇒ function
- * [~createExport(data, options)](#Actions.module_PlatformActions..createExport) ⇒ function
+ * [~getExistingExports(existingExports, notifications)](#Actions.module_PlatformActions..getExistingExports) ⇒ function
+ * [~deleteExistingExports(existingExports, notifications)](#Actions.module_PlatformActions..deleteExistingExports) ⇒ function
+ * [~getExistingExportsStatus(notifications)](#Actions.module_PlatformActions..getExistingExportsStatus) ⇒ function
+ * [~createExport(id, data, options, notifications)](#Actions.module_PlatformActions..createExport) ⇒ function
* [~hideGlobalFilter(isHidden)](#Actions.module_PlatformActions..hideGlobalFilter) ⇒ Object
@@ -131,28 +131,31 @@ Get an emulated and combined API response from the platforms "getUser" and "getU
-
+
-### PlatformActions~getExport(id) ⇒ function
-Get a specific export download package.
+### PlatformActions~getExistingExports(existingExports, notifications) ⇒ function
+Get all existing exports, if pending poll, and when complete download. Includes toast notifications.
**Kind**: inner method of [PlatformActions
](#Actions.module_PlatformActions)
- Param Type
+ Param Type Description
- id string
+ existingExports Array
+
+ notifications object
Apply notification options
+
-
+
-### PlatformActions~setExportStatus(dispatch) ⇒ function
-Return a "dispatch ready" export poll status check.
+### PlatformActions~deleteExistingExports(existingExports, notifications) ⇒ function
+Delete all existing exports. Includes toast notifications.
**Kind**: inner method of [PlatformActions
](#Actions.module_PlatformActions)
@@ -163,33 +166,35 @@ Return a "dispatch ready" export poll status check.
- dispatch function
+ existingExports Array.<{id: string}>
+
+ notifications object
-
+
-### PlatformActions~getExportStatus(options) ⇒ function
-Get a specific, or all, export status.
+### PlatformActions~getExistingExportsStatus(notifications) ⇒ function
+Get a status from any existing exports. Display a confirmation for downloading, or ignoring, the exports.
+Includes toast notifications.
**Kind**: inner method of [PlatformActions
](#Actions.module_PlatformActions)
- Param Type Description
+ Param Type
- options object
Apply polling options
-
+ notifications object
-### PlatformActions~createExport(data, options) ⇒ function
-Create an export for download.
+### PlatformActions~createExport(id, data, options, notifications) ⇒ function
+Create an export for download. Includes toast notifications.
**Kind**: inner method of [PlatformActions
](#Actions.module_PlatformActions)
@@ -200,10 +205,14 @@ Create an export for download.
+ id string
+
data object
options object
Apply polling options
+
+ notifications object
diff --git a/src/redux/actions/__tests__/__snapshots__/platformActions.test.js.snap b/src/redux/actions/__tests__/__snapshots__/platformActions.test.js.snap
index b8e2f2f71..b586d9b1e 100644
--- a/src/redux/actions/__tests__/__snapshots__/platformActions.test.js.snap
+++ b/src/redux/actions/__tests__/__snapshots__/platformActions.test.js.snap
@@ -7,23 +7,58 @@ exports[`PlatformActions Should return a dispatch object for the hideGlobalFilte
}
`;
-exports[`PlatformActions Should return response content for getExport method: dispatch object 1`] = `
+exports[`PlatformActions Should return response content for createExport method: dispatch object 1`] = `
[
[
{
+ "meta": {
+ "id": undefined,
+ "notifications": {},
+ },
"payload": Promise {},
- "type": "GET_PLATFORM_EXPORT",
+ "type": "SET_PLATFORM_EXPORT_CREATE",
},
],
]
`;
-exports[`PlatformActions Should return response content for setExportStatus method: dispatch object 1`] = `
+exports[`PlatformActions Should return response content for getExistingExports method: dispatch object 1`] = `
[
[
{
+ "meta": {
+ "notifications": {},
+ },
"payload": Promise {},
- "type": "SET_PLATFORM_EXPORT_STATUS",
+ "type": "GET_PLATFORM_EXPORT_EXISTING",
+ },
+ ],
+]
+`;
+
+exports[`PlatformActions Should return response content for getExistingExportsStatus method: dispatch object 1`] = `
+[
+ [
+ {
+ "meta": {
+ "notifications": undefined,
+ },
+ "payload": Promise {},
+ "type": "SET_PLATFORM_EXPORT_EXISTING_STATUS",
+ },
+ ],
+]
+`;
+
+exports[`PlatformActions Should return response content for removeExistingExports method: dispatch object 1`] = `
+[
+ [
+ {
+ "meta": {
+ "notifications": undefined,
+ },
+ "payload": Promise {},
+ "type": "DELETE_PLATFORM_EXPORT_EXISTING",
},
],
]
diff --git a/src/redux/actions/__tests__/platformActions.test.js b/src/redux/actions/__tests__/platformActions.test.js
index 36e571828..6269e9edc 100644
--- a/src/redux/actions/__tests__/platformActions.test.js
+++ b/src/redux/actions/__tests__/platformActions.test.js
@@ -1,7 +1,7 @@
import { applyMiddleware, combineReducers, legacy_createStore as createStore } from 'redux';
import moxios from 'moxios';
import { promiseMiddleware } from '../../middleware';
-import { platformActions, setExportStatus } from '../platformActions';
+import { platformActions } from '../platformActions';
import { appReducer } from '../../reducers';
describe('PlatformActions', () => {
@@ -43,41 +43,33 @@ describe('PlatformActions', () => {
});
});
- it('Should return response content for createExport method', done => {
- const store = generateStore();
- const dispatcher = platformActions.createExport();
-
- dispatcher(store.dispatch).then(() => {
- const response = store.getState().app;
- expect(response.exports.fulfilled).toBe(true);
- done();
- });
- });
-
- it('Should return response content for getExport method', () => {
+ it('Should return response content for createExport method', () => {
const mockDispatch = jest.fn();
- platformActions.getExport()(mockDispatch);
+ platformActions.createExport(undefined, undefined, {
+ poll: { location: undefined, status: undefined, validate: undefined }
+ })(mockDispatch);
expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch object');
});
- it('Should return response content for setExportStatus method', () => {
+ it('Should return response content for getExistingExports method', () => {
const mockDispatch = jest.fn();
- setExportStatus(mockDispatch)();
+ platformActions.getExistingExports([])(mockDispatch);
expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch object');
});
- it('Should return response content for getExportStatus method', done => {
- const store = generateStore();
- const dispatcher = platformActions.getExportStatus();
-
- dispatcher(store.dispatch).then(() => {
- const response = store.getState().app;
- expect(response.exports.fulfilled).toBe(true);
- done();
- });
+ it('Should return response content for getExistingExportsStatus method', () => {
+ const mockDispatch = jest.fn();
+ platformActions.getExistingExportsStatus()(mockDispatch);
+ expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch object');
});
it('Should return a dispatch object for the hideGlobalFilter method', () => {
expect(platformActions.hideGlobalFilter()).toMatchSnapshot('dispatch object');
});
+
+ it('Should return response content for removeExistingExports method', () => {
+ const mockDispatch = jest.fn();
+ platformActions.deleteExistingExports([])(mockDispatch);
+ expect(mockDispatch.mock.calls).toMatchSnapshot('dispatch object');
+ });
});
diff --git a/src/redux/actions/platformActions.js b/src/redux/actions/platformActions.js
index 5bb2492c0..7a83d65eb 100644
--- a/src/redux/actions/platformActions.js
+++ b/src/redux/actions/platformActions.js
@@ -19,7 +19,12 @@ import { platformServices } from '../../services/platform/platformServices';
* @param {object} data
* @returns {*}
*/
-const addNotification = data => RcsAddNotification(data);
+const addNotification = data => dispatch => {
+ if (data.id) {
+ dispatch(RcsRemoveNotification(data.id));
+ }
+ return dispatch(RcsAddNotification(data));
+};
/**
* Remove a platform plugin toast notification.
@@ -27,14 +32,14 @@ const addNotification = data => RcsAddNotification(data);
* @param {string} id
* @returns {*}
*/
-const removeNotification = id => RcsRemoveNotification(id);
+const removeNotification = id => dispatch => dispatch(RcsRemoveNotification(id));
/**
* Clear all platform plugin toast notifications.
*
* @returns {*}
*/
-const clearNotifications = () => RcsClearNotifications();
+const clearNotifications = () => dispatch => dispatch(RcsClearNotifications());
/**
* Get an emulated and combined API response from the platforms "getUser" and "getUserPermissions" global methods.
@@ -49,64 +54,74 @@ const authorizeUser = appName => dispatch =>
});
/**
- * Get a specific export download package.
+ * Get all existing exports, if pending poll, and when complete download. Includes toast notifications.
*
- * @param {string} id
+ * @param {Array} existingExports
+ * @param {object} notifications Apply notification options
* @returns {Function}
*/
-const getExport = id => dispatch =>
- dispatch({
- type: platformTypes.GET_PLATFORM_EXPORT,
- payload: platformServices.getExport(id)
- });
+const getExistingExports =
+ (existingExports, notifications = {}) =>
+ dispatch =>
+ dispatch({
+ type: platformTypes.GET_PLATFORM_EXPORT_EXISTING,
+ payload: platformServices.getExistingExports(existingExports),
+ meta: {
+ notifications
+ }
+ });
/**
- * Return a "dispatch ready" export poll status check.
+ * Delete all existing exports. Includes toast notifications.
*
- * @param {Function} dispatch
+ * @param {Array<{ id: string }>} existingExports
+ * @param {object} notifications
* @returns {Function}
*/
-const setExportStatus =
- dispatch =>
- (success = {}, error) =>
- dispatch({
- type: platformTypes.SET_PLATFORM_EXPORT_STATUS,
- payload: (error && Promise.reject(error)) || Promise.resolve(success)
- });
+const deleteExistingExports = (existingExports, notifications) => dispatch =>
+ dispatch({
+ type: platformTypes.DELETE_PLATFORM_EXPORT_EXISTING,
+ payload: Promise.all(existingExports.map(({ id }) => platformServices.deleteExport(id))),
+ meta: {
+ notifications
+ }
+ });
/**
- * Get a specific, or all, export status.
+ * Get a status from any existing exports. Display a confirmation for downloading, or ignoring, the exports.
+ * Includes toast notifications.
*
- * @param {object} options Apply polling options
+ * @param {object} notifications
* @returns {Function}
*/
-const getExportStatus =
- (options = {}) =>
- dispatch =>
- dispatch({
- type: platformTypes.SET_PLATFORM_EXPORT_STATUS,
- payload: platformServices.getExportStatus(undefined, undefined, {
- ...options,
- poll: { ...options.poll, status: setExportStatus(dispatch) }
- })
- });
+const getExistingExportsStatus = notifications => dispatch =>
+ dispatch({
+ type: platformTypes.SET_PLATFORM_EXPORT_EXISTING_STATUS,
+ payload: platformServices.getExistingExportsStatus(),
+ meta: {
+ notifications
+ }
+ });
/**
- * Create an export for download.
+ * Create an export for download. Includes toast notifications.
*
+ * @param {string} id
* @param {object} data
* @param {object} options Apply polling options
+ * @param {object} notifications
* @returns {Function}
*/
const createExport =
- (data = {}, options = {}) =>
+ (id, data = {}, options = {}, notifications = {}) =>
dispatch =>
dispatch({
- type: platformTypes.SET_PLATFORM_EXPORT_STATUS,
- payload: platformServices.postExport(data, {
- ...options,
- poll: { ...options.poll, status: setExportStatus(dispatch) }
- })
+ type: platformTypes.SET_PLATFORM_EXPORT_CREATE,
+ payload: platformServices.postExport(data, options),
+ meta: {
+ id,
+ notifications
+ }
});
/**
@@ -126,9 +141,9 @@ const platformActions = {
clearNotifications,
authorizeUser,
createExport,
- getExport,
- setExportStatus,
- getExportStatus,
+ deleteExistingExports,
+ getExistingExports,
+ getExistingExportsStatus,
hideGlobalFilter
};
@@ -140,8 +155,8 @@ export {
clearNotifications,
authorizeUser,
createExport,
- getExport,
- setExportStatus,
- getExportStatus,
+ deleteExistingExports,
+ getExistingExports,
+ getExistingExportsStatus,
hideGlobalFilter
};
diff --git a/src/redux/reducers/__tests__/__snapshots__/appReducer.test.js.snap b/src/redux/reducers/__tests__/__snapshots__/appReducer.test.js.snap
index caf968bda..9aa6f5cc6 100644
--- a/src/redux/reducers/__tests__/__snapshots__/appReducer.test.js.snap
+++ b/src/redux/reducers/__tests__/__snapshots__/appReducer.test.js.snap
@@ -6,6 +6,7 @@ exports[`UserReducer should handle all defined error types: rejected types DELET
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"error": true,
@@ -26,6 +27,7 @@ exports[`UserReducer should handle all defined error types: rejected types GET_U
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"error": true,
@@ -53,6 +55,7 @@ exports[`UserReducer should handle all defined error types: rejected types PLATF
},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {},
},
@@ -60,12 +63,13 @@ exports[`UserReducer should handle all defined error types: rejected types PLATF
}
`;
-exports[`UserReducer should handle all defined error types: rejected types SET_PLATFORM_EXPORT_STATUS 1`] = `
+exports[`UserReducer should handle all defined error types: rejected types SET_PLATFORM_EXPORT_EXISTING_STATUS 1`] = `
{
"result": {
"auth": {},
"errors": {},
- "exports": {
+ "exports": {},
+ "exportsExisting": {
"error": true,
"errorMessage": "MESSAGE",
"fulfilled": false,
@@ -76,7 +80,7 @@ exports[`UserReducer should handle all defined error types: rejected types SET_P
"locale": {},
"optin": {},
},
- "type": "SET_PLATFORM_EXPORT_STATUS_REJECTED",
+ "type": "SET_PLATFORM_EXPORT_EXISTING_STATUS_REJECTED",
}
`;
@@ -86,6 +90,7 @@ exports[`UserReducer should handle all defined error types: rejected types UPDAT
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"error": true,
@@ -106,6 +111,7 @@ exports[`UserReducer should handle all defined error types: rejected types USER_
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {
"error": true,
"errorMessage": "MESSAGE",
@@ -126,6 +132,7 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"data": {
@@ -158,6 +165,7 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"data": {
@@ -209,6 +217,7 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {},
},
@@ -216,12 +225,13 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
}
`;
-exports[`UserReducer should handle all defined fulfilled types: fulfilled types SET_PLATFORM_EXPORT_STATUS 1`] = `
+exports[`UserReducer should handle all defined fulfilled types: fulfilled types SET_PLATFORM_EXPORT_EXISTING_STATUS 1`] = `
{
"result": {
"auth": {},
"errors": {},
- "exports": {
+ "exports": {},
+ "exportsExisting": {
"data": {
"permissions": [],
"test": "success",
@@ -244,7 +254,7 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
"locale": {},
"optin": {},
},
- "type": "SET_PLATFORM_EXPORT_STATUS_FULFILLED",
+ "type": "SET_PLATFORM_EXPORT_EXISTING_STATUS_FULFILLED",
}
`;
@@ -254,6 +264,7 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"data": {
@@ -286,6 +297,7 @@ exports[`UserReducer should handle all defined fulfilled types: fulfilled types
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {
"data": {
"permissions": [],
@@ -318,6 +330,7 @@ exports[`UserReducer should handle all defined pending types: pending types DELE
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"error": false,
@@ -337,6 +350,7 @@ exports[`UserReducer should handle all defined pending types: pending types GET_
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"error": false,
@@ -362,6 +376,7 @@ exports[`UserReducer should handle all defined pending types: pending types PLAT
},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {},
},
@@ -369,12 +384,13 @@ exports[`UserReducer should handle all defined pending types: pending types PLAT
}
`;
-exports[`UserReducer should handle all defined pending types: pending types SET_PLATFORM_EXPORT_STATUS 1`] = `
+exports[`UserReducer should handle all defined pending types: pending types SET_PLATFORM_EXPORT_EXISTING_STATUS 1`] = `
{
"result": {
"auth": {},
"errors": {},
- "exports": {
+ "exports": {},
+ "exportsExisting": {
"error": false,
"errorMessage": "",
"fulfilled": false,
@@ -384,7 +400,7 @@ exports[`UserReducer should handle all defined pending types: pending types SET_
"locale": {},
"optin": {},
},
- "type": "SET_PLATFORM_EXPORT_STATUS_PENDING",
+ "type": "SET_PLATFORM_EXPORT_EXISTING_STATUS_PENDING",
}
`;
@@ -394,6 +410,7 @@ exports[`UserReducer should handle all defined pending types: pending types UPDA
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {
"error": false,
@@ -413,6 +430,7 @@ exports[`UserReducer should handle all defined pending types: pending types USER
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {
"error": false,
"errorMessage": "",
@@ -426,12 +444,32 @@ exports[`UserReducer should handle all defined pending types: pending types USER
}
`;
+exports[`UserReducer should handle specific defined types: defined type SET_PLATFORM_EXPORT_STATUS 1`] = `
+{
+ "result": {
+ "auth": {},
+ "errors": {},
+ "exports": {
+ "test_id": {
+ "isPending": true,
+ "pending": [],
+ },
+ },
+ "exportsExisting": {},
+ "locale": {},
+ "optin": {},
+ },
+ "type": "SET_PLATFORM_EXPORT_STATUS",
+}
+`;
+
exports[`UserReducer should handle specific http status types: http status 4XX 400 1`] = `
{
"result": {
"auth": {},
"errors": {},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {},
},
@@ -452,6 +490,7 @@ exports[`UserReducer should handle specific http status types: http status 4XX 4
"status": 401,
},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {},
},
@@ -472,6 +511,7 @@ exports[`UserReducer should handle specific http status types: http status 4XX 4
"status": 403,
},
"exports": {},
+ "exportsExisting": {},
"locale": {},
"optin": {},
},
diff --git a/src/redux/reducers/__tests__/appReducer.test.js b/src/redux/reducers/__tests__/appReducer.test.js
index 6038396ea..444391841 100644
--- a/src/redux/reducers/__tests__/appReducer.test.js
+++ b/src/redux/reducers/__tests__/appReducer.test.js
@@ -43,10 +43,31 @@ describe('UserReducer', () => {
});
});
+ it('should handle specific defined types', () => {
+ const specificTypes = [platformTypes.SET_PLATFORM_EXPORT_STATUS];
+
+ specificTypes.forEach(value => {
+ if (!value) {
+ return;
+ }
+
+ const dispatched = {
+ type: value,
+ id: 'test_id',
+ isPending: true,
+ pending: []
+ };
+
+ const resultState = appReducer(undefined, dispatched);
+
+ expect({ type: value, result: resultState }).toMatchSnapshot(`defined type ${value}`);
+ });
+ });
+
it('should handle all defined error types', () => {
const specificTypes = [
platformTypes.PLATFORM_USER_AUTH,
- platformTypes.SET_PLATFORM_EXPORT_STATUS,
+ platformTypes.SET_PLATFORM_EXPORT_EXISTING_STATUS,
types.USER_LOCALE,
types.DELETE_USER_OPTIN,
types.GET_USER_OPTIN,
@@ -80,7 +101,7 @@ describe('UserReducer', () => {
it('should handle all defined pending types', () => {
const specificTypes = [
platformTypes.PLATFORM_USER_AUTH,
- platformTypes.SET_PLATFORM_EXPORT_STATUS,
+ platformTypes.SET_PLATFORM_EXPORT_EXISTING_STATUS,
types.USER_LOCALE,
types.DELETE_USER_OPTIN,
types.GET_USER_OPTIN,
@@ -103,7 +124,7 @@ describe('UserReducer', () => {
it('should handle all defined fulfilled types', () => {
const specificTypes = [
platformTypes.PLATFORM_USER_AUTH,
- platformTypes.SET_PLATFORM_EXPORT_STATUS,
+ platformTypes.SET_PLATFORM_EXPORT_EXISTING_STATUS,
types.USER_LOCALE,
types.DELETE_USER_OPTIN,
types.GET_USER_OPTIN,
diff --git a/src/redux/reducers/appReducer.js b/src/redux/reducers/appReducer.js
index 09e8daa97..6eed96eb7 100644
--- a/src/redux/reducers/appReducer.js
+++ b/src/redux/reducers/appReducer.js
@@ -14,12 +14,13 @@ import { reduxHelpers } from '../common';
* Initial state.
*
* @private
- * @type {{auth: {}, exports: {}, optin: {}, locale: {}, errors: {}}}
+ * @type {{auth: {}, exports: {}, exportsExisting: {}, optin: {}, locale: {}, errors: {}}}
*/
const initialState = {
auth: {},
errors: {},
exports: {},
+ exportsExisting: {},
locale: {},
optin: {}
};
@@ -55,13 +56,26 @@ const appReducer = (state = initialState, action) => {
}
return state;
-
+ case platformTypes.SET_PLATFORM_EXPORT_STATUS:
+ return reduxHelpers.setStateProp(
+ 'exports',
+ {
+ [action.id]: {
+ isPending: action.isPending,
+ pending: action.pending
+ }
+ },
+ {
+ state,
+ initialState
+ }
+ );
default:
return reduxHelpers.generatedPromiseActionReducer(
[
{ ref: 'locale', type: appTypes.USER_LOCALE },
{ ref: 'optin', type: [appTypes.DELETE_USER_OPTIN, appTypes.GET_USER_OPTIN, appTypes.UPDATE_USER_OPTIN] },
- { ref: 'exports', type: platformTypes.SET_PLATFORM_EXPORT_STATUS },
+ { ref: 'exportsExisting', type: platformTypes.SET_PLATFORM_EXPORT_EXISTING_STATUS },
{ ref: 'auth', type: platformTypes.PLATFORM_USER_AUTH }
],
state,
diff --git a/src/redux/types/__tests__/__snapshots__/index.test.js.snap b/src/redux/types/__tests__/__snapshots__/index.test.js.snap
index 7d4a7792e..a56c47b25 100644
--- a/src/redux/types/__tests__/__snapshots__/index.test.js.snap
+++ b/src/redux/types/__tests__/__snapshots__/index.test.js.snap
@@ -36,12 +36,15 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] =
"SET_BANNER_MESSAGES": "SET_BANNER_MESSAGES",
},
"platform": {
- "GET_PLATFORM_EXPORT": "GET_PLATFORM_EXPORT",
+ "DELETE_PLATFORM_EXPORT_EXISTING": "DELETE_PLATFORM_EXPORT_EXISTING",
+ "GET_PLATFORM_EXPORT_EXISTING": "GET_PLATFORM_EXPORT_EXISTING",
"PLATFORM_ADD_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/ADD_NOTIFICATION",
"PLATFORM_CLEAR_NOTIFICATIONS": "@@INSIGHTS-CORE/NOTIFICATIONS/CLEAR_NOTIFICATIONS",
"PLATFORM_GLOBAL_FILTER_HIDE": "PLATFORM_GLOBAL_FILTER_HIDE",
"PLATFORM_REMOVE_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/REMOVE_NOTIFICATION",
"PLATFORM_USER_AUTH": "PLATFORM_USER_AUTH",
+ "SET_PLATFORM_EXPORT_CREATE": "SET_PLATFORM_EXPORT_CREATE",
+ "SET_PLATFORM_EXPORT_EXISTING_STATUS": "SET_PLATFORM_EXPORT_EXISTING_STATUS",
"SET_PLATFORM_EXPORT_STATUS": "SET_PLATFORM_EXPORT_STATUS",
},
"query": {
@@ -78,12 +81,15 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] =
"SET_BANNER_MESSAGES": "SET_BANNER_MESSAGES",
},
"platformTypes": {
- "GET_PLATFORM_EXPORT": "GET_PLATFORM_EXPORT",
+ "DELETE_PLATFORM_EXPORT_EXISTING": "DELETE_PLATFORM_EXPORT_EXISTING",
+ "GET_PLATFORM_EXPORT_EXISTING": "GET_PLATFORM_EXPORT_EXISTING",
"PLATFORM_ADD_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/ADD_NOTIFICATION",
"PLATFORM_CLEAR_NOTIFICATIONS": "@@INSIGHTS-CORE/NOTIFICATIONS/CLEAR_NOTIFICATIONS",
"PLATFORM_GLOBAL_FILTER_HIDE": "PLATFORM_GLOBAL_FILTER_HIDE",
"PLATFORM_REMOVE_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/REMOVE_NOTIFICATION",
"PLATFORM_USER_AUTH": "PLATFORM_USER_AUTH",
+ "SET_PLATFORM_EXPORT_CREATE": "SET_PLATFORM_EXPORT_CREATE",
+ "SET_PLATFORM_EXPORT_EXISTING_STATUS": "SET_PLATFORM_EXPORT_EXISTING_STATUS",
"SET_PLATFORM_EXPORT_STATUS": "SET_PLATFORM_EXPORT_STATUS",
},
"queryTypes": {
@@ -120,12 +126,15 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] =
"SET_BANNER_MESSAGES": "SET_BANNER_MESSAGES",
},
"platform": {
- "GET_PLATFORM_EXPORT": "GET_PLATFORM_EXPORT",
+ "DELETE_PLATFORM_EXPORT_EXISTING": "DELETE_PLATFORM_EXPORT_EXISTING",
+ "GET_PLATFORM_EXPORT_EXISTING": "GET_PLATFORM_EXPORT_EXISTING",
"PLATFORM_ADD_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/ADD_NOTIFICATION",
"PLATFORM_CLEAR_NOTIFICATIONS": "@@INSIGHTS-CORE/NOTIFICATIONS/CLEAR_NOTIFICATIONS",
"PLATFORM_GLOBAL_FILTER_HIDE": "PLATFORM_GLOBAL_FILTER_HIDE",
"PLATFORM_REMOVE_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/REMOVE_NOTIFICATION",
"PLATFORM_USER_AUTH": "PLATFORM_USER_AUTH",
+ "SET_PLATFORM_EXPORT_CREATE": "SET_PLATFORM_EXPORT_CREATE",
+ "SET_PLATFORM_EXPORT_EXISTING_STATUS": "SET_PLATFORM_EXPORT_EXISTING_STATUS",
"SET_PLATFORM_EXPORT_STATUS": "SET_PLATFORM_EXPORT_STATUS",
},
"query": {
@@ -189,12 +198,15 @@ exports[`ReduxTypes should have specific type properties: specific types 1`] = `
"SET_BANNER_MESSAGES": "SET_BANNER_MESSAGES",
},
"platform": {
- "GET_PLATFORM_EXPORT": "GET_PLATFORM_EXPORT",
+ "DELETE_PLATFORM_EXPORT_EXISTING": "DELETE_PLATFORM_EXPORT_EXISTING",
+ "GET_PLATFORM_EXPORT_EXISTING": "GET_PLATFORM_EXPORT_EXISTING",
"PLATFORM_ADD_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/ADD_NOTIFICATION",
"PLATFORM_CLEAR_NOTIFICATIONS": "@@INSIGHTS-CORE/NOTIFICATIONS/CLEAR_NOTIFICATIONS",
"PLATFORM_GLOBAL_FILTER_HIDE": "PLATFORM_GLOBAL_FILTER_HIDE",
"PLATFORM_REMOVE_NOTIFICATION": "@@INSIGHTS-CORE/NOTIFICATIONS/REMOVE_NOTIFICATION",
"PLATFORM_USER_AUTH": "PLATFORM_USER_AUTH",
+ "SET_PLATFORM_EXPORT_CREATE": "SET_PLATFORM_EXPORT_CREATE",
+ "SET_PLATFORM_EXPORT_EXISTING_STATUS": "SET_PLATFORM_EXPORT_EXISTING_STATUS",
"SET_PLATFORM_EXPORT_STATUS": "SET_PLATFORM_EXPORT_STATUS",
},
"query": {
diff --git a/src/redux/types/index.js b/src/redux/types/index.js
index 329be66d9..8d7ea5202 100644
--- a/src/redux/types/index.js
+++ b/src/redux/types/index.js
@@ -59,9 +59,10 @@ const messageTypes = {
/**
* Platform action, reducer types.
*
- * @type {{PLATFORM_USER_AUTH: string, SET_PLATFORM_EXPORT_STATUS: string, PLATFORM_GLOBAL_FILTER_HIDE: string,
- * PLATFORM_CLEAR_NOTIFICATIONS: string, PLATFORM_ADD_NOTIFICATION: string, PLATFORM_REMOVE_NOTIFICATION: string,
- * GET_PLATFORM_EXPORT: string}}
+ * @type {{PLATFORM_USER_AUTH: string, DELETE_PLATFORM_EXPORT_EXISTING: string, SET_PLATFORM_EXPORT_EXISTING_STATUS:
+ * string, SET_PLATFORM_EXPORT_STATUS: string, PLATFORM_GLOBAL_FILTER_HIDE: string, PLATFORM_CLEAR_NOTIFICATIONS:
+ * string, GET_PLATFORM_EXPORT_EXISTING: string, PLATFORM_ADD_NOTIFICATION: string, PLATFORM_REMOVE_NOTIFICATION:
+ * string, SET_PLATFORM_EXPORT_CREATE: string}}
*/
const platformTypes = {
PLATFORM_ADD_NOTIFICATION: ADD_NOTIFICATION,
@@ -69,8 +70,11 @@ const platformTypes = {
PLATFORM_CLEAR_NOTIFICATIONS: CLEAR_NOTIFICATIONS,
PLATFORM_GLOBAL_FILTER_HIDE: 'PLATFORM_GLOBAL_FILTER_HIDE',
PLATFORM_USER_AUTH: 'PLATFORM_USER_AUTH',
- GET_PLATFORM_EXPORT: 'GET_PLATFORM_EXPORT',
- SET_PLATFORM_EXPORT_STATUS: 'SET_PLATFORM_EXPORT_STATUS'
+ DELETE_PLATFORM_EXPORT_EXISTING: 'DELETE_PLATFORM_EXPORT_EXISTING',
+ GET_PLATFORM_EXPORT_EXISTING: 'GET_PLATFORM_EXPORT_EXISTING',
+ SET_PLATFORM_EXPORT_EXISTING_STATUS: 'SET_PLATFORM_EXPORT_EXISTING_STATUS',
+ SET_PLATFORM_EXPORT_STATUS: 'SET_PLATFORM_EXPORT_STATUS',
+ SET_PLATFORM_EXPORT_CREATE: 'SET_PLATFORM_EXPORT_CREATE'
};
/**
diff --git a/src/services/README.md b/src/services/README.md
index b7712a8bc..4f7d16888 100644
--- a/src/services/README.md
+++ b/src/services/README.md
@@ -253,6 +253,7 @@ page or wait the "maxAge".
* [~PLATFORM_API_EXPORT_STATUS_TYPES](#Platform.module_PlatformConstants..PLATFORM_API_EXPORT_STATUS_TYPES) : Object
* [~PLATFORM_API_EXPORT_SOURCE_TYPES](#Platform.module_PlatformConstants..PLATFORM_API_EXPORT_SOURCE_TYPES) : Object
* [~PLATFORM_API_EXPORT_POST_TYPES](#Platform.module_PlatformConstants..PLATFORM_API_EXPORT_POST_TYPES) : Object
+ * [~PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES](#Platform.module_PlatformConstants..PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES) : Object
* [~PLATFORM_API_EXPORT_RESPONSE_TYPES](#Platform.module_PlatformConstants..PLATFORM_API_EXPORT_RESPONSE_TYPES) : Object
* [~PLATFORM_API_RESPONSE_USER_ENTITLEMENTS](#Platform.module_PlatformConstants..PLATFORM_API_RESPONSE_USER_ENTITLEMENTS) : string
* [~PLATFORM_API_RESPONSE_USER_ENTITLEMENTS_APP_TYPES](#Platform.module_PlatformConstants..PLATFORM_API_RESPONSE_USER_ENTITLEMENTS_APP_TYPES) : Object
@@ -314,6 +315,12 @@ Platform Export, available response, POST source types.
### PlatformConstants~PLATFORM\_API\_EXPORT\_POST\_TYPES : Object
Platform Export, available POST types.
+**Kind**: inner constant of [PlatformConstants
](#Platform.module_PlatformConstants)
+
+
+### PlatformConstants~PLATFORM\_API\_EXPORT\_POST\_SUBSCRIPTIONS\_FILTER\_TYPES : Object
+Platform Export, available SUBSCRIPTION FILTER POST types.
+
**Kind**: inner constant of [PlatformConstants
](#Platform.module_PlatformConstants)
@@ -419,8 +426,10 @@ Emulated service calls for platform globals.
* [~getUser(options)](#Platform.module_PlatformServices..getUser) ⇒ Promise.<\*>
* [~getUserPermissions(appName, options)](#Platform.module_PlatformServices..getUserPermissions) ⇒ Promise.<\*>
* [~hideGlobalFilter(isHidden)](#Platform.module_PlatformServices..hideGlobalFilter) ⇒ Promise.<\*>
+ * [~deleteExport(id, options)](#Platform.module_PlatformServices..deleteExport) ⇒ Promise.<\*>
+ * [~getExistingExportsStatus(id, params, options)](#Platform.module_PlatformServices..getExistingExportsStatus) ⇒ Promise.<\*>
* [~getExport(id, options)](#Platform.module_PlatformServices..getExport) ⇒ Promise.<\*>
- * [~getExportStatus(id, params, options)](#Platform.module_PlatformServices..getExportStatus) ⇒ Promise.<\*>
+ * [~getExistingExports(idList, params, options)](#Platform.module_PlatformServices..getExistingExports) ⇒ Promise.<\*>
* [~postExport(data, options)](#Platform.module_PlatformServices..postExport) ⇒ Promise.<\*>
@@ -479,6 +488,58 @@ Disables the Platform's global filter display.
+
+
+### PlatformServices~deleteExport(id, options) ⇒ Promise.<\*>
+Delete an export. Useful for clean up. Helps avoid having to deal with export lists and most recent exports.
+
+**Kind**: inner method of [PlatformServices
](#Platform.module_PlatformServices)
+
+
+
+ Param Type Description
+
+
+
+
+ id string
ID of export to delete
+
+
+ options object
+
+ options.cancel boolean
+
+ options.cancelId string
+
+
+
+
+
+### PlatformServices~getExistingExportsStatus(id, params, options) ⇒ Promise.<\*>
+Get multiple export status, or a single status after setup.
+
+**Kind**: inner method of [PlatformServices
](#Platform.module_PlatformServices)
+
+
+
+ Param Type Description
+
+
+
+
+ id string
| undefined
| null
Export ID
+
+
+ params object
+
+ options object
+
+ options.cancel boolean
+
+ options.cancelId string
+
+
+
### PlatformServices~getExport(id, options) ⇒ Promise.<\*>
@@ -501,13 +562,17 @@ Get an export after setup.
options.cancel boolean
options.cancelId string
+
+ options.fileName string
+
+ options.fileType string
-
+
-### PlatformServices~getExportStatus(id, params, options) ⇒ Promise.<\*>
-Get multiple export status, or a single status after setup.
+### PlatformServices~getExistingExports(idList, params, options) ⇒ Promise.<\*>
+Convenience wrapper for setting up global export status with status polling, and download with clean-up.
**Kind**: inner method of [PlatformServices
](#Platform.module_PlatformServices)
@@ -518,7 +583,7 @@ Get multiple export status, or a single status after setup.
- id string
| undefined
| null
Export ID
+ idList Array.<{id: string, fileName: string}>
A list of export IDs to finish
params object
@@ -534,7 +599,7 @@ Get multiple export status, or a single status after setup.
### PlatformServices~postExport(data, options) ⇒ Promise.<\*>
-Post to create an export.
+Convenience wrapper for posting to create an export with status polling, then performing a download with clean-up.
**Kind**: inner method of [PlatformServices
](#Platform.module_PlatformServices)
diff --git a/src/services/platform/__tests__/__snapshots__/platformConstants.test.js.snap b/src/services/platform/__tests__/__snapshots__/platformConstants.test.js.snap
index af4282025..b615a5646 100644
--- a/src/services/platform/__tests__/__snapshots__/platformConstants.test.js.snap
+++ b/src/services/platform/__tests__/__snapshots__/platformConstants.test.js.snap
@@ -6,10 +6,18 @@ exports[`Platform Constants should have specific properties: all exported consta
"SUBSCRIPTIONS": "subscriptions",
},
"PLATFORM_API_EXPORT_CONTENT_TYPES": {
- "CSV": "csv",
"JSON": "json",
},
"PLATFORM_API_EXPORT_FILENAME_PREFIX": "swatch",
+ "PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES": {
+ "BILLING_ACCOUNT_ID": "billing_account_id",
+ "BILLING_PROVIDER": "billing_provider",
+ "CATEGORY": "category",
+ "METRIC_ID": "metric_id",
+ "PRODUCT_ID": "product_id",
+ "SLA": "sla",
+ "USAGE": "usage",
+ },
"PLATFORM_API_EXPORT_POST_TYPES": {
"EXPIRES_AT": "expires_at",
"FORMAT": "format",
@@ -35,7 +43,7 @@ exports[`Platform Constants should have specific properties: all exported consta
"RESOURCE": "resource",
},
"PLATFORM_API_EXPORT_STATUS_TYPES": {
- "COMPLETED": "completed",
+ "COMPLETE": "complete",
"FAILED": "failed",
"PARTIAL": "partial",
"PENDING": "pending",
@@ -72,10 +80,18 @@ exports[`Platform Constants should have specific properties: all exported consta
"SUBSCRIPTIONS": "subscriptions",
},
"PLATFORM_API_EXPORT_CONTENT_TYPES": {
- "CSV": "csv",
"JSON": "json",
},
"PLATFORM_API_EXPORT_FILENAME_PREFIX": "swatch",
+ "PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES": {
+ "BILLING_ACCOUNT_ID": "billing_account_id",
+ "BILLING_PROVIDER": "billing_provider",
+ "CATEGORY": "category",
+ "METRIC_ID": "metric_id",
+ "PRODUCT_ID": "product_id",
+ "SLA": "sla",
+ "USAGE": "usage",
+ },
"PLATFORM_API_EXPORT_POST_TYPES": {
"EXPIRES_AT": "expires_at",
"FORMAT": "format",
@@ -101,7 +117,7 @@ exports[`Platform Constants should have specific properties: all exported consta
"RESOURCE": "resource",
},
"PLATFORM_API_EXPORT_STATUS_TYPES": {
- "COMPLETED": "completed",
+ "COMPLETE": "complete",
"FAILED": "failed",
"PARTIAL": "partial",
"PENDING": "pending",
@@ -139,10 +155,18 @@ exports[`Platform Constants should have specific properties: all exported consta
"SUBSCRIPTIONS": "subscriptions",
},
"PLATFORM_API_EXPORT_CONTENT_TYPES": {
- "CSV": "csv",
"JSON": "json",
},
"PLATFORM_API_EXPORT_FILENAME_PREFIX": "swatch",
+ "PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES": {
+ "BILLING_ACCOUNT_ID": "billing_account_id",
+ "BILLING_PROVIDER": "billing_provider",
+ "CATEGORY": "category",
+ "METRIC_ID": "metric_id",
+ "PRODUCT_ID": "product_id",
+ "SLA": "sla",
+ "USAGE": "usage",
+ },
"PLATFORM_API_EXPORT_POST_TYPES": {
"EXPIRES_AT": "expires_at",
"FORMAT": "format",
@@ -168,7 +192,7 @@ exports[`Platform Constants should have specific properties: all exported consta
"RESOURCE": "resource",
},
"PLATFORM_API_EXPORT_STATUS_TYPES": {
- "COMPLETED": "completed",
+ "COMPLETE": "complete",
"FAILED": "failed",
"PARTIAL": "partial",
"PENDING": "pending",
@@ -210,10 +234,18 @@ exports[`Platform Constants should have specific properties: specific constants
"SUBSCRIPTIONS": "subscriptions",
},
"PLATFORM_API_EXPORT_CONTENT_TYPES": {
- "CSV": "csv",
"JSON": "json",
},
"PLATFORM_API_EXPORT_FILENAME_PREFIX": "swatch",
+ "PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES": {
+ "BILLING_ACCOUNT_ID": "billing_account_id",
+ "BILLING_PROVIDER": "billing_provider",
+ "CATEGORY": "category",
+ "METRIC_ID": "metric_id",
+ "PRODUCT_ID": "product_id",
+ "SLA": "sla",
+ "USAGE": "usage",
+ },
"PLATFORM_API_EXPORT_POST_TYPES": {
"EXPIRES_AT": "expires_at",
"FORMAT": "format",
@@ -239,7 +271,7 @@ exports[`Platform Constants should have specific properties: specific constants
"RESOURCE": "resource",
},
"PLATFORM_API_EXPORT_STATUS_TYPES": {
- "COMPLETED": "completed",
+ "COMPLETE": "complete",
"FAILED": "failed",
"PARTIAL": "partial",
"PENDING": "pending",
diff --git a/src/services/platform/__tests__/__snapshots__/platformServices.test.js.snap b/src/services/platform/__tests__/__snapshots__/platformServices.test.js.snap
index 681d3f34b..828416de1 100644
--- a/src/services/platform/__tests__/__snapshots__/platformServices.test.js.snap
+++ b/src/services/platform/__tests__/__snapshots__/platformServices.test.js.snap
@@ -1,5 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`PlatformServices should export a specific properties, methods and classes: properties 1`] = `
+{
+ "deleteExport": [Function],
+ "getExistingExports": [Function],
+ "getExistingExportsStatus": [Function],
+ "getExport": [Function],
+ "getUser": [Function],
+ "getUserPermissions": [Function],
+ "hideGlobalFilter": [Function],
+ "postExport": [Function],
+}
+`;
+
exports[`PlatformServices should return a failed getUser: failed authorized user 1`] = `
{
"data": undefined,
diff --git a/src/services/platform/__tests__/__snapshots__/platformTransformers.test.js.snap b/src/services/platform/__tests__/__snapshots__/platformTransformers.test.js.snap
index 4d10fabd6..068dbec7e 100644
--- a/src/services/platform/__tests__/__snapshots__/platformTransformers.test.js.snap
+++ b/src/services/platform/__tests__/__snapshots__/platformTransformers.test.js.snap
@@ -69,7 +69,13 @@ exports[`Platform Transformers should attempt to parse a user response: user, pa
exports[`Platform Transformers should attempt to parse an exports response: exports, default 1`] = `
{
- "data": {},
+ "data": {
+ "completed": [],
+ "isAnythingCompleted": false,
+ "isAnythingPending": false,
+ "pending": [],
+ "products": {},
+ },
"meta": {},
}
`;
@@ -77,15 +83,36 @@ exports[`Platform Transformers should attempt to parse an exports response: expo
exports[`Platform Transformers should attempt to parse an exports response: exports, parsed single 1`] = `
{
"data": {
- "RHEL for x86": [
+ "completed": [],
+ "isAnythingCompleted": false,
+ "isAnythingPending": true,
+ "pending": [
{
+ "fileName": "20190720_000000_swatch_report_rhel_for_x_86",
"format": undefined,
"id": "0123456789",
"name": "swatch-RHEL for x86",
+ "productId": "RHEL for x86",
"status": "pending",
},
],
- "isAnythingPending": true,
+ "products": {
+ "RHEL for x86": {
+ "completed": [],
+ "isCompleted": false,
+ "isPending": true,
+ "pending": [
+ {
+ "fileName": "20190720_000000_swatch_report_rhel_for_x_86",
+ "format": undefined,
+ "id": "0123456789",
+ "name": "swatch-RHEL for x86",
+ "productId": "RHEL for x86",
+ "status": "pending",
+ },
+ ],
+ },
+ },
},
"meta": {},
}
diff --git a/src/services/platform/__tests__/platformServices.test.js b/src/services/platform/__tests__/platformServices.test.js
index 58bf75323..426b3ab56 100644
--- a/src/services/platform/__tests__/platformServices.test.js
+++ b/src/services/platform/__tests__/platformServices.test.js
@@ -29,25 +29,24 @@ describe('PlatformServices', () => {
moxios.uninstall();
});
- it('should export a specific number of methods and classes', () => {
- expect(Object.keys(platformServices)).toHaveLength(6);
- });
-
- it('should have specific methods', () => {
- expect(platformServices.getUser).toBeDefined();
- expect(platformServices.getUserPermissions).toBeDefined();
- expect(platformServices.hideGlobalFilter).toBeDefined();
- expect(platformServices.postExport).toBeDefined();
- expect(platformServices.getExport).toBeDefined();
- expect(platformServices.getExportStatus).toBeDefined();
+ it('should export a specific properties, methods and classes', () => {
+ expect(platformServices).toMatchSnapshot('properties');
});
/**
- * timeout errors associated with this test sometimes stem from endpoint
- * settings or missing globals, see "before" above, or the "setupTests" config
+ * Notes:
+ * - Timeout errors associated with this test sometimes stem from endpoint
+ * settings or missing globals, see "before" above, or the "setupTests" config
+ * - Reset polling for testing
*/
it('should return async for most methods and resolve successfully', async () => {
- const promises = Object.keys(platformServices).map(value => platformServices[value]());
+ const promises = Object.keys(platformServices).map(value =>
+ platformServices[value](
+ undefined,
+ { poll: { location: undefined, status: undefined, validate: undefined } },
+ { poll: { location: undefined, status: undefined, validate: undefined } }
+ )
+ );
const response = await Promise.all(promises);
expect(response.length).toEqual(Object.keys(platformServices).length);
diff --git a/src/services/platform/__tests__/platformTransformers.test.js b/src/services/platform/__tests__/platformTransformers.test.js
index a0690a711..93ddc39f8 100644
--- a/src/services/platform/__tests__/platformTransformers.test.js
+++ b/src/services/platform/__tests__/platformTransformers.test.js
@@ -18,23 +18,25 @@ describe('Platform Transformers', () => {
const parsedSingle = platformTransformers.exports({
[platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.NAME]:
- `${platformConstants.PLATFORM_API_EXPORT_FILENAME_PREFIX}-${rhsmConstants.RHSM_API_PATH_PRODUCT_TYPES.RHEL_X86}`,
+ `${helpers.CONFIG_EXPORT_SERVICE_NAME_PREFIX}-${rhsmConstants.RHSM_API_PATH_PRODUCT_TYPES.RHEL_X86}`,
[platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.EXPIRES_AT]: '2019-07-14T00:00:00Z',
[platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.ID]: '0123456789',
[platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.STATUS]:
platformConstants.PLATFORM_API_EXPORT_STATUS_TYPES.PENDING
});
- const parsedArray = platformTransformers.exports([
- {
- [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.NAME]:
- `${platformConstants.PLATFORM_API_EXPORT_FILENAME_PREFIX}-${rhsmConstants.RHSM_API_PATH_PRODUCT_TYPES.RHEL_X86}`,
- [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.EXPIRES_AT]: '2019-07-14T00:00:00Z',
- [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.ID]: '0123456789',
- [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.STATUS]:
- platformConstants.PLATFORM_API_EXPORT_STATUS_TYPES.PENDING
- }
- ]);
+ const parsedArray = platformTransformers.exports({
+ [platformConstants.PLATFORM_API_EXPORT_RESPONSE_DATA]: [
+ {
+ [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.NAME]:
+ `${helpers.CONFIG_EXPORT_SERVICE_NAME_PREFIX}-${rhsmConstants.RHSM_API_PATH_PRODUCT_TYPES.RHEL_X86}`,
+ [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.EXPIRES_AT]: '2019-07-14T00:00:00Z',
+ [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.ID]: '0123456789',
+ [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.STATUS]:
+ platformConstants.PLATFORM_API_EXPORT_STATUS_TYPES.PENDING
+ }
+ ]
+ });
expect(parsedSingle).toMatchSnapshot('exports, parsed single');
expect(parsedSingle).toMatchObject(parsedArray);
diff --git a/src/services/platform/platformConstants.js b/src/services/platform/platformConstants.js
index e71361ed3..d9c228ee0 100644
--- a/src/services/platform/platformConstants.js
+++ b/src/services/platform/platformConstants.js
@@ -42,7 +42,7 @@ const PLATFORM_API_EXPORT_RESOURCE_TYPES = {
* @type {{CSV: string, JSON: string}}
*/
const PLATFORM_API_EXPORT_CONTENT_TYPES = {
- CSV: 'csv',
+ // CSV: 'csv',
JSON: 'json'
};
@@ -56,11 +56,11 @@ const PLATFORM_API_EXPORT_FILENAME_PREFIX = 'swatch';
/**
* Platform Export, available status types.
*
- * @type {{COMPLETED: string, FAILED: string, RUNNING: string, PARTIAL: string, PENDING: string}}
+ * @type {{COMPLETE: string, FAILED: string, RUNNING: string, PARTIAL: string, PENDING: string}}
*/
const PLATFORM_API_EXPORT_STATUS_TYPES = {
FAILED: 'failed',
- COMPLETED: 'completed',
+ COMPLETE: 'complete',
PARTIAL: 'partial',
PENDING: 'pending',
RUNNING: 'running'
@@ -89,6 +89,22 @@ const PLATFORM_API_EXPORT_POST_TYPES = {
SOURCES: 'sources'
};
+/**
+ * Platform Export, available SUBSCRIPTION FILTER POST types.
+ *
+ * @type {{BILLING_ACCOUNT_ID: string, USAGE: string, CATEGORY: string, METRIC_ID: string, SLA: string,
+ * BILLING_PROVIDER: string, PRODUCT_ID: string}}
+ */
+const PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES = {
+ BILLING_PROVIDER: 'billing_provider',
+ BILLING_ACCOUNT_ID: 'billing_account_id',
+ CATEGORY: 'category',
+ METRIC_ID: 'metric_id',
+ PRODUCT_ID: 'product_id',
+ SLA: 'sla',
+ USAGE: 'usage'
+};
+
/**
* Platform Export, available response types.
*
@@ -177,6 +193,7 @@ const platformConstants = {
PLATFORM_API_EXPORT_CONTENT_TYPES,
PLATFORM_API_EXPORT_FILENAME_PREFIX,
PLATFORM_API_EXPORT_POST_TYPES,
+ PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES,
PLATFORM_API_EXPORT_RESOURCE_TYPES,
PLATFORM_API_EXPORT_RESPONSE_DATA,
PLATFORM_API_EXPORT_RESPONSE_META,
@@ -201,6 +218,7 @@ export {
PLATFORM_API_EXPORT_CONTENT_TYPES,
PLATFORM_API_EXPORT_FILENAME_PREFIX,
PLATFORM_API_EXPORT_POST_TYPES,
+ PLATFORM_API_EXPORT_POST_SUBSCRIPTIONS_FILTER_TYPES,
PLATFORM_API_EXPORT_RESOURCE_TYPES,
PLATFORM_API_EXPORT_RESPONSE_DATA,
PLATFORM_API_EXPORT_RESPONSE_META,
diff --git a/src/services/platform/platformServices.js b/src/services/platform/platformServices.js
index 0325191fe..0b43fdbbc 100644
--- a/src/services/platform/platformServices.js
+++ b/src/services/platform/platformServices.js
@@ -109,21 +109,15 @@ const hideGlobalFilter = async (isHidden = true) => {
};
/**
- * @api {get} /api/export/v1/exports/:id
- * @apiDescription Get an export by id
+ * @apiMock {ForceStatus} 202
+ * @api {delete} /api/export/v1/exports/:id
+ * @apiDescription Create an export
*
* Reference [EXPORTS API](https://github.com/RedHatInsights/export-service-go/blob/main/static/spec/openapi.yaml)
*
- * @apiSuccessExample {zip} Success-Response:
- * HTTP/1.1 200 OK
- * Success
- *
- * @apiErrorExample {json} Error-Response:
- * HTTP/1.1 400 Bad Request
- * {
- * message: "'---' is not a valid export UUID",
- * code: 400
- * }
+ * @apiSuccessExample {json} Success-Response:
+ * HTTP/1.1 202 OK
+ * {}
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 500 Internal Server Error
@@ -131,31 +125,23 @@ const hideGlobalFilter = async (isHidden = true) => {
* }
*/
/**
- * Get an export after setup.
+ * Delete an export. Useful for clean up. Helps avoid having to deal with export lists and most recent exports.
*
- * @param {string} id Export ID
+ * @param {string} id ID of export to delete
* @param {object} options
* @param {boolean} options.cancel
* @param {string} options.cancelId
* @returns {Promise<*>}
*/
-const getExport = (id, options = {}) => {
+const deleteExport = (id, options = {}) => {
const { cache = false, cancel = true, cancelId } = options;
return axiosServiceCall({
url: `${process.env.REACT_APP_SERVICES_PLATFORM_EXPORT}/${id}`,
- responseType: 'blob',
+ method: 'delete',
cache,
cancel,
cancelId
- }).then(
- success =>
- (helpers.TEST_MODE && success.data) ||
- downloadHelpers.downloadData({
- data: success.data,
- fileName: `swatch_report_${id}.tar.gz`,
- fileType: 'application/gzip'
- })
- );
+ });
};
/**
@@ -177,16 +163,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "partial",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "instances",
- * "filters": {},
- * "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
- * "status": "pending"
- * }
- * ]
+ * "status": "pending"
* },
* {
* "id": "x123456-5717-4562-b3fc-2c963f66afa6",
@@ -195,16 +172,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "completed",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "subscriptions",
- * "filters": {},
- * "id": "x123456-5717-4562-b3fc-2c963f66afa6",
- * "status": "completed"
- * }
- * ]
+ * "status": "complete"
* }
* ]
* }
@@ -220,16 +188,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "partial",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "instances",
- * "filters": {},
- * "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
- * "status": "pending"
- * }
- * ]
+ * "status": "pending"
* },
* {
* "id": "x123456-5717-4562-b3fc-2c963f66afa6",
@@ -238,16 +197,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "partial",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "subscriptions",
- * "filters": {},
- * "id": "x123456-5717-4562-b3fc-2c963f66afa6",
- * "status": "pending"
- * }
- * ]
+ * "status": "pending"
* }
* ]
* }
@@ -263,16 +213,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "completed",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "instances",
- * "filters": {},
- * "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
- * "status": "completed"
- * }
- * ]
+ * "status": "complete"
* },
* {
* "id": "x123456-5717-4562-b3fc-2c963f66afa6",
@@ -281,16 +222,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "completed",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "subscriptions",
- * "filters": {},
- * "id": "x123456-5717-4562-b3fc-2c963f66afa6",
- * "status": "completed"
- * }
- * ]
+ * "status": "complete"
* },
* {
* "id": "x123456-5717-4562-b3fc-2c963f66afa6",
@@ -299,16 +231,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "partial",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "subscriptions",
- * "filters": {},
- * "id": "x123456-5717-4562-b3fc-2c963f66afa6",
- * "status": "pending"
- * }
- * ]
+ * "status": "partial"
* }
* ]
* }
@@ -340,16 +263,7 @@ const getExport = (id, options = {}) => {
* "completed_at": "2024-01-24T16:20:31.229Z",
* "expires_at": "2024-01-24T16:20:31.229Z",
* "format": "json",
- * "status": "partial",
- * "sources": [
- * {
- * "application": "subscriptions",
- * "resource": "instances",
- * "filters": {},
- * "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
- * "status": "pending"
- * }
- * ]
+ * "status": "partial"
* }
*
* @apiErrorExample {json} Error-Response:
@@ -374,11 +288,10 @@ const getExport = (id, options = {}) => {
* @param {string} options.cancelId
* @returns {Promise<*>}
*/
-const getExportStatus = (id, params = {}, options = {}) => {
+const getExistingExportsStatus = (id, params = {}, options = {}) => {
const {
cache = false,
cancel = true,
- // cancelId = 'export-status',
cancelId,
schema = [platformSchemas.exports],
transform = [platformTransformers.exports],
@@ -398,6 +311,122 @@ const getExportStatus = (id, params = {}, options = {}) => {
});
};
+/**
+ * @api {get} /api/export/v1/exports/:id
+ * @apiDescription Get an export by id
+ *
+ * Reference [EXPORTS API](https://github.com/RedHatInsights/export-service-go/blob/main/static/spec/openapi.yaml)
+ *
+ * @apiSuccessExample {zip} Success-Response:
+ * HTTP/1.1 200 OK
+ * Success
+ *
+ * @apiErrorExample {json} Error-Response:
+ * HTTP/1.1 400 Bad Request
+ * {
+ * message: "'---' is not a valid export UUID",
+ * code: 400
+ * }
+ *
+ * @apiErrorExample {json} Error-Response:
+ * HTTP/1.1 500 Internal Server Error
+ * {
+ * }
+ */
+/**
+ * Get an export after setup.
+ *
+ * @param {string} id Export ID
+ * @param {object} options
+ * @param {boolean} options.cancel
+ * @param {string} options.cancelId
+ * @param {string} options.fileName
+ * @param {string} options.fileType
+ * @returns {Promise<*>}
+ */
+const getExport = (id, options = {}) => {
+ const {
+ cache = false,
+ cancel = true,
+ cancelId,
+ fileName = `swatch_report_${id}`,
+ fileType = 'application/gzip'
+ } = options;
+ return axiosServiceCall({
+ url: `${process.env.REACT_APP_SERVICES_PLATFORM_EXPORT}/${id}`,
+ responseType: 'blob',
+ cache,
+ cancel,
+ cancelId
+ })
+ .then(
+ success =>
+ (helpers.TEST_MODE && success.data) ||
+ downloadHelpers.downloadData({
+ data: success.data,
+ fileName: `${fileName}.tar.gz`,
+ fileType
+ })
+ )
+ .then(() => deleteExport(id));
+};
+
+/**
+ * Convenience wrapper for setting up global export status with status polling, and download with clean-up.
+ *
+ * @param {Array<{id: string, fileName: string}>} idList A list of export IDs to finish
+ * @param {object} params
+ * @param {object} options
+ * @param {boolean} options.cancel
+ * @param {string} options.cancelId
+ * @returns {Promise<*>}
+ */
+const getExistingExports = (idList, params = {}, options = {}) => {
+ const {
+ cache = false,
+ cancel = true,
+ cancelId = 'all-exports',
+ poll,
+ schema = [platformSchemas.exports],
+ transform = [platformTransformers.exports],
+ ...restOptions
+ } = options;
+
+ return axiosServiceCall({
+ ...restOptions,
+ poll: {
+ location: {
+ url: process.env.REACT_APP_SERVICES_PLATFORM_EXPORT,
+ ...poll?.location
+ },
+ validate: response => {
+ const completedResults = response?.data?.data?.completed;
+ const isIdListCompleted =
+ idList.filter(({ id }) => completedResults.find(({ id: completedId }) => completedId === id) !== undefined)
+ .length === idList.length;
+
+ if (isIdListCompleted && completedResults.length > 0) {
+ Promise.all(idList.map(({ id, fileName }) => getExport(id, { fileName })));
+ }
+
+ return isIdListCompleted;
+ },
+ ...poll
+ },
+ url: process.env.REACT_APP_SERVICES_PLATFORM_EXPORT,
+ params,
+ cache,
+ cancel,
+ cancelId,
+ schema,
+ transform
+ });
+};
+
+/**
+ * Note: 202 status appears to be only response that returns a sources list, OR it's variable depending on
+ * partial/pending status.
+ */
/**
* @apiMock {ForceStatus} 202
* @api {post} /api/export/v1/exports
@@ -432,7 +461,7 @@ const getExportStatus = (id, params = {}, options = {}) => {
* }
*/
/**
- * Post to create an export.
+ * Convenience wrapper for posting to create an export with status polling, then performing a download with clean-up.
*
* @param {object} data JSON data to submit
* @param {object} options
@@ -440,17 +469,50 @@ const getExportStatus = (id, params = {}, options = {}) => {
* @param {string} options.cancelId
* @returns {Promise<*>}
*/
-const postExport = (data = {}, options = {}) => {
+const postExport = async (data = {}, options = {}) => {
const {
cache = false,
- cancel = true,
- cancelId, // = 'export-status',
+ cancel = false,
+ cancelId,
+ poll,
schema = [platformSchemas.exports],
- transform = [platformTransformers.exports],
+ transform = [],
...restOptions
} = options;
- return axiosServiceCall({
+
+ let downloadId;
+ const postResponse = await axiosServiceCall({
...restOptions,
+ poll: {
+ ...poll,
+ location: {
+ url: process.env.REACT_APP_SERVICES_PLATFORM_EXPORT,
+ config: {
+ cache: false,
+ cancel: false,
+ schema: [platformSchemas.exports],
+ transform: [platformTransformers.exports]
+ },
+ ...poll?.location
+ },
+ status: (successResponse, ...args) => {
+ if (typeof poll?.status === 'function') {
+ poll.status.call(null, successResponse, ...args);
+ }
+ },
+ validate: response => {
+ const foundDownload = response?.data?.data?.completed.find(
+ ({ id }) => downloadId !== undefined && id === downloadId
+ );
+
+ if (foundDownload) {
+ const { id, fileName } = foundDownload;
+ getExport(id, { fileName });
+ }
+
+ return foundDownload !== undefined;
+ }
+ },
method: 'post',
url: process.env.REACT_APP_SERVICES_PLATFORM_EXPORT,
data,
@@ -460,11 +522,16 @@ const postExport = (data = {}, options = {}) => {
schema,
transform
});
+
+ downloadId = postResponse.data.id;
+ return postResponse;
};
const platformServices = {
+ deleteExport,
+ getExistingExports,
+ getExistingExportsStatus,
getExport,
- getExportStatus,
getUser,
getUserPermissions,
hideGlobalFilter,
@@ -479,8 +546,10 @@ helpers.browserExpose({ platformServices });
export {
platformServices as default,
platformServices,
+ deleteExport,
+ getExistingExports,
+ getExistingExportsStatus,
getExport,
- getExportStatus,
getUser,
getUserPermissions,
hideGlobalFilter,
diff --git a/src/services/platform/platformTransformers.js b/src/services/platform/platformTransformers.js
index d83d2b8d6..8125331b9 100644
--- a/src/services/platform/platformTransformers.js
+++ b/src/services/platform/platformTransformers.js
@@ -1,12 +1,13 @@
+import moment from 'moment';
+import _snakeCase from 'lodash/snakeCase';
import { rbacConfig } from '../../config';
import {
platformConstants,
PLATFORM_API_EXPORT_STATUS_TYPES,
- PLATFORM_API_EXPORT_FILENAME_PREFIX as EXPORT_PREFIX,
PLATFORM_API_RESPONSE_USER_PERMISSION_OPERATION_TYPES as OPERATION_TYPES,
PLATFORM_API_RESPONSE_USER_PERMISSION_RESOURCE_TYPES as RESOURCE_TYPES
} from './platformConstants';
-import { helpers } from '../../common';
+import { helpers, dateHelpers } from '../../common';
/**
* Transform export responses. Combines multiple exports, or a single export,
@@ -32,6 +33,12 @@ const exports = response => {
[platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.STATUS]: status
} = response || {};
+ updatedResponse.data.isAnythingPending = false;
+ updatedResponse.data.isAnythingCompleted = false;
+ updatedResponse.data.pending ??= [];
+ updatedResponse.data.completed ??= [];
+ updatedResponse.data.products = {};
+
/**
* Pull a product id from an export name. Fallback filtering for product identifiers.
*
@@ -40,7 +47,7 @@ const exports = response => {
*/
const getProductId = str => {
const updatedStr = str;
- const attemptId = updatedStr?.replace(`${EXPORT_PREFIX}-`, '')?.trim();
+ const attemptId = updatedStr?.replace(`${helpers.CONFIG_EXPORT_SERVICE_NAME_PREFIX}-`, '')?.trim();
if (attemptId === updatedStr) {
return undefined;
@@ -61,7 +68,7 @@ const exports = response => {
if (
updatedStr === PLATFORM_API_EXPORT_STATUS_TYPES.FAILED ||
- updatedStr === PLATFORM_API_EXPORT_STATUS_TYPES.COMPLETED
+ updatedStr === PLATFORM_API_EXPORT_STATUS_TYPES.COMPLETE
) {
updatedStatus = updatedStr;
}
@@ -82,23 +89,32 @@ const exports = response => {
const productId = getProductId(exportName);
const focusedStatus = getStatus(exportStatus);
- if (updatedResponse.data.isAnythingPending !== true) {
- updatedResponse.data.isAnythingPending = focusedStatus === PLATFORM_API_EXPORT_STATUS_TYPES.PENDING;
- }
-
- updatedResponse.data[productId] ??= [];
- updatedResponse.data[productId].push({
+ const updatedExportData = {
+ fileName: `${moment.utc(dateHelpers.getCurrentDate()).format('YYYYMMDD_HHmmss')}_${helpers.CONFIG_EXPORT_FILENAME.replace('{0}', _snakeCase(productId))}`,
format: exportFormat,
id: exportId,
name: exportName,
+ productId,
status: focusedStatus
- });
+ };
+
+ updatedResponse.data.products[productId] ??= {};
+ updatedResponse.data.products[productId].pending ??= [];
+ updatedResponse.data.products[productId].completed ??= [];
+
+ if (focusedStatus === PLATFORM_API_EXPORT_STATUS_TYPES.PENDING) {
+ updatedResponse.data.pending.push(updatedExportData);
+ updatedResponse.data.products[productId].pending.push(updatedExportData);
+ } else if (focusedStatus === PLATFORM_API_EXPORT_STATUS_TYPES.COMPLETE) {
+ updatedResponse.data.completed.push(updatedExportData);
+ updatedResponse.data.products[productId].completed.push(updatedExportData);
+ }
};
if (Array.isArray(data)) {
data
.filter(({ [platformConstants.PLATFORM_API_EXPORT_RESPONSE_TYPES.NAME]: exportName }) =>
- new RegExp(`^${EXPORT_PREFIX}`, 'i').test(exportName)
+ new RegExp(`^${helpers.CONFIG_EXPORT_SERVICE_NAME_PREFIX}`, 'i').test(exportName)
)
.forEach(
({
@@ -110,10 +126,18 @@ const exports = response => {
restructureResponse({ exportName, exportStatus, exportFormat, exportId });
}
);
- } else if (id && status && new RegExp(`^${EXPORT_PREFIX}`, 'i').test(name)) {
+ } else if (id && status && new RegExp(`^${helpers.CONFIG_EXPORT_SERVICE_NAME_PREFIX}`, 'i').test(name)) {
restructureResponse({ exportName: name, exportStatus: status, exportFormat: format, exportId: id });
}
+ updatedResponse.data.isAnythingPending = updatedResponse.data.pending.length > 0;
+ updatedResponse.data.isAnythingCompleted = updatedResponse.data.completed.length > 0;
+
+ Object.entries(updatedResponse.data.products).forEach(([productId, { pending, completed }]) => {
+ updatedResponse.data.products[productId].isPending = pending.length > 0;
+ updatedResponse.data.products[productId].isCompleted = completed.length > 0;
+ });
+
return updatedResponse;
};
diff --git a/src/styles/_toolbar.scss b/src/styles/_toolbar.scss
index 963fd6620..9eea89ea4 100644
--- a/src/styles/_toolbar.scss
+++ b/src/styles/_toolbar.scss
@@ -6,6 +6,10 @@
padding-left: 0 !important;
padding-right: 0 !important;
}
+
+ &__export-confirmation > p {
+ padding-top: var(--pf-global--spacer--md);
+ }
}
.curiosity-page-columns-column {
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 5a7e6b4b1..3d76f9d92 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -7,7 +7,7 @@ $pf-global--breakpoint--sm: $pf-v5-global--breakpoint--sm;
$pf-global--breakpoint--md: $pf-v5-global--breakpoint--md;
$pf-global--breakpoint--lg: $pf-v5-global--breakpoint--lg;
-.curiosity {
+.curiosity, .curiosity-vars {
--pf-global--BackgroundColor--dark-100: var(--pf-v5-global--BackgroundColor--dark-100);
--pf-global--BackgroundColor--light-300: var(--pf-v5-global--BackgroundColor--light-300);
--pf-global--BackgroundColor--100: var(--pf-v5-global--BackgroundColor--100);
diff --git a/tests/__snapshots__/dist.test.js.snap b/tests/__snapshots__/dist.test.js.snap
index 00a3d686f..3bc5230af 100644
--- a/tests/__snapshots__/dist.test.js.snap
+++ b/tests/__snapshots__/dist.test.js.snap
@@ -822,6 +822,7 @@ exports[`Build distribution should match a specific file output 1`] = `
"./dist/js/8795*js",
"./dist/js/8803*js",
"./dist/js/8923*js",
+ "./dist/js/9006*js",
"./dist/js/9026*js",
"./dist/js/9067*js",
"./dist/js/9205*js",
@@ -950,6 +951,7 @@ exports[`Build distribution should match a specific file output 1`] = `
"./dist/sourcemaps/8795*map",
"./dist/sourcemaps/8803*map",
"./dist/sourcemaps/8923*map",
+ "./dist/sourcemaps/9006*map",
"./dist/sourcemaps/9026*map",
"./dist/sourcemaps/9205*map",
"./dist/sourcemaps/9387*map",