diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 262506a14..e775a1f9b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,17 @@ updates: pull-request-branch-name: separator: "_" open-pull-requests-limit: 15 + groups: + lint-dependencies: + patterns: + - "*eslint*" + patternfly-dependencies: + patterns: + - "*patternfly*" + test-dependencies: + patterns: + - "*jest*" + - "*testing-library*" + ci-dependencies: + patterns: + - "*" # update remaining deps in single PR diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 92298b9c6..8bee9661a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -44,6 +44,3 @@ jobs: - name: Test if: ${{ success() }} run: npm test --coverage --maxWorkers=4 - - name: Code coverage - if: ${{ success() }} - uses: codecov/codecov-action@v1 diff --git a/fec.config.js b/fec.config.js index 794c26ab3..e36a70737 100644 --- a/fec.config.js +++ b/fec.config.js @@ -1,4 +1,4 @@ -// Based on https://github.com/RedHatInsights/frontend-components/blob/master/packages/config/src/scripts/dev.webpack.config.js +// Based on https://github.com/RedHatInsights/frontend-components/blob/master/packages/config/src/bin/dev.webpack.config.ts const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); diff --git a/locales/data.json b/locales/data.json index 64e3cbb18..74600a0ac 100644 --- a/locales/data.json +++ b/locales/data.json @@ -1941,6 +1941,12 @@ "value": "Create rate" } ], + "createTagMapping": [ + { + "type": 0, + "value": "Create tag mapping" + } + ], "currency": [ { "type": 0, @@ -2356,6 +2362,72 @@ "value": "supplementaryCost" } ], + "dataDetails": [ + { + "type": 0, + "value": "Data details" + } + ], + "dataDetailsAvailability": [ + { + "type": 0, + "value": "Data availability" + } + ], + "dataDetailsCloudData": [ + { + "type": 0, + "value": "Cloud data" + } + ], + "dataDetailsCloudIntegration": [ + { + "type": 0, + "value": "Cloud integration data" + } + ], + "dataDetailsCloudIntegrationStatus": [ + { + "type": 0, + "value": "Cloud integration status" + } + ], + "dataDetailsClusterData": [ + { + "type": 0, + "value": "Cluster data" + } + ], + "dataDetailsCostManagementData": [ + { + "type": 0, + "value": "Cost Management data" + } + ], + "dataDetailsIntegrationAndFinalization": [ + { + "type": 0, + "value": "Data integration and finalization" + } + ], + "dataDetailsIntegrationStatus": [ + { + "type": 0, + "value": "Red Hat integration status" + } + ], + "dataDetailsProcessing": [ + { + "type": 0, + "value": "Data processing" + } + ], + "dataDetailsRetrieval": [ + { + "type": 0, + "value": "Data retrieval" + } + ], "dataTableAriaLabel": [ { "type": 0, @@ -2892,6 +2964,14 @@ "value": "Tag names" } ] + }, + "tag_key": { + "value": [ + { + "type": 0, + "value": "Tag keys" + } + ] } }, "type": 5, @@ -3530,6 +3610,12 @@ "value": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/using_cost_models/assembly-using-cost-models#understanding-cost-distribution_using-cost-models" } ], + "docsTagMapping": [ + { + "type": 0, + "value": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/managing_cost_data_using_tagging/index" + } + ], "docsTags": [ { "type": 0, @@ -7080,6 +7166,30 @@ "value": "Input for tag name" } ] + }, + "tag_key": { + "value": [ + { + "type": 0, + "value": "Input for tag key" + } + ] + }, + "tag_key_child": { + "value": [ + { + "type": 0, + "value": "Input for child tag key" + } + ] + }, + "tag_key_parent": { + "value": [ + { + "type": 0, + "value": "Input for parent tag key" + } + ] } }, "type": 5, @@ -7288,6 +7398,30 @@ } ] }, + "tag_key": { + "value": [ + { + "type": 0, + "value": "Filter by tag key" + } + ] + }, + "tag_key_child": { + "value": [ + { + "type": 0, + "value": "Filter by child tag key" + } + ] + }, + "tag_key_parent": { + "value": [ + { + "type": 0, + "value": "Filter by parent tag key" + } + ] + }, "workload": { "value": [ { @@ -7523,6 +7657,30 @@ } ] }, + "tag_key": { + "value": [ + { + "type": 0, + "value": "Tag key" + } + ] + }, + "tag_key_child": { + "value": [ + { + "type": 0, + "value": "Child tag Key" + } + ] + }, + "tag_key_parent": { + "value": [ + { + "type": 0, + "value": "Parent tag Key" + } + ] + }, "workload": { "value": [ { @@ -10115,6 +10273,12 @@ "value": "value" } ], + "metricsOperatorVersion": [ + { + "type": 0, + "value": "Cost Management operator version" + } + ], "monthOverMonthChange": [ { "type": 0, @@ -10193,6 +10357,36 @@ "value": "There are no export files available" } ], + "noMappedTags": [ + { + "type": 0, + "value": "No mapped tags" + } + ], + "noMappedTagsDesc": [ + { + "type": 0, + "value": "Map multiple tags across data sources to be used as a single tag key for report grouping and filtering. " + }, + { + "type": 1, + "value": "warning" + }, + { + "type": 0, + "value": " Changes will be reflected within 24 hours. " + }, + { + "type": 1, + "value": "learnMore" + } + ], + "noMappedTagsWarning": [ + { + "type": 0, + "value": "Tags must be enabled to be mapped." + } + ], "noOptimizationsDesc": [ { "type": 0, @@ -11515,6 +11709,42 @@ "settingsSuccessTags": [ { "options": { + "add": { + "value": [ + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tag key added" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tag key added" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "count" + } + ] + }, "disable": { "value": [ { @@ -11589,6 +11819,42 @@ }, "other": { "value": [] + }, + "remove": { + "value": [ + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tag key removed" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " tag key removed" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "count" + } + ] } }, "type": 5, @@ -11884,12 +12150,212 @@ "value": "Value" } ], + "tagKeyChild": [ + { + "type": 0, + "value": "Child tag keys" + } + ], + "tagKeyParent": [ + { + "type": 0, + "value": "Parent tag key" + } + ], + "tagKeyParentSource": [ + { + "type": 0, + "value": "Parent integration" + } + ], "tagLabels": [ { "type": 0, "value": "Tags and labels" } ], + "tagLabelsEnable": [ + { + "type": 0, + "value": "Enable tags and labels" + } + ], + "tagLabelsMap": [ + { + "type": 0, + "value": "Map tags and labels" + } + ], + "tagMappingAddChildTags": [ + { + "type": 0, + "value": "Add child tags" + } + ], + "tagMappingAddChildTagsDesc": [ + { + "type": 0, + "value": "Select additional tag key(s) that will be mapped to the " + }, + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": " tag map. Tags that have been already mapped will not be available for selection." + } + ], + "tagMappingDelete": [ + { + "type": 0, + "value": "Delete tag mapping" + } + ], + "tagMappingDeleteDesc": [ + { + "type": 0, + "value": "This action will remove the " + }, + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": " tag mapping. Changes will be reflected within 24 hours." + } + ], + "tagMappingDesc": [ + { + "type": 0, + "value": "Combine multiple tags across your cloud integrations to group and filter similar tags with one tag key. " + }, + { + "type": 1, + "value": "warning" + }, + { + "type": 0, + "value": " Changes will be reflected within 24 hours. " + }, + { + "type": 1, + "value": "learnMore" + } + ], + "tagMappingSelectChildTags": [ + { + "type": 0, + "value": "Select child tags" + } + ], + "tagMappingSelectChildTagsDesc": [ + { + "type": 0, + "value": "Select the child tags that you want to map to a parent key. Tags that have been already mapped will not be available for selection. " + }, + { + "type": 1, + "value": "learnMore" + } + ], + "tagMappingSelectParentTags": [ + { + "type": 0, + "value": "Select parent tag" + } + ], + "tagMappingSelectParentTagsDesc": [ + { + "type": 0, + "value": "Select a parent tag key that will be mapped to the " + }, + { + "type": 1, + "value": "count" + }, + { + "type": 0, + "value": " child tags you selected in the previous step. This tag will be available for filtering in Cost Management." + } + ], + "tagMappingWarning": [ + { + "type": 0, + "value": "You must enable tags to use tag mapping." + } + ], + "tagMappingWizardDesc": [ + { + "type": 0, + "value": "Map multiple tags across data sources to be used as a single tag key for report grouping and filtering. Changes will be reflected within 24 hours." + } + ], + "tagMappingWizardNavToCreateTagMapping": [ + { + "type": 0, + "value": "Create another tag mapping" + } + ], + "tagMappingWizardNavToTagMapping": [ + { + "type": 0, + "value": "Go back to Cost Management Settings" + } + ], + "tagMappingWizardReview": [ + { + "type": 0, + "value": "Review details" + } + ], + "tagMappingWizardReviewDesc": [ + { + "type": 0, + "value": "Review and confirm the tag mappings. Click " + }, + { + "type": 1, + "value": "create" + }, + { + "type": 0, + "value": " to create the mappings, or " + }, + { + "type": 1, + "value": "back" + }, + { + "type": 0, + "value": " to revise. Changes to the reports will be reflected within 24 hours." + } + ], + "tagMappingWizardSelectChildTags": [ + { + "type": 0, + "value": "Select child tags" + } + ], + "tagMappingWizardSelectParentTag": [ + { + "type": 0, + "value": "Select parent tag" + } + ], + "tagMappingWizardSuccess": [ + { + "type": 0, + "value": "Tag mapping successful" + } + ], + "tagMappingWizardSuccessDesc": [ + { + "type": 0, + "value": "Your tag keys were successfully mapped. Changes will be reflected in report summarizations within 24 hours." + } + ], "tagNames": [ { "type": 0, @@ -11987,6 +12453,14 @@ } ] }, + "cluster_month": { + "value": [ + { + "type": 0, + "value": "cluster-month" + } + ] + }, "core_hours": { "value": [ { @@ -12023,7 +12497,7 @@ } ] }, - "gb_mo": { + "gb_month": { "value": [ { "type": 1, @@ -12047,6 +12521,30 @@ } ] }, + "gib_hours": { + "value": [ + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": " GiB-hours" + } + ] + }, + "gib_month": { + "value": [ + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": " GiB-month" + } + ] + }, "gibibyte_month": { "value": [ { @@ -12103,6 +12601,26 @@ } ] }, + "pvc_month": { + "value": [ + { + "type": 0, + "value": "PVC-month" + } + ] + }, + "tag_month": { + "value": [ + { + "type": 1, + "value": "value" + }, + { + "type": 0, + "value": " tag-month" + } + ] + }, "vm_hours": { "value": [ { @@ -12131,6 +12649,14 @@ } ] }, + "cluster_month": { + "value": [ + { + "type": 0, + "value": "cluster-month" + } + ] + }, "core": { "value": [ { @@ -12163,7 +12689,7 @@ } ] }, - "gb_mo": { + "gb_month": { "value": [ { "type": 0, @@ -12179,6 +12705,22 @@ } ] }, + "gib_hours": { + "value": [ + { + "type": 0, + "value": "GiB-hours" + } + ] + }, + "gib_month": { + "value": [ + { + "type": 0, + "value": "GiB-month" + } + ] + }, "gibibyte_month": { "value": [ { @@ -12214,6 +12756,22 @@ "other": { "value": [] }, + "pvc_month": { + "value": [ + { + "type": 0, + "value": "PVC-month" + } + ] + }, + "tag_month": { + "value": [ + { + "type": 0, + "value": "tag-month" + } + ] + }, "vm_hours": { "value": [ { @@ -12227,6 +12785,12 @@ "value": "units" } ], + "updateAvailable": [ + { + "type": 0, + "value": "Update available" + } + ], "usage": [ { "type": 0, diff --git a/locales/translations.json b/locales/translations.json index 947b8c971..c1e813d7a 100644 --- a/locales/translations.json +++ b/locales/translations.json @@ -183,6 +183,7 @@ "createCostModelNoContinue": "No, I want to continue", "createCostModelTitle": "Create a cost model", "createRate": "Create rate", + "createTagMapping": "Create tag mapping", "currency": "Currency", "currencyAbbreviations": "{symbol, select, billion {{value} B} million {{value} M} quadrillion {{value} q} thousand {{value} K} trillion {{value} t} other {}}", "currencyCalcuationsTitle": "Currency and calculations", @@ -195,6 +196,17 @@ "dashboardNetworkTitle": "Network services cost", "dashboardStorageTitle": "Storage services usage", "dashboardTotalCostTooltip": "This total cost is the sum of the infrastructure cost {infrastructureCost} and supplementary cost {supplementaryCost}", + "dataDetails": "Data details", + "dataDetailsAvailability": "Data availability", + "dataDetailsCloudData": "Cloud data", + "dataDetailsCloudIntegration": "Cloud integration data", + "dataDetailsCloudIntegrationStatus": "Cloud integration status", + "dataDetailsClusterData": "Cluster data", + "dataDetailsCostManagementData": "Cost Management data", + "dataDetailsIntegrationAndFinalization": "Data integration and finalization", + "dataDetailsIntegrationStatus": "Red Hat integration status", + "dataDetailsProcessing": "Data processing", + "dataDetailsRetrieval": "Data retrieval", "dataTableAriaLabel": "Details table", "datePickerAfterError": "Date is after the allowable range", "datePickerBeforeError": "Date is before the allowable range", @@ -212,7 +224,7 @@ "detailsEmptyState": "Processing data to generate a list of all services that sums to a total cost...", "detailsMore": "{value} more...", "detailsMoreClusters": ", {value} more...", - "detailsResourceNames": "{value, select, account {Account names} aws_category {Cost category names} cluster {Cluster names} gcp_project {GCP project names} group {Group} name {Name} node {Node names} org_unit_id {Organizational unit names} payer_tenant_id {Account names} product_service {Service names} project {Project names} region {Region names} resource_location {Region names} service {Service names} service_name {Service names} status {Status} subscription_guid {Account names} source_type {Integration} tag {Tag names} other {}}", + "detailsResourceNames": "{value, select, account {Account names} aws_category {Cost category names} cluster {Cluster names} gcp_project {GCP project names} group {Group} name {Name} node {Node names} org_unit_id {Organizational unit names} payer_tenant_id {Account names} product_service {Service names} project {Project names} region {Region names} resource_location {Region names} service {Service names} service_name {Service names} status {Status} subscription_guid {Account names} source_type {Integration} tag {Tag names} tag_key {Tag keys} other {}}", "detailsSummaryModalTitle": "{groupBy, select, account {{name} accounts} aws_category {{name} cost categories} cluster {{name} clusters} gcp_project {{name} GCP projects} node {{name} nodes} org_unit_id {{name} organizational units} payer_tenant_id {{name} accounts} product_service {{name} services} project {{name} projects} region {{name} regions} resource_location {{name} regions} service {{name} services} service_name {{name} services} subscription_guid {{name} accounts} tag {{name} tags} other {}}", "detailsUnusedCapacityLabel": "Unused capacity", "detailsUnusedRequestsLabel": "Unrequested capacity", @@ -240,6 +252,7 @@ "docsCostModelsMarkup": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/using_cost_models/assembly-setting-up-cost-models#creating-an-AWS-Azure-cost-model_setting-up-cost-models", "docsCostModelsOcp": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/using_cost_models/assembly-setting-up-cost-models#creating-an-ocp-cost-model_setting-up-cost-models", "docsPlatformProjects": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/using_cost_models/assembly-using-cost-models#understanding-cost-distribution_using-cost-models", + "docsTagMapping": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/managing_cost_data_using_tagging/index", "docsTags": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html/managing_cost_data_using_tagging/assembly-configuring-tags-and-labels-in-cost-management", "docsUsingCostModels": "https://access.redhat.com/documentation/en-us/cost_management_service/1-latest/html-single/using_cost_models/index", "download": "Download", @@ -298,15 +311,15 @@ "filterByCostCategoryKeyAriaLabel": "Cost category keys", "filterByCostCategoryValueAriaLabel": "Cost category values", "filterByCostCategoryValueButtonAriaLabel": "Filter button for cost category value", - "filterByInputAriaLabel": "{value, select, account {Input for account name} aws_category {Input for cost category name} cluster {Input for cluster name} gcp_project {Input for GCP project name} name {Input for name} node {Input for node name} org_unit_id {Input for organizational unit name} payer_tenant_id {Input for account name} product_service {Input for service_name} project {Input for project name} region {Input for region name} resource_location {Input for region name} service {Input for service name} service_name {Input for service_name} subscription_guid {Input for account name} status {Input for status value} tag {Input for tag name} other {}}", + "filterByInputAriaLabel": "{value, select, account {Input for account name} aws_category {Input for cost category name} cluster {Input for cluster name} gcp_project {Input for GCP project name} name {Input for name} node {Input for node name} org_unit_id {Input for organizational unit name} payer_tenant_id {Input for account name} product_service {Input for service_name} project {Input for project name} region {Input for region name} resource_location {Input for region name} service {Input for service name} service_name {Input for service_name} subscription_guid {Input for account name} status {Input for status value} tag {Input for tag name} tag_key {Input for tag key} tag_key_child {Input for child tag key} tag_key_parent {Input for parent tag key} other {}}", "filterByOrgUnitAriaLabel": "Organizational units", "filterByOrgUnitPlaceholder": "Choose unit", - "filterByPlaceholder": "{value, select, account {Filter by account} aws_category {Filter by cost category} cluster {Filter by cluster} container {Filter by container} description {Filter by description} gcp_project {Filter by GCP project} group {Filter by group} name {Filter by name} node {Filter by node} org_unit_id {Filter by organizational unit} payer_tenant_id {Filter by account} persistent_volume_claim {Filter by persistent volume claim} product_service {Filter by service} project {Filter by project} region {Filter by region} resource_location {Filter by region} service {Filter by service} service_name {Filter by service} source_type {Filter by integration} status {Filter by status} storage_class {Filter by StorageClass} subscription_guid {Filter by account} workload {Filter by workload name} workload_type {Filter by workload type} tag {Filter by tag} other {}}", + "filterByPlaceholder": "{value, select, account {Filter by account} aws_category {Filter by cost category} cluster {Filter by cluster} container {Filter by container} description {Filter by description} gcp_project {Filter by GCP project} group {Filter by group} name {Filter by name} node {Filter by node} org_unit_id {Filter by organizational unit} payer_tenant_id {Filter by account} persistent_volume_claim {Filter by persistent volume claim} product_service {Filter by service} project {Filter by project} region {Filter by region} resource_location {Filter by region} service {Filter by service} service_name {Filter by service} source_type {Filter by integration} status {Filter by status} storage_class {Filter by StorageClass} subscription_guid {Filter by account} workload {Filter by workload name} workload_type {Filter by workload type} tag {Filter by tag} tag_key {Filter by tag key} tag_key_child {Filter by child tag key} tag_key_parent {Filter by parent tag key} other {}}", "filterByTagKeyAriaLabel": "Tag keys", "filterByTagValueAriaLabel": "Tag values", "filterByTagValueButtonAriaLabel": "Filter button for tag value", "filterByValuePlaceholder": "Filter by value", - "filterByValues": "{value, select, account {Account} aws_category {Cost category} cluster {Cluster} container {Container} default {Default} gcp_project {GCP project} group {Group} name {Name} node {Node} org_unit_id {Organizational unit} payer_tenant_id {Account} persistent_volume_claim {Persistent volume claim} product_service {Service} project {Project} region {Region} resource_location {Region} service {Service} service_name {Service} source_type {Integration} status {Status} storage_class {StorageClass} subscription_guid {Account} tag {Tag} workload {Workload name} workload_type {Workload type} other {}}", + "filterByValues": "{value, select, account {Account} aws_category {Cost category} cluster {Cluster} container {Container} default {Default} gcp_project {GCP project} group {Group} name {Name} node {Node} org_unit_id {Organizational unit} payer_tenant_id {Account} persistent_volume_claim {Persistent volume claim} product_service {Service} project {Project} region {Region} resource_location {Region} service {Service} service_name {Service} source_type {Integration} status {Status} storage_class {StorageClass} subscription_guid {Account} tag {Tag} tag_key {Tag key} tag_key_child {Child tag Key} tag_key_parent {Parent tag Key} workload {Workload name} workload_type {Workload type} other {}}", "filterByValuesAriaLabel": "Values", "forDate": "{value} for {dateRange}", "gcp": "Google Cloud Platform", @@ -363,6 +376,7 @@ "metric": "Metric", "metricPlaceholder": "Filter by metrics", "metricValues": "{value, select, cpu {CPU} cluster {Cluster} memory {Memory} node {Node} persistent_volume_claims {Persistent volume claims} storage {Storage} other {}}", + "metricsOperatorVersion": "Cost Management operator version", "monthOverMonthChange": "Month over month change", "names": "{count, plural, one {Name} other {Names}}", "next": "next", @@ -372,6 +386,9 @@ "noDataStateRefresh": "Refresh this page", "noDataStateTitle": "Still processing the data", "noExportsStateTitle": "There are no export files available", + "noMappedTags": "No mapped tags", + "noMappedTagsDesc": "Map multiple tags across data sources to be used as a single tag key for report grouping and filtering. {warning} Changes will be reflected within 24 hours. {learnMore}", + "noMappedTagsWarning": "Tags must be enabled to be mapped.", "noOptimizationsDesc": "Resource Optimization is now available in preview for select customers. If your organization wants to participate, tell us through the Feedback button, which is purple and located on the right. Otherwise, there is not enough data available to generate an optimization.", "noOptimizationsTitle": "No optimizations available", "noProvidersStateAwsDesc": "Add an Amazon Web Services account to see a total cost breakdown of your spend by accounts, organizational units, services, regions, or tags.", @@ -513,7 +530,7 @@ "settingsSuccessCostCategories": "{value, select, enable {{count, plural, one {{count} cost category key enabled} other {{count} cost category keys enabled}}} disable {{count, plural, one {{count} cost category key disabled} other {{count} cost category keys disabled}}} other {}}", "settingsSuccessDesc": "Settings for Cost Management were replaced with new values", "settingsSuccessPlatformProjects": "{value, select, add {{count, plural, one {{count} projects added to Platform projects} other {{count} project added to Platform projects}}} remove {{count, plural, one {{count} projects removed from Platform projects} other {{count} project removed from Platform projects}}} other {}}", - "settingsSuccessTags": "{value, select, enable {{count, plural, one {{count} tag enabled} other {{count} tags enabled}}} disable {{count, plural, one {{count} tag disabled} other {{count} tags disabled}}} other {}}", + "settingsSuccessTags": "{value, select, add {{count, plural, one {{count} tag key added} other {{count} tag key added}}} enable {{count, plural, one {{count} tag enabled} other {{count} tags enabled}}} disable {{count, plural, one {{count} tag disabled} other {{count} tags disabled}}} remove {{count, plural, one {{count} tag key removed} other {{count} tag key removed}}} other {}}", "settingsSuccessTitle": "Application settings saved", "settingsTagsErrorDesc": "You currently have {value} tags enabled", "settingsTagsErrorTitle": "You can not enable more than {value} tags total", @@ -535,7 +552,31 @@ "tagHeadingKey": "Key", "tagHeadingTitle": "Tags ({value})", "tagHeadingValue": "Value", + "tagKeyChild": "Child tag keys", + "tagKeyParent": "Parent tag key", + "tagKeyParentSource": "Parent integration", "tagLabels": "Tags and labels", + "tagLabelsEnable": "Enable tags and labels", + "tagLabelsMap": "Map tags and labels", + "tagMappingAddChildTags": "Add child tags", + "tagMappingAddChildTagsDesc": "Select additional tag key(s) that will be mapped to the {value} tag map. Tags that have been already mapped will not be available for selection.", + "tagMappingDelete": "Delete tag mapping", + "tagMappingDeleteDesc": "This action will remove the {value} tag mapping. Changes will be reflected within 24 hours.", + "tagMappingDesc": "Combine multiple tags across your cloud integrations to group and filter similar tags with one tag key. {warning} Changes will be reflected within 24 hours. {learnMore}", + "tagMappingSelectChildTags": "Select child tags", + "tagMappingSelectChildTagsDesc": "Select the child tags that you want to map to a parent key. Tags that have been already mapped will not be available for selection. {learnMore}", + "tagMappingSelectParentTags": "Select parent tag", + "tagMappingSelectParentTagsDesc": "Select a parent tag key that will be mapped to the {count} child tags you selected in the previous step. This tag will be available for filtering in Cost Management.", + "tagMappingWarning": "You must enable tags to use tag mapping.", + "tagMappingWizardDesc": "Map multiple tags across data sources to be used as a single tag key for report grouping and filtering. Changes will be reflected within 24 hours.", + "tagMappingWizardNavToCreateTagMapping": "Create another tag mapping", + "tagMappingWizardNavToTagMapping": "Go back to Cost Management Settings", + "tagMappingWizardReview": "Review details", + "tagMappingWizardReviewDesc": "Review and confirm the tag mappings. Click {create} to create the mappings, or {back} to revise. Changes to the reports will be reflected within 24 hours.", + "tagMappingWizardSelectChildTags": "Select child tags", + "tagMappingWizardSelectParentTag": "Select parent tag", + "tagMappingWizardSuccess": "Tag mapping successful", + "tagMappingWizardSuccessDesc": "Your tag keys were successfully mapped. Changes will be reflected in report summarizations within 24 hours.", "tagNames": "Tag names", "timeOfExport": "Time of export", "to": "to", @@ -547,8 +588,9 @@ "toolBarPriceListMeasurementPlaceHolder": "Filter by measurements", "toolBarPriceListMetricPlaceHolder": "Filter by metrics", "typeaheadAriaClear": "Clear button and input", - "unitTooltips": "{units, select, byte_ms {{value} Byte-ms} core_hours {{value} core-hours} gb {{value} GB} gb_hours {{value} GB-hours} gb_mo {{value} GB-month} gb_ms {{value} GB-ms} gibibyte_month {{value} GiB-month} hour {{value} hours} hrs {{value} hours} ms {{value} milliseconds} vm_hours {{value} VM-hours} other {{value}}}", - "units": "{units, select, byte_ms {Byte-ms} core {core} core_hours {core-hours} gb {GB} gb_hours {GB-hours} gb_mo {GB-month} gb_ms {GB-ms} gibibyte_month {GiB-month} hour {hours} hrs {hours} ms {milliseconds} vm_hours {VM-hours} other {}}", + "unitTooltips": "{units, select, byte_ms {{value} Byte-ms} cluster_month {cluster-month} core_hours {{value} core-hours} gb {{value} GB} gb_hours {{value} GB-hours} gb_month {{value} GB-month} gb_ms {{value} GB-ms} gib_hours {{value} GiB-hours} gib_month {{value} GiB-month} gibibyte_month {{value} GiB-month} hour {{value} hours} hrs {{value} hours} ms {{value} milliseconds} pvc_month {PVC-month} tag_month {{value} tag-month} vm_hours {{value} VM-hours} other {{value}}}", + "units": "{units, select, byte_ms {Byte-ms} cluster_month {cluster-month} core {core} core_hours {core-hours} gb {GB} gb_hours {GB-hours} gb_month {GB-month} gb_ms {GB-ms} gib_hours {GiB-hours} gib_month {GiB-month} gibibyte_month {GiB-month} hour {hours} hrs {hours} ms {milliseconds} pvc_month {PVC-month} tag_month {tag-month} vm_hours {VM-hours} other {}}", + "updateAvailable": "Update available", "usage": "Usage", "usageCostDesc": "The portion of cost calculated by applying hourly and/or monthly price list rates to metrics.", "usageCostTitle": "Usage cost", diff --git a/package-lock.json b/package-lock.json index 5a6a6ebd6..aba5a8a46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,68 +10,67 @@ "hasInstallScript": true, "license": "GNU AGPLv3", "dependencies": { - "@patternfly/patternfly": "^5.2.0", - "@patternfly/react-charts": "^7.2.0", - "@patternfly/react-component-groups": "^5.0.0", - "@patternfly/react-core": "^5.2.0", - "@patternfly/react-icons": "^5.2.0", - "@patternfly/react-table": "^5.2.0", - "@patternfly/react-tokens": "^5.2.0", - "@redhat-cloud-services/frontend-components": "^4.2.3", + "@patternfly/patternfly": "5.2.0", + "@patternfly/react-charts": "7.2.2", + "@patternfly/react-component-groups": "5.1.0", + "@patternfly/react-core": "5.2.0", + "@patternfly/react-icons": "5.2.0", + "@patternfly/react-table": "5.2.0", + "@patternfly/react-tokens": "5.2.0", + "@redhat-cloud-services/frontend-components": "^4.2.5", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-translations": "^3.2.7", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.7", "@redhat-cloud-services/rbac-client": "^1.2.13", - "@reduxjs/toolkit": "^2.1.0", - "@unleash/proxy-client-react": "^4.1.2", - "axios": "^1.6.7", - "date-fns": "^3.3.1", + "@reduxjs/toolkit": "^2.2.1", + "@unleash/proxy-client-react": "^4.2.2", + "axios": "^1.6.8", + "date-fns": "^3.6.0", "js-file-download": "^0.4.12", "lodash": "^4.17.21", - "qs": "^6.11.2", + "qs": "^6.12.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-intl": "^6.6.2", "react-redux": "^9.1.0", - "react-router-dom": "^6.22.0", + "react-router-dom": "^6.22.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", - "unleash-proxy-client": "^3.3.1", - "victory-core": "36.8.6", - "yaml": "^2.3.4" + "unleash-proxy-client": "^3.3.2", + "victory-core": "^37.0.0" }, "devDependencies": { "@formatjs/cli": "^6.2.7", "@formatjs/ecma402-abstract": "^1.18.2", "@formatjs/icu-messageformat-parser": "^2.7.6", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", - "@redhat-cloud-services/frontend-components-config": "^6.0.9", - "@redhat-cloud-services/tsc-transform-imports": "^1.0.7", + "@redhat-cloud-services/frontend-components-config": "^6.0.11", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.8", "@swc/core": "1.3.105", "@swc/jest": "^0.2.36", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", - "@types/qs": "^6.9.11", - "@types/react": "^18.2.53", - "@types/react-dom": "^18.2.18", + "@types/qs": "^6.9.12", + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", "@types/react-redux": "^7.1.33", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-plugin-formatjs": "^4.12.2", "eslint-plugin-jest-dom": "^5.1.0", - "eslint-plugin-jsdoc": "^48.0.4", - "eslint-plugin-markdown": "^3.0.1", + "eslint-plugin-jsdoc": "^48.2.1", + "eslint-plugin-markdown": "^4.0.1", "eslint-plugin-patternfly-react": "^5.2.1", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-simple-import-sort": "^12.0.0", "eslint-plugin-sort-keys-fix": "^1.1.2", "eslint-plugin-testing-library": "^6.2.0", "git-revision-webpack-plugin": "^5.0.0", @@ -87,7 +86,7 @@ "rimraf": "^5.0.5", "ts-jest": "^29.1.2", "ts-patch": "^3.1.2", - "typescript": "^5.3.3", + "typescript": "^5.4.2", "webpack-bundle-analyzer": "^4.10.1" }, "engines": { @@ -111,13 +110,13 @@ "dev": true }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -146,9 +145,9 @@ } }, "node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -156,11 +155,11 @@ "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -290,9 +289,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", "dev": true, "engines": { "node": ">=6.9.0" @@ -350,14 +349,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dev": true, "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -378,9 +377,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -567,9 +566,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -578,23 +577,23 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", @@ -603,8 +602,8 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -613,9 +612,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -742,9 +741,9 @@ "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==" }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", - "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz", + "integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==", "dev": true, "dependencies": { "comment-parser": "1.4.1", @@ -864,9 +863,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2009,45 +2008,45 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -2057,9 +2056,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2131,9 +2130,9 @@ } }, "node_modules/@openshift/dynamic-plugin-sdk-webpack": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.0.1.tgz", - "integrity": "sha512-ZlY57t1WIl8B8XNPoq+CuU/+Ll4/ZX/7IO/dxn+7dp1S/NUmdvgwv01mXpUcjviOUhhgWl/dK2WvCQTzz6CoZg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.0.2.tgz", + "integrity": "sha512-zgRLt9WM63aGYygrQEtd8QtWoP5zYWr67kzlRKzGcHeRbWDgiHnY7FOiDUM04bYeb4lL6qv3iErEnrtvVpyh0Q==", "dev": true, "dependencies": { "lodash": "^4.17.21", @@ -2216,42 +2215,60 @@ "integrity": "sha512-phdsXcCRO+JICFXIKtORxSbOWoBr9zRCgtFTKTJ8hAIzm6wEUCdcHZrvsd+SXNR3q/4b/+KlmHUC4Q4KGUiuYw==" }, "node_modules/@patternfly/react-charts": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-7.2.0.tgz", - "integrity": "sha512-IDg5KfF4VK/4C8qXfnFz9Tsq041RFJlHOGAH5Blb2ZTzTXp7ssZtuPxZvqqKxnIfAoMO+sy9DM8+vgvYPeH0aw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-7.2.2.tgz", + "integrity": "sha512-1PFuvXz3mm/o/O+BQ2/2e66ncvtV8XIYxFaimurslCLTygodOvjBDDu/D/5tNa3HLxvA+fm2Q58893POGZi+bw==", "dependencies": { - "@patternfly/react-styles": "^5.2.0", - "@patternfly/react-tokens": "^5.2.0", + "@patternfly/react-styles": "^5.2.1", + "@patternfly/react-tokens": "^5.2.1", "hoist-non-react-statics": "^3.3.0", "lodash": "^4.17.21", "tslib": "^2.5.0", - "victory-area": "^36.8.1", - "victory-axis": "^36.8.1", - "victory-bar": "^36.8.1", - "victory-box-plot": "^36.8.1", - "victory-chart": "^36.8.1", - "victory-core": "^36.8.1", - "victory-create-container": "^36.8.1", - "victory-cursor-container": "^36.8.1", - "victory-group": "^36.8.1", - "victory-legend": "^36.8.1", - "victory-line": "^36.8.1", - "victory-pie": "^36.8.1", - "victory-scatter": "^36.8.1", - "victory-stack": "^36.8.1", - "victory-tooltip": "^36.8.1", - "victory-voronoi-container": "^36.8.1", - "victory-zoom-container": "^36.8.1" + "victory-area": "^36.9.1", + "victory-axis": "^36.9.1", + "victory-bar": "^36.9.1", + "victory-box-plot": "^36.9.1", + "victory-chart": "^36.9.1", + "victory-core": "^36.9.1", + "victory-create-container": "^36.9.1", + "victory-cursor-container": "^36.9.1", + "victory-group": "^36.9.1", + "victory-legend": "^36.9.1", + "victory-line": "^36.9.1", + "victory-pie": "^36.9.1", + "victory-scatter": "^36.9.1", + "victory-stack": "^36.9.1", + "victory-tooltip": "^36.9.1", + "victory-voronoi-container": "^36.9.1", + "victory-zoom-container": "^36.9.1" }, "peerDependencies": { "react": "^17 || ^18", "react-dom": "^17 || ^18" } }, + "node_modules/@patternfly/react-charts/node_modules/@patternfly/react-tokens": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-5.2.1.tgz", + "integrity": "sha512-8GYz/jnJTGAWUJt5eRAW5dtyiHPKETeFJBPGHaUQnvi/t1ZAkoy8i4Kd/RlHsDC7ktiu813SKCmlzwBwldAHKg==" + }, + "node_modules/@patternfly/react-charts/node_modules/victory-core": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", + "dependencies": { + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.0", + "victory-vendor": "^36.9.2" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, "node_modules/@patternfly/react-component-groups": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.0.0.tgz", - "integrity": "sha512-ON4h4SKOCgLRgZLd/FOj44wU19ytvFPjflxPSYU0KfCWlVgb6F62+l316/Va/tzDo/AwZypnUxOEksXDv+C2+A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-component-groups/-/react-component-groups-5.1.0.tgz", + "integrity": "sha512-DCEKc7Iyuf/7prI2a6mWyM/qwgBBtEzxDnNSpkZyes+Q3os0F5lUxW5qZVrg3JcNp0/J1183vwLdp1VoJRmcJw==", "dependencies": { "@patternfly/react-core": "^5.1.1", "@patternfly/react-icons": "^5.1.1", @@ -2291,9 +2308,9 @@ } }, "node_modules/@patternfly/react-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.2.0.tgz", - "integrity": "sha512-u8in9RSU8YzcT0npgVeiIHi1Bdp7UdER9azWGi7vlJWooRI1hgQjIDpm22wopGFg0h8VOqhfIFWIyvqxuzhW6A==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.2.1.tgz", + "integrity": "sha512-GT96hzI1QenBhq6Pfc51kxnj9aVLjL1zSLukKZXcYVe0HPOy0BFm90bT1Fo4e/z7V9cDYw4SqSX1XLc3O4jsTw==" }, "node_modules/@patternfly/react-table": { "version": "5.2.0", @@ -2390,9 +2407,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, "node_modules/@redhat-cloud-services/eslint-config-redhat-cloud-services": { @@ -2450,22 +2467,22 @@ } }, "node_modules/@redhat-cloud-services/frontend-components": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.2.3.tgz", - "integrity": "sha512-mc1HNFupS4EQY1ipZa8Tw/lR8L1J4NEtcSVbvMeKwvfFLlrUIG8isTnEHE+e8bjIcMhnCOO37b0K089uSzJHOw==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components/-/frontend-components-4.2.5.tgz", + "integrity": "sha512-lTVew1R6LccTMqDoaevtk6WBbH0VoiNPspPMSuEYT+/JIQLjs5NgpBNWTGImyp/4/JFwMiQq0ewtimQLq0E3Ig==", "dependencies": { "@patternfly/react-component-groups": "^5.0.0", "@redhat-cloud-services/frontend-components-utilities": "^4.0.0", "@redhat-cloud-services/types": "^0.0.24", "@scalprum/core": "^0.7.0", "@scalprum/react-core": "^0.7.0", + "classnames": "^2.2.5", "sanitize-html": "^2.7.2" }, "peerDependencies": { "@patternfly/react-core": "^5.0.0", "@patternfly/react-icons": "^5.0.0", "@patternfly/react-table": "^5.0.0", - "classnames": "^2.2.5", "lodash": "^4.17.15", "prop-types": "^15.6.2", "react": "^18.2.0", @@ -2476,17 +2493,17 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.0.9.tgz", - "integrity": "sha512-2W0AHnCvGPKF1WTnMFdEkF/A/TCtHo+biDhH5MdJZzITvBbi4fXTn0IbdN9BVqnwrRujJO273TQ5aVU1WiIb0w==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-config/-/frontend-components-config-6.0.11.tgz", + "integrity": "sha512-HVyF5rP3YetC/9dCY7uGmVzpfKS4lLzvKx2slS/XpeZoQtcoalWwtmjmiWYm7KT148gzxIdxSP8el5H1Bc5DhA==", "dev": true, "dependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", - "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.4", - "@redhat-cloud-services/tsc-transform-imports": "^1.0.3", + "@redhat-cloud-services/frontend-components-config-utilities": "^3.0.5", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.8", "@swc/core": "^1.3.76", "assert": "^2.0.0", - "axios": "^0.27.2", + "axios": "^0.28.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "chalk": "^4.1.2", @@ -2626,13 +2643,14 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-config/node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz", + "integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==", "dev": true, "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/@redhat-cloud-services/frontend-components-config/node_modules/chalk": { @@ -2894,14 +2912,15 @@ "peer": true }, "node_modules/@redhat-cloud-services/frontend-components-utilities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.2.tgz", - "integrity": "sha512-LUAaJwpi8EmyrNrGum53HcSpO0rrwvXkdEmaXjfooRlvVtLz8twsjaiM2jFfqWXbZMq43gQufn/wy8nquRoq6w==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/frontend-components-utilities/-/frontend-components-utilities-4.0.7.tgz", + "integrity": "sha512-Mr0u1rThsspet0SkR12mQRYO2xbA4zKCxSpHs/QKLlSIgQRKqVzmdf7J2SSXGXJJ9FeSE2wXJdI8a2F9TVVQnQ==", "dependencies": { + "@redhat-cloud-services/rbac-client": "^1.0.100", "@redhat-cloud-services/types": "^0.0.24", "@sentry/browser": "^5.30.0", "awesome-debounce-promise": "^2.1.0", - "axios": "^0.27.2", + "axios": "^0.28.0", "commander": "^2.20.3", "mkdirp": "^1.0.4", "react-content-loader": "^6.2.0" @@ -2909,7 +2928,6 @@ "peerDependencies": { "@patternfly/react-core": "^5.0.0", "@patternfly/react-table": "^5.0.0", - "@redhat-cloud-services/rbac-client": "^1.0.100", "cypress": ">=12.0.0 < 14.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2918,12 +2936,13 @@ } }, "node_modules/@redhat-cloud-services/frontend-components-utilities/node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz", + "integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/@redhat-cloud-services/rbac-client": { @@ -2944,9 +2963,9 @@ } }, "node_modules/@redhat-cloud-services/tsc-transform-imports": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@redhat-cloud-services/tsc-transform-imports/-/tsc-transform-imports-1.0.7.tgz", - "integrity": "sha512-ps5OL6r/8jQRThydh1lzgyUcY/7jObHK5nOhk2c9Ztb6qphjDgsm4lzcWFQcPGsdneXf9swsAF4O6vvcq810yw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@redhat-cloud-services/tsc-transform-imports/-/tsc-transform-imports-1.0.8.tgz", + "integrity": "sha512-5QO9OC8bDU8DIPFUbbUtLP8sArv36vSAwva7F8YYRIUWe5py7GbJTqA1PSxBeD1i9wemmbLkW09gVDYJMt5peg==", "dev": true, "dependencies": { "glob": "10.3.3" @@ -2983,9 +3002,9 @@ "integrity": "sha512-P50stc+mnWLycID46/AKmD/760r5N1eoam//O6MUVriqVorUdht7xkUL78aJZU1vw8WW6xlrDHwz3F6BM148qg==" }, "node_modules/@reduxjs/toolkit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.1.0.tgz", - "integrity": "sha512-nfJ/b4ZhzUevQ1ZPKjlDL6CMYxO4o7ZL7OSsvSOxzT/EN11LsBDgTqP7aedHtBrFSVoK7oTP1SbMWUwGb30NLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz", + "integrity": "sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw==", "dependencies": { "immer": "^10.0.3", "redux": "^5.0.1", @@ -3006,9 +3025,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", - "integrity": "sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", "engines": { "node": ">=14.0.0" } @@ -3143,9 +3162,9 @@ "dev": true }, "node_modules/@sindresorhus/merge-streams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", - "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "dev": true, "engines": { "node": ">=18" @@ -3394,10 +3413,13 @@ } }, "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", + "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } }, "node_modules/@testing-library/dom": { "version": "9.3.4", @@ -3755,9 +3777,9 @@ } }, "node_modules/@types/d3-path": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", - "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" }, "node_modules/@types/d3-scale": { "version": "4.0.8", @@ -3791,9 +3813,9 @@ "integrity": "sha512-awNxydYSU+E2vL7EiOAMtiSOfL5gUM5X4YSE2A92qpxDJQ/rXz6oMPYBFDcDywlUmvIDI6zsqgq17cGm5CITQw==" }, "node_modules/@types/eslint": { - "version": "8.56.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", - "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", + "version": "8.56.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.5.tgz", + "integrity": "sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw==", "dev": true, "dependencies": { "@types/estree": "*", @@ -3991,9 +4013,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==" }, "node_modules/@types/mdast": { "version": "3.0.15", @@ -4017,9 +4039,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", "devOptional": true, "dependencies": { "undici-types": "~5.26.4" @@ -4052,9 +4074,9 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "version": "6.9.12", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", + "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==", "dev": true }, "node_modules/@types/range-parser": { @@ -4064,9 +4086,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.53.tgz", - "integrity": "sha512-52IHsMDT8qATp9B9zoOyobW8W3/0QhaJQTw1HwRj0UY2yBpCAQ7+S/CqHYQ8niAm3p4ji+rWUQ9UCib0GxQ60w==", + "version": "18.2.67", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.67.tgz", + "integrity": "sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4074,9 +4096,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.18", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", - "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", "dev": true, "dependencies": { "@types/react": "*" @@ -4127,9 +4149,9 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/send": { @@ -4208,9 +4230,9 @@ "dev": true }, "node_modules/@types/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-Hm/T0kV3ywpJyMGNbsItdivRhYNCQQf1IIsYsXnoVPES4t+FMLyDe0/K+Ea7ahWtMtSNb22ZdY7MIyoD9rqARg==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz", + "integrity": "sha512-TU+fZFBTBcXj/GpDpDaBmgWk/gn96kMZ+uocaFUlV2f8a6WdMzzI44QBCmGcCiYR0Y6ZlNRiyUyKKt5nl/lbzQ==", "dev": true, "dependencies": { "source-map": "^0.6.1" @@ -4305,16 +4327,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4330,8 +4352,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -4373,15 +4395,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -4392,7 +4414,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -4401,13 +4423,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4418,13 +4440,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -4436,7 +4458,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -4445,9 +4467,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4458,13 +4480,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4519,17 +4541,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" }, "engines": { @@ -4540,7 +4562,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { @@ -4577,12 +4599,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4612,9 +4634,9 @@ "dev": true }, "node_modules/@unleash/proxy-client-react": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@unleash/proxy-client-react/-/proxy-client-react-4.1.2.tgz", - "integrity": "sha512-AW6CKMHfLoUDzLahXHTD9bCKMKC31kC0kYLDrg089vf9gTAuGNnrY0bIEmrkh0M06JVkbXq9H8As3EIXdioizg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@unleash/proxy-client-react/-/proxy-client-react-4.2.2.tgz", + "integrity": "sha512-KRg1dAQfxLSBe8O2i6GIGDM+7HUdUu/ntREy+JeYbmJ2b6ApFtLX/wt+3T/CjrTYjwRlZKZYJDx5E4fRDOQqpQ==", "engines": { "node": ">=16.0.0" }, @@ -4623,9 +4645,9 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -4645,9 +4667,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -4668,15 +4690,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -4704,28 +4726,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -4733,24 +4755,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -4759,12 +4781,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -5183,17 +5205,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.4.tgz", + "integrity": "sha512-BMtLxpV+8BD+6ZPFIWmnUBpQoy+A+ujcg4rhp2iwCRJYA7PEh2MS4NL3lz8EiDlLrJPp2hg9qWihr5pd//jcGw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5238,6 +5279,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, "node_modules/array.prototype.tosorted": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", @@ -5331,15 +5384,6 @@ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "peer": true }, - "node_modules/asynciterator.prototype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", - "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5363,10 +5407,13 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", - "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -5435,11 +5482,11 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -5633,7 +5680,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -5688,12 +5736,15 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bl": { @@ -5744,13 +5795,13 @@ "peer": true }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -5758,7 +5809,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -5844,9 +5895,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -5863,8 +5914,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -6018,13 +6069,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6059,9 +6115,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001584", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz", - "integrity": "sha512-LOz7CCQ9M1G7OjJOF9/mzmqmj3jE/7VOmrfw6Mgs0E8cjOsbRXQJHsPBfmBOXDskXKrHLyyW3n7kpDW/4BsfpQ==", + "version": "1.0.30001599", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", + "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", "dev": true, "funding": [ { @@ -6153,16 +6209,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -6175,6 +6225,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -6223,8 +6276,7 @@ "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "peer": true + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clean-css": { "version": "5.3.3", @@ -6581,7 +6633,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concurrently": { "version": "7.6.0", @@ -6821,12 +6874,12 @@ } }, "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", - "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", + "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", "dev": true, "dependencies": { - "@sindresorhus/merge-streams": "^1.0.0", + "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", "ignore": "^5.2.4", "path-type": "^5.0.0", @@ -6899,9 +6952,9 @@ "peer": true }, "node_modules/core-js-pure": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.1.tgz", - "integrity": "sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -6939,15 +6992,6 @@ "node": ">=10" } }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -7276,9 +7320,9 @@ } }, "node_modules/cypress/node_modules/@types/node": { - "version": "16.18.79", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.79.tgz", - "integrity": "sha512-Qd7jdLR5zmnIyMhfDrfPqN5tUCvreVpP3Qrf2oSM+F7SNzlb/MwHISGUkdFHtevfkPJ3iAGyeQI/jsbh9EStgQ==", + "version": "16.18.89", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.89.tgz", + "integrity": "sha512-QlrE8QI5z62nfnkiUZysUsAaxWaTMoGqFVcB3PvK1WxJ0c699bacErV4Fabe9Hki6ZnaHalgzihLbTl2d34XfQ==", "peer": true }, "node_modules/cypress/node_modules/ansi-styles": { @@ -7580,10 +7624,61 @@ "node": ">=12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", - "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -7757,16 +7852,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -8110,9 +8208,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.656", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", - "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "version": "1.4.708", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz", + "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==", "dev": true }, "node_modules/emittery": { @@ -8161,9 +8259,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", + "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -8228,50 +8326,57 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", + "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" @@ -8286,6 +8391,17 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -8315,25 +8431,28 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", - "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", + "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==", "dev": true, "dependencies": { - "asynciterator.prototype": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.1", - "es-set-tostringtag": "^2.0.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", + "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.0.1" + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-module-lexer": { @@ -8342,15 +8461,27 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -8437,16 +8568,16 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -8655,9 +8786,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -8720,28 +8851,173 @@ "eslint": ">=8" } }, - "node_modules/eslint-plugin-formatjs": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-4.12.2.tgz", - "integrity": "sha512-b4iEsi0Y3zy7J6xjxlhrIaDFJa27OiLwardvCRBRHALoZs8rNJ0oQIW6ymUgELLEMeFuEMAd2837M+n5SHJutg==", + "node_modules/eslint-plugin-formatjs": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-4.12.2.tgz", + "integrity": "sha512-b4iEsi0Y3zy7J6xjxlhrIaDFJa27OiLwardvCRBRHALoZs8rNJ0oQIW6ymUgELLEMeFuEMAd2837M+n5SHJutg==", + "dev": true, + "dependencies": { + "@formatjs/icu-messageformat-parser": "2.7.6", + "@formatjs/ts-transformer": "3.13.12", + "@types/eslint": "7 || 8", + "@types/picomatch": "^2.3.0", + "@typescript-eslint/utils": "^6.18.1", + "emoji-regex": "^10.2.1", + "magic-string": "^0.30.0", + "picomatch": "^2.3.1", + "tslib": "2.6.2", + "typescript": "5", + "unicode-emoji-utils": "^1.2.0" + }, + "peerDependencies": { + "eslint": "7 || 8" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-formatjs/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.6", - "@formatjs/ts-transformer": "3.13.12", - "@types/eslint": "7 || 8", - "@types/picomatch": "^2.3.0", - "@typescript-eslint/utils": "^6.18.1", - "emoji-regex": "^10.2.1", - "magic-string": "^0.30.0", - "picomatch": "^2.3.1", - "tslib": "2.6.2", - "typescript": "5", - "unicode-emoji-utils": "^1.2.0" + "lru-cache": "^6.0.0" }, - "peerDependencies": { - "eslint": "7 || 8" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/eslint-plugin-formatjs/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/eslint-plugin-import": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", @@ -8817,9 +9093,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "27.6.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.3.tgz", - "integrity": "sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==", + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^5.10.0" @@ -8828,7 +9104,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0", + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", "eslint": "^7.0.0 || ^8.0.0", "jest": "*" }, @@ -9011,19 +9287,19 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.0.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.4.tgz", - "integrity": "sha512-A0cH+5svWPXzGZszBjXA1t0aAqVGS+/x3i02KFmb73rU0iMLnadEcVWcD/dGBZHIfAMKr3YpWh58f6wn4N909w==", + "version": "48.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.1.tgz", + "integrity": "sha512-iUvbcyDZSO/9xSuRv2HQBw++8VkV/pt3UWtX9cpPH0l7GKPq78QC/6+PmyQHHvNZaTjAce6QVciEbnc6J/zH5g==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.41.0", + "@es-joy/jsdoccomment": "~0.42.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", + "semver": "^7.6.0", "spdx-expression-parse": "^4.0.0" }, "engines": { @@ -9146,18 +9422,18 @@ } }, "node_modules/eslint-plugin-markdown": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-3.0.1.tgz", - "integrity": "sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-markdown/-/eslint-plugin-markdown-4.0.1.tgz", + "integrity": "sha512-5/MnGvYU0i8MbHH5cg8S+Vl3DL+bqRNYshk1xUO86DilNBaxtTkhH+5FD0/yO03AmlI6+lfNFdk2yOw72EPzpA==", "dev": true, "dependencies": { "mdast-util-from-markdown": "^0.8.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": ">=8" } }, "node_modules/eslint-plugin-n": { @@ -9402,27 +9678,29 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.34.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", + "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.0.17", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.10" }, "engines": { "node": ">=4" @@ -9504,9 +9782,9 @@ } }, "node_modules/eslint-plugin-simple-import-sort": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz", - "integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.0.0.tgz", + "integrity": "sha512-8o0dVEdAkYap0Cn5kNeklaKcT1nUsa3LITWEuFk3nJifOoD+5JQGoyDUW2W/iPWwBsNBJpyJS9y4je/BgxLcyQ==", "dev": true, "peerDependencies": { "eslint": ">=5.0.0" @@ -10144,14 +10422,14 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", + "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.5.0", @@ -10513,9 +10791,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/focus-trap": { @@ -10527,9 +10805,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -10819,7 +11097,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -10889,11 +11168,11 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.3.tgz", - "integrity": "sha512-JIcZczvcMVE7AUOP+X72bh8HqHBRxFdz5PDHYtNG/lE3yk9b3KZBJlwFcTyPYjg3L4RLLmZJzvjxhaZVapxFrQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "es-errors": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -10931,13 +11210,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -10947,9 +11227,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", "dev": true, "peer": true, "dependencies": { @@ -10993,6 +11273,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11030,6 +11311,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11039,6 +11321,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -11205,20 +11488,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -11253,9 +11536,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -11347,9 +11630,9 @@ } }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "dev": true, "funding": [ { @@ -11746,9 +12029,9 @@ } }, "node_modules/immer": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", - "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz", + "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -11816,6 +12099,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -11824,7 +12108,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "2.0.0", @@ -11941,12 +12226,12 @@ } }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -12159,6 +12444,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -12305,10 +12605,13 @@ } }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12330,9 +12633,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -12449,21 +12752,27 @@ } }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12543,10 +12852,13 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12564,13 +12876,16 @@ } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12624,14 +12939,14 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -12731,9 +13046,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -15622,9 +15937,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -15864,9 +16179,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", - "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", + "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", "dev": true, "dependencies": { "schema-utils": "^4.0.0", @@ -16336,13 +16651,13 @@ } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -16832,6 +17147,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -17096,10 +17412,19 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.36.tgz", + "integrity": "sha512-/n7eumA6ZjFHAsbX30yhHup/IMkOmlmvtEi7P+6RMYf+bGJSUHc3geH4a0NSZxAz/RJfiS9tooCTs9LAVYUZKw==", "funding": [ { "type": "opencollective", @@ -17117,7 +17442,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.1.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -17183,9 +17508,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -17405,11 +17730,11 @@ ] }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -17462,9 +17787,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -17624,11 +17949,11 @@ } }, "node_modules/react-router": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.0.tgz", - "integrity": "sha512-q2yemJeg6gw/YixRlRnVx6IRJWZD6fonnfZhN1JIOhV2iJCPeRNSH3V1ISwHf+JWcESzLC3BOLD1T07tmO5dmg==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", "dependencies": { - "@remix-run/router": "1.15.0" + "@remix-run/router": "1.15.3" }, "engines": { "node": ">=14.0.0" @@ -17638,12 +17963,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.0.tgz", - "integrity": "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", "dependencies": { - "@remix-run/router": "1.15.0", - "react-router": "6.22.0" + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" }, "engines": { "node": ">=14.0.0" @@ -17761,16 +18086,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", - "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0", - "get-intrinsic": "^1.2.3", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -17787,14 +18112,15 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -18067,13 +18393,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", - "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -18104,13 +18430,13 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "engines": { @@ -18126,9 +18452,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sanitize-html": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz", - "integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", + "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -18208,9 +18534,9 @@ } }, "node_modules/sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", + "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -18478,29 +18804,31 @@ } }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -18558,13 +18886,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18672,9 +19004,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.1.0.tgz", + "integrity": "sha512-9vC2SfsJzlej6MAaMPLu8HiBSHGdRAJ9hVFYN1ibZoNkeanmDmLUcIrj6G9DGL7XMJ54AKg/G75akXl1/izTOw==", "engines": { "node": ">=0.10.0" } @@ -18758,9 +19090,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", - "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -18774,9 +19106,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/spdy": { @@ -19003,14 +19335,15 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -19020,14 +19353,14 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -19200,9 +19533,9 @@ } }, "node_modules/terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "version": "5.29.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", + "integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -19408,30 +19741,12 @@ } }, "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "peer": true, - "dependencies": { - "rimraf": "^3.0.0" - }, "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.14" } }, "node_modules/tmpl": { @@ -19528,12 +19843,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.0.tgz", - "integrity": "sha512-d+3WxW4r8WQy2cZWpNRPPGExX8ffOLGcIhheUANKbL5Sqjbhkneki76fRAWeXkaslV2etTb4tSJBSxOsH5+CJw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=18" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -19982,29 +20297,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -20014,16 +20330,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -20033,14 +20350,20 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -20055,9 +20378,9 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -20143,9 +20466,9 @@ } }, "node_modules/unleash-proxy-client": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/unleash-proxy-client/-/unleash-proxy-client-3.3.1.tgz", - "integrity": "sha512-tekoTRWBK+B0uG9o/5CJ+LAKNZv+OdLPFVXFESQU2JHnyjtedLyTAmdWhPIxC/eCKiBBay1/2BEWOUCJaL1vzQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/unleash-proxy-client/-/unleash-proxy-client-3.3.2.tgz", + "integrity": "sha512-ccv+85nMJAhaaE3q4BJd4Sl894kCEbYr7CwMglvuFrBhdoOGAM5s61NXksk/aA7AGXLeAzNGH5zwY5dn0H9i9A==", "dependencies": { "tiny-emitter": "^2.1.0", "uuid": "^9.0.1" @@ -20359,494 +20682,514 @@ } }, "node_modules/victory-area": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-area/-/victory-area-36.9.0.tgz", - "integrity": "sha512-4oiQJe+1hVqYyTS08iUwy6NxOqgKS2vj0laM169r/AMqART002z9YHTd2PB958xD3qXTpUbdi0CmvglpEyOqpQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-area/-/victory-area-36.9.2.tgz", + "integrity": "sha512-32aharvPf2RgdQB+/u1j3/ajYFNH/7ugLX9ZRpdd65gP6QEbtXL+58gS6CxvFw6gr/y8a0xMlkMKkpDVacXLpw==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0", - "victory-vendor": "^36.9.0" + "victory-core": "^36.9.2", + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-area/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-axis": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-axis/-/victory-axis-36.9.0.tgz", - "integrity": "sha512-tFNr5S9QInDFxgYRJuLmkInFjqWmnAHhYEP+sGp8yv0y9Amiq/Vkeign2ITSxEVn/TOqSOiNV82UCxR+3rn2xg==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-axis/-/victory-axis-36.9.2.tgz", + "integrity": "sha512-4Odws+IAjprJtBg2b2ZCxEPgrQ6LgIOa22cFkGghzOSfTyNayN4M3AauNB44RZyn2O/hDiM1gdBkEg1g9YDevQ==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-axis/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-bar": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-bar/-/victory-bar-36.9.0.tgz", - "integrity": "sha512-t2XbSi1CXgwVkCipkQLxggKxMHv5dI5FG6EwBxuK6gSeouwZCdkWczgjX3X/vpNnNql/N/KOCAOmhv4KbgA8KQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-bar/-/victory-bar-36.9.2.tgz", + "integrity": "sha512-R3LFoR91FzwWcnyGK2P8DHNVv9gsaWhl5pSr2KdeNtvLbZVEIvUkTeVN9RMBMzterSFPw0mbWhS1Asb3sV6PPw==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0", - "victory-vendor": "^36.9.0" + "victory-core": "^36.9.2", + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-bar/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-box-plot": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-box-plot/-/victory-box-plot-36.9.0.tgz", - "integrity": "sha512-esOYzfo0Tyv43fRRQWCfOgIX3aJmKOcKWuspVuTZJX02uT/PXN6aAQRKIjHZYS/0t9grpWLb5ecksuvy6Qalng==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-box-plot/-/victory-box-plot-36.9.2.tgz", + "integrity": "sha512-nUD45V/YHDkAKZyak7YDsz+Vk1F9N0ica3jWQe0AY0JqD9DleHa8RY/olSVws26kLyEj1I+fQqva6GodcLaIqQ==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0", - "victory-vendor": "^36.9.0" + "victory-core": "^36.9.2", + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-box-plot/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-brush-container": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-36.9.0.tgz", - "integrity": "sha512-t25+S6b6IVzKfNIokEfKndIN0wtrlkBazDJcPOn7ogSbEQx6mJfPn8yP6R2C5qXhZcSv9LaD6vcc5+IYw21FMg==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-36.9.2.tgz", + "integrity": "sha512-KcQjzFeo40tn52cJf1A02l5MqeR9GKkk3loDqM3T2hfi1PCyUrZXEUjGN5HNlLizDRvtcemaAHNAWlb70HbG/g==", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-brush-container/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-chart": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-chart/-/victory-chart-36.9.0.tgz", - "integrity": "sha512-gnSAJYLXd5HDda6yxlfBYk0GZXqXLRnDDBjKpyEFrYDYoZDaaWom2zXx+eNsdxZW1JxGsBWDFT/JImeAwOogcw==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-chart/-/victory-chart-36.9.2.tgz", + "integrity": "sha512-dMNcS0BpqL3YiGvI4BSEmPR76FCksCgf3K4CSZ7C/MGyrElqB6wWwzk7afnlB1Qr71YIHXDmdwsPNAl/iEwTtA==", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-axis": "^36.9.0", - "victory-core": "^36.9.0", - "victory-polar-axis": "^36.9.0", - "victory-shared-events": "^36.9.0" + "victory-axis": "^36.9.2", + "victory-core": "^36.9.2", + "victory-polar-axis": "^36.9.2", + "victory-shared-events": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-chart/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-core": { - "version": "36.8.6", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.8.6.tgz", - "integrity": "sha512-fT8SYy4wmtNTloV3npRC9pwEG8UkFF48ZmNGKywykpRuJXHVgV1GXeFEDH8/Ra0qHACVciR9Tcjk6X89GTRw4A==", + "version": "37.0.0", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-37.0.0.tgz", + "integrity": "sha512-aN5ZK9OO/HukifkX942Nsr0RidL/jsi/kTsBW68Ir6c6HazsfRFp0Ee3fIxy1ikUGe2XUaOu0eBv1v06+hfJKQ==", "dependencies": { "lodash": "^4.17.21", - "prop-types": "^15.8.1", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.8.6" + "victory-vendor": "^37.0.0" }, "peerDependencies": { "react": ">=16.6.0" } }, + "node_modules/victory-core/node_modules/victory-vendor": { + "version": "37.0.0", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.0.0.tgz", + "integrity": "sha512-jNqpyUTCw2EbNplG8KsbVkHCBPSOL5fTQHVf/1oOBecfVoO8dwAGDm0xFCAD4w4c9llck6zgIV3eqR04jVYRHA==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/victory-create-container": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-create-container/-/victory-create-container-36.9.0.tgz", - "integrity": "sha512-2lO81PW35HlcjsuwZOTwRT2nHHHGNzIZBsMWg88KsgAajN6ojQYzeH11azABTk5mAfmO0txFuuSFvpqneTuHHA==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-create-container/-/victory-create-container-36.9.2.tgz", + "integrity": "sha512-uA0dh1R0YDzuXyE/7StZvq4qshet+WYceY7R1UR5mR/F9079xy+iQsa2Ca4h97/GtVZoLO6r1eKLWBt9TN+U7A==", "dependencies": { "lodash": "^4.17.19", - "victory-brush-container": "^36.9.0", - "victory-core": "^36.9.0", - "victory-cursor-container": "^36.9.0", - "victory-selection-container": "^36.9.0", - "victory-voronoi-container": "^36.9.0", - "victory-zoom-container": "^36.9.0" + "victory-brush-container": "^36.9.2", + "victory-core": "^36.9.2", + "victory-cursor-container": "^36.9.2", + "victory-selection-container": "^36.9.2", + "victory-voronoi-container": "^36.9.2", + "victory-zoom-container": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-create-container/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-cursor-container": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-cursor-container/-/victory-cursor-container-36.9.0.tgz", - "integrity": "sha512-yG33xBgaM/eprQSzAbypTD/fdjJvUCEZhijvcOrDNSKpHJ/hM6xKZXgVnSAxuHk33K8Dpx4xmjIc9R/sYVN23A==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-cursor-container/-/victory-cursor-container-36.9.2.tgz", + "integrity": "sha512-jidab4j3MaciF3fGX70jTj4H9rrLcY8o2LUrhJ67ZLvEFGGmnPtph+p8Fe97Umrag7E/DszjNxQZolpwlgUh3g==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-cursor-container/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-group": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-group/-/victory-group-36.9.0.tgz", - "integrity": "sha512-DOvOb8ei6diW1XIjh5tv/eHB2tldaryiHGAA87DKnTJApMCUpjFrVSqUNycAIh3UH9/K/sqFzbQlNdHO5vG61g==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-group/-/victory-group-36.9.2.tgz", + "integrity": "sha512-wBmpsjBTKva8mxHvHNY3b8RE58KtnpLLItEyyAHaYkmExwt3Uj8Cld3sF3vmeuijn2iR64NPKeMbgMbfZJzycw==", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "^36.9.0", - "victory-shared-events": "^36.9.0" + "victory-core": "^36.9.2", + "victory-shared-events": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-group/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-legend": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-legend/-/victory-legend-36.9.0.tgz", - "integrity": "sha512-tc6YSskNasm/oW3GTxsbfIyHsKzn4DDWRAT4VPVw0z5RzsZy+7WW+H2ku34Ev6k6Zk0wa0ahSYIiFgFV1feoOw==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-legend/-/victory-legend-36.9.2.tgz", + "integrity": "sha512-cucFJpv6fty+yXp5pElQFQnHBk1TqA4guGUMI+XF/wLlnuM4bhdAtASobRIIBkz0mHGBaCAAV4PzL9azPU/9dg==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-legend/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-line": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-line/-/victory-line-36.9.0.tgz", - "integrity": "sha512-TJR5EwXs1Cp8Zg8FUPILyNgBuIkffya0pbHQ72/EYUA+BOqovjZe3/Qcl409SyRy6SOkyawxsSqhkjrEsODuBg==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-line/-/victory-line-36.9.2.tgz", + "integrity": "sha512-kmYFZUo0o2xC8cXRsmt/oUBRQSZJVT2IJnAkboUepypoj09e6CY5tRH4TSdfEDGkBk23xQkn7d4IFgl4kAGnSA==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0", - "victory-vendor": "^36.9.0" + "victory-core": "^36.9.2", + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-line/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-pie": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-36.9.0.tgz", - "integrity": "sha512-GPMvod6L8VcEUcEaOgjIRshbUYUN58FGcbMAvnz0yyiuqIR8eIp+VXnnOe1P2mZg35pUrcoHzKqRTsQGQAQYSw==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-36.9.2.tgz", + "integrity": "sha512-i3zWezvy5wQEkhXKt4rS9ILGH7Vr9Q5eF9fKO4GMwDPBdYOTE3Dh2tVaSrfDC8g9zFIc0DKzOtVoJRTb+0AkPg==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0", - "victory-vendor": "^36.9.0" + "victory-core": "^36.9.2", + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-pie/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-polar-axis": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-polar-axis/-/victory-polar-axis-36.9.0.tgz", - "integrity": "sha512-nhxL4+fGjyLNnSoj6FD3fZSnAco9k5xsRbE0WXSRrr0RQCsBGUjN/ErOL19LRbSwh39fs3m+SGIC0GPkAPC1nQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-polar-axis/-/victory-polar-axis-36.9.2.tgz", + "integrity": "sha512-HBR90FF4M56yf/atXjSmy3DMps1vSAaLXmdVXLM/A5g+0pUS7HO719r5x6dsR3I6Rm+8x6Kk8xJs0qgpnGQIEw==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-polar-axis/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-scatter": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-scatter/-/victory-scatter-36.9.0.tgz", - "integrity": "sha512-q03QFbApuaureOT9+1m3GSS9SSupnoJWy3UwQhRNB1i4WtIQF5G89E6R/LJmTDvzJmf0bj+p5DMhyujBk36mig==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-scatter/-/victory-scatter-36.9.2.tgz", + "integrity": "sha512-hK9AtbJQfaW05i8BH7Lf1HK7vWMAfQofj23039HEQJqTKbCL77YT+Q0LhZw1a1BRCpC/5aSg9EuqblhfIYw2wg==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-scatter/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-selection-container": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-selection-container/-/victory-selection-container-36.9.0.tgz", - "integrity": "sha512-jXl84CSxE29a6MEOv0LzK4bh51VdvZuWLif7VBILRIFSTB11Rg16mcfgDX0xanmkZCcjC2e18tlDW3eN2x7LxQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-selection-container/-/victory-selection-container-36.9.2.tgz", + "integrity": "sha512-chboroEwqqVlMB60kveXM2WznJ33ZM00PWkFVCoJDzHHlYs7TCADxzhqet2S67SbZGSyvSprY2YztSxX8kZ+XQ==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-selection-container/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-shared-events": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-shared-events/-/victory-shared-events-36.9.0.tgz", - "integrity": "sha512-Ucwg31n3XldQ5nOS79ZJoBu96j3wE/v9bVrbQBTrMO/GSw/pchGl5NNgWYfywRFNNBDsMJKYRHzsXaM9Ki0fSQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-shared-events/-/victory-shared-events-36.9.2.tgz", + "integrity": "sha512-W/atiw3Or6MnpBuhluFv6007YrixIRh5NtiRvtFLGxNuQJLYjaSh6koRAih5xJer5Pj7YUx0tL9x67jTRcJ6Dg==", "dependencies": { "json-stringify-safe": "^5.0.1", "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-shared-events/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-stack": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-stack/-/victory-stack-36.9.0.tgz", - "integrity": "sha512-BLhCVFGzcG3Dn/eaJ9vlQCTMbBjeuKhJqZnAMNbcJNQBYJB4VtpR6sbZdWxvouZ9k30SSD5AzgpEwwTv8ooVyw==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-stack/-/victory-stack-36.9.2.tgz", + "integrity": "sha512-imR6FniVlDFlBa/B3Est8kTryNhWj2ZNpivmVOebVDxkKcVlLaDg3LotCUOI7NzOhBQaro0UzeE9KmZV93JcYA==", "dependencies": { "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "^36.9.0", - "victory-shared-events": "^36.9.0" + "victory-core": "^36.9.2", + "victory-shared-events": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-stack/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-tooltip": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-tooltip/-/victory-tooltip-36.9.0.tgz", - "integrity": "sha512-774IH8aT4PmP9VBGsyMFdJag7XsbYEzRZUmpUPZ47KTuESwUAMTfjGghPJjTQ1eXdajpehD9/oo+Jo9rnca7sQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-tooltip/-/victory-tooltip-36.9.2.tgz", + "integrity": "sha512-76seo4TWD1WfZHJQH87IP3tlawv38DuwrUxpnTn8+uW6/CUex82poQiVevYdmJzhataS9jjyCWv3w7pOmLBCLg==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-tooltip/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-vendor": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.0.tgz", - "integrity": "sha512-n1A0J1xgwHb5nh56M0d8XlQabMCeTktvEqqr5WNAHspWEsVVGGaaaRg0TcQUtyC1akX0Cox1lMZdIv0Jl7o0ew==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", @@ -20865,53 +21208,53 @@ } }, "node_modules/victory-voronoi-container": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-voronoi-container/-/victory-voronoi-container-36.9.0.tgz", - "integrity": "sha512-cEackV1/+Nmq/A3SC9A8gh09AzwQJyWsmGo3osWs2hnDGlxB0gGuj3bRQ/ZNGh0Vzm14Dlhs1u21v7Rbh3IGfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-voronoi-container/-/victory-voronoi-container-36.9.2.tgz", + "integrity": "sha512-NIVYqck9N4OQnEz9mgQ4wILsci3OBWWK7RLuITGHyoD7Ne/+WH1i0Pv2y9eIx+f55rc928FUTugPPhkHvXyH3A==", "dependencies": { "delaunay-find": "0.0.6", "lodash": "^4.17.19", "react-fast-compare": "^3.2.0", - "victory-core": "^36.9.0", - "victory-tooltip": "^36.9.0" + "victory-core": "^36.9.2", + "victory-tooltip": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-voronoi-container/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-zoom-container": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-zoom-container/-/victory-zoom-container-36.9.0.tgz", - "integrity": "sha512-kODhPyJooO8WRiz9Fpch/RVV4uXoMyYNHjzbeX6t88n25A86w5I5DhUIM+GUGlU7AGvuwLMCa/Te8P9B9JXDaQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-zoom-container/-/victory-zoom-container-36.9.2.tgz", + "integrity": "sha512-pXa2Ji6EX/pIarKT6Hcmmu2n7IG/x8Vs0D2eACQ/nbpvZa+DXWIxCRW4hcg2Va35fmXcDIEpGaX3/soXzZ+pbw==", "dependencies": { "lodash": "^4.17.19", - "victory-core": "^36.9.0" + "victory-core": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" } }, "node_modules/victory-zoom-container/node_modules/victory-core": { - "version": "36.9.0", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.0.tgz", - "integrity": "sha512-RawQkhPQaV4Oi+8KLHm8mzEwxL6mirdQOcn5+BaGrUnP9MK7jjEdjzNvDESuJdk/bDuemXvWEEriG+ofDmvzfQ==", + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.9.2.tgz", + "integrity": "sha512-AzmMy+9MYMaaRmmZZovc/Po9urHne3R3oX7bbXeQdVuK/uMBrlPiv11gVJnuEH2SXLVyep43jlKgaBp8ef9stQ==", "dependencies": { "lodash": "^4.17.21", "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.9.0" + "victory-vendor": "^36.9.2" }, "peerDependencies": { "react": ">=16.6.0" @@ -20949,9 +21292,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -20989,9 +21332,9 @@ } }, "node_modules/webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -21519,31 +21862,34 @@ } }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", - "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -21722,11 +22068,12 @@ "dev": true }, "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index f1c067436..4fbdce394 100644 --- a/package.json +++ b/package.json @@ -50,68 +50,67 @@ "verify": "npm-run-all build lint test" }, "dependencies": { - "@patternfly/patternfly": "^5.2.0", - "@patternfly/react-charts": "^7.2.0", - "@patternfly/react-component-groups": "^5.0.0", - "@patternfly/react-core": "^5.2.0", - "@patternfly/react-icons": "^5.2.0", - "@patternfly/react-table": "^5.2.0", - "@patternfly/react-tokens": "^5.2.0", - "@redhat-cloud-services/frontend-components": "^4.2.3", + "@patternfly/patternfly": "5.2.0", + "@patternfly/react-charts": "7.2.2", + "@patternfly/react-component-groups": "5.1.0", + "@patternfly/react-core": "5.2.0", + "@patternfly/react-icons": "5.2.0", + "@patternfly/react-table": "5.2.0", + "@patternfly/react-tokens": "5.2.0", + "@redhat-cloud-services/frontend-components": "^4.2.5", "@redhat-cloud-services/frontend-components-notifications": "^4.1.0", "@redhat-cloud-services/frontend-components-translations": "^3.2.7", - "@redhat-cloud-services/frontend-components-utilities": "^4.0.2", + "@redhat-cloud-services/frontend-components-utilities": "^4.0.7", "@redhat-cloud-services/rbac-client": "^1.2.13", - "@reduxjs/toolkit": "^2.1.0", - "@unleash/proxy-client-react": "^4.1.2", - "axios": "^1.6.7", - "date-fns": "^3.3.1", + "@reduxjs/toolkit": "^2.2.1", + "@unleash/proxy-client-react": "^4.2.2", + "axios": "^1.6.8", + "date-fns": "^3.6.0", "js-file-download": "^0.4.12", "lodash": "^4.17.21", - "qs": "^6.11.2", + "qs": "^6.12.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-intl": "^6.6.2", "react-redux": "^9.1.0", - "react-router-dom": "^6.22.0", + "react-router-dom": "^6.22.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "typesafe-actions": "^5.1.0", - "unleash-proxy-client": "^3.3.1", - "victory-core": "36.8.6", - "yaml": "^2.3.4" + "unleash-proxy-client": "^3.3.2", + "victory-core": "^37.0.0" }, "devDependencies": { "@formatjs/cli": "^6.2.7", "@formatjs/ecma402-abstract": "^1.18.2", "@formatjs/icu-messageformat-parser": "^2.7.6", "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", - "@redhat-cloud-services/frontend-components-config": "^6.0.9", - "@redhat-cloud-services/tsc-transform-imports": "^1.0.7", + "@redhat-cloud-services/frontend-components-config": "^6.0.11", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.8", "@swc/core": "1.3.105", "@swc/jest": "^0.2.36", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.12", - "@types/qs": "^6.9.11", - "@types/react": "^18.2.53", - "@types/react-dom": "^18.2.18", + "@types/qs": "^6.9.12", + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", "@types/react-redux": "^7.1.33", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", "aphrodite": "^2.4.0", "copy-webpack-plugin": "^12.0.2", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-plugin-formatjs": "^4.12.2", "eslint-plugin-jest-dom": "^5.1.0", - "eslint-plugin-jsdoc": "^48.0.4", - "eslint-plugin-markdown": "^3.0.1", + "eslint-plugin-jsdoc": "^48.2.1", + "eslint-plugin-markdown": "^4.0.1", "eslint-plugin-patternfly-react": "^5.2.1", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-simple-import-sort": "^12.0.0", "eslint-plugin-sort-keys-fix": "^1.1.2", "eslint-plugin-testing-library": "^6.2.0", "git-revision-webpack-plugin": "^5.0.0", @@ -127,10 +126,11 @@ "rimraf": "^5.0.5", "ts-jest": "^29.1.2", "ts-patch": "^3.1.2", - "typescript": "^5.3.3", + "typescript": "^5.4.2", "webpack-bundle-analyzer": "^4.10.1" }, "overrides": { + "@typescript-eslint/eslint-plugin": "^7.2.0", "redux": "^5.0.1" }, "insights": { diff --git a/src/api/providers.ts b/src/api/providers.ts index 73a89025c..249db649c 100644 --- a/src/api/providers.ts +++ b/src/api/providers.ts @@ -2,6 +2,13 @@ import axios from 'axios'; import type { PagedMetaData, PagedResponse } from './api'; +export interface ProviderAdditionalContext { + operator_version?: string; + operator_airgapped?: boolean; + operator_certified?: boolean; + operator_update_available?: boolean; +} + export interface ProviderAuthentication { credentials?: { cluster_id?: string; @@ -33,13 +40,30 @@ export interface ProviderCostModel { uuid: string; } +export interface ProviderInfrastructureState { + end?: string; + start?: string; + state?: string; +} + export interface ProviderInfrastructure { + cloud_provider_state?: { + download?: ProviderInfrastructureState; + processing?: ProviderInfrastructureState; + summary?: ProviderInfrastructureState; + }; + paused?: boolean; + source_status?: { + availability_status?: string; + availability_status_error?: string; + }; type?: string; uuid?: string; } export interface Provider { active?: boolean; + additional_context?: ProviderAdditionalContext; authentication?: ProviderAuthentication; billing_source?: ProviderBillingSource; created_by?: ProviderCreatedBy; @@ -50,10 +74,16 @@ export interface Provider { has_data?: boolean; id?: string; infrastructure?: ProviderInfrastructure; + last_payload_received_at?: string; name?: string; paused?: boolean; previous_month_data?: boolean; source_type?: string; + status?: { + download?: ProviderInfrastructureState; + processing?: ProviderInfrastructureState; + summary?: ProviderInfrastructureState; + }; type?: string; uuid?: string; } diff --git a/src/api/settings.ts b/src/api/settings.ts index 372dfe060..4ca8508cc 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -10,6 +10,7 @@ export interface SettingsData { uuid?: string; key?: string; enabled?: boolean; + source_type?: string; } export interface PagedMetaDataExt extends PagedMetaData { @@ -26,6 +27,8 @@ export interface Settings { } export interface SettingsPayload { + parent?: string; + children?: string[]; ids?: string[]; } @@ -40,6 +43,12 @@ export const enum SettingsType { tags = 'tags', tagsEnable = 'tagsEnable', tagsDisable = 'tagsDisable', + tagsMappings = 'tagsMappings', + tagsMappingsChild = 'tagsMappingsChild', + tagsMappingsChildAdd = 'tagsMappingsChildAdd', + tagsMappingsChildRemove = 'tagsMappingsChildRemove', + tagsMappingsParent = 'tagsMappingsParent', + tagsMappingsParentRemove = 'tagsMappingsParentRemove', } export const SettingsTypePaths: Partial> = { @@ -52,6 +61,12 @@ export const SettingsTypePaths: Partial> = { [SettingsType.tags]: 'settings/tags', [SettingsType.tagsEnable]: 'settings/tags/enable/', [SettingsType.tagsDisable]: 'settings/tags/disable/', + [SettingsType.tagsMappings]: 'settings/tags/mappings', + [SettingsType.tagsMappingsChild]: 'settings/tags/mappings/child', + [SettingsType.tagsMappingsChildAdd]: 'settings/tags/mappings/child/add', + [SettingsType.tagsMappingsChildRemove]: 'settings/tags/mappings/child/remove', + [SettingsType.tagsMappingsParent]: 'settings/tags/mappings/parent', + [SettingsType.tagsMappingsParentRemove]: 'settings/tags/mappings/parent/remove', }; export function fetchSettings(settingsType: SettingsType, query: string) { diff --git a/src/app.tsx b/src/app.tsx index e410f05dd..fec7429a0 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useLayoutEffect } from 'react'; import { invalidateSession } from 'utils/sessionStorage'; import pkg from '../package.json'; -import { useFeatureFlags } from './components/featureFlags'; +import { useFeatureToggle } from './components/featureToggle'; import { Routes } from './routes'; const App = () => { @@ -22,8 +22,8 @@ const App = () => { updateDocumentTitle(pkg.insights.appname); }, []); - // Initialize Unleash feature flags - useFeatureFlags(); + // Initialize Unleash feature toggles + useFeatureToggle(); // Clear local storage value if current session is not valid invalidateSession(); diff --git a/src/components/featureFlags/featureFlags.tsx b/src/components/featureFlags/featureFlags.tsx deleted file mode 100644 index 6eefac1de..000000000 --- a/src/components/featureFlags/featureFlags.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; -import { useUnleashClient, useUnleashContext } from '@unleash/proxy-client-react'; -import { useLayoutEffect, useRef } from 'react'; -import { useDispatch } from 'react-redux'; -import { featureFlagsActions } from 'store/featureFlags'; - -// eslint-disable-next-line no-shadow -export const enum FeatureToggle { - clusterInfo = 'cost-management.ui.cluster-info', // https://issues.redhat.com/browse/COST-4559 - exports = 'cost-management.ui.exports', // Async exports https://issues.redhat.com/browse/COST-2223 - finsights = 'cost-management.ui.finsights', // RHEL support for FINsights https://issues.redhat.com/browse/COST-3306 - ibm = 'cost-management.ui.ibm', // IBM https://issues.redhat.com/browse/COST-935 - ros = 'cost-management.ui.ros', // ROS support https://issues.redhat.com/browse/COST-3477 - rosBeta = 'cost-management.ui.ros-beta', // ROS support https://issues.redhat.com/browse/COST-3477 - settingsPlatform = 'cost-management.ui.settings.platform', // Platform projects https://issues.redhat.com/browse/COST-3818 -} - -// The FeatureFlags component saves feature flags in store for places where Unleash hooks not available -const useFeatureFlags = () => { - const updateContext = useUnleashContext(); - const client = useUnleashClient(); - const dispatch = useDispatch(); - const { auth, isBeta } = useChrome(); - - const fetchUser = callback => { - auth.getUser().then(user => { - callback((user as any).identity.account_number); - }); - }; - - const isMounted = useRef(false); - useLayoutEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); - - // Update everytime or flags may be false - useLayoutEffect(() => { - fetchUser(userId => { - if (isMounted.current) { - updateContext({ - userId, - }); - } - }); - }); - - useLayoutEffect(() => { - // Wait for the new flags to pull in from the different context - const fetchFlags = async userId => { - await updateContext({ userId }).then(() => { - dispatch( - featureFlagsActions.setFeatureFlags({ - isClusterInfoFeatureEnabled: client.isEnabled(FeatureToggle.clusterInfo), - isExportsFeatureEnabled: client.isEnabled(FeatureToggle.exports), - isFinsightsFeatureEnabled: client.isEnabled(FeatureToggle.finsights), - isIbmFeatureEnabled: client.isEnabled(FeatureToggle.ibm), - isRosFeatureEnabled: - client.isEnabled(FeatureToggle.ros) || (client.isEnabled(FeatureToggle.rosBeta) && isBeta()), // Need to check beta in prod - isSettingsPlatformFeatureEnabled: client.isEnabled(FeatureToggle.settingsPlatform), - }) - ); - }); - }; - fetchUser(userId => { - if (isMounted.current) { - fetchFlags(userId); - } - }); - }); -}; - -export default useFeatureFlags; diff --git a/src/components/featureFlags/index.ts b/src/components/featureFlags/index.ts deleted file mode 100644 index fff73107b..000000000 --- a/src/components/featureFlags/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as useFeatureFlags, FeatureToggle } from './featureFlags'; diff --git a/src/components/featureToggle/featureToggle.tsx b/src/components/featureToggle/featureToggle.tsx new file mode 100644 index 000000000..5cb1c139a --- /dev/null +++ b/src/components/featureToggle/featureToggle.tsx @@ -0,0 +1,110 @@ +import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'; +import { useUnleashClient } from '@unleash/proxy-client-react'; +import { useLayoutEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { FeatureToggleActions } from 'store/featureToggle'; + +// eslint-disable-next-line no-shadow +export const enum FeatureToggle { + clusterInfo = 'cost-management.ui.cluster.info', // https://issues.redhat.com/browse/COST-4559 + debug = 'cost-management.ui.debug', + exports = 'cost-management.ui.exports', // Async exports https://issues.redhat.com/browse/COST-2223 + finsights = 'cost-management.ui.finsights', // RHEL support for FINsights https://issues.redhat.com/browse/COST-3306 + ibm = 'cost-management.ui.ibm', // IBM https://issues.redhat.com/browse/COST-935 + ros = 'cost-management.ui.ros', // ROS support https://issues.redhat.com/browse/COST-3477 + rosBeta = 'cost-management.ui.ros-beta', // ROS support https://issues.redhat.com/browse/COST-3477 + settingsPlatform = 'cost-management.ui.settings.platform', // Platform projects https://issues.redhat.com/browse/COST-3818 + tagMapping = 'cost-management.ui.tag.mapping', // https://issues.redhat.com/browse/COST-3824 +} + +const useIsToggleEnabled = (toggle: FeatureToggle) => { + const client = useUnleashClient(); + return client.isEnabled(toggle); +}; + +export const useIsDebugToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.debug); +}; + +export const useIsClusterInfoToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.clusterInfo); +}; + +export const useIsExportsToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.exports); +}; + +export const useIsFinsightsToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.finsights); +}; + +export const useIsIbmToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.ibm); +}; + +export const useIsRosToggleEnabled = () => { + const { isBeta } = useChrome(); + const isRosToggleEnabled = useIsToggleEnabled(FeatureToggle.ros); + const isRosFeatureBetaEnabled = useIsToggleEnabled(FeatureToggle.rosBeta) && isBeta(); // Enabled for prod-beta + return isRosToggleEnabled || isRosFeatureBetaEnabled; +}; + +export const useIsSettingsPlatformToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.settingsPlatform); +}; + +export const useIsTagMappingToggleEnabled = () => { + return useIsToggleEnabled(FeatureToggle.tagMapping); +}; + +// The FeatureToggle component saves feature toggles in store for places where Unleash hooks not available +export const useFeatureToggle = () => { + const dispatch = useDispatch(); + const { auth } = useChrome(); + + const isClusterInfoToggleEnabled = useIsClusterInfoToggleEnabled(); + const isDebugToggleEnabled = useIsDebugToggleEnabled(); + const isExportsToggleEnabled = useIsExportsToggleEnabled(); + const isFinsightsToggleEnabled = useIsFinsightsToggleEnabled(); + const isIbmToggleEnabled = useIsIbmToggleEnabled(); + const isRosToggleEnabled = useIsRosToggleEnabled(); + const isSettingsPlatformToggleEnabled = useIsSettingsPlatformToggleEnabled(); + const isTagMappingToggleEnabled = useIsTagMappingToggleEnabled(); + + const fetchUser = callback => { + auth.getUser().then(user => { + callback((user as any).identity); + }); + }; + + useLayoutEffect(() => { + // Workaround for code that doesn't use hooks + dispatch( + FeatureToggleActions.setFeatureToggle({ + isClusterInfoToggleEnabled, + isDebugToggleEnabled, + isExportsToggleEnabled, + isFinsightsToggleEnabled, + isIbmToggleEnabled, + isRosToggleEnabled, + isSettingsPlatformToggleEnabled, + isTagMappingToggleEnabled, + }) + ); + if (isDebugToggleEnabled) { + // eslint-disable-next-line no-console + fetchUser(identity => console.log('User identity:', identity)); + } + }, [ + isClusterInfoToggleEnabled, + isDebugToggleEnabled, + isExportsToggleEnabled, + isFinsightsToggleEnabled, + isIbmToggleEnabled, + isRosToggleEnabled, + isSettingsPlatformToggleEnabled, + isTagMappingToggleEnabled, + ]); +}; + +export default useFeatureToggle; diff --git a/src/components/featureToggle/index.ts b/src/components/featureToggle/index.ts new file mode 100644 index 000000000..86f8a50c7 --- /dev/null +++ b/src/components/featureToggle/index.ts @@ -0,0 +1,2 @@ +export * from './featureToggle'; +export { default as useFeatureToggle } from './featureToggle'; diff --git a/src/components/inactiveSources/inactiveSources.tsx b/src/components/inactiveSources/inactiveSources.tsx index d68c8fc21..5acacf707 100644 --- a/src/components/inactiveSources/inactiveSources.tsx +++ b/src/components/inactiveSources/inactiveSources.tsx @@ -35,7 +35,7 @@ class InactiveSourcesBase extends React.Component { if (providers && providers.data) { providers.data.map(data => { - if (data.active !== true && data.paused !== true) { + if (data.active === false && data.paused === false) { sources.push(data.name); } }); diff --git a/src/components/permissions/permissions.tsx b/src/components/permissions/permissions.tsx index 02802657c..3ad91ede1 100644 --- a/src/components/permissions/permissions.tsx +++ b/src/components/permissions/permissions.tsx @@ -9,7 +9,7 @@ import { Loading } from 'routes/components/page/loading'; import { NotAuthorized } from 'routes/components/page/notAuthorized'; import { NotAvailable } from 'routes/components/page/notAvailable'; import { createMapStateToProps, FetchStatus } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { userAccessQuery, userAccessSelectors } from 'store/userAccess'; import type { ChromeComponentProps } from 'utils/chrome'; import { withChrome } from 'utils/chrome'; @@ -32,9 +32,9 @@ interface PermissionsOwnProps extends ChromeComponentProps { } interface PermissionsStateProps { - isFinsightsFeatureEnabled?: boolean; - isIbmFeatureEnabled?: boolean; - isRosFeatureEnabled?: boolean; + isFinsightsToggleEnabled?: boolean; + isIbmToggleEnabled?: boolean; + isRosToggleEnabled?: boolean; userAccess: UserAccess; userAccessError: AxiosError; userAccessFetchStatus: FetchStatus; @@ -46,9 +46,9 @@ type PermissionsProps = PermissionsOwnProps & PermissionsStateProps; const PermissionsBase: React.FC = ({ children = null, // chrome, - isFinsightsFeatureEnabled, - isIbmFeatureEnabled, - isRosFeatureEnabled, + isFinsightsToggleEnabled, + isIbmToggleEnabled, + isRosToggleEnabled, userAccess, userAccessError, userAccessFetchStatus, @@ -62,11 +62,11 @@ const PermissionsBase: React.FC = ({ const azure = hasAzureAccess(userAccess); const costModel = hasCostModelAccess(userAccess); const gcp = hasGcpAccess(userAccess); - const ibm = isIbmFeatureEnabled && hasIbmAccess(userAccess); + const ibm = isIbmToggleEnabled && hasIbmAccess(userAccess); const oci = hasOciAccess(userAccess); const ocp = hasOcpAccess(userAccess); - const rhel = isFinsightsFeatureEnabled && hasRhelAccess(userAccess); - const ros = isRosFeatureEnabled && hasRosAccess(userAccess); + const rhel = isFinsightsToggleEnabled && hasRhelAccess(userAccess); + const ros = isRosToggleEnabled && hasRosAccess(userAccess); const settings = costModel || hasSettingsAccess(userAccess); switch (pathname) { @@ -133,9 +133,9 @@ const mapStateToProps = createMapStateToProps { - constructor(props: DataTableProps) { - super(props); - this.handleOnSelect = this.handleOnSelect.bind(this); - this.handleOnSort = this.handleOnSort.bind(this); - } - private getEmptyState = () => { const { emptyState, filterBy, intl } = this.props; @@ -119,7 +114,7 @@ class DataTable extends React.Component { }; public render() { - const { columns, intl, isActionsCell = false, isLoading, isSelectable = true, rows } = this.props; + const { columns, intl, isActionsCell, isLoading, isSelectable, rows, variant } = this.props; return ( <> @@ -168,6 +163,7 @@ class DataTable extends React.Component { isSelected: row.selected, onSelect: (_evt, isSelected) => this.handleOnSelect(isSelected, rowIndex), rowIndex, + variant, }} style={item.style} /> diff --git a/src/routes/components/dataTable/expandableTable.tsx b/src/routes/components/dataTable/expandableTable.tsx new file mode 100644 index 000000000..551a7fc94 --- /dev/null +++ b/src/routes/components/dataTable/expandableTable.tsx @@ -0,0 +1,213 @@ +import './dataTable.scss'; + +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateHeader, + EmptyStateIcon, + Spinner, +} from '@patternfly/react-core'; +import { CalculatorIcon } from '@patternfly/react-icons/dist/esm/icons/calculator-icon'; +import type { ThProps } from '@patternfly/react-table'; +import { SortByDirection, Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import messages from 'locales/messages'; +import React from 'react'; +import type { WrappedComponentProps } from 'react-intl'; +import { injectIntl } from 'react-intl'; +import { EmptyFilterState } from 'routes/components/state/emptyFilterState'; +import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; +import type { RouterComponentProps } from 'utils/router'; +import { withRouter } from 'utils/router'; + +import { styles } from './dataTable.styles'; + +interface ExpandableTableOwnProps { + columns?: any[]; + emptyState?: React.ReactNode; + filterBy: any; + isActionsCell?: boolean; + isAllExpanded?: boolean; + isLoading?: boolean; + onSort(value: string, isSortAscending: boolean); + orderBy: any; + rows?: any[]; + selectedItems?: ComputedReportItem[]; +} + +interface ExpandableTableState { + expandedRows?: Set; + rows?: any[]; +} + +type ExpandableTableProps = ExpandableTableOwnProps & RouterComponentProps & WrappedComponentProps; + +class ExpandableTable extends React.Component { + public state: ExpandableTableState = { + expandedRows: new Set(), + }; + + private getEmptyState = () => { + const { emptyState, filterBy, intl } = this.props; + + if (filterBy) { + for (const val of Object.values(filterBy)) { + if (val !== '*') { + return ; + } + } + } + return emptyState ? ( + emptyState + ) : ( + + } /> + {intl.formatMessage(messages.detailsEmptyState)} + + ); + }; + + private getSortBy = index => { + const { columns, orderBy } = this.props; + + const direction = orderBy && orderBy[columns[index].orderBy]; + + return direction + ? { + index, + direction, + } + : {}; + }; + + private getSortParams = (index: number): ThProps['sort'] => { + return { + sortBy: this.getSortBy(index), + onSort: (_evt, i, direction) => this.handleOnSort(i, direction), + columnIndex: index, + }; + }; + + private handleOnSort = (index, direction) => { + const { columns, onSort } = this.props; + + if (onSort) { + const orderBy = columns[index].orderBy; + const isSortAscending = direction === SortByDirection.asc; + onSort(orderBy, isSortAscending); + } + }; + + private handleOnToggle = (item: any) => { + const { expandedRows } = this.state; + + if (expandedRows.has(item)) { + expandedRows.delete(item); + } else { + expandedRows.add(item); + } + this.setState({ expandedRows }); + }; + + public render() { + const { columns, intl, isActionsCell, isAllExpanded, isLoading, rows } = this.props; + const { expandedRows } = this.state; + + return ( + <> + + + + {columns.map((col, index) => ( + + ))} + + + + {isLoading ? ( + + + + ) : ( + rows.map((row, rowIndex) => { + const isExpanded = isAllExpanded || expandedRows.has(row?.item); + return ( + + + {row.cells.map((item, cellIndex) => + cellIndex === 0 ? ( + + ) + )} + + {row?.children?.map((child, childIndex) => ( + + {child.cells.map((item, cellIndex) => + cellIndex === 0 ? ( + + ) + )} + + ))} + + ); + }) + )} + +
+ {col.name} +
+ +
+ +
+
+
this.handleOnToggle(row?.item), + }} + key={`cell-${cellIndex}-${rowIndex}`} + /> + ) : ( + + {item.value} +
+ ) : ( + + {item.value} +
+ {rows.length === 0 &&
{this.getEmptyState()}
} + + ); + } +} + +export default injectIntl(withRouter(ExpandableTable)); diff --git a/src/routes/components/dataTable/index.ts b/src/routes/components/dataTable/index.ts index afd6ba4cd..139a68465 100644 --- a/src/routes/components/dataTable/index.ts +++ b/src/routes/components/dataTable/index.ts @@ -1,2 +1,3 @@ export { default as DataTable } from './dataTable'; +export { default as ExpandableTable } from './expandableTable'; export { default as SelectableTable } from './selectableTable'; diff --git a/src/routes/components/dataToolbar/basicToolbar.tsx b/src/routes/components/dataToolbar/basicToolbar.tsx index f18c1abaa..ed24adf3b 100644 --- a/src/routes/components/dataToolbar/basicToolbar.tsx +++ b/src/routes/components/dataToolbar/basicToolbar.tsx @@ -48,6 +48,7 @@ interface BasicToolbarOwnProps { selectedItems?: any[]; showBulkSelect?: boolean; // Show bulk select showBulkSelectAll?: boolean; // Show bulk select all option + showBulkSelectPage?: boolean; // Show bulk select page option showFilter?: boolean; // Show export icon style?: React.CSSProperties; useActiveFilters?: boolean; @@ -136,6 +137,7 @@ export class BasicToolbarBase extends React.Component void; selectedItems?: ComputedReportItem[]; showSelectAll?: boolean; + showSelectPage?: boolean; }) => { const numSelected = isAllSelected ? itemsTotal : selectedItems ? selectedItems.length : 0; const allSelected = (isAllSelected || numSelected === itemsTotal) && itemsTotal > 0; @@ -43,11 +45,14 @@ export const getBulkSelect = ({ onBulkSelectClicked('none')}> {intl.formatMessage(messages.toolBarBulkSelectNone)} , - onBulkSelectClicked('page')}> - {intl.formatMessage(messages.toolBarBulkSelectPage, { value: itemsPerPage })} - , ]; - + if (showSelectPage) { + dropdownItems.push( + onBulkSelectClicked('page')}> + {intl.formatMessage(messages.toolBarBulkSelectPage, { value: itemsPerPage })} + + ); + } if (showSelectAll) { dropdownItems.push( onBulkSelectClicked('all')}> diff --git a/src/routes/components/export/exportModal.tsx b/src/routes/components/export/exportModal.tsx index ad3afd5e1..b58123923 100644 --- a/src/routes/components/export/exportModal.tsx +++ b/src/routes/components/export/exportModal.tsx @@ -24,7 +24,7 @@ import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { tagPrefix } from 'utils/props'; import { styles } from './exportModal.styles'; @@ -46,7 +46,7 @@ export interface ExportModalOwnProps { } interface ExportModalStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; } interface ExportModalState { @@ -143,7 +143,7 @@ export class ExportModalBase extends React.Component {error && }
- {isExportsFeatureEnabled ? ( + {isExportsToggleEnabled ? ( {intl.formatMessage(messages.exportDesc, { value: {intl.formatMessage(messages.exportsTitle)} })} @@ -227,7 +227,7 @@ export class ExportModalBase extends React.Component
- {isExportsFeatureEnabled && ( + {isExportsToggleEnabled && ( )} - {showFormatType && isExportsFeatureEnabled && ( + {showFormatType && isExportsToggleEnabled && ( {formatTypeOptions.map((option, index) => ( @@ -328,7 +328,7 @@ export class ExportModalBase extends React.Component(state => { return { - isExportsFeatureEnabled: featureFlagsSelectors.selectIsExportsFeatureEnabled(state), + isExportsToggleEnabled: FeatureToggleSelectors.selectIsExportsToggleEnabled(state), }; }); diff --git a/src/routes/components/export/exportSubmit.tsx b/src/routes/components/export/exportSubmit.tsx index 81ecab6a4..a01d4ecbd 100644 --- a/src/routes/components/export/exportSubmit.tsx +++ b/src/routes/components/export/exportSubmit.tsx @@ -17,7 +17,7 @@ import type { ComputedReportItem } from 'routes/utils/computedReport/getComputed import { getDateRangeFromQuery } from 'routes/utils/dateRange'; import { createMapStateToProps, FetchStatus } from 'store/common'; import { exportActions, exportSelectors } from 'store/export'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { getToday } from 'utils/dates'; import { orgUnitIdKey, tagPrefix } from 'utils/props'; import type { RouterComponentProps } from 'utils/router'; @@ -48,7 +48,7 @@ interface ExportSubmitStateProps { exportFetchStatus?: FetchStatus; exportQueryString: string; exportReport: Export; - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; startDate: string; } @@ -83,9 +83,9 @@ export class ExportSubmitBase extends React.Component { - const { exportQueryString, fetchExport, isExportsFeatureEnabled, reportPathsType } = this.props; + const { exportQueryString, fetchExport, isExportsToggleEnabled, reportPathsType } = this.props; - fetchExport(reportPathsType, reportType, exportQueryString, isExportsFeatureEnabled); + fetchExport(reportPathsType, reportType, exportQueryString, isExportsToggleEnabled); this.setState( { @@ -244,7 +244,7 @@ const mapStateToProps = createMapStateToProps { return ( {intl.formatMessage(messages.noDataStateTitle)}} + titleText={intl.formatMessage(messages.noDataStateTitle)} icon={} headingLevel="h5" /> diff --git a/src/routes/components/page/noOptimizations/noOptimizationsState.tsx b/src/routes/components/page/noOptimizations/noOptimizationsState.tsx index 771c8275f..4b78a7148 100644 --- a/src/routes/components/page/noOptimizations/noOptimizationsState.tsx +++ b/src/routes/components/page/noOptimizations/noOptimizationsState.tsx @@ -24,7 +24,7 @@ class NoOptimizationsStateBase extends React.Component {intl.formatMessage(messages.noOptimizationsTitle)}} + titleText={intl.formatMessage(messages.noOptimizationsTitle)} icon={} headingLevel="h1" /> diff --git a/src/routes/components/page/noProviders/noProvidersState.tsx b/src/routes/components/page/noProviders/noProvidersState.tsx index 175364ee1..c944ef6c5 100644 --- a/src/routes/components/page/noProviders/noProvidersState.tsx +++ b/src/routes/components/page/noProviders/noProvidersState.tsx @@ -84,7 +84,7 @@ class NoProvidersStateBase extends React.Component { return ( {intl.formatMessage(titleKey)}} + titleText={intl.formatMessage(titleKey)} icon={} headingLevel="h1" /> diff --git a/src/routes/components/perspective/perspective.tsx b/src/routes/components/perspective/perspective.tsx index 8baa4885e..4fbfdd7d9 100644 --- a/src/routes/components/perspective/perspective.tsx +++ b/src/routes/components/perspective/perspective.tsx @@ -53,8 +53,8 @@ interface PerspectiveProps { hasOcpCloud?: boolean; hasRhel?: boolean; isDisabled?: boolean; - isIbmFeatureEnabled?: boolean; - isOciFeatureEnabled?: boolean; + isIbmToggleEnabled?: boolean; + isOciToggleEnabled?: boolean; isInfrastructureTab?: boolean; // Used by the overview page isRhelTab?: boolean; // Used by the overview page, onSelect?: (value: string) => void; @@ -70,7 +70,7 @@ const getInfrastructureOptions = ({ hasIbm, hasIbmOcp, hasOci, - isIbmFeatureEnabled, + isIbmToggleEnabled, }) => { const options = []; @@ -89,7 +89,7 @@ const getInfrastructureOptions = ({ if (hasIbm) { options.push(...infrastructureIbmOptions); } - if (hasIbmOcp && isIbmFeatureEnabled) { + if (hasIbmOcp && isIbmToggleEnabled) { options.push(...infrastructureIbmOcpOptions); } if (hasAzure) { @@ -119,7 +119,7 @@ const Perspective: React.FC = ({ hasOcpCloud, hasRhel, isDisabled, - isIbmFeatureEnabled, + isIbmToggleEnabled, isInfrastructureTab, isRhelTab, onSelect, @@ -144,7 +144,7 @@ const Perspective: React.FC = ({ hasIbm, hasIbmOcp, hasOci, - isIbmFeatureEnabled, + isIbmToggleEnabled, }) ); } else if (isRhelTab) { @@ -175,7 +175,7 @@ const Perspective: React.FC = ({ hasIbm, hasIbmOcp, hasOci, - isIbmFeatureEnabled, + isIbmToggleEnabled, }) ); } diff --git a/src/routes/components/reports/reportSummary/reportSummaryItem.test.tsx b/src/routes/components/reports/reportSummary/reportSummaryItem.test.tsx index e635b6404..06f4cb778 100644 --- a/src/routes/components/reports/reportSummary/reportSummaryItem.test.tsx +++ b/src/routes/components/reports/reportSummary/reportSummaryItem.test.tsx @@ -19,7 +19,7 @@ test('formats value', () => { expect(screen.getByText(/label/i)).not.toBeNull(); expect( screen.getByText( - '{value} {units} ({percent} %){"percent":"10","units":"{units, select, byte_ms {Byte-ms} core {core} core_hours {core-hours} gb {GB} gb_hours {GB-hours} gb_mo {GB-month} gb_ms {GB-ms} gibibyte_month {GiB-month} hour {hours} hrs {hours} ms {milliseconds} vm_hours {VM-hours} other {}}{}","value":100}' + '{value} {units} ({percent} %){"percent":"10","units":"{units, select, byte_ms {Byte-ms} cluster_month {cluster-month} core {core} core_hours {core-hours} gb {GB} gb_hours {GB-hours} gb_month {GB-month} gb_ms {GB-ms} gib_hours {GiB-hours} gib_month {GiB-month} gibibyte_month {GiB-month} hour {hours} hrs {hours} ms {milliseconds} pvc_month {PVC-month} tag_month {tag-month} vm_hours {VM-hours} other {}}{}","value":100}' ) ).not.toBeNull(); expect(screen.getByRole('progressbar').getAttribute('aria-valuenow')).toBe('10'); diff --git a/src/routes/components/state/emptyFilterState/emptyFilterState.tsx b/src/routes/components/state/emptyFilterState/emptyFilterState.tsx index 9f88006dd..04a61cb87 100644 --- a/src/routes/components/state/emptyFilterState/emptyFilterState.tsx +++ b/src/routes/components/state/emptyFilterState/emptyFilterState.tsx @@ -57,7 +57,18 @@ const EmptyFilterStateBase: React.FC = ({ let showEmptyState1 = false; let showEmptyState2 = false; - if (filter && filter.length && !Array.isArray(filter)) { + if (filter && Array.isArray(filter)) { + for (const val of filter) { + if (isFilter1(val)) { + showEmptyState1 = true; + break; + } + if (isFilter2(val)) { + showEmptyState2 = true; + break; + } + } + } else if (filter && !Array.isArray(filter)) { for (const val of filter.split(',')) { if (isFilter1(val)) { showEmptyState1 = true; @@ -114,7 +125,7 @@ const EmptyFilterStateBase: React.FC = ({ > {getItem()} - {intl.formatMessage(title)}} headingLevel="h2" /> + {intl.formatMessage(subTitle)}
diff --git a/src/routes/components/state/errorState/errorState.tsx b/src/routes/components/state/errorState/errorState.tsx index 9daa76d01..fc3f60cf1 100644 --- a/src/routes/components/state/errorState/errorState.tsx +++ b/src/routes/components/state/errorState/errorState.tsx @@ -30,7 +30,7 @@ const ErrorStateBase: React.FC = ({ error, icon = ErrorCircleOI return ( - {title}} icon={} headingLevel="h5" /> + } headingLevel="h5" /> {subTitle} ); diff --git a/src/routes/components/state/loadingState/loadingState.tsx b/src/routes/components/state/loadingState/loadingState.tsx index eed2a95d8..0ae19d140 100644 --- a/src/routes/components/state/loadingState/loadingState.tsx +++ b/src/routes/components/state/loadingState/loadingState.tsx @@ -14,14 +14,13 @@ interface LoadingStateProps extends WrappedComponentProps { // defaultIntl required for testing const LoadingStateBase: React.FC = ({ intl = defaultIntl, - body = intl.formatMessage(messages.loadingStateDesc), heading = intl.formatMessage(messages.loadingStateTitle), }) => { return ( - {heading}} headingLevel="h5" /> + {body} ); diff --git a/src/routes/details/awsDetails/awsDetails.tsx b/src/routes/details/awsDetails/awsDetails.tsx index 2b9f40a7c..cf7669bd3 100644 --- a/src/routes/details/awsDetails/awsDetails.tsx +++ b/src/routes/details/awsDetails/awsDetails.tsx @@ -206,7 +206,7 @@ class AwsDetails extends React.Component { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/details/awsDetails/detailsHeader.tsx b/src/routes/details/awsDetails/detailsHeader.tsx index 71200ed90..0e2aef4a5 100644 --- a/src/routes/details/awsDetails/detailsHeader.tsx +++ b/src/routes/details/awsDetails/detailsHeader.tsx @@ -21,7 +21,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAwsRe import { filterProviders } from 'routes/utils/providers'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; @@ -39,7 +39,7 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -75,7 +75,7 @@ class DetailsHeaderBase extends React.Component { costType, currency, groupBy, - isExportsFeatureEnabled, + isExportsToggleEnabled, onCurrencySelect, onGroupBySelect, providers, @@ -95,7 +95,7 @@ class DetailsHeaderBase extends React.Component {
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -146,7 +146,7 @@ const mapStateToProps = createMapStateToProps }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/details/azureDetails/detailsHeader.tsx b/src/routes/details/azureDetails/detailsHeader.tsx index 120e88046..2fd695472 100644 --- a/src/routes/details/azureDetails/detailsHeader.tsx +++ b/src/routes/details/azureDetails/detailsHeader.tsx @@ -18,7 +18,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedAzure import { filterProviders } from 'routes/utils/providers'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; @@ -34,7 +34,7 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -59,7 +59,7 @@ class DetailsHeaderBase extends React.Component { const { currency, groupBy, - isExportsFeatureEnabled, + isExportsToggleEnabled, onCurrencySelect, onGroupBySelect, providers, @@ -79,7 +79,7 @@ class DetailsHeaderBase extends React.Component {
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -123,7 +123,7 @@ const mapStateToProps = createMapStateToProps { } private getAvailableTabs = () => { - const { costOverviewComponent, historicalDataComponent, isRosFeatureEnabled, optimizationsComponent } = this.props; + const { costOverviewComponent, historicalDataComponent, isRosToggleEnabled, optimizationsComponent } = this.props; const availableTabs = []; if (costOverviewComponent) { @@ -140,7 +141,7 @@ class BreakdownBase extends React.Component { tab: BreakdownTab.historicalData, }); } - if (optimizationsComponent && isRosFeatureEnabled) { + if (optimizationsComponent && isRosToggleEnabled) { availableTabs.push({ contentRef: React.createRef(), showBadge: true, @@ -269,6 +270,7 @@ class BreakdownBase extends React.Component { costDistribution, costType, currency, + dataDetailsComponent, description, detailsURL, emptyStateTitle, @@ -316,6 +318,7 @@ class BreakdownBase extends React.Component { : undefined } clusterInfoComponent={clusterInfoComponent} + dataDetailsComponent={dataDetailsComponent} costDistribution={costDistribution} costType={costType} currency={currency} diff --git a/src/routes/details/components/breakdown/breakdownHeader.styles.ts b/src/routes/details/components/breakdown/breakdownHeader.styles.ts index 08225ef34..20a98dc0a 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.styles.ts +++ b/src/routes/details/components/breakdown/breakdownHeader.styles.ts @@ -31,6 +31,7 @@ export const styles = { description: { color: global_disabled_color_100.value, fontSize: global_FontSize_xs.value, + paddingLeft: '1px', }, header: { backgroundColor: global_BackgroundColor_100.var, diff --git a/src/routes/details/components/breakdown/breakdownHeader.tsx b/src/routes/details/components/breakdown/breakdownHeader.tsx index 99981c7a2..9a1666ed4 100644 --- a/src/routes/details/components/breakdown/breakdownHeader.tsx +++ b/src/routes/details/components/breakdown/breakdownHeader.tsx @@ -18,7 +18,7 @@ import { Currency } from 'routes/components/currency'; import { TagLink } from 'routes/details/components/tag'; import { getGroupByCostCategory, getGroupByOrgValue, getGroupByTagKey } from 'routes/utils/groupBy'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { getTotalCostDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; import { awsCategoryKey, orgUnitIdKey, tagKey } from 'utils/props'; @@ -33,6 +33,7 @@ interface BreakdownHeaderOwnProps extends RouterComponentProps { costDistribution?: string; costType?: string; currency?: string; + dataDetailsComponent?: React.ReactNode; detailsURL?: string; description?: string; groupBy?: string; @@ -50,7 +51,7 @@ interface BreakdownHeaderOwnProps extends RouterComponentProps { } interface BreakdownHeaderStateProps { - isClusterInfoFeatureEnabled?: boolean; + isClusterInfoToggleEnabled?: boolean; } interface BreakdownHeaderDispatchProps { @@ -95,10 +96,11 @@ class BreakdownHeader extends React.Component { costDistribution, costType, currency, + dataDetailsComponent, description, groupBy, intl, - isClusterInfoFeatureEnabled, + isClusterInfoToggleEnabled, onCostDistributionSelect, onCostTypeSelect, onCurrencySelect, @@ -157,13 +159,14 @@ class BreakdownHeader extends React.Component {
{intl.formatMessage(messages.breakdownTitle, { value: title })} - {description && ( - <div> - <span style={styles.description}>{description}</span> - {clusterInfoComponent && isClusterInfoFeatureEnabled ? clusterInfoComponent : null} - </div> - )} + {description && ( +
+ {description} + {clusterInfoComponent && isClusterInfoToggleEnabled ? clusterInfoComponent : null} + {dataDetailsComponent && isClusterInfoToggleEnabled ?
{dataDetailsComponent}
: null} +
+ )} {showCostDistribution && (
@@ -201,7 +204,7 @@ class BreakdownHeader extends React.Component { const mapStateToProps = createMapStateToProps(state => { return { - isClusterInfoFeatureEnabled: featureFlagsSelectors.selectIsClusterInfoFeatureEnabled(state), + isClusterInfoToggleEnabled: FeatureToggleSelectors.selectIsClusterInfoToggleEnabled(state), }; }); diff --git a/src/routes/details/components/pvcChart/modal/pvcContent.tsx b/src/routes/details/components/pvcChart/modal/pvcContent.tsx index fb6848a54..993897226 100644 --- a/src/routes/details/components/pvcChart/modal/pvcContent.tsx +++ b/src/routes/details/components/pvcChart/modal/pvcContent.tsx @@ -91,7 +91,7 @@ const PvcContent: React.FC = () => { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/details/components/pvcChart/modal/pvcTable.tsx b/src/routes/details/components/pvcChart/modal/pvcTable.tsx index 768810b25..d0152b266 100644 --- a/src/routes/details/components/pvcChart/modal/pvcTable.tsx +++ b/src/routes/details/components/pvcChart/modal/pvcTable.tsx @@ -130,7 +130,6 @@ const PvcTable: React.FC = ({ filterBy, isLoading, onSort, orderB columns={columns} filterBy={filterBy} isLoading={isLoading} - isSelectable={false} onSort={handleOnSort} orderBy={orderBy} rows={rows} diff --git a/src/routes/details/gcpDetails/detailsHeader.tsx b/src/routes/details/gcpDetails/detailsHeader.tsx index b30c6a39c..b7a7adc8e 100644 --- a/src/routes/details/gcpDetails/detailsHeader.tsx +++ b/src/routes/details/gcpDetails/detailsHeader.tsx @@ -18,7 +18,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedGcpRe import { filterProviders } from 'routes/utils/providers'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; @@ -34,7 +34,7 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -60,7 +60,7 @@ class DetailsHeaderBase extends React.Component { const { currency, groupBy, - isExportsFeatureEnabled, + isExportsToggleEnabled, onCurrencySelect, onGroupBySelect, providers, @@ -80,7 +80,7 @@ class DetailsHeaderBase extends React.Component {
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -124,7 +124,7 @@ const mapStateToProps = createMapStateToProps { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/details/ibmDetails/detailsHeader.tsx b/src/routes/details/ibmDetails/detailsHeader.tsx index f4cbef710..80904af9f 100644 --- a/src/routes/details/ibmDetails/detailsHeader.tsx +++ b/src/routes/details/ibmDetails/detailsHeader.tsx @@ -18,7 +18,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedIbmRe import { filterProviders } from 'routes/utils/providers'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; @@ -34,7 +34,7 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -60,7 +60,7 @@ class DetailsHeaderBase extends React.Component { const { currency, groupBy, - isExportsFeatureEnabled, + isExportsToggleEnabled, onCurrencySelect, onGroupBySelect, providers, @@ -80,7 +80,7 @@ class DetailsHeaderBase extends React.Component {
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -124,7 +124,7 @@ const mapStateToProps = createMapStateToProps { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/details/ociDetails/detailsHeader.tsx b/src/routes/details/ociDetails/detailsHeader.tsx index 45b3d0332..908d2b40c 100644 --- a/src/routes/details/ociDetails/detailsHeader.tsx +++ b/src/routes/details/ociDetails/detailsHeader.tsx @@ -18,7 +18,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOciRe import { filterProviders } from 'routes/utils/providers'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; @@ -34,7 +34,7 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -59,7 +59,7 @@ class DetailsHeaderBase extends React.Component { const { currency, groupBy, - isExportsFeatureEnabled, + isExportsToggleEnabled, onCurrencySelect, onGroupBySelect, providers, @@ -79,7 +79,7 @@ class DetailsHeaderBase extends React.Component {
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -123,7 +123,7 @@ const mapStateToProps = createMapStateToProps { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/details/ocpBreakdown/clusterInfo/cloudIntegration.tsx b/src/routes/details/ocpBreakdown/clusterInfo/cloudIntegration.tsx deleted file mode 100644 index 3c09ef270..000000000 --- a/src/routes/details/ocpBreakdown/clusterInfo/cloudIntegration.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { Text, TextList, TextListItem, TextVariants } from '@patternfly/react-core'; -import type { Provider } from 'api/providers'; -import { ProviderType } from 'api/providers'; -import type { AxiosError } from 'axios'; -import messages from 'locales/messages'; -import React, { useEffect } from 'react'; -import { useIntl } from 'react-intl'; -import { useDispatch, useSelector } from 'react-redux'; -import type { AnyAction } from 'redux'; -import type { ThunkDispatch } from 'redux-thunk'; -import { routes } from 'routes'; -import { NotAvailable } from 'routes/components/page/notAvailable'; -import { LoadingState } from 'routes/components/state/loadingState'; -import type { RootState } from 'store'; -import { FetchStatus } from 'store/common'; -import { providersActions, providersSelectors } from 'store/providers'; -import { formatPath, getReleasePath } from 'utils/paths'; - -import { styles } from './modal.styles'; - -interface CloudIntegrationOwnProps { - uuid?: string; -} - -export interface CloudIntegrationStateProps { - provider: Provider; - providerError: AxiosError; - providerFetchStatus: FetchStatus; - providerQueryString: string; - uuid?: string; -} - -export interface CloudIntegrationMapProps { - // TBD... -} - -type CloudIntegrationProps = CloudIntegrationOwnProps; - -const CloudIntegration: React.FC = ({ uuid }: CloudIntegrationProps) => { - const intl = useIntl(); - - const { provider, providerError, providerFetchStatus } = useMapToProps({ uuid }); - - const title = intl.formatMessage(messages.optimizations); - - if (providerError) { - return ; - } - - const release = getReleasePath(); - - if (providerFetchStatus === FetchStatus.inProgress) { - return ( -
- -
- ); - } - return ( - <> - {intl.formatMessage(messages.cloudIntegration)} - - - - {intl.formatMessage(messages.source, { value: provider?.source_type?.toLowerCase() })} - - {provider?.name} - - {provider?.cost_models?.length === 0 && ( - - {intl.formatMessage(messages.assignCostModel)} - - )} - - - ); -}; - -// eslint-disable-next-line no-empty-pattern -const useMapToProps = ({ uuid }): CloudIntegrationStateProps => { - const dispatch: ThunkDispatch = useDispatch(); - - const providerQueryString = uuid; - const provider = useSelector( - (state: RootState) => providersSelectors.selectProviders(state, ProviderType.uuid, providerQueryString) as Provider - ); - const providerError = useSelector((state: RootState) => - providersSelectors.selectProvidersError(state, ProviderType.uuid, providerQueryString) - ); - const providerFetchStatus = useSelector((state: RootState) => - providersSelectors.selectProvidersFetchStatus(state, ProviderType.uuid, providerQueryString) - ); - - useEffect(() => { - if (!providerError && providerFetchStatus !== FetchStatus.inProgress) { - dispatch(providersActions.fetchProviders(ProviderType.uuid, providerQueryString)); - } - }, []); - - return { - provider, - providerError, - providerFetchStatus, - providerQueryString, - }; -}; - -export { CloudIntegration }; diff --git a/src/routes/details/ocpBreakdown/ocpBreakdown.tsx b/src/routes/details/ocpBreakdown/ocpBreakdown.tsx index f18cc0ab2..3e5107d14 100644 --- a/src/routes/details/ocpBreakdown/ocpBreakdown.tsx +++ b/src/routes/details/ocpBreakdown/ocpBreakdown.tsx @@ -12,11 +12,13 @@ import { connect } from 'react-redux'; import { routes } from 'routes'; import type { BreakdownStateProps } from 'routes/details/components/breakdown'; import { BreakdownBase } from 'routes/details/components/breakdown'; +import { ClusterInfo } from 'routes/details/ocpBreakdown/providerDetails/clusterInfo'; +import { DataDetails } from 'routes/details/ocpBreakdown/providerDetails/dataDetails'; import { getGroupById, getGroupByValue } from 'routes/utils/groupBy'; import { filterProviders } from 'routes/utils/providers'; import { getQueryState } from 'routes/utils/queryState'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { uiActions } from 'store/ui'; @@ -26,7 +28,6 @@ import type { RouterComponentProps } from 'utils/router'; import { withRouter } from 'utils/router'; import { getCostDistribution, getCurrency } from 'utils/sessionStorage'; -import { ClusterInfo } from './clusterInfo/clusterInfo'; import { CostOverview } from './costOverview'; import { HistoricalData } from './historicalData'; import { OcpBreakdownOptimizations } from './ocpBreakdownOptimizations'; @@ -98,6 +99,7 @@ const mapStateToProps = createMapStateToProps : undefined, + dataDetailsComponent: groupBy === 'cluster' ? : undefined, costDistribution, costOverviewComponent: ( , isOptimizationsTab: queryFromRoute.optimizationsTab !== undefined, - isRosFeatureEnabled: featureFlagsSelectors.selectIsRosFeatureEnabled(state), + isRosToggleEnabled: FeatureToggleSelectors.selectIsRosToggleEnabled(state), optimizationsComponent: groupBy === 'project' && groupByValue !== '*' ? : undefined, providers: filterProviders(providers, ProviderType.ocp), providersFetchStatus, diff --git a/src/routes/details/ocpBreakdown/clusterInfo/modal.styles.ts b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts similarity index 58% rename from src/routes/details/ocpBreakdown/clusterInfo/modal.styles.ts rename to src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts index 44efd2022..a82b201a2 100644 --- a/src/routes/details/ocpBreakdown/clusterInfo/modal.styles.ts +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.styles.ts @@ -1,20 +1,21 @@ import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; -import global_FontSize_md from '@patternfly/react-tokens/dist/js/global_FontSize_md'; import global_FontSize_xs from '@patternfly/react-tokens/dist/js/global_FontSize_xs'; +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import global_warning_color_100 from '@patternfly/react-tokens/dist/js/global_warning_color_100'; import type React from 'react'; export const styles = { clusterInfo: { fontSize: global_FontSize_xs.value, }, - container: { - overflow: 'auto', - }, loading: { backgroundColor: global_BackgroundColor_light_100.value, - minHeight: '520px', }, - spacing: { - marginRight: global_FontSize_md.value, + spacingRight: { + marginRight: global_spacer_md.value, + }, + updateAvailable: { + color: global_warning_color_100.value, + paddingLeft: '5px', }, } as { [className: string]: React.CSSProperties }; diff --git a/src/routes/details/ocpBreakdown/clusterInfo/clusterInfo.tsx b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx similarity index 82% rename from src/routes/details/ocpBreakdown/clusterInfo/clusterInfo.tsx rename to src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx index cee7f7ba9..fb74fcac8 100644 --- a/src/routes/details/ocpBreakdown/clusterInfo/clusterInfo.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfo.tsx @@ -4,8 +4,8 @@ import messages from 'locales/messages'; import React, { useState } from 'react'; import { useIntl } from 'react-intl'; -import { ClusterContent } from './clusterContent'; -import { styles } from './modal.styles'; +import { styles } from './clusterInfo.styles'; +import { ClusterInfoContent } from './clusterInfoContent'; interface ClusterInfoOwnProps { clusterId?: string; @@ -17,7 +17,7 @@ const ClusterInfo: React.FC = ({ clusterId }: ClusterInfoProps const intl = useIntl(); const [isOpen, setIsOpen] = useState(false); - const handleClose = () => { + const handleOnClose = () => { setIsOpen(false); }; @@ -33,14 +33,14 @@ const ClusterInfo: React.FC = ({ clusterId }: ClusterInfoProps - + - + ); }; -export { ClusterInfo }; +export default ClusterInfo; diff --git a/src/routes/details/ocpBreakdown/clusterInfo/clusterContent.scss b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfoContent.scss similarity index 100% rename from src/routes/details/ocpBreakdown/clusterInfo/clusterContent.scss rename to src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfoContent.scss diff --git a/src/routes/details/ocpBreakdown/clusterInfo/clusterContent.tsx b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfoContent.tsx similarity index 58% rename from src/routes/details/ocpBreakdown/clusterInfo/clusterContent.tsx rename to src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfoContent.tsx index fcfd1844a..b6bd7ce84 100644 --- a/src/routes/details/ocpBreakdown/clusterInfo/clusterContent.tsx +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/clusterInfoContent.tsx @@ -1,64 +1,49 @@ -import './clusterContent.scss'; +import './clusterInfoContent.scss'; -import { Text, TextContent, TextList, TextListItem, TextVariants } from '@patternfly/react-core'; +import { Icon, Text, TextContent, TextList, TextListItem, TextVariants } from '@patternfly/react-core'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; import type { Providers } from 'api/providers'; import { ProviderType } from 'api/providers'; import { getProvidersQuery } from 'api/queries/providersQuery'; import type { AxiosError } from 'axios'; import messages from 'locales/messages'; -import React, { useEffect } from 'react'; +import React from 'react'; import { useIntl } from 'react-intl'; -import { useDispatch, useSelector } from 'react-redux'; -import type { AnyAction } from 'redux'; -import type { ThunkDispatch } from 'redux-thunk'; +import { useSelector } from 'react-redux'; import { routes } from 'routes'; import { NotAvailable } from 'routes/components/page/notAvailable'; import { LoadingState } from 'routes/components/state/loadingState'; +import { CloudIntegration } from 'routes/details/ocpBreakdown/providerDetails/clusterInfo/components/cloudIntegration'; import { filterProviders } from 'routes/utils/providers'; import type { RootState } from 'store'; import { FetchStatus } from 'store/common'; -import { providersActions, providersQuery, providersSelectors } from 'store/providers'; +import { providersQuery, providersSelectors } from 'store/providers'; import { formatPath, getReleasePath } from 'utils/paths'; -import { CloudIntegration } from './cloudIntegration'; -import { styles } from './modal.styles'; +import { styles } from './clusterInfo.styles'; interface ClusterInfoContentOwnProps { clusterId?: string; } -export interface ClusterInfoContentStateProps { +interface ClusterInfoContentStateProps { providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; providersQueryString: string; } -export interface ClusterInfoContentMapProps { - // TBD... -} - type ClusterInfoContentProps = ClusterInfoContentOwnProps; -const ClusterContent: React.FC = ({ clusterId }: ClusterInfoContentProps) => { +const ClusterInfoContent: React.FC = ({ clusterId }: ClusterInfoContentProps) => { const intl = useIntl(); const { providers, providersError, providersFetchStatus } = useMapToProps(); - const title = intl.formatMessage(messages.optimizations); - if (providersError) { - return ; + return ; } - // Filter OCP providers to skip an extra API request - const ocpProviders = filterProviders(providers, ProviderType.ocp); - const clusterInfo = ocpProviders?.data?.find( - cluster => cluster.authentication?.credentials?.cluster_id === clusterId - ); - - const release = getReleasePath(); - if (providersFetchStatus === FetchStatus.inProgress) { return (
@@ -66,30 +51,52 @@ const ClusterContent: React.FC = ({ clusterId }: Cluste
); } + + // Filter OCP providers to skip an extra API request + const ocpProviders = filterProviders(providers, ProviderType.ocp); + const clusterProvider = ocpProviders?.data?.find(val => val.authentication?.credentials?.cluster_id === clusterId); + const cloudProvider = providers?.data?.find(val => val.uuid === clusterProvider?.infrastructure?.uuid); + + const release = getReleasePath(); + return ( {intl.formatMessage(messages.clusterId)} - {clusterId} + {clusterId} {intl.formatMessage(messages.ocpClusterDetails)} - {clusterInfo?.cost_models?.length === 0 && ( + {clusterProvider?.cost_models?.length === 0 && ( {intl.formatMessage(messages.assignCostModel)} )} - {clusterInfo && ( + {intl.formatMessage(messages.metricsOperatorVersion)} + + + {clusterProvider?.additional_context?.operator_version} + {clusterProvider?.additional_context?.operator_update_available && ( + <> + + + + {intl.formatMessage(messages.updateAvailable)} + + )} + + + {clusterProvider && ( <> {intl.formatMessage(messages.redHatIntegration)} - {intl.formatMessage(messages.source, { value: 'ocp' })} - {clusterInfo.name} + {intl.formatMessage(messages.source, { value: 'ocp' })} + {clusterProvider.name} - {clusterInfo?.infrastructure?.uuid && } + {cloudProvider && } )} @@ -98,9 +105,7 @@ const ClusterContent: React.FC = ({ clusterId }: Cluste // eslint-disable-next-line no-empty-pattern const useMapToProps = (): ClusterInfoContentStateProps => { - const dispatch: ThunkDispatch = useDispatch(); - - // PermissionsWraper has already made an API request + // PermissionsWrapper has already made an API request const providersQueryString = getProvidersQuery(providersQuery); const providers = useSelector((state: RootState) => providersSelectors.selectProviders(state, ProviderType.all, providersQueryString) @@ -112,12 +117,6 @@ const useMapToProps = (): ClusterInfoContentStateProps => { providersSelectors.selectProvidersFetchStatus(state, ProviderType.all, providersQueryString) ); - useEffect(() => { - if (!providersError && providersFetchStatus !== FetchStatus.inProgress) { - dispatch(providersActions.fetchProviders(ProviderType.all, providersQueryString)); - } - }, []); - return { providers, providersError, @@ -126,4 +125,4 @@ const useMapToProps = (): ClusterInfoContentStateProps => { }; }; -export { ClusterContent }; +export { ClusterInfoContent }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/components/cloudIntegration.tsx b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/components/cloudIntegration.tsx new file mode 100644 index 000000000..630f5646e --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/components/cloudIntegration.tsx @@ -0,0 +1,40 @@ +import { Text, TextList, TextListItem, TextVariants } from '@patternfly/react-core'; +import type { Provider } from 'api/providers'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { routes } from 'routes'; +import { SourceLink } from 'routes/details/ocpBreakdown/providerDetails/components/sourceLink'; +import { formatPath } from 'utils/paths'; + +interface CloudIntegrationOwnProps { + provider: Provider; +} + +type CloudIntegrationProps = CloudIntegrationOwnProps; + +const CloudIntegration: React.FC = ({ provider }: CloudIntegrationProps) => { + const intl = useIntl(); + + if (!provider) { + return null; + } + + return ( + <> + {intl.formatMessage(messages.cloudIntegration)} + + + + + {provider?.cost_models?.length === 0 && ( + + {intl.formatMessage(messages.assignCostModel)} + + )} + + + ); +}; + +export { CloudIntegration }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/index.ts b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/index.ts new file mode 100644 index 000000000..43774fc38 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/clusterInfo/index.ts @@ -0,0 +1 @@ +export { default as ClusterInfo } from './clusterInfo'; diff --git a/src/routes/details/ocpBreakdown/providerDetails/components/sourceLink.styles.ts b/src/routes/details/ocpBreakdown/providerDetails/components/sourceLink.styles.ts new file mode 100644 index 000000000..81c67d5f0 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/components/sourceLink.styles.ts @@ -0,0 +1,8 @@ +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import type React from 'react'; + +export const styles = { + spacingRight: { + marginRight: global_spacer_md.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/components/sourceLink.tsx b/src/routes/details/ocpBreakdown/providerDetails/components/sourceLink.tsx new file mode 100644 index 000000000..66d4ea5a0 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/components/sourceLink.tsx @@ -0,0 +1,38 @@ +import type { Provider } from 'api/providers'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { normalize } from 'routes/details/ocpBreakdown/providerDetails/utils/normailize'; +import { getReleasePath } from 'utils/paths'; + +import { styles } from './sourceLink.styles'; + +interface SourceLinkOwnProps { + provider: Provider; + showLabel?: boolean; +} + +type SourceLinkProps = SourceLinkOwnProps; + +const SourceLink: React.FC = ({ provider, showLabel = true }: SourceLinkProps) => { + const intl = useIntl(); + + if (!provider) { + return null; + } + + const release = getReleasePath(); + + return ( + <> + {showLabel && ( + + {intl.formatMessage(messages.source, { value: normalize(provider?.source_type) })} + + )} + {provider?.name || provider?.uuid} + + ); +}; + +export { SourceLink }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx new file mode 100644 index 000000000..d3ed48b1c --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData.tsx @@ -0,0 +1,82 @@ +import { ProgressStep, ProgressStepper, Text, TextContent, TextVariants } from '@patternfly/react-core'; +import type { Provider } from 'api/providers'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { SourceLink } from 'routes/details/ocpBreakdown/providerDetails/components/sourceLink'; +import { styles } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles'; +import { formatDate } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/format'; +import { getProgressStepIcon } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon'; +import { getProviderAvailability } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status'; +import { getProgressStepVariant } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/variant'; + +interface CloudDataOwnProps { + provider: Provider; +} + +type CloudDataProps = CloudDataOwnProps; + +const CloudIData: React.FC = ({ provider }: CloudDataProps) => { + const intl = useIntl(); + + if (!provider) { + return null; + } + + return ( + <> + + {intl.formatMessage(messages.dataDetailsCloudData)} + + + + {intl.formatMessage(messages.dataDetailsCloudIntegrationStatus)} +
+ +
+
+ + {intl.formatMessage(messages.dataDetailsAvailability)} +
{formatDate(provider.last_payload_received_at)}
+
+ + {intl.formatMessage(messages.dataDetailsRetrieval)} +
+ {formatDate(provider.status.download.end || provider.status.download.start)} +
+
+ + {intl.formatMessage(messages.dataDetailsProcessing)} +
+ {formatDate(provider.status.processing.end || provider.status.processing.start)} +
+
+
+ + ); +}; + +export { CloudIData }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx new file mode 100644 index 000000000..a4fa4a099 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData.tsx @@ -0,0 +1,77 @@ +import { ProgressStep, ProgressStepper, Text, TextContent, TextVariants } from '@patternfly/react-core'; +import type { Provider } from 'api/providers'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { SourceLink } from 'routes/details/ocpBreakdown/providerDetails/components/sourceLink'; +import { styles } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles'; +import { formatDate } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/format'; +import { getProgressStepIcon } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon'; +import { getProviderAvailability } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status'; +import { getProgressStepVariant } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/variant'; + +interface ClusterDataOwnProps { + provider: Provider; +} + +type ClusterDataProps = ClusterDataOwnProps; + +const ClusterData: React.FC = ({ provider }: ClusterDataProps) => { + const intl = useIntl(); + + if (!provider) { + return null; + } + + return ( + <> + + {intl.formatMessage(messages.dataDetailsClusterData)} + + + + {intl.formatMessage(messages.dataDetailsIntegrationStatus)} +
+ +
+
+ + {intl.formatMessage(messages.dataDetailsRetrieval)} +
+ {formatDate(provider.status.download.end || provider.status.download.start)} +
+
+ + {intl.formatMessage(messages.dataDetailsProcessing)} +
+ {formatDate(provider.status.processing.end || provider.status.processing.start)} +
+
+
+ + ); +}; + +export { ClusterData }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/costData.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/costData.tsx new file mode 100644 index 000000000..7422427d9 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/costData.tsx @@ -0,0 +1,51 @@ +import { ProgressStep, ProgressStepper, Text, TextContent, TextVariants } from '@patternfly/react-core'; +import type { Provider } from 'api/providers'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { styles } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles'; +import { formatDate } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/format'; +import { getProgressStepIcon } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon'; +import { getProgressStepVariant } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/variant'; + +interface CostDataOwnProps { + provider: Provider; +} + +type CostDataProps = CostDataOwnProps; + +const CostData: React.FC = ({ provider }: CostDataProps) => { + const intl = useIntl(); + + if (!provider) { + return null; + } + + return ( + <> + + {intl.formatMessage(messages.dataDetailsCostManagementData)} + + + + {intl.formatMessage(messages.dataDetailsIntegrationAndFinalization)} +
+ {formatDate(provider.status.summary.end || provider.status.summary.start)} +
+
+
+ + ); +}; + +export { CostData }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx new file mode 100644 index 000000000..69a2a45e4 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus.tsx @@ -0,0 +1,98 @@ +import type { Providers } from 'api/providers'; +import { ProviderType } from 'api/providers'; +import { getProvidersQuery } from 'api/queries/providersQuery'; +import type { AxiosError } from 'axios/index'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { styles } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles'; +import { getOverallStatusIcon } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon'; +import { + getProviderAvailability, + getProviderStatus, + StatusType, +} from 'routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status'; +import { filterProviders } from 'routes/utils/providers'; +import type { RootState } from 'store'; +import type { FetchStatus } from 'store/common'; +import { providersQuery, providersSelectors } from 'store/providers'; + +interface OverallStatusOwnProps { + clusterId?: string; +} + +interface OverallStatusStateProps { + providers: Providers; + providersError: AxiosError; + providersFetchStatus: FetchStatus; + providersQueryString: string; +} + +type OverallStatusProps = OverallStatusOwnProps; + +const OverallStatus: React.FC = ({ clusterId }: OverallStatusProps) => { + const { providers, providersError } = useMapToProps(); + + if (!providers || providersError) { + return null; + } + + // Filter OCP providers to skip an extra API request + const ocpProviders = filterProviders(providers, ProviderType.ocp); + const clusterProvider = ocpProviders?.data?.find(val => val.authentication?.credentials?.cluster_id === clusterId); + const cloudProvider = providers?.data?.find(val => val.uuid === clusterProvider?.infrastructure?.uuid); + + const getOverallStatus = () => { + let status; + + const cloudAvailability = getProviderAvailability(cloudProvider); + const clusterAvailability = getProviderAvailability(clusterProvider); + const cloudStatus = getProviderStatus(cloudProvider); + const clusterStatus = getProviderStatus(clusterProvider); + + if (cloudAvailability === StatusType.failed || clusterAvailability === StatusType.failed) { + status = StatusType.failed; + } else if (cloudStatus === StatusType.failed || clusterStatus === StatusType.failed) { + status = 'failed'; + } else if (cloudAvailability === StatusType.paused || clusterAvailability === StatusType.paused) { + status = 'paused'; + } else if (cloudStatus === StatusType.inProgress || clusterStatus === StatusType.inProgress) { + status = 'in_progress'; + } else if (cloudStatus === StatusType.pending || clusterStatus === StatusType.pending) { + status = 'pending'; + } else if ( + cloudStatus === StatusType.complete && + clusterStatus === StatusType.complete && + cloudAvailability === StatusType.complete && + clusterAvailability === StatusType.complete + ) { + status = 'complete'; + } + return status; + }; + + return {getOverallStatusIcon(getOverallStatus())}; +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = (): OverallStatusStateProps => { + // PermissionsWrapper has already made an API request + const providersQueryString = getProvidersQuery(providersQuery); + const providers = useSelector((state: RootState) => + providersSelectors.selectProviders(state, ProviderType.all, providersQueryString) + ); + const providersError = useSelector((state: RootState) => + providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString) + ); + const providersFetchStatus = useSelector((state: RootState) => + providersSelectors.selectProvidersFetchStatus(state, ProviderType.all, providersQueryString) + ); + + return { + providers, + providersError, + providersFetchStatus, + providersQueryString, + }; +}; + +export { OverallStatus }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts new file mode 100644 index 000000000..4f7833cec --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.styles.ts @@ -0,0 +1,34 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_disabled_color_100 from '@patternfly/react-tokens/dist/js/global_disabled_color_100'; +import global_FontSize_xs from '@patternfly/react-tokens/dist/js/global_FontSize_xs'; +import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg'; +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm'; +import type React from 'react'; + +export const styles = { + dataDetailsButton: { + fontSize: global_FontSize_xs.value, + paddingLeft: global_spacer_sm.value, + }, + description: { + color: global_disabled_color_100.value, + fontSize: global_FontSize_xs.value, + }, + loading: { + backgroundColor: global_BackgroundColor_light_100.value, + }, + sourceLink: { + fontSize: global_FontSize_xs.value, + }, + spacingRight: { + marginRight: global_spacer_md.value, + }, + statusIcon: { + paddingLeft: '1px', + paddingRight: '5px', + }, + stepper: { + margin: global_spacer_lg.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx new file mode 100644 index 000000000..448fae66a --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetails.tsx @@ -0,0 +1,50 @@ +import { Button, ButtonVariant } from '@patternfly/react-core'; +import { Modal, ModalBody, ModalHeader, ModalVariant } from '@patternfly/react-core/next'; +import messages from 'locales/messages'; +import React, { useState } from 'react'; +import { useIntl } from 'react-intl'; +import { OverallStatus } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/components/overallStatus'; + +import { styles } from './dataDetails.styles'; +import { DataDetailsContent } from './dataDetailsContent'; + +interface DataDetailsOwnProps { + clusterId?: string; +} + +type DataDetailsProps = DataDetailsOwnProps; + +const DataDetails: React.FC = ({ clusterId }: DataDetailsProps) => { + const intl = useIntl(); + const [isOpen, setIsOpen] = useState(false); + + const handleOnClose = () => { + setIsOpen(false); + }; + + const handleOnClick = () => { + setIsOpen(!isOpen); + }; + + // PatternFly modal appends to document.body, which is outside the scoped "costManagement" dom tree. + // Use className="costManagement" to override PatternFly styles or append the modal to an element within the tree + + return ( + <> +
+ + +
+ + + + + + + + ); +}; + +export default DataDetails; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetailsContent.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetailsContent.tsx new file mode 100644 index 000000000..a0c285dad --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/dataDetailsContent.tsx @@ -0,0 +1,89 @@ +import type { Providers } from 'api/providers'; +import { ProviderType } from 'api/providers'; +import { getProvidersQuery } from 'api/queries/providersQuery'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { useSelector } from 'react-redux'; +import { NotAvailable } from 'routes/components/page/notAvailable'; +import { LoadingState } from 'routes/components/state/loadingState'; +import { CloudIData } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/components/cloudIData'; +import { ClusterData } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/components/clusterData'; +import { CostData } from 'routes/details/ocpBreakdown/providerDetails/dataDetails/components/costData'; +import { filterProviders } from 'routes/utils/providers'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { providersQuery, providersSelectors } from 'store/providers'; + +import { styles } from './dataDetails.styles'; + +interface DataDetailsContentOwnProps { + clusterId?: string; +} + +interface DataDetailsContentStateProps { + providers: Providers; + providersError: AxiosError; + providersFetchStatus: FetchStatus; + providersQueryString: string; +} + +type DataDetailsContentProps = DataDetailsContentOwnProps; + +const DataDetailsContent: React.FC = ({ clusterId }: DataDetailsContentProps) => { + const intl = useIntl(); + + const { providers, providersError, providersFetchStatus } = useMapToProps(); + + const title = intl.formatMessage(messages.optimizations); + + if (providersError) { + return ; + } + + if (providersFetchStatus === FetchStatus.inProgress) { + return ( +
+ +
+ ); + } + + // Filter OCP providers to skip an extra API request + const ocpProviders = filterProviders(providers, ProviderType.ocp); + const clusterProvider = ocpProviders?.data?.find(val => val.authentication?.credentials?.cluster_id === clusterId); + const cloudProvider = providers?.data?.find(val => val.uuid === clusterProvider?.infrastructure?.uuid); + + return ( + <> + {cloudProvider && } + {clusterProvider && } + {clusterProvider && } + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = (): DataDetailsContentStateProps => { + // PermissionsWrapper has already made an API request + const providersQueryString = getProvidersQuery(providersQuery); + const providers = useSelector((state: RootState) => + providersSelectors.selectProviders(state, ProviderType.all, providersQueryString) + ); + const providersError = useSelector((state: RootState) => + providersSelectors.selectProvidersError(state, ProviderType.all, providersQueryString) + ); + const providersFetchStatus = useSelector((state: RootState) => + providersSelectors.selectProvidersFetchStatus(state, ProviderType.all, providersQueryString) + ); + + return { + providers, + providersError, + providersFetchStatus, + providersQueryString, + }; +}; + +export { DataDetailsContent }; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/index.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/index.ts new file mode 100644 index 000000000..21ecfb844 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/index.ts @@ -0,0 +1 @@ +export { default as DataDetails } from './dataDetails'; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/format.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/format.ts new file mode 100644 index 000000000..81ec20a7d --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/format.ts @@ -0,0 +1,17 @@ +import { intl } from 'components/i18n'; + +export const formatDate = (date: string) => { + if (!date) { + return null; + } + return intl.formatDate(date, { + day: 'numeric', + hour: 'numeric', + hour12: false, + minute: 'numeric', + month: 'short', + timeZone: 'UTC', + timeZoneName: 'short', + year: 'numeric', + }); +}; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon.tsx b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon.tsx new file mode 100644 index 000000000..74e85ec1f --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/icon.tsx @@ -0,0 +1,50 @@ +import { Icon } from '@patternfly/react-core'; +import { CheckCircleIcon } from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; +import { ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +import { InProgressIcon } from '@patternfly/react-icons/dist/esm/icons/in-progress-icon'; +import { PauseIcon } from '@patternfly/react-icons/dist/esm/icons/pause-icon'; +import { PendingIcon } from '@patternfly/react-icons/dist/esm/icons/pending-icon'; +import React from 'react'; + +import { lookupKey, StatusType } from './status'; + +export const getProgressStepIcon = (value: string) => { + switch (lookupKey(value)) { + case StatusType.inProgress: + return ; + case StatusType.paused: + return ; + case StatusType.pending: + return ; + default: + return undefined; + } +}; + +export const getOverallStatusIcon = (status: StatusType) => { + let icon; + let variant; + + switch (status) { + case StatusType.complete: + icon = ; + variant = 'success'; // Use green color + break; + case StatusType.failed: + icon = ; + variant = 'danger'; // Use red color + break; + case StatusType.inProgress: + icon = ; + break; + case StatusType.paused: + icon = ; + break; + case StatusType.pending: + icon = ; + break; + default: + break; + } + return icon ? {icon} : null; +}; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts new file mode 100644 index 000000000..e82b3f938 --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/status.ts @@ -0,0 +1,82 @@ +import type { Provider } from 'api/providers'; +import { normalize } from 'routes/details/ocpBreakdown/providerDetails/utils/normailize'; + +// eslint-disable-next-line no-shadow +export const enum StatusType { + complete = 'complete', + failed = 'failed', + inProgress = 'in_progress', + paused = 'paused', + pending = 'pending', +} + +export const lookupKey = (value: string) => { + switch (normalize(value)) { + case 'complete': + return StatusType.complete; + case 'failed': + return StatusType.failed; + case 'in_progress': + return StatusType.inProgress; + case 'paused': + return StatusType.paused; + case 'pending': + return StatusType.pending; + default: + return undefined; + } +}; + +export const getProviderAvailability = (provider: Provider) => { + let status; + if (!provider) { + return status; + } + + if (provider.active === false && provider.paused === false) { + status = StatusType.failed; // Inactive sources + } else if (provider.paused === true) { + status = StatusType.paused; + } else { + status = StatusType.complete; + } + return status; +}; + +export const getProviderStatus = (provider: Provider) => { + let status; + if (!provider) { + return status; + } + + const downloadState = lookupKey(provider.status.download.state); + const processingState = lookupKey(provider.status.processing.state); + const summaryState = lookupKey(provider.status.summary.state); + + if ( + downloadState === StatusType.failed || + processingState === StatusType.failed || + summaryState === StatusType.failed + ) { + status = StatusType.failed; + } else if ( + downloadState === StatusType.inProgress || + processingState === StatusType.inProgress || + summaryState === StatusType.inProgress + ) { + status = StatusType.inProgress; + } else if ( + downloadState === StatusType.pending || + processingState === StatusType.pending || + summaryState === StatusType.pending + ) { + status = StatusType.pending; + } else if ( + downloadState === StatusType.complete || + processingState === StatusType.complete || + summaryState === StatusType.complete + ) { + status = StatusType.complete; + } + return status; +}; diff --git a/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/variant.ts b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/variant.ts new file mode 100644 index 000000000..c1d881cfe --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/dataDetails/utils/variant.ts @@ -0,0 +1,16 @@ +import { lookupKey, StatusType } from './status'; + +export const getProgressStepVariant = (value: string) => { + switch (lookupKey(value)) { + case StatusType.complete: + return 'success'; + case StatusType.failed: + return 'danger'; + case StatusType.pending: + return 'pending'; + case StatusType.inProgress: // Use 'default' status with custom icon + case StatusType.paused: + default: + return 'default'; + } +}; diff --git a/src/routes/details/ocpBreakdown/providerDetails/utils/normailize.ts b/src/routes/details/ocpBreakdown/providerDetails/utils/normailize.ts new file mode 100644 index 000000000..232421e0e --- /dev/null +++ b/src/routes/details/ocpBreakdown/providerDetails/utils/normailize.ts @@ -0,0 +1,3 @@ +export const normalize = (value: string) => { + return value ? value.toLowerCase().replace('-', '_') : undefined; +}; diff --git a/src/routes/details/ocpDetails/detailsHeader.tsx b/src/routes/details/ocpDetails/detailsHeader.tsx index 8df28f0c5..ff5daf412 100644 --- a/src/routes/details/ocpDetails/detailsHeader.tsx +++ b/src/routes/details/ocpDetails/detailsHeader.tsx @@ -21,7 +21,7 @@ import { getIdKeyForGroupBy } from 'routes/utils/computedReport/getComputedOcpRe import { filterProviders } from 'routes/utils/providers'; import type { FetchStatus } from 'store/common'; import { createMapStateToProps } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { getSinceDateRangeString } from 'utils/dates'; import { formatCurrency } from 'utils/format'; @@ -39,7 +39,7 @@ interface DetailsHeaderOwnProps { } interface DetailsHeaderStateProps { - isExportsFeatureEnabled?: boolean; + isExportsToggleEnabled?: boolean; providers: Providers; providersError: AxiosError; providersFetchStatus: FetchStatus; @@ -70,7 +70,7 @@ class DetailsHeaderBase extends React.Component
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -167,7 +167,7 @@ const mapStateToProps = createMapStateToProps; isAllSelected?: boolean; isLoading?: boolean; - isRosFeatureEnabled?: boolean; + isRosToggleEnabled?: boolean; onSelect(items: ComputedReportItem[], isSelected: boolean); onSort(value: string, isSortAscending: boolean); orderBy?: any; @@ -95,7 +95,7 @@ class DetailsTableBase extends React.Component { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; private getTable = () => { - const { costDistribution, isRosFeatureEnabled, query, report, reportFetchStatus, reportQueryString, router } = + const { costDistribution, isRosToggleEnabled, query, report, reportFetchStatus, reportQueryString, router } = this.props; const { hiddenColumns, isAllSelected, selectedItems } = this.state; @@ -266,7 +266,7 @@ class OcpDetails extends React.Component { hiddenColumns={hiddenColumns} isAllSelected={isAllSelected} isLoading={reportFetchStatus === FetchStatus.inProgress} - isRosFeatureEnabled={isRosFeatureEnabled} + isRosToggleEnabled={isRosToggleEnabled} onSelect={this.handleOnSelect} onSort={(sortType, isSortAscending) => handleOnSort(query, router, sortType, isSortAscending)} orderBy={query.order_by} @@ -519,7 +519,7 @@ const mapStateToProps = createMapStateToProps { const { currency, groupBy, - isExportsFeatureEnabled, + isExportsToggleEnabled, onCurrencySelect, onGroupBySelect, providers, @@ -105,7 +105,7 @@ class DetailsHeaderBase extends React.Component {
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -154,7 +154,7 @@ const mapStateToProps = createMapStateToProps { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/explorer/explorer.tsx b/src/routes/explorer/explorer.tsx index ae80fe356..587c90607 100644 --- a/src/routes/explorer/explorer.tsx +++ b/src/routes/explorer/explorer.tsx @@ -39,7 +39,7 @@ import { handleOnSort, } from 'routes/utils/queryNavigate'; import { createMapStateToProps, FetchStatus } from 'store/common'; -import { featureFlagsSelectors } from 'store/featureFlags'; +import { FeatureToggleSelectors } from 'store/featureToggle'; import { providersQuery, providersSelectors } from 'store/providers'; import { reportActions, reportSelectors } from 'store/reports'; import { userAccessQuery, userAccessSelectors } from 'store/userAccess'; @@ -81,7 +81,7 @@ interface ExplorerStateProps { dateRangeType: DateRangeType; gcpProviders: Providers; ibmProviders: Providers; - isFinsightsFeatureEnabled?: boolean; + isFinsightsToggleEnabled?: boolean; ocpProviders: Providers; perspective: PerspectiveType; providers: Providers; @@ -243,7 +243,7 @@ class Explorer extends React.Component { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; @@ -421,8 +421,8 @@ class Explorer extends React.Component { }; private isRhelAvailable = () => { - const { isFinsightsFeatureEnabled, rhelProviders, userAccess } = this.props; - return isFinsightsFeatureEnabled && isRhelAvailable(userAccess, rhelProviders); + const { isFinsightsToggleEnabled, rhelProviders, userAccess } = this.props; + return isFinsightsToggleEnabled && isRhelAvailable(userAccess, rhelProviders); }; private updateReport = () => { @@ -664,7 +664,7 @@ const mapStateToProps = createMapStateToProps { - const { isIbmFeatureEnabled } = this.props; + const { isIbmToggleEnabled } = this.props; const { currentPerspective } = this.state; const hasAws = this.isAwsAvailable(); @@ -158,7 +158,7 @@ class ExplorerHeaderBase extends React.Component ); @@ -243,8 +243,8 @@ class ExplorerHeaderBase extends React.Component { - const { isFinsightsFeatureEnabled, rhelProviders, userAccess } = this.props; - return isFinsightsFeatureEnabled && isRhelAvailable(userAccess, rhelProviders); + const { isFinsightsToggleEnabled, rhelProviders, userAccess } = this.props; + return isFinsightsToggleEnabled && isRhelAvailable(userAccess, rhelProviders); }; public render() { @@ -254,7 +254,7 @@ class ExplorerHeaderBase extends React.Component
- {isExportsFeatureEnabled && } + {isExportsToggleEnabled && }
@@ -379,9 +379,9 @@ const mapStateToProps = createMapStateToProps string; - isRosFeatureEnabled?: boolean; + isRosToggleEnabled?: boolean; previousReport?: Report; previousReportError?: AxiosError; previousReportFetchStatus?: number; @@ -573,9 +573,9 @@ class DashboardWidgetBase extends React.Component { } private getAvailableTabs = () => { - const { isFinsightsFeatureEnabled } = this.props; + const { isFinsightsToggleEnabled } = this.props; const availableTabs = []; const infrastructureTabs = @@ -230,7 +230,7 @@ class OverviewBase extends React.Component { ] : undefined; - if (isFinsightsFeatureEnabled) { + if (isFinsightsToggleEnabled) { if (infrastructureTabs) { availableTabs.push(...infrastructureTabs); } @@ -276,7 +276,7 @@ class OverviewBase extends React.Component { }; private getCurrentTab = () => { - const { isFinsightsFeatureEnabled } = this.props; + const { isFinsightsToggleEnabled } = this.props; const { activeTabKey } = this.state; const hasAws = this.isAwsAvailable(); @@ -300,7 +300,7 @@ class OverviewBase extends React.Component { } else if (showRhelOnly) { return OverviewTab.rhel; } else { - if (isFinsightsFeatureEnabled) { + if (isFinsightsToggleEnabled) { switch (activeTabKey) { case 0: return OverviewTab.infrastructure; @@ -387,7 +387,7 @@ class OverviewBase extends React.Component { }; private getPerspective = () => { - const { isIbmFeatureEnabled } = this.props; + const { isIbmToggleEnabled } = this.props; const { currentInfrastructurePerspective, currentOcpPerspective, currentRhelPerspective } = this.state; const hasAws = this.isAwsAvailable(); @@ -432,7 +432,7 @@ class OverviewBase extends React.Component { hasOcp={hasOcp} hasOcpCloud={this.isOcpCloudAvailable()} hasRhel={hasRhel} - isIbmFeatureEnabled={isIbmFeatureEnabled} + isIbmToggleEnabled={isIbmToggleEnabled} isInfrastructureTab={OverviewTab.infrastructure === currentTab} isRhelTab={OverviewTab.rhel === currentTab} onSelect={this.handleOnPerspectiveSelect} @@ -565,10 +565,10 @@ class OverviewBase extends React.Component { }; private getTabTitle = (tab: OverviewTab) => { - const { intl, isFinsightsFeatureEnabled } = this.props; + const { intl, isFinsightsToggleEnabled } = this.props; if (tab === OverviewTab.infrastructure) { - if (isFinsightsFeatureEnabled) { + if (isFinsightsToggleEnabled) { return intl.formatMessage(messages.summary); } return intl.formatMessage(messages.infrastructure); @@ -698,12 +698,12 @@ class OverviewBase extends React.Component { }; private isRhelAvailable = () => { - const { isFinsightsFeatureEnabled, rhelProviders, userAccess } = this.props; - return isFinsightsFeatureEnabled && isRhelAvailable(userAccess, rhelProviders); + const { isFinsightsToggleEnabled, rhelProviders, userAccess } = this.props; + return isFinsightsToggleEnabled && isRhelAvailable(userAccess, rhelProviders); }; public render() { - const { providersFetchStatus, intl, isFinsightsFeatureEnabled, isIbmFeatureEnabled, userAccessFetchStatus } = + const { providersFetchStatus, intl, isFinsightsToggleEnabled, isIbmToggleEnabled, userAccessFetchStatus } = this.props; // Note: No need to test OCP on cloud here, since that requires at least one provider @@ -746,7 +746,7 @@ class OverviewBase extends React.Component {

{intl.formatMessage(messages.openShift)}

{intl.formatMessage(messages.openShiftDesc)}


- {isFinsightsFeatureEnabled && ( + {isFinsightsToggleEnabled && ( <>

{intl.formatMessage(messages.rhel)}

{intl.formatMessage(messages.rhelDesc)}

@@ -758,7 +758,7 @@ class OverviewBase extends React.Component {

{intl.formatMessage(messages.gcp)}

{intl.formatMessage(messages.gcpDesc)}

- {isIbmFeatureEnabled && ( + {isIbmToggleEnabled && ( <>

{intl.formatMessage(messages.ibm)}

@@ -840,8 +840,8 @@ const mapStateToProps = createMapStateToProps = ({ canWrite }) => { + const [costType, setCostType] = useState(getAccountCostType()); + const [currency, setCurrency] = useState(getAccountCostType()); -type SettingsProps = SettingsOwnProps & - SettingsStateProps & - SettingsDispatchProps & - RouterComponentProps & - WrappedComponentProps; + const dispatch: ThunkDispatch = useDispatch(); + const intl = useIntl(); -class SettingsBase extends React.Component { - protected defaultState: SettingsState = { - currentCostType: getAccountCostType(), - currentCurrency: getAccountCurrency(), - }; - public state: SettingsState = { ...this.defaultState }; - - private getCostType = () => { - const { canWrite, intl } = this.props; - const { currentCostType } = this.state; + const { costTypeAccountSettingsUpdateStatus, currencyAccountSettingsUpdateStatus } = useMapToProps(); + const getCostType = () => { return (
@@ -59,12 +43,12 @@ class SettingsBase extends React.Component<SettingsProps, SettingsState> { {intl.formatMessage(messages.costTypeSettingsDesc)}
- {this.getTooltip( + {getTooltip( )} @@ -73,10 +57,7 @@ class SettingsBase extends React.Component { ); }; - private getCurrency = () => { - const { canWrite, intl } = this.props; - const { currentCurrency } = this.state; - + const getCurrency = () => { return (
@@ -84,12 +65,12 @@ class SettingsBase extends React.Component<SettingsProps, SettingsState> { {intl.formatMessage(messages.currencyDesc)}
- {this.getTooltip( + {getTooltip( )} @@ -98,64 +79,52 @@ class SettingsBase extends React.Component { ); }; - private getTooltip = comp => { - const { canWrite, intl } = this.props; - + const getTooltip = comp => { return !canWrite ? {comp} : comp; }; - private handleOnCostTypeSelected = value => { - const { updateCostType } = this.props; - - this.setState({ currentCostType: value }, () => { - updateCostType(AccountSettingsType.costType, { + const handleOnCostTypeSelected = value => { + dispatch( + accountSettingsActions.updateAccountSettings(AccountSettingsType.costType, { cost_type: value, - }); - }); + }) + ); }; - private handleOnCurrencySelected = value => { - const { updateCurrency } = this.props; - - this.setState({ currentCurrency: value }, () => { - updateCurrency(AccountSettingsType.currency, { + const handleOnCurrencySelected = value => { + dispatch( + accountSettingsActions.updateAccountSettings(AccountSettingsType.currency, { currency: value, - }); - }); + }) + ); }; - public render() { - return ( - - {this.getCurrency()} - {this.getCostType()} - - ); - } -} + useEffect(() => { + setCostType(getAccountCostType()); + setCurrency(getAccountCurrency()); + }, [costTypeAccountSettingsUpdateStatus, currencyAccountSettingsUpdateStatus]); -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const mapStateToProps = createMapStateToProps(state => { - const updateCostTypeStatus = accountSettingsSelectors.selectAccountSettingsUpdateStatus( - state, - AccountSettingsType.costType + return ( + + {getCurrency()} + {getCostType()} + ); - const updateCurrencyStatus = accountSettingsSelectors.selectAccountSettingsUpdateStatus( - state, - AccountSettingsType.currency +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = (): CalculationsStateProps => { + const costTypeAccountSettingsUpdateStatus = useSelector((state: RootState) => + accountSettingsSelectors.selectAccountSettingsUpdateStatus(state, AccountSettingsType.costType) + ); + const currencyAccountSettingsUpdateStatus = useSelector((state: RootState) => + accountSettingsSelectors.selectAccountSettingsUpdateStatus(state, AccountSettingsType.currency) ); return { - updateCostTypeStatus, - updateCurrencyStatus, + costTypeAccountSettingsUpdateStatus, + currencyAccountSettingsUpdateStatus, }; -}); - -const mapDispatchToProps: SettingsDispatchProps = { - updateCostType: accountSettingsActions.updateAccountSettings, - updateCurrency: accountSettingsActions.updateAccountSettings, }; -const Calculations = injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(SettingsBase))); - export default Calculations; diff --git a/src/routes/settings/costCategory/costCategory.tsx b/src/routes/settings/costCategory/costCategory.tsx index f30de1546..c90466927 100644 --- a/src/routes/settings/costCategory/costCategory.tsx +++ b/src/routes/settings/costCategory/costCategory.tsx @@ -86,7 +86,7 @@ const CostCategory: React.FC = ({ canWrite }) => { }), }} variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} - widgetId={`exports-pagination${isBottom ? '-bottom' : ''}`} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} /> ); }; diff --git a/src/routes/settings/costCategory/costCategoryTable.tsx b/src/routes/settings/costCategory/costCategoryTable.tsx index da7fdfce4..3eeaa1d81 100644 --- a/src/routes/settings/costCategory/costCategoryTable.tsx +++ b/src/routes/settings/costCategory/costCategoryTable.tsx @@ -3,14 +3,11 @@ import 'routes/components/dataTable/dataTable.scss'; import { Label } from '@patternfly/react-core'; import type { Settings, SettingsData } from 'api/settings'; import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; import { DataTable } from 'routes/components/dataTable'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; -interface CostCategoryOwnProps extends RouterComponentProps, WrappedComponentProps { +interface CostCategoryTableOwnProps { canWrite?: boolean; filterBy?: any; isLoading?: boolean; @@ -21,43 +18,31 @@ interface CostCategoryOwnProps extends RouterComponentProps, WrappedComponentPro settings: Settings; } -interface CostCategoryState { - columns?: any[]; - rows?: any[]; -} - -type CostCategoryProps = CostCategoryOwnProps; - -class CostCategoryBase extends React.Component { - public state: CostCategoryState = { - columns: [], - rows: [], - }; - - public componentDidMount() { - this.initDatum(); - } - - public componentDidUpdate(prevProps: CostCategoryProps) { - const { selectedItems, settings } = this.props; - const currentReport = settings?.data ? JSON.stringify(settings.data) : ''; - const previousReport = prevProps?.settings.data ? JSON.stringify(prevProps.settings.data) : ''; - - if (previousReport !== currentReport || prevProps.selectedItems !== selectedItems) { - this.initDatum(); - } - } - - private initDatum = () => { - const { canWrite, intl, selectedItems, settings } = this.props; +type CostCategoryTableProps = CostCategoryTableOwnProps; + +const CostCategoryTable: React.FC = ({ + canWrite, + filterBy, + isLoading, + onSelect, + onSort, + orderBy, + selectedItems, + settings, +}) => { + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState([]); + const intl = useIntl(); + + const initDatum = () => { if (!settings) { return; } - const rows = []; + const newRows = []; const categories = settings?.data ? (settings.data as any) : []; - const columns = [ + const newColumns = [ { name: '', // Selection column }, @@ -74,7 +59,7 @@ class CostCategoryBase extends React.Component { - rows.push({ + newRows.push({ cells: [ {}, // Empty cell for row selection { @@ -94,37 +79,33 @@ class CostCategoryBase extends React.Component !column.hidden); - const filteredRows = rows.map(({ ...row }) => { + const filteredColumns = (newColumns as any[]).filter(column => !column.hidden); + const filteredRows = newRows.map(({ ...row }) => { row.cells = row.cells.filter(cell => !cell.hidden); return row; }); - this.setState({ - columns: filteredColumns, - rows: filteredRows, - }); + setColumns(filteredColumns); + setRows(filteredRows); }; - public render() { - const { filterBy, isLoading, onSelect, onSort, orderBy, selectedItems } = this.props; - const { columns, rows } = this.state; - - return ( - - ); - } -} - -const CostCategoryTable = injectIntl(withRouter(CostCategoryBase)); + useEffect(() => { + initDatum(); + }, [selectedItems, settings]); + + return ( + + ); +}; export { CostCategoryTable }; diff --git a/src/routes/settings/costCategory/costCategoryToolbar.tsx b/src/routes/settings/costCategory/costCategoryToolbar.tsx index b77be50d2..44cba283b 100644 --- a/src/routes/settings/costCategory/costCategoryToolbar.tsx +++ b/src/routes/settings/costCategory/costCategoryToolbar.tsx @@ -4,13 +4,10 @@ import { ResourcePathsType } from 'api/resources/resource'; import type { SettingsData } from 'api/settings'; import messages from 'locales/messages'; import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; +import { useIntl } from 'react-intl'; import { BasicToolbar } from 'routes/components/dataToolbar'; import type { ToolbarChipGroupExt } from 'routes/components/dataToolbar/utils/common'; import type { Filter } from 'routes/utils/filter'; -import { createMapStateToProps } from 'store/common'; import { styles } from './costCategory.styles'; @@ -32,52 +29,36 @@ interface CostCategoryToolbarOwnProps { showBulkSelectAll?: boolean; } -interface CostCategoryToolbarStateProps { - // TBD... -} - -interface CostCategoryToolbarDispatchProps { - // TBD... -} - -interface CostCategoryToolbarState { - categoryOptions?: ToolbarChipGroupExt[]; -} - -type CostCategoryToolbarProps = CostCategoryToolbarOwnProps & - CostCategoryToolbarStateProps & - CostCategoryToolbarDispatchProps & - WrappedComponentProps; - -export class CostCategoryToolbarBase extends React.Component { - protected defaultState: CostCategoryToolbarState = {}; - public state: CostCategoryToolbarState = { ...this.defaultState }; - - public componentDidMount() { - this.setState({ - categoryOptions: this.getCategoryOptions(), - }); - } - - private getActions = () => { - const { - canWrite, - intl, - isPrimaryActionDisabled, - isSecondaryActionDisabled, - onDisableTags, - onEnableTags, - selectedItems, - } = this.props; - - const isDisabled = !canWrite || selectedItems.length === 0; +type CostCategoryToolbarProps = CostCategoryToolbarOwnProps; + +const CostCategoryToolbar: React.FC = ({ + canWrite, + isDisabled, + isPrimaryActionDisabled, + isSecondaryActionDisabled, + itemsPerPage, + itemsTotal, + onBulkSelect, + onDisableTags, + onEnableTags, + onFilterAdded, + onFilterRemoved, + pagination, + query, + selectedItems, + showBulkSelectAll, +}) => { + const intl = useIntl(); + + const getActions = () => { + const isAriaDisabled = !canWrite || isDisabled || selectedItems.length === 0; const tooltip = intl.formatMessage(!canWrite ? messages.readOnlyPermissions : messages.selectCategories); return ( <> + + + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = (): ChildTagMappingStateProps => { + const settingsUpdateStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, SettingsType.tagsMappingsChildAdd) + ); + const settingsUpdateError = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateError(state, SettingsType.tagsMappingsChildAdd) + ); + + return { + settingsUpdateError, + settingsUpdateStatus, + }; +}; + +export default ChildTagMapping; diff --git a/src/routes/settings/tagLabels/tagMapping/components/childTagMapping/index.ts b/src/routes/settings/tagLabels/tagMapping/components/childTagMapping/index.ts new file mode 100644 index 000000000..b7b26a638 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/childTagMapping/index.ts @@ -0,0 +1 @@ +export { default as ChildTagMapping } from './childTagMapping'; diff --git a/src/routes/settings/tagLabels/tagMapping/components/childTags/childTags.tsx b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTags.tsx new file mode 100644 index 000000000..b82536730 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTags.tsx @@ -0,0 +1,222 @@ +import Unavailable from '@patternfly/react-component-groups/dist/esm/UnavailableContent'; +import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import type { Query } from 'api/queries/query'; +import { getQuery } from 'api/queries/query'; +import type { Settings, SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { LoadingState } from 'routes/components/state/loadingState'; +import * as queryUtils from 'routes/utils/query'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { settingsActions, settingsSelectors } from 'store/settings'; + +import { ChildTagsTable } from './childTagsTable'; +import { styles } from './childTagsTable.styles'; +import { ChildTagsToolbar } from './childTagsToolbar'; + +interface ChildTagsOwnProps { + onBulkSelect(items: SettingsData[]); + onSelect(items: SettingsData[], isSelected: boolean); + selectedItems?: SettingsData[]; +} + +interface ChildTagsMapProps { + query?: Query; +} + +interface ChildTagsStateProps { + settings?: Settings; + settingsError?: AxiosError; + settingsStatus?: FetchStatus; + settingsQueryString?: string; +} + +type ChildTagsProps = ChildTagsOwnProps; + +const baseQuery: Query = { + limit: 10, + offset: 0, + filter_by: {}, + order_by: { + parent: 'asc', + }, +}; + +const ChildTags: React.FC = ({ onBulkSelect, onSelect, selectedItems }: ChildTagsProps) => { + const [query, setQuery] = useState({ ...baseQuery }); + const { settings, settingsError, settingsStatus } = useMapToProps({ query }); + + const intl = useIntl(); + + const getMappings = () => { + if (settings) { + return settings.data as any; + } + return []; + }; + + const getPagination = (isDisabled = false, isBottom = false) => { + const count = settings?.meta ? settings.meta.count : 0; + const limit = settings?.meta ? settings.meta.limit : baseQuery.limit; + const offset = settings?.meta ? settings.meta.offset : baseQuery.offset; + const page = Math.trunc(offset / limit + 1); + + return ( + handleOnPerPageSelect(perPage)} + onSetPage={(event, pageNumber) => handleOnSetPage(pageNumber)} + page={page} + perPage={limit} + titles={{ + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { + title: intl.formatMessage(messages.openShift), + placement: isBottom ? 'bottom' : 'top', + }), + }} + variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} + /> + ); + }; + + const getTable = () => { + return ( + handleOnSort(sortType, isSortAscending)} + selectedItems={selectedItems} + settings={settings} + /> + ); + }; + + const getToolbar = (mappings: SettingsData[]) => { + const itemsTotal = settings?.meta ? settings.meta.count : 0; + + return ( + handleOnFilterAdded(filter)} + onFilterRemoved={filter => handleOnFilterRemoved(filter)} + pagination={getPagination(isDisabled)} + selectedItems={selectedItems} + query={query} + /> + ); + }; + + const handleOnBulkSelect = (action: string) => { + if (action === 'none') { + onBulkSelect([]); + } else if (action === 'page') { + const newSelectedItems = [...selectedItems]; + getMappings().map(val => { + if (!newSelectedItems.find(item => item.uuid === val.uuid)) { + newSelectedItems.push(val); + } + }); + onBulkSelect(newSelectedItems); + } + }; + + const handleOnFilterAdded = filter => { + const newQuery = queryUtils.handleOnFilterAdded(query, filter); + setQuery(newQuery); + }; + + const handleOnFilterRemoved = filter => { + const newQuery = queryUtils.handleOnFilterRemoved(query, filter); + setQuery(newQuery); + }; + + const handleOnPerPageSelect = perPage => { + const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); + setQuery(newQuery); + }; + + const handleOnSetPage = pageNumber => { + const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber, true); + setQuery(newQuery); + }; + + const handleOnSort = (sortType, isSortAscending) => { + const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); + setQuery(newQuery); + }; + + if (settingsError) { + return ; + } + + const mappings = getMappings(); + const isDisabled = mappings.length === 0; + + return ( + <> + {getToolbar(mappings)} + {settingsStatus === FetchStatus.inProgress ? ( +
+ +
+ ) : ( + <> + {getTable()} +
{getPagination(isDisabled, true)}
+ + )} + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = ({ query }: ChildTagsMapProps): ChildTagsStateProps => { + const dispatch: ThunkDispatch = useDispatch(); + + const settingsQuery = { + filter_by: query.filter_by, + limit: query.limit, + offset: query.offset, + order_by: query.order_by, + }; + const settingsQueryString = getQuery(settingsQuery); + const settings = useSelector((state: RootState) => + settingsSelectors.selectSettings(state, SettingsType.tagsMappingsChild, settingsQueryString) + ); + const settingsStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsStatus(state, SettingsType.tagsMappingsChild, settingsQueryString) + ); + const settingsError = useSelector((state: RootState) => + settingsSelectors.selectSettingsError(state, SettingsType.tagsMappingsChild, settingsQueryString) + ); + + useEffect(() => { + if (!settingsError && settingsStatus !== FetchStatus.inProgress) { + dispatch(settingsActions.fetchSettings(SettingsType.tagsMappingsChild, settingsQueryString)); + } + }, [query]); + + return { + settings, + settingsError, + settingsStatus, + settingsQueryString, + }; +}; + +export default ChildTags; diff --git a/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsTable.styles.ts b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsTable.styles.ts new file mode 100644 index 000000000..95218da16 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsTable.styles.ts @@ -0,0 +1,15 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import type React from 'react'; + +export const styles = { + loading: { + backgroundColor: global_BackgroundColor_light_100.value, + minHeight: '520px', + }, + pagination: { + backgroundColor: global_BackgroundColor_light_100.value, + paddingBottom: global_spacer_md.value, + paddingTop: global_spacer_md.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsTable.tsx b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsTable.tsx new file mode 100644 index 000000000..253423566 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsTable.tsx @@ -0,0 +1,107 @@ +import 'routes/components/dataTable/dataTable.scss'; + +import type { Settings } from 'api/settings'; +import type { SettingsData } from 'api/settings'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { DataTable } from 'routes/components/dataTable'; + +interface ChildTagsTableOwnProps { + filterBy?: any; + isAllSelected?: boolean; + isLoading?: boolean; + onSelect(items: SettingsData[], isSelected: boolean); + onSort(value: string, isSortAscending: boolean); + orderBy?: any; + selectedItems?: SettingsData[]; + settings: Settings; +} + +type ChildTagsTableProps = ChildTagsTableOwnProps; + +const ChildTagsTable: React.FC = ({ + filterBy, + isLoading, + onSelect, + onSort, + orderBy, + selectedItems, + settings, +}) => { + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState([]); + const intl = useIntl(); + + const initDatum = () => { + if (!settings) { + return; + } + + const newRows = []; + const tags = settings?.data ? (settings.data as any) : []; + + const newColumns = [ + // Sorting with tag keys is not supported + { + name: '', + }, + { + orderBy: 'key', + name: intl.formatMessage(messages.detailsResourceNames, { value: 'tag_key' }), + ...(tags.length && { isSortable: true }), + }, + { + orderBy: 'provider_type', // Todo: Rename as source_type? + name: intl.formatMessage(messages.sourceType), + ...(tags.length && { isSortable: true }), + }, + ]; + + tags.map(item => { + newRows.push({ + cells: [ + { + name: '', + }, + { + value: item.key ? item.key : '', + }, + { + value: intl.formatMessage(messages.sourceTypes, { value: item?.source_type?.toLowerCase() }), + }, + ], + item, + selected: selectedItems && selectedItems.find(val => val.uuid === item.uuid) !== undefined, + }); + }); + + const filteredColumns = (newColumns as any[]).filter(column => !column.hidden); + const filteredRows = newRows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + + setColumns(filteredColumns); + setRows(filteredRows); + }; + + useEffect(() => { + initDatum(); + }, [selectedItems, settings]); + + return ( + + ); +}; + +export { ChildTagsTable }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsToolbar.tsx b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsToolbar.tsx new file mode 100644 index 000000000..248b1db73 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/childTags/childTagsToolbar.tsx @@ -0,0 +1,99 @@ +import type { Query } from 'api/queries/query'; +import type { SettingsData } from 'api/settings'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { BasicToolbar } from 'routes/components/dataToolbar'; +import type { ToolbarChipGroupExt } from 'routes/components/dataToolbar/utils/common'; +import type { Filter } from 'routes/utils/filter'; + +interface ChildTagsToolbarOwnProps { + isDisabled?: boolean; + itemsPerPage?: number; + itemsTotal?: number; + onBulkSelect(action: string); + onFilterAdded(filter: Filter); + onFilterRemoved(filter: Filter); + pagination?: React.ReactNode; + selectedItems?: SettingsData[]; + query?: Query; +} + +type ChildTagsToolbarProps = ChildTagsToolbarOwnProps; + +const ChildTagsToolbar: React.FC = ({ + isDisabled, + itemsPerPage, + itemsTotal, + onBulkSelect, + onFilterAdded, + onFilterRemoved, + pagination, + selectedItems, + query, +}) => { + const intl = useIntl(); + + const getCategoryOptions = (): ToolbarChipGroupExt[] => { + const options = [ + { + ariaLabelKey: 'tag_key', + placeholderKey: 'tag_key', + key: 'key', + name: intl.formatMessage(messages.filterByValues, { value: 'tag_key' }), + }, + { + key: 'source_type', + name: intl.formatMessage(messages.filterByValues, { value: 'source_type' }), + selectClassName: 'selectOverride', // A selector from routes/components/dataToolbar/dataToolbar.scss + selectOptions: [ + { + key: 'AWS', + name: intl.formatMessage(messages.aws), + }, + { + key: 'Azure', + name: intl.formatMessage(messages.azure), + }, + { + key: 'GCP', + name: intl.formatMessage(messages.gcp), + }, + // { + // key: 'IBM', + // name: intl.formatMessage(messages.ibm), // Todo: enable when supported by API + // }, + { + key: 'OCI', + name: intl.formatMessage(messages.oci), + }, + { + key: 'OCP', + name: intl.formatMessage(messages.openShift), + }, + ], + }, + ]; + return options; + }; + + return ( + + ); +}; + +export { ChildTagsToolbar }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/childTags/index.ts b/src/routes/settings/tagLabels/tagMapping/components/childTags/index.ts new file mode 100644 index 000000000..96a8105cf --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/childTags/index.ts @@ -0,0 +1 @@ +export { default as ChildTags } from './childTags'; diff --git a/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.scss b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.scss new file mode 100644 index 000000000..563ac152c --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.scss @@ -0,0 +1,5 @@ +.iconOverride { + .pf-v5-c-modal-box__title-icon { + color: var(--pf-v5-global--warning-color--100); + } +} diff --git a/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx new file mode 100644 index 000000000..d6585bbdf --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMapping.tsx @@ -0,0 +1,98 @@ +import './deleteTagMapping.scss'; + +import { Button } from '@patternfly/react-core'; +import { Modal, ModalBody, ModalFooter, ModalHeader, ModalVariant } from '@patternfly/react-core/next'; +import type { SettingsData } from 'api/settings'; +import type { SettingsType } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { settingsActions, settingsSelectors } from 'store/settings'; + +interface DeleteTagMappingOwnProps { + isOpen?: boolean; + item: SettingsData; + onClose?: () => void; + settingsType: SettingsType; +} + +interface DeleteTagMappingMapProps { + settingsType: SettingsType; +} + +interface DeleteTagMappingStateProps { + settingsUpdateError?: AxiosError; + settingsUpdateStatus?: FetchStatus; +} + +type DeleteTagMappingProps = DeleteTagMappingOwnProps; + +const DeleteTagMapping: React.FC = ({ isOpen, item, onClose, settingsType }) => { + const [isFinish, setIsFinish] = useState(false); + const { settingsUpdateError, settingsUpdateStatus } = useMapToProps({ settingsType }); + + const dispatch: ThunkDispatch = useDispatch(); + const intl = useIntl(); + + const handleOnDelete = () => { + if (settingsUpdateStatus !== FetchStatus.inProgress) { + setIsFinish(true); + dispatch( + settingsActions.updateSettings(settingsType, { + ids: [item.uuid], + }) + ); + } + }; + + useEffect(() => { + if (isFinish && settingsUpdateStatus === FetchStatus.complete && !settingsUpdateError) { + onClose(); + } + }, [isFinish, settingsUpdateError, settingsUpdateStatus]); + + // PatternFly modal appends to document.body, which is outside the scoped "costManagement" dom tree. + // Use className="costManagement" to override PatternFly styles or append the modal to an element within the tree + + return ( + + + {intl.formatMessage(messages.tagMappingDeleteDesc, { value: {item.key} })} + + + + + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = ({ settingsType }: DeleteTagMappingMapProps): DeleteTagMappingStateProps => { + const settingsUpdateStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, settingsType) + ); + const settingsUpdateError = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateError(state, settingsType) + ); + + return { + settingsUpdateError, + settingsUpdateStatus, + }; +}; + +export default DeleteTagMapping; diff --git a/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMappingAction.tsx b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMappingAction.tsx new file mode 100644 index 000000000..328c358bf --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/deleteTagMappingAction.tsx @@ -0,0 +1,75 @@ +import './deleteTagMapping.scss'; + +import { Button, ButtonVariant, Tooltip } from '@patternfly/react-core'; +import { MinusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/minus-circle-icon'; +import type { SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; +import messages from 'locales/messages'; +import React, { useState } from 'react'; +import { useIntl } from 'react-intl'; + +import DeleteTagMapping from './deleteTagMapping'; + +interface DeleteTagMappingActionOwnProps { + canWrite?: boolean; + isDisabled?: boolean; + item: SettingsData; + onClose?: () => void; +} + +type DeleteTagMappingActionProps = DeleteTagMappingActionOwnProps; + +const DeleteTagMappingAction: React.FC = ({ canWrite, isDisabled, item, onClose }) => { + const [isOpen, setIsOpen] = useState(false); + const intl = useIntl(); + + const getActions = () => { + const getTooltip = children => { + if (!canWrite) { + const disableTagsTooltip = intl.formatMessage(messages.readOnlyPermissions); + return {children}; + } + return children; + }; + + return getTooltip( + + ); + }; + + const handleOnClose = () => { + setIsOpen(false); + if (onClose) { + onClose(); + } + }; + + const handleOnClick = () => { + setIsOpen(!isOpen); + }; + + // PatternFly modal appends to document.body, which is outside the scoped "costManagement" dom tree. + // Use className="costManagement" to override PatternFly styles or append the modal to an element within the tree + + return ( + <> + {getActions()} + + + ); +}; + +export default DeleteTagMappingAction; diff --git a/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/index.ts b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/index.ts new file mode 100644 index 000000000..42a5f77fa --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/deleteTagMapping/index.ts @@ -0,0 +1,2 @@ +export { default as DeleteTagMappingAction } from './deleteTagMappingAction'; +export { default as DeleteTagMapping } from './deleteTagMapping'; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/index.ts b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/index.ts new file mode 100644 index 000000000..ceafe1725 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/index.ts @@ -0,0 +1 @@ +export { default as ParentTagMapping } from './parentTagMapping'; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMapping.styles.ts b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMapping.styles.ts new file mode 100644 index 000000000..81737fd94 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMapping.styles.ts @@ -0,0 +1,31 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_FontSize_md from '@patternfly/react-tokens/dist/js/global_FontSize_md'; +import type React from 'react'; + +export const styles = { + alert: { + marginBottom: global_FontSize_md.value, + }, + descContainer: { + marginTop: global_FontSize_md.value, + }, + emptyState: { + margin: global_FontSize_md.value, + }, + icon: { + margin: global_FontSize_md.value, + }, + loading: { + backgroundColor: global_BackgroundColor_light_100.value, + minHeight: '520px', + }, + reviewDescContainer: { + marginBottom: global_FontSize_md.value, + }, + reviewTable: { + marginTop: '-10px', + }, + spacing: { + marginRight: global_FontSize_md.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMapping.tsx b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMapping.tsx new file mode 100644 index 000000000..5eca8b378 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMapping.tsx @@ -0,0 +1,276 @@ +import { + Button, + ButtonVariant, + Title, + TitleSizes, + Tooltip, + Wizard, + WizardHeader, + WizardStep, +} from '@patternfly/react-core'; +import { Modal, ModalVariant } from '@patternfly/react-core/next'; +import type { SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { ChildTags } from 'routes/settings/tagLabels/tagMapping/components/childTags'; +import { ParentTags } from 'routes/settings/tagLabels/tagMapping/components/parentTags'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { settingsActions, settingsSelectors } from 'store/settings'; + +import { styles } from './parentTagMapping.styles'; +import { ParentTagMappingEmptyState } from './parentTagMappingEmptyState'; +import { ParentTagMappingReview } from './parentTagMappingReview'; + +interface ParentTagMappingOwnProps { + canWrite?: boolean; + isDisabled?: boolean; + onClose?: () => void; +} + +interface ParentTagMappingStateProps { + settingsUpdateError?: AxiosError; + settingsUpdateStatus?: FetchStatus; +} + +type ParentTagMappingProps = ParentTagMappingOwnProps; + +const ParentTagMapping: React.FC = ({ + canWrite, + isDisabled, + onClose, +}: ParentTagMappingProps) => { + const [childTags, setChildTags] = useState([]); + const [isFinish, setIsFinish] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [parentTags, setParentTags] = useState([]); + + const dispatch: ThunkDispatch = useDispatch(); + const intl = useIntl(); + + const { settingsUpdateError, settingsUpdateStatus } = useMapToProps(); + + const getActions = () => { + const getTooltip = children => { + if (!canWrite) { + const disableTagsTooltip = intl.formatMessage(messages.readOnlyPermissions); + return {children}; + } + return children; + }; + + return getTooltip( + + ); + }; + + const getSuccessEmptyState = () => { + return ( + +
+ +
+ +
+
+
+ ); + }; + + // PatternFly modal appends to document.body, which is outside the scoped "costManagement" dom tree. + // Use className="costManagement" to override PatternFly styles or append the modal to an element within the tree + + const getWizard = () => { + return ( + + + } + isVisitRequired + onClose={handleOnClose} + > + + + {intl.formatMessage(messages.tagMappingSelectChildTags)} + +
+ {intl.formatMessage(messages.tagMappingSelectChildTagsDesc, { + learnMore: ( + + {intl.formatMessage(messages.learnMore)} + + ), + })} +
+ +
+ + + {intl.formatMessage(messages.tagMappingSelectParentTags)} + +
+ {intl.formatMessage(messages.tagMappingSelectParentTagsDesc, { count: {childTags.length} })} +
+ +
+ + + +
+
+ ); + }; + + const handleOnBulkSelectChild = (items: SettingsData[]) => { + setChildTags(items); + }; + + const handleOnBulkSelectParent = (items: SettingsData[]) => { + setParentTags(items); + }; + + const handleOnClick = () => { + setIsOpen(!isOpen); + }; + + const handleOnClose = () => { + handleOnReset(); + setIsOpen(false); + if (onClose) { + onClose(); + } + }; + + const handleOnCreateTagMapping = () => { + if (settingsUpdateStatus !== FetchStatus.inProgress) { + dispatch( + settingsActions.updateSettings(SettingsType.tagsMappingsChildAdd, { + parent: parentTags.length ? parentTags[0].uuid : undefined, + children: childTags.map(item => item.uuid), + }) + ); + } + }; + + const handleOnReset = () => { + setChildTags([]); + setParentTags([]); + setIsFinish(false); + }; + + const handleOnSelectChild = (items: SettingsData[], isSelected: boolean = false) => { + let newItems = [...childTags]; + if (items && items.length > 0) { + if (isSelected) { + items.map(item => newItems.push(item)); + } else { + items.map(item => { + newItems = newItems.filter(val => val.uuid !== item.uuid); + }); + } + } + setChildTags(newItems); + }; + + const handleOnSelectParent = (items: SettingsData[], isSelected: boolean = false) => { + let newItems = []; + if (items && items.length > 0) { + if (isSelected) { + items.map(item => newItems.push(item)); + } else { + items.map(item => { + newItems = newItems.filter(val => val.uuid !== item.uuid); + }); + } + } + setParentTags(newItems); + }; + + useEffect(() => { + if (isOpen && settingsUpdateStatus === FetchStatus.complete && !settingsUpdateError) { + setIsFinish(true); + } + }, [settingsUpdateError, settingsUpdateStatus]); + + // Clear error state if tags changed + useEffect(() => { + if (settingsUpdateError) { + dispatch(settingsActions.resetStatus()); + } + }, [childTags, parentTags]); + + return ( + <> + {getActions()} + {isFinish ? getSuccessEmptyState() : getWizard()} + + ); +}; + +const useMapToProps = (): ParentTagMappingStateProps => { + const settingsUpdateStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, SettingsType.tagsMappingsChildAdd) + ); + const settingsUpdateError = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateError(state, SettingsType.tagsMappingsChildAdd) + ); + + return { + settingsUpdateError, + settingsUpdateStatus, + }; +}; + +export default ParentTagMapping; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMappingEmptyState.tsx b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMappingEmptyState.tsx new file mode 100644 index 000000000..c10455276 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMappingEmptyState.tsx @@ -0,0 +1,69 @@ +import { + Button, + ButtonVariant, + EmptyState, + EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, + EmptyStateIcon, + EmptyStateVariant, + Icon, +} from '@patternfly/react-core'; +import { CheckCircleIcon } from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; + +import { styles } from './parentTagMapping.styles'; + +interface ParentTagMappingEmptyStateOwnProps { + onClose(event); + onReset(event); +} + +type ParentTagMappingEmptyStateProps = ParentTagMappingEmptyStateOwnProps; + +const ParentTagMappingEmptyState: React.FC = ({ + onClose, + onReset, +}: ParentTagMappingEmptyStateProps) => { + const intl = useIntl(); + + return ( + + + + + } + headingLevel="h5" + /> + + {intl.formatMessage(messages.tagMappingWizardSuccessDesc, { + learnMore: ( + + {intl.formatMessage(messages.learnMore)} + + ), + warning: {intl.formatMessage(messages.noMappedTagsWarning)}, + })} + + +
+ +
+
+ +
+
+
+ ); +}; + +export { ParentTagMappingEmptyState }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMappingReview.tsx b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMappingReview.tsx new file mode 100644 index 000000000..737294164 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTagMapping/parentTagMappingReview.tsx @@ -0,0 +1,117 @@ +import { + Alert, + Stack, + StackItem, + Text, + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, + TitleSizes, +} from '@patternfly/react-core'; +import { Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import type { SettingsData } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { parseApiError } from 'routes/settings/tagLabels/tagMapping/utils/parseApiError'; +import { FetchStatus } from 'store/common'; + +import { styles } from './parentTagMapping.styles'; + +interface ParentTagMappingReviewOwnProps { + childTags?: SettingsData[]; + parentTags?: SettingsData[]; + settingsError?: AxiosError; + settingsStatus?: FetchStatus; +} + +type ParentTagMappingReviewProps = ParentTagMappingReviewOwnProps; + +const ParentTagMappingReview: React.FC = ({ + childTags = [], + parentTags = [], + settingsError, + settingsStatus, +}: ParentTagMappingReviewProps) => { + const intl = useIntl(); + + return ( + <> + {settingsStatus === FetchStatus.complete && settingsError && ( + + )} + + + + {intl.formatMessage(messages.tagMappingWizardReview)} + + + + + + {intl.formatMessage(messages.tagMappingWizardReviewDesc, { + create: {intl.formatMessage(messages.create)}, + back: {intl.formatMessage(messages.back)}, + })} + + + + + + + + {intl.formatMessage(messages.tagKeyChild)} + + +
+ + + + + + + + + {childTags.map((item, index) => ( + + + + + ))} + +
+ {intl.formatMessage(messages.detailsResourceNames, { value: 'tag_key' })} + {intl.formatMessage(messages.sourceType)}
{item.key} + {intl.formatMessage(messages.sourceTypes, { value: item?.source_type?.toLowerCase() })} +
+
+
+ + {intl.formatMessage(messages.tagKeyParent)} + + {parentTags.map(item => item.key)} + + {intl.formatMessage(messages.tagKeyParentSource)} + + + {parentTags.map(item => + intl.formatMessage(messages.sourceTypes, { value: item?.source_type?.toLowerCase() }) + )} + +
+
+
+
+ + ); +}; + +export { ParentTagMappingReview }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTags/index.ts b/src/routes/settings/tagLabels/tagMapping/components/parentTags/index.ts new file mode 100644 index 000000000..146974365 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTags/index.ts @@ -0,0 +1 @@ +export { default as ParentTags } from './parentTags'; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTags.tsx b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTags.tsx new file mode 100644 index 000000000..d58a6d43e --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTags.tsx @@ -0,0 +1,229 @@ +import Unavailable from '@patternfly/react-component-groups/dist/esm/UnavailableContent'; +import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import type { Query } from 'api/queries/query'; +import { getQuery } from 'api/queries/query'; +import type { Settings, SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { LoadingState } from 'routes/components/state/loadingState'; +import * as queryUtils from 'routes/utils/query'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { settingsActions, settingsSelectors } from 'store/settings'; + +import { ParentTagsTable } from './parentTagsTable'; +import { styles } from './parentTagsTable.styles'; +import { ParentTagsToolbar } from './parentTagsToolbar'; + +interface ParentTagsOwnProps { + onBulkSelect(items: SettingsData[]); + onSelect(items: SettingsData[], isSelected: boolean); + selectedItems?: SettingsData[]; + unavailableItems?: SettingsData[]; +} + +interface ParentTagsMapProps { + query?: Query; +} + +interface ParentTagsStateProps { + settings?: Settings; + settingsError?: AxiosError; + settingsStatus?: FetchStatus; + settingsQueryString?: string; +} + +type ParentTagsProps = ParentTagsOwnProps; + +const baseQuery: Query = { + limit: 10, + offset: 0, + filter_by: {}, + order_by: { + parent: 'asc', + }, +}; + +const ParentTags: React.FC = ({ + onBulkSelect, + onSelect, + selectedItems, + unavailableItems, +}: ParentTagsProps) => { + const [query, setQuery] = useState({ ...baseQuery }); + const { settings, settingsError, settingsStatus } = useMapToProps({ query }); + + const intl = useIntl(); + + const getMappings = () => { + if (settings) { + return settings.data as any; + } + return []; + }; + + const getPagination = (isDisabled = false, isBottom = false) => { + const count = settings?.meta ? settings.meta.count : 0; + const limit = settings?.meta ? settings.meta.limit : baseQuery.limit; + const offset = settings?.meta ? settings.meta.offset : baseQuery.offset; + const page = Math.trunc(offset / limit + 1); + + return ( + handleOnPerPageSelect(perPage)} + onSetPage={(event, pageNumber) => handleOnSetPage(pageNumber)} + page={page} + perPage={limit} + titles={{ + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { + title: intl.formatMessage(messages.openShift), + placement: isBottom ? 'bottom' : 'top', + }), + }} + variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} + /> + ); + }; + + const getTable = () => { + return ( + handleOnSort(sortType, isSortAscending)} + selectedItems={selectedItems} + settings={settings} + unavailableItems={unavailableItems} + /> + ); + }; + + const getToolbar = (mappings: SettingsData[]) => { + const itemsTotal = settings?.meta ? settings.meta.count : 0; + + return ( + handleOnFilterAdded(filter)} + onFilterRemoved={filter => handleOnFilterRemoved(filter)} + pagination={getPagination(isDisabled)} + selectedItems={selectedItems} + query={query} + /> + ); + }; + + const handleOnBulkSelect = (action: string) => { + if (action === 'none') { + onBulkSelect([]); + } else if (action === 'page') { + const newSelectedItems = [...selectedItems]; + getMappings().map(val => { + if (!newSelectedItems.find(item => item.uuid === val.uuid)) { + newSelectedItems.push(val); + } + }); + onBulkSelect(newSelectedItems); + } + }; + + const handleOnFilterAdded = filter => { + const newQuery = queryUtils.handleOnFilterAdded(query, filter); + setQuery(newQuery); + }; + + const handleOnFilterRemoved = filter => { + const newQuery = queryUtils.handleOnFilterRemoved(query, filter); + setQuery(newQuery); + }; + + const handleOnPerPageSelect = perPage => { + const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); + setQuery(newQuery); + }; + + const handleOnSetPage = pageNumber => { + const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber, true); + setQuery(newQuery); + }; + + const handleOnSort = (sortType, isSortAscending) => { + const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); + setQuery(newQuery); + }; + + if (settingsError) { + return ; + } + + const mappings = getMappings(); + const isDisabled = mappings.length === 0; + + return ( + <> + {getToolbar(mappings)} + {settingsStatus === FetchStatus.inProgress ? ( +
+ +
+ ) : ( + <> + {getTable()} +
{getPagination(isDisabled, true)}
+ + )} + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = ({ query }: ParentTagsMapProps): ParentTagsStateProps => { + const dispatch: ThunkDispatch = useDispatch(); + + const settingsQuery = { + filter_by: query.filter_by, + limit: query.limit, + offset: query.offset, + order_by: query.order_by, + }; + const settingsQueryString = getQuery(settingsQuery); + const settings = useSelector((state: RootState) => + settingsSelectors.selectSettings(state, SettingsType.tagsMappingsParent, settingsQueryString) + ); + const settingsStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsStatus(state, SettingsType.tagsMappingsParent, settingsQueryString) + ); + const settingsError = useSelector((state: RootState) => + settingsSelectors.selectSettingsError(state, SettingsType.tagsMappingsParent, settingsQueryString) + ); + + useEffect(() => { + if (!settingsError && settingsStatus !== FetchStatus.inProgress) { + dispatch(settingsActions.fetchSettings(SettingsType.tagsMappingsParent, settingsQueryString)); + } + }, [query]); + + return { + settings, + settingsError, + settingsStatus, + settingsQueryString, + }; +}; + +export default ParentTags; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsTable.styles.ts b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsTable.styles.ts new file mode 100644 index 000000000..95218da16 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsTable.styles.ts @@ -0,0 +1,15 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import type React from 'react'; + +export const styles = { + loading: { + backgroundColor: global_BackgroundColor_light_100.value, + minHeight: '520px', + }, + pagination: { + backgroundColor: global_BackgroundColor_light_100.value, + paddingBottom: global_spacer_md.value, + paddingTop: global_spacer_md.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsTable.tsx b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsTable.tsx new file mode 100644 index 000000000..f2df28e42 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsTable.tsx @@ -0,0 +1,110 @@ +import 'routes/components/dataTable/dataTable.scss'; + +import type { Settings } from 'api/settings'; +import type { SettingsData } from 'api/settings'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { DataTable } from 'routes/components/dataTable'; + +interface ParentTagsTableOwnProps { + filterBy?: any; + isLoading?: boolean; + onSelect(items: SettingsData[], isSelected: boolean); + onSort(value: string, isSortAscending: boolean); + orderBy?: any; + selectedItems?: SettingsData[]; + settings: Settings; + unavailableItems?: SettingsData[]; +} + +type ParentTagsTableProps = ParentTagsTableOwnProps; + +const ParentTagsTable: React.FC = ({ + filterBy, + isLoading, + onSelect, + onSort, + orderBy, + selectedItems, + settings, + unavailableItems, +}) => { + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState([]); + const intl = useIntl(); + + const initDatum = () => { + if (!settings) { + return; + } + + const newRows = []; + const tags = settings?.data ? (settings.data as any) : []; + + const newColumns = [ + // Sorting with tag keys is not supported + { + name: '', + }, + { + orderBy: 'key', + name: intl.formatMessage(messages.detailsResourceNames, { value: 'tag_key' }), + ...(tags.length && { isSortable: true }), + }, + { + orderBy: 'provider_type', // Todo: Rename as source_type? + name: intl.formatMessage(messages.sourceType), + ...(tags.length && { isSortable: true }), + }, + ]; + + tags.map(item => { + newRows.push({ + cells: [ + { + name: '', + }, + { + value: item.key ? item.key : '', + }, + { + value: intl.formatMessage(messages.sourceTypes, { value: item?.source_type?.toLowerCase() }), + }, + ], + item, + selected: selectedItems?.find(val => val.uuid === item.uuid) !== undefined, + selectionDisabled: unavailableItems?.find(val => val.uuid === item.uuid), + }); + }); + + const filteredColumns = (newColumns as any[]).filter(column => !column.hidden); + const filteredRows = newRows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + + setColumns(filteredColumns); + setRows(filteredRows); + }; + + useEffect(() => { + initDatum(); + }, [selectedItems, settings]); + + return ( + + ); +}; + +export { ParentTagsTable }; diff --git a/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsToolbar.tsx b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsToolbar.tsx new file mode 100644 index 000000000..53d308eda --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/components/parentTags/parentTagsToolbar.tsx @@ -0,0 +1,100 @@ +import type { Query } from 'api/queries/query'; +import type { SettingsData } from 'api/settings'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { BasicToolbar } from 'routes/components/dataToolbar'; +import type { ToolbarChipGroupExt } from 'routes/components/dataToolbar/utils/common'; +import type { Filter } from 'routes/utils/filter'; + +interface ParentTagsToolbarOwnProps { + isDisabled?: boolean; + itemsPerPage?: number; + itemsTotal?: number; + onBulkSelect(action: string); + onFilterAdded(filter: Filter); + onFilterRemoved(filter: Filter); + pagination?: React.ReactNode; + selectedItems?: SettingsData[]; + query?: Query; +} + +type ParentTagsToolbarProps = ParentTagsToolbarOwnProps; + +const ParentTagsToolbar: React.FC = ({ + isDisabled, + itemsPerPage, + itemsTotal, + onBulkSelect, + onFilterAdded, + onFilterRemoved, + pagination, + selectedItems, + query, +}) => { + const intl = useIntl(); + + const getCategoryOptions = (): ToolbarChipGroupExt[] => { + const options = [ + { + ariaLabelKey: 'tag_key', + placeholderKey: 'tag_key', + key: 'key', + name: intl.formatMessage(messages.filterByValues, { value: 'tag_key' }), + }, + { + key: 'source_type', + name: intl.formatMessage(messages.filterByValues, { value: 'source_type' }), + selectClassName: 'selectOverride', // A selector from routes/components/dataToolbar/dataToolbar.scss + selectOptions: [ + { + key: 'AWS', + name: intl.formatMessage(messages.aws), + }, + { + key: 'Azure', + name: intl.formatMessage(messages.azure), + }, + { + key: 'GCP', + name: intl.formatMessage(messages.gcp), + }, + // { + // key: 'IBM', + // name: intl.formatMessage(messages.ibm), // Todo: enable when supported by API + // }, + { + key: 'OCI', + name: intl.formatMessage(messages.oci), + }, + { + key: 'OCP', + name: intl.formatMessage(messages.openShift), + }, + ], + }, + ]; + return options; + }; + + return ( + + ); +}; + +export { ParentTagsToolbar }; diff --git a/src/routes/settings/tagLabels/tagMapping/index.ts b/src/routes/settings/tagLabels/tagMapping/index.ts new file mode 100644 index 000000000..892c8673e --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/index.ts @@ -0,0 +1 @@ +export { default as TagMapping } from './tagMapping'; diff --git a/src/routes/settings/tagLabels/tagMapping/tagMapping.styles.ts b/src/routes/settings/tagLabels/tagMapping/tagMapping.styles.ts new file mode 100644 index 000000000..fd7b8a0a8 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/tagMapping.styles.ts @@ -0,0 +1,29 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_spacer_2xl from '@patternfly/react-tokens/dist/js/global_spacer_2xl'; +import global_spacer_lg from '@patternfly/react-tokens/dist/js/global_spacer_lg'; +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import global_spacer_sm from '@patternfly/react-tokens/dist/js/global_spacer_sm'; +import type React from 'react'; + +export const styles = { + action: { + marginLeft: global_spacer_md.var, + }, + childActionColumn: { + paddingRight: global_spacer_2xl.value, + }, + childSourceTypeColumn: { + paddingRight: global_spacer_sm.value, + }, + childTagKeyColumn: { + paddingLeft: global_spacer_lg.value, + }, + emptyStateContainer: { + paddingTop: global_spacer_md.value, + }, + pagination: { + backgroundColor: global_BackgroundColor_light_100.value, + paddingBottom: global_spacer_md.value, + paddingTop: global_spacer_md.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/settings/tagLabels/tagMapping/tagMapping.tsx b/src/routes/settings/tagLabels/tagMapping/tagMapping.tsx new file mode 100644 index 000000000..2f9a5258a --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/tagMapping.tsx @@ -0,0 +1,232 @@ +import Unavailable from '@patternfly/react-component-groups/dist/esm/UnavailableContent'; +import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import type { Query } from 'api/queries/query'; +import { getQuery } from 'api/queries/query'; +import type { Settings, SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { LoadingState } from 'routes/components/state/loadingState'; +import * as queryUtils from 'routes/utils/query'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { settingsActions, settingsSelectors } from 'store/settings'; + +import { styles } from './tagMapping.styles'; +import { TagMappingEmptyState } from './tagMappingEmptyState'; +import { TagMappingTable } from './tagMappingTable'; +import { TagMappingToolbar } from './tagMappingToolbar'; + +interface MappingsOwnProps { + canWrite?: boolean; +} + +interface MappingsMapProps { + query?: Query; +} + +interface MappingsStateProps { + settings?: Settings; + settingsError?: AxiosError; + settingsStatus?: FetchStatus; + settingsQueryString?: string; +} + +type MappingsProps = MappingsOwnProps; + +const baseQuery: Query = { + limit: 10, + offset: 0, + filter_by: {}, + order_by: { + parent: 'asc', + }, +}; + +const TagMapping: React.FC = ({ canWrite }) => { + const [query, setQuery] = useState({ ...baseQuery }); + const { settings, settingsError, settingsStatus } = useMapToProps({ + query, + }); + + const intl = useIntl(); + + const getMappings = () => { + if (settings) { + return settings.data as any; + } + return []; + }; + + const getPagination = (isDisabled = false, isBottom = false) => { + const count = settings?.meta ? settings.meta.count : 0; + const limit = settings?.meta?.limit ? settings.meta.limit : baseQuery.limit; + const offset = settings?.meta ? settings.meta.offset : baseQuery.offset; + const page = Math.trunc(offset / limit + 1); + + return ( + handleOnPerPageSelect(perPage)} + onSetPage={(event, pageNumber) => handleOnSetPage(pageNumber)} + page={page} + perPage={limit} + titles={{ + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { + title: intl.formatMessage(messages.openShift), + placement: isBottom ? 'bottom' : 'top', + }), + }} + variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} + /> + ); + }; + + const getTable = () => { + return ( + handleOnSort(sortType, isSortAscending)} + orderBy={query.order_by} + settings={settings} + /> + ); + }; + + const getToolbar = (mappings: SettingsData[]) => { + const itemsTotal = settings?.meta ? settings.meta.count : 0; + + return ( + handleOnFilterAdded(filter)} + onFilterRemoved={filter => handleOnFilterRemoved(filter)} + pagination={getPagination(isDisabled)} + query={query} + /> + ); + }; + + const handleOnClose = () => { + refresh(); + }; + + const handleOnFilterAdded = filter => { + const newQuery = queryUtils.handleOnFilterAdded(query, filter); + setQuery(newQuery); + }; + + const handleOnFilterRemoved = filter => { + const newQuery = queryUtils.handleOnFilterRemoved(query, filter); + setQuery(newQuery); + }; + + const handleOnPerPageSelect = perPage => { + const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); + setQuery(newQuery); + }; + + const handleOnSetPage = pageNumber => { + const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber, true); + setQuery(newQuery); + }; + + const handleOnSort = (sortType, isSortAscending) => { + const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); + setQuery(newQuery); + }; + + // Force refresh + const refresh = () => { + setQuery({ ...query }); + }; + + if (settingsError) { + return ; + } + + const mappings = getMappings(); + const isDisabled = mappings.length === 0; + const hasMappings = mappings.length > 0 || Object.keys(query.filter_by || {}).length > 0; // filter may be applied + + return ( + <> +
+ {intl.formatMessage(messages.tagMappingDesc, { + learnMore: ( + + {intl.formatMessage(messages.learnMore)} + + ), + warning: {intl.formatMessage(messages.tagMappingWarning)}, + })} +
+ {hasMappings && getToolbar(mappings)} + {settingsStatus === FetchStatus.inProgress ? ( + + ) : hasMappings ? ( + <> + {getTable()} +
{getPagination(isDisabled, true)}
+ + ) : ( +
+ +
+ )} + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = ({ query }: MappingsMapProps): MappingsStateProps => { + const dispatch: ThunkDispatch = useDispatch(); + + const settingsQuery = { + filter_by: query.filter_by, + limit: query.limit, + offset: query.offset, + order_by: query.order_by, + }; + const settingsQueryString = getQuery(settingsQuery); + const settings = useSelector((state: RootState) => + settingsSelectors.selectSettings(state, SettingsType.tagsMappings, settingsQueryString) + ); + const settingsStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsStatus(state, SettingsType.tagsMappings, settingsQueryString) + ); + const settingsError = useSelector((state: RootState) => + settingsSelectors.selectSettingsError(state, SettingsType.tagsMappings, settingsQueryString) + ); + + useEffect(() => { + if (!settingsError && settingsStatus !== FetchStatus.inProgress) { + dispatch(settingsActions.fetchSettings(SettingsType.tagsMappings, settingsQueryString)); + } + }, [query]); + + return { + settings, + settingsError, + settingsStatus, + settingsQueryString, + }; +}; + +export default TagMapping; diff --git a/src/routes/settings/tagLabels/tagMapping/tagMappingEmptyState.tsx b/src/routes/settings/tagLabels/tagMapping/tagMappingEmptyState.tsx new file mode 100644 index 000000000..c5964cb81 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/tagMappingEmptyState.tsx @@ -0,0 +1,57 @@ +import { + EmptyState, + EmptyStateActions, + EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, + EmptyStateIcon, + EmptyStateVariant, +} from '@patternfly/react-core'; +import { PlusCircleIcon } from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { ParentTagMapping } from 'routes/settings/tagLabels/tagMapping/components/parentTagMapping'; + +interface TagMappingEmptyStateOwnProps { + canWrite?: boolean; + isDisabled?: boolean; + onWizardClose(); +} + +type TagMappingEmptyStateProps = TagMappingEmptyStateOwnProps; + +const TagMappingEmptyState: React.FC = ({ + canWrite, + isDisabled, + onWizardClose, +}: TagMappingEmptyStateOwnProps) => { + const intl = useIntl(); + + return ( + + } + headingLevel="h5" + /> + + {intl.formatMessage(messages.noMappedTagsDesc, { + learnMore: ( + + {intl.formatMessage(messages.learnMore)} + + ), + warning: {intl.formatMessage(messages.noMappedTagsWarning)}, + })} + + + + + + + + ); +}; + +export { TagMappingEmptyState }; diff --git a/src/routes/settings/tagLabels/tagMapping/tagMappingTable.tsx b/src/routes/settings/tagLabels/tagMapping/tagMappingTable.tsx new file mode 100644 index 000000000..3b63973a2 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/tagMappingTable.tsx @@ -0,0 +1,137 @@ +import 'routes/components/dataTable/dataTable.scss'; + +import type { Settings } from 'api/settings'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { ExpandableTable } from 'routes/components/dataTable'; +import { Actions } from 'routes/settings/tagLabels/tagMapping/components/actions'; +import { DeleteTagMappingAction } from 'routes/settings/tagLabels/tagMapping/components/deleteTagMapping'; + +import { styles } from './tagMapping.styles'; + +interface TagMappingTableOwnProps { + canWrite?: boolean; + filterBy?: any; + isDisabled?: boolean; + isLoading?: boolean; + onClose?: () => void; + onSort(value: string, isSortAscending: boolean); + orderBy?: any; + settings: Settings; +} + +type TagMappingTableProps = TagMappingTableOwnProps; + +const TagMappingTable: React.FC = ({ + canWrite, + filterBy, + isDisabled, + isLoading, + onClose, + onSort, + orderBy, + settings, +}) => { + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState([]); + + const intl = useIntl(); + + const initDatum = () => { + if (!settings) { + return; + } + + const newRows = []; + const tagMapping = settings?.data ? (settings.data as any) : []; + + const newColumns = [ + { + name: '', + }, + { + orderBy: 'parent', + name: intl.formatMessage(messages.detailsResourceNames, { value: 'tag_key' }), + ...(tagMapping.length && { isSortable: true }), + }, + { + orderBy: 'source_type', + name: intl.formatMessage(messages.sourceType), + ...(tagMapping.length && { isSortable: true }), + }, + { + name: '', + }, + ]; + + tagMapping.map(item => { + const parent = item.parent; + newRows.push({ + cells: [ + {}, // Empty cell for expand toggle + { + value: parent.key ? parent.key : '', + }, + { + value: intl.formatMessage(messages.sourceTypes, { value: parent?.source_type?.toLowerCase() }), + }, + { + value: , + }, + ], + children: item.children?.map(child => { + return { + cells: [ + {}, // Empty cell for expand toggle + { + value: child.key ? child.key : '', + style: styles.childTagKeyColumn, + }, + { + value: intl.formatMessage(messages.sourceTypes, { value: child?.source_type?.toLowerCase() }), + style: styles.childSourceTypeColumn, + }, + { + value: ( + + ), + style: styles.childActionColumn, + }, + ], + item: child, + }; + }), + item: parent, + }); + }); + + const filteredColumns = (newColumns as any[]).filter(column => !column.hidden); + const filteredRows = newRows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + + setColumns(filteredColumns); + setRows(filteredRows); + }; + + useEffect(() => { + initDatum(); + }, [settings]); + + return ( + key === 'child') : false} + isLoading={isLoading} + onSort={onSort} + orderBy={orderBy} + rows={rows} + /> + ); +}; + +export { TagMappingTable }; diff --git a/src/routes/settings/tagLabels/tagMapping/tagMappingToolbar.tsx b/src/routes/settings/tagLabels/tagMapping/tagMappingToolbar.tsx new file mode 100644 index 000000000..eebe474d9 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/tagMappingToolbar.tsx @@ -0,0 +1,106 @@ +import type { Query } from 'api/queries/query'; +import messages from 'locales/messages'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import { BasicToolbar } from 'routes/components/dataToolbar'; +import type { ToolbarChipGroupExt } from 'routes/components/dataToolbar/utils/common'; +import { ParentTagMapping } from 'routes/settings/tagLabels/tagMapping/components/parentTagMapping'; +import type { Filter } from 'routes/utils/filter'; + +interface TagMappingToolbarOwnProps { + canWrite?: boolean; + isAllSelected?: boolean; + isDisabled?: boolean; + itemsPerPage?: number; + itemsTotal?: number; + onClose?: () => void; + onFilterAdded(filter: Filter); + onFilterRemoved(filter: Filter); + pagination?: React.ReactNode; + query?: Query; +} + +type TagMappingToolbarProps = TagMappingToolbarOwnProps; + +const TagMappingToolbar: React.FC = ({ + canWrite, + isAllSelected, + isDisabled, + itemsPerPage, + itemsTotal, + onClose, + onFilterAdded, + onFilterRemoved, + pagination, + query, +}) => { + const intl = useIntl(); + + const getCategoryOptions = (): ToolbarChipGroupExt[] => { + const options = [ + { + ariaLabelKey: 'tag_key_parent', + placeholderKey: 'tag_key_parent', + key: 'parent', + name: intl.formatMessage(messages.filterByValues, { value: 'tag_key_parent' }), + }, + { + ariaLabelKey: 'tag_key_child', + placeholderKey: 'tag_key_child', + key: 'child', + name: intl.formatMessage(messages.filterByValues, { value: 'tag_key_child' }), + }, + { + key: 'source_type', + name: intl.formatMessage(messages.filterByValues, { value: 'source_type' }), + selectClassName: 'selectOverride', // A selector from routes/components/dataToolbar/dataToolbar.scss + selectOptions: [ + { + key: 'AWS', + name: intl.formatMessage(messages.aws), + }, + { + key: 'Azure', + name: intl.formatMessage(messages.azure), + }, + { + key: 'GCP', + name: intl.formatMessage(messages.gcp), + }, + // { + // key: 'IBM', + // name: intl.formatMessage(messages.ibm), // Todo: enable when supported by API + // }, + { + key: 'OCI', + name: intl.formatMessage(messages.oci), + }, + { + key: 'OCP', + name: intl.formatMessage(messages.openShift), + }, + ], + }, + ]; + return options; + }; + + return ( + } + categoryOptions={getCategoryOptions()} + isAllSelected={isAllSelected} + isDisabled={isDisabled} + isReadOnly={!canWrite} + itemsPerPage={itemsPerPage} + itemsTotal={itemsTotal} + onFilterAdded={onFilterAdded} + onFilterRemoved={onFilterRemoved} + pagination={pagination} + query={query} + showFilter + /> + ); +}; + +export { TagMappingToolbar }; diff --git a/src/routes/settings/tagLabels/tagMapping/utils/parseApiError.ts b/src/routes/settings/tagLabels/tagMapping/utils/parseApiError.ts new file mode 100644 index 000000000..272940840 --- /dev/null +++ b/src/routes/settings/tagLabels/tagMapping/utils/parseApiError.ts @@ -0,0 +1,13 @@ +export const parseApiError = error => { + if (error.response && error.response.data) { + if (error.response.data.Error) { + return error.response.data.Error; + } + if (error.response.data.errors) { + return error.response.data.errors.map(er => `${er.source}: ${er.detail}`).join(', '); + } + } else if (error.message) { + return error.message; + } + return 'unknown'; +}; diff --git a/src/routes/settings/tagLabels/tagTable.tsx b/src/routes/settings/tagLabels/tagTable.tsx deleted file mode 100644 index 37444a32b..000000000 --- a/src/routes/settings/tagLabels/tagTable.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import 'routes/components/dataTable/dataTable.scss'; - -import { Label } from '@patternfly/react-core'; -import type { Settings, SettingsData } from 'api/settings'; -import messages from 'locales/messages'; -import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { DataTable } from 'routes/components/dataTable'; -import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; -import type { RouterComponentProps } from 'utils/router'; -import { withRouter } from 'utils/router'; - -interface TagTableOwnProps extends RouterComponentProps, WrappedComponentProps { - canWrite?: boolean; - filterBy?: any; - isAllSelected?: boolean; - isLoading?: boolean; - onSelect(items: ComputedReportItem[], isSelected: boolean); - onSort(value: string, isSortAscending: boolean); - orderBy?: any; - selectedItems?: SettingsData[]; - settings: Settings; -} - -interface TagTableState { - columns?: any[]; - rows?: any[]; -} - -type TagTableProps = TagTableOwnProps; - -class TagTableBase extends React.Component { - public state: TagTableState = { - columns: [], - rows: [], - }; - - public componentDidMount() { - this.initDatum(); - } - - public componentDidUpdate(prevProps: TagTableProps) { - const { selectedItems, settings } = this.props; - const currentReport = settings?.data ? JSON.stringify(settings.data) : ''; - const previousReport = prevProps?.settings.data ? JSON.stringify(prevProps.settings.data) : ''; - - if (previousReport !== currentReport || prevProps.selectedItems !== selectedItems) { - this.initDatum(); - } - } - - private initDatum = () => { - const { canWrite, intl, selectedItems, settings } = this.props; - if (!settings) { - return; - } - - const rows = []; - const tags = settings?.data ? (settings.data as any) : []; - - const columns = [ - { - name: '', // Selection column - }, - { - orderBy: 'key', - name: intl.formatMessage(messages.detailsResourceNames, { value: 'name' }), - ...(tags.length && { isSortable: true }), - }, - { - orderBy: 'enabled', - name: intl.formatMessage(messages.detailsResourceNames, { value: 'status' }), - ...(tags.length && { isSortable: true }), - }, - { - orderBy: 'source_type', - name: intl.formatMessage(messages.sourceType), - ...(tags.length && { isSortable: true }), - }, - ]; - - tags.map(item => { - rows.push({ - cells: [ - {}, // Empty cell for row selection - { - value: item.key ? item.key : '', - }, - { - value: item.enabled ? ( - - ) : ( - - ), - }, - { - value: intl.formatMessage(messages.sourceTypes, { value: item?.source_type?.toLowerCase() }), - }, - ], - item, - selected: selectedItems && selectedItems.find(val => val.uuid === item.uuid) !== undefined, - selectionDisabled: !canWrite, - }); - }); - - const filteredColumns = (columns as any[]).filter(column => !column.hidden); - const filteredRows = rows.map(({ ...row }) => { - row.cells = row.cells.filter(cell => !cell.hidden); - return row; - }); - - this.setState({ - columns: filteredColumns, - rows: filteredRows, - }); - }; - - public render() { - const { filterBy, isLoading, onSelect, onSort, orderBy, selectedItems } = this.props; - const { columns, rows } = this.state; - - return ( - - ); - } -} - -const TagTable = injectIntl(withRouter(TagTableBase)); - -export { TagTable }; diff --git a/src/routes/settings/tagLabels/tags/index.ts b/src/routes/settings/tagLabels/tags/index.ts new file mode 100644 index 000000000..be9338902 --- /dev/null +++ b/src/routes/settings/tagLabels/tags/index.ts @@ -0,0 +1 @@ +export { default as Tags } from './tags'; diff --git a/src/routes/settings/tagLabels/tags/tags.styles.ts b/src/routes/settings/tagLabels/tags/tags.styles.ts new file mode 100644 index 000000000..690536658 --- /dev/null +++ b/src/routes/settings/tagLabels/tags/tags.styles.ts @@ -0,0 +1,14 @@ +import global_BackgroundColor_light_100 from '@patternfly/react-tokens/dist/js/global_BackgroundColor_light_100'; +import global_spacer_md from '@patternfly/react-tokens/dist/js/global_spacer_md'; +import type React from 'react'; + +export const styles = { + action: { + marginLeft: global_spacer_md.var, + }, + pagination: { + backgroundColor: global_BackgroundColor_light_100.value, + paddingBottom: global_spacer_md.value, + paddingTop: global_spacer_md.value, + }, +} as { [className: string]: React.CSSProperties }; diff --git a/src/routes/settings/tagLabels/tags/tags.tsx b/src/routes/settings/tagLabels/tags/tags.tsx new file mode 100644 index 000000000..482d99ed2 --- /dev/null +++ b/src/routes/settings/tagLabels/tags/tags.tsx @@ -0,0 +1,295 @@ +import Unavailable from '@patternfly/react-component-groups/dist/esm/UnavailableContent'; +import { Pagination, PaginationVariant } from '@patternfly/react-core'; +import type { Query } from 'api/queries/query'; +import { getQuery } from 'api/queries/query'; +import type { Settings, SettingsData } from 'api/settings'; +import { SettingsType } from 'api/settings'; +import type { AxiosError } from 'axios'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import type { AnyAction } from 'redux'; +import type { ThunkDispatch } from 'redux-thunk'; +import { LoadingState } from 'routes/components/state/loadingState'; +import * as queryUtils from 'routes/utils/query'; +import type { RootState } from 'store'; +import { FetchStatus } from 'store/common'; +import { settingsActions, settingsSelectors } from 'store/settings'; +import { useStateCallback } from 'utils/hooks'; + +import { styles } from './tags.styles'; +import { TagsTable } from './tagsTable'; +import { TagsToolbar } from './tagsToolbar'; + +interface TagsOwnProps { + canWrite?: boolean; +} + +interface TagsMapProps { + query?: Query; +} + +interface TagsStateProps { + settings?: Settings; + settingsError?: AxiosError; + settingsStatus?: FetchStatus; + settingsQueryString?: string; +} + +type TagsProps = TagsOwnProps; + +const baseQuery: Query = { + limit: 10, + offset: 0, + filter_by: {}, + order_by: { + key: 'asc', + }, +}; + +const Tags: React.FC = ({ canWrite }) => { + const [query, setQuery] = useState({ ...baseQuery }); + const [selectedItems, setSelectedItems] = useStateCallback([]); + const dispatch: ThunkDispatch = useDispatch(); + const intl = useIntl(); + + const { settings, settingsError, settingsStatus } = useMapToProps({ query }); + + const getTags = () => { + if (settings) { + return settings.data as any; + } + return []; + }; + + const getPagination = (isDisabled = false, isBottom = false) => { + const count = settings?.meta ? settings.meta.count : 0; + const limit = settings?.meta ? settings.meta.limit : baseQuery.limit; + const offset = settings?.meta ? settings.meta.offset : baseQuery.offset; + const page = Math.trunc(offset / limit + 1); + + return ( + handleOnPerPageSelect(perPage)} + onSetPage={(event, pageNumber) => handleOnSetPage(pageNumber)} + page={page} + perPage={limit} + titles={{ + paginationAriaLabel: intl.formatMessage(messages.paginationTitle, { + title: intl.formatMessage(messages.openShift), + placement: isBottom ? 'bottom' : 'top', + }), + }} + variant={isBottom ? PaginationVariant.bottom : PaginationVariant.top} + widgetId={`pagination${isBottom ? '-bottom' : ''}`} + /> + ); + }; + + const getTable = () => { + return ( + handleOnSort(sortType, isSortAscending)} + settings={settings} + selectedItems={selectedItems} + /> + ); + }; + + const getToolbar = (tags: SettingsData[]) => { + const hasEnabledItem = selectedItems.find(item => item.enabled); + const hasDisabledItem = selectedItems.find(item => !item.enabled); + const itemsTotal = settings?.meta ? settings.meta.count : 0; + const enabledTagsCount = settings?.meta ? settings.meta.enabled_tags_count : 0; + const enabledTagsLimit = settings?.meta ? settings.meta.enabled_tags_limit : 0; + + return ( + handleOnFilterAdded(filter)} + onFilterRemoved={filter => handleOnFilterRemoved(filter)} + pagination={getPagination(isDisabled)} + query={query} + selectedItems={selectedItems} + showBulkSelectAll={false} + /> + ); + }; + + const handleOnBulkSelect = (action: string) => { + if (action === 'none') { + setSelectedItems([]); + } else if (action === 'page') { + const newSelectedItems = [...selectedItems]; + getTags().map(val => { + if (!newSelectedItems.find(item => item.uuid === val.uuid)) { + newSelectedItems.push(val); + } + }); + setSelectedItems(newSelectedItems); + } + }; + + const handleOnDisableTags = () => { + if (selectedItems.length > 0) { + setSelectedItems([], () => { + dispatch( + settingsActions.updateSettings(SettingsType.tagsDisable, { + ids: selectedItems.map(item => item.uuid), + }) + ); + }); + } + }; + + const handleOnEnableTags = () => { + if (selectedItems.length > 0) { + setSelectedItems([], () => { + dispatch( + settingsActions.updateSettings(SettingsType.tagsEnable, { + ids: selectedItems.map(item => item.uuid), + }) + ); + }); + } + }; + + const handleOnFilterAdded = filter => { + const newQuery = queryUtils.handleOnFilterAdded(query, filter); + setQuery(newQuery); + }; + + const handleOnFilterRemoved = filter => { + const newQuery = queryUtils.handleOnFilterRemoved(query, filter); + setQuery(newQuery); + }; + + const handleOnPerPageSelect = perPage => { + const newQuery = queryUtils.handleOnPerPageSelect(query, perPage, true); + setQuery(newQuery); + }; + + const handleOnSetPage = pageNumber => { + const newQuery = queryUtils.handleOnSetPage(query, settings, pageNumber, true); + setQuery(newQuery); + }; + + const handleOnSelect = (items: SettingsData[], isSelected: boolean = false) => { + let newItems = [...selectedItems]; + if (items && items.length > 0) { + if (isSelected) { + items.map(item => newItems.push(item)); + } else { + items.map(item => { + newItems = newItems.filter(val => val.uuid !== item.uuid); + }); + } + } + setSelectedItems(newItems); + }; + + const handleOnSort = (sortType, isSortAscending) => { + const newQuery = queryUtils.handleOnSort(query, sortType, isSortAscending); + setQuery(newQuery); + }; + + if (settingsError) { + return ; + } + + const tags = getTags(); + const isDisabled = tags.length === 0; + const enabledTagsLimit = settings?.meta ? settings.meta.enabled_tags_limit : 0; + + return ( + <> +
+ {intl.formatMessage(messages.tagDesc, { + count: enabledTagsLimit, + learnMore: ( + + {intl.formatMessage(messages.learnMore)} + + ), + })} +
+ {getToolbar(tags)} + {settingsStatus === FetchStatus.inProgress ? ( + + ) : ( + <> + {getTable()} +
{getPagination(isDisabled, true)}
+ + )} + + ); +}; + +// eslint-disable-next-line no-empty-pattern +const useMapToProps = ({ query }: TagsMapProps): TagsStateProps => { + const dispatch: ThunkDispatch = useDispatch(); + + const settingsQuery = { + filter_by: query.filter_by, + limit: query.limit, + offset: query.offset, + order_by: query.order_by, + }; + const settingsQueryString = getQuery(settingsQuery); + const settings = useSelector((state: RootState) => + settingsSelectors.selectSettings(state, SettingsType.tags, settingsQueryString) + ); + const settingsStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsStatus(state, SettingsType.tags, settingsQueryString) + ); + const settingsError = useSelector((state: RootState) => + settingsSelectors.selectSettingsError(state, SettingsType.tags, settingsQueryString) + ); + + const settingsUpdateDisableStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, SettingsType.tagsDisable) + ); + const settingsUpdateEnableStatus = useSelector((state: RootState) => + settingsSelectors.selectSettingsUpdateStatus(state, SettingsType.tagsEnable) + ); + + useEffect(() => { + if ( + !settingsError && + settingsStatus !== FetchStatus.inProgress && + settingsUpdateDisableStatus !== FetchStatus.inProgress && + settingsUpdateEnableStatus !== FetchStatus.inProgress + ) { + dispatch(settingsActions.fetchSettings(SettingsType.tags, settingsQueryString)); + } + }, [query, settingsUpdateDisableStatus, settingsUpdateEnableStatus]); + + return { + settings, + settingsError, + settingsStatus, + settingsQueryString, + }; +}; + +export default Tags; diff --git a/src/routes/settings/tagLabels/tags/tagsTable.tsx b/src/routes/settings/tagLabels/tags/tagsTable.tsx new file mode 100644 index 000000000..6722d6d03 --- /dev/null +++ b/src/routes/settings/tagLabels/tags/tagsTable.tsx @@ -0,0 +1,119 @@ +import 'routes/components/dataTable/dataTable.scss'; + +import { Label } from '@patternfly/react-core'; +import type { Settings, SettingsData } from 'api/settings'; +import messages from 'locales/messages'; +import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; +import { DataTable } from 'routes/components/dataTable'; + +interface TagsTableOwnProps { + canWrite?: boolean; + filterBy?: any; + isLoading?: boolean; + onSelect(items: SettingsData[], isSelected: boolean); + onSort(value: string, isSortAscending: boolean); + orderBy?: any; + selectedItems?: SettingsData[]; + settings: Settings; +} + +type TagsTableProps = TagsTableOwnProps; + +const TagsTable: React.FC = ({ + canWrite, + filterBy, + isLoading, + onSelect, + onSort, + orderBy, + selectedItems, + settings, +}) => { + const [columns, setColumns] = useState([]); + const [rows, setRows] = useState([]); + const intl = useIntl(); + + const initDatum = () => { + if (!settings) { + return; + } + + const newRows = []; + const tags = settings?.data ? (settings.data as any) : []; + + const newColumns = [ + { + name: '', // Selection column + }, + { + orderBy: 'key', + name: intl.formatMessage(messages.detailsResourceNames, { value: 'name' }), + ...(tags.length && { isSortable: true }), + }, + { + orderBy: 'enabled', + name: intl.formatMessage(messages.detailsResourceNames, { value: 'status' }), + ...(tags.length && { isSortable: true }), + }, + { + orderBy: 'source_type', + name: intl.formatMessage(messages.sourceType), + ...(tags.length && { isSortable: true }), + }, + ]; + + tags.map(item => { + newRows.push({ + cells: [ + {}, // Empty cell for row selection + { + value: item.key ? item.key : '', + }, + { + value: item.enabled ? ( + + ) : ( + + ), + }, + { + value: intl.formatMessage(messages.sourceTypes, { value: item?.source_type?.toLowerCase() }), + }, + ], + item, + selected: selectedItems && selectedItems.find(val => val.uuid === item.uuid) !== undefined, + selectionDisabled: !canWrite, + }); + }); + + const filteredColumns = (newColumns as any[]).filter(column => !column.hidden); + const filteredRows = newRows.map(({ ...row }) => { + row.cells = row.cells.filter(cell => !cell.hidden); + return row; + }); + + setColumns(filteredColumns); + setRows(filteredRows); + }; + + useEffect(() => { + initDatum(); + }, [selectedItems, settings]); + + return ( + + ); +}; + +export { TagsTable }; diff --git a/src/routes/settings/tagLabels/tagToolbar.tsx b/src/routes/settings/tagLabels/tags/tagsToolbar.tsx similarity index 52% rename from src/routes/settings/tagLabels/tagToolbar.tsx rename to src/routes/settings/tagLabels/tags/tagsToolbar.tsx index 0db966184..b44ab0708 100644 --- a/src/routes/settings/tagLabels/tagToolbar.tsx +++ b/src/routes/settings/tagLabels/tags/tagsToolbar.tsx @@ -1,20 +1,16 @@ import { Button, ButtonVariant, Tooltip } from '@patternfly/react-core'; -import type { OcpQuery } from 'api/queries/ocpQuery'; -import { ResourcePathsType } from 'api/resources/resource'; +import type { Query } from 'api/queries/query'; +import type { SettingsData } from 'api/settings'; import messages from 'locales/messages'; import React from 'react'; -import type { WrappedComponentProps } from 'react-intl'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; +import { useIntl } from 'react-intl'; import { BasicToolbar } from 'routes/components/dataToolbar'; import type { ToolbarChipGroupExt } from 'routes/components/dataToolbar/utils/common'; -import type { ComputedReportItem } from 'routes/utils/computedReport/getComputedReportItems'; import type { Filter } from 'routes/utils/filter'; -import { createMapStateToProps } from 'store/common'; -import { styles } from './tagLabels.styles'; +import { styles } from './tags.styles'; -interface TagToolbarOwnProps { +interface TagsToolbarOwnProps { canWrite?: boolean; enabledTagsCount?: number; enabledTagsLimit?: number; @@ -30,51 +26,39 @@ interface TagToolbarOwnProps { onFilterAdded(filter: Filter); onFilterRemoved(filter: Filter); pagination?: React.ReactNode; - query?: OcpQuery; - selectedItems?: ComputedReportItem[]; + query?: Query; + selectedItems?: SettingsData[]; showBulkSelectAll?: boolean; } -interface TagToolbarStateProps { - // TBD... -} - -interface TagToolbarDispatchProps { - // TBD... -} - -interface TagToolbarState { - categoryOptions?: ToolbarChipGroupExt[]; -} - -type TagToolbarProps = TagToolbarOwnProps & TagToolbarStateProps & TagToolbarDispatchProps & WrappedComponentProps; - -export class TagToolbarBase extends React.Component { - protected defaultState: TagToolbarState = {}; - public state: TagToolbarState = { ...this.defaultState }; - - public componentDidMount() { - this.setState({ - categoryOptions: this.getCategoryOptions(), - }); - } - - private getActions = () => { - const { - canWrite, - enabledTagsCount = 0, - enabledTagsLimit = 0, - intl, - isPrimaryActionDisabled, - isSecondaryActionDisabled, - onDisableTags, - onEnableTags, - selectedItems, - } = this.props; - +type TagsToolbarProps = TagsToolbarOwnProps; + +const TagsToolbar: React.FC = ({ + canWrite, + enabledTagsCount = 0, + enabledTagsLimit = 0, + isAllSelected, + isDisabled, + isPrimaryActionDisabled, + isSecondaryActionDisabled, + itemsPerPage, + itemsTotal, + onBulkSelect, + onDisableTags, + onEnableTags, + onFilterAdded, + onFilterRemoved, + pagination, + query, + selectedItems, + showBulkSelectAll, +}) => { + const intl = useIntl(); + + const getActions = () => { const disabledItems = selectedItems.filter((item: any) => !item.enabled); const isLimit = disabledItems.length + enabledTagsCount > enabledTagsLimit; - const isDisabled = !canWrite || selectedItems.length === 0; + const isAriaDisabled = !canWrite || isDisabled || selectedItems.length === 0; const enableTagsTooltip = intl.formatMessage( !canWrite ? messages.readOnlyPermissions : isLimit ? messages.deselectTags : messages.selectTags, { count: enabledTagsLimit } @@ -85,7 +69,7 @@ export class TagToolbarBase extends React.Component