@rancher/shell 3.0.8-rc.1 → 3.0.8-rc.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/brand/suse/banner.svg +1 -0
- package/assets/brand/suse/dark/banner.svg +1 -0
- package/assets/brand/suse/dark/login-landscape.svg +1 -0
- package/assets/brand/suse/dark/rancher-logo.svg +1 -1
- package/assets/brand/suse/favicon.png +0 -0
- package/assets/brand/suse/login-landscape.svg +1 -0
- package/assets/brand/suse/metadata.json +11 -1
- package/assets/brand/suse/rancher-logo.svg +1 -1
- package/assets/fonts/suse/suse-v2-latin-300.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-300.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-600.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-600.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-700.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-700.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-800.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-800.woff2 +0 -0
- package/assets/fonts/suse/suse-v2-latin-regular.woff +0 -0
- package/assets/fonts/suse/suse-v2-latin-regular.woff2 +0 -0
- package/assets/images/content/README.md +5 -0
- package/assets/images/content/cloud-native.svg +84 -0
- package/assets/images/content/dark/cloud-native.svg +21 -0
- package/assets/images/content/dark/shield.svg +59 -0
- package/assets/images/content/dark/suse.svg +10 -0
- package/assets/images/content/shield.svg +59 -0
- package/assets/images/content/suse.svg +10 -0
- package/assets/styles/base/_typography.scss +1 -0
- package/assets/styles/fonts/_fontstack.scss +53 -1
- package/assets/styles/global/_cards.scss +0 -3
- package/assets/styles/global/_layout.scss +21 -35
- package/assets/styles/themes/_dark.scss +1 -1
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +11 -3
- package/assets/styles/themes/_suse.scss +116 -24
- package/assets/translations/en-us.yaml +94 -10
- package/components/AutoscalerCard.vue +113 -0
- package/components/AutoscalerTab.vue +94 -0
- package/components/BackLink.vue +8 -0
- package/components/BannerGraphic.vue +36 -21
- package/components/BrandImage.vue +17 -6
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/Cron/CronExpressionEditor.vue +1 -1
- package/components/Cron/CronExpressionEditorModal.vue +1 -1
- package/components/Drawer/Chrome.vue +2 -6
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
- package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
- package/components/Drawer/ResourceDetailDrawer/index.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/types.ts +17 -0
- package/components/Drawer/types.ts +3 -0
- package/components/DynamicContent/DynamicContentBanner.vue +102 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +42 -0
- package/components/DynamicContent/DynamicContentIcon.vue +132 -0
- package/components/DynamicContent/DynamicContentPanel.vue +112 -0
- package/components/DynamicContent/content.ts +78 -0
- package/components/EmberPage.vue +1 -1
- package/components/IconOrSvg.vue +2 -2
- package/components/PaginatedResourceTable.vue +2 -6
- package/components/PopoverCard.vue +192 -0
- package/components/Questions/__tests__/index.test.ts +159 -0
- package/components/Resource/Detail/CopyToClipboard.vue +4 -1
- package/components/Resource/Detail/FetchLoader/composables.ts +18 -4
- package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +1 -1
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +4 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +1 -1
- package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
- package/components/Resource/Detail/Metadata/composables.ts +9 -9
- package/components/Resource/Detail/Metadata/index.vue +3 -3
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -19
- package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +0 -29
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +132 -150
- package/components/Resource/Detail/ResourcePopover/index.vue +54 -159
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -6
- package/components/Resource/Detail/composables.ts +12 -0
- package/components/ResourceDetail/Masthead/latest.vue +29 -0
- package/components/ResourceDetail/index.vue +4 -1
- package/components/ResourceList/Masthead.vue +1 -1
- package/components/ResourceTable.vue +1 -1
- package/components/SortableTable/index.vue +2 -1
- package/components/Tabbed/__tests__/index.test.ts +86 -0
- package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
- package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
- package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
- package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
- package/components/__tests__/AutoscalerCard.test.ts +154 -0
- package/components/__tests__/AutoscalerTab.test.ts +125 -0
- package/components/__tests__/PopoverCard.test.ts +204 -0
- package/components/auth/SelectPrincipal.vue +24 -6
- package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
- package/components/auth/login/ldap.vue +3 -3
- package/components/form/NodeScheduling.vue +2 -2
- package/components/formatter/Autoscaler.vue +97 -0
- package/components/formatter/InternalExternalIP.vue +198 -24
- package/components/formatter/__tests__/Autoscaler.test.ts +156 -0
- package/components/formatter/__tests__/InternalExternalIP.test.ts +133 -0
- package/components/google/util/__tests__/formatter.test.ts +47 -0
- package/components/google/util/formatter.ts +5 -2
- package/components/nav/Group.vue +21 -5
- package/components/nav/Header.vue +37 -17
- package/components/nav/NamespaceFilter.vue +13 -1
- package/components/nav/NotificationCenter/index.vue +2 -1
- package/components/nav/TopLevelMenu.helper.ts +16 -6
- package/components/nav/TopLevelMenu.vue +4 -2
- package/components/nav/Type.vue +8 -3
- package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
- package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
- package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
- package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
- package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
- package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
- package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
- package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
- package/components/nav/WindowManager/constants.ts +23 -0
- package/components/nav/WindowManager/index.vue +61 -575
- package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
- package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
- package/components/nav/__tests__/Type.test.ts +59 -0
- package/components/templates/default.vue +4 -40
- package/components/templates/home.vue +31 -5
- package/components/templates/plain.vue +30 -4
- package/components/templates/standalone.vue +1 -1
- package/composables/useI18n.ts +10 -1
- package/composables/useInterval.ts +15 -0
- package/config/__test__/uiplugins.test.ts +309 -0
- package/config/labels-annotations.js +9 -1
- package/config/product/explorer.js +3 -1
- package/config/product/manager.js +20 -9
- package/config/router/navigation-guards/clusters.js +3 -3
- package/config/router/navigation-guards/products.js +1 -1
- package/config/router/routes.js +10 -2
- package/config/settings.ts +2 -1
- package/config/store.js +4 -2
- package/config/table-headers.js +8 -0
- package/config/types.js +9 -0
- package/config/uiplugins.js +46 -2
- package/config/version.js +1 -1
- package/core/__test__/extension-manager-impl.test.js +236 -0
- package/core/extension-manager-impl.js +21 -4
- package/core/plugin-helpers.ts +4 -2
- package/core/plugins-loader.js +2 -2
- package/core/types-provisioning.ts +8 -1
- package/detail/pod.vue +1 -0
- package/detail/provisioning.cattle.io.cluster.vue +19 -7
- package/dialog/DeveloperLoadExtensionDialog.vue +13 -4
- package/dialog/RollbackWorkloadDialog.vue +2 -5
- package/dialog/SearchDialog.vue +1 -0
- package/directives/ui-context.ts +103 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +2 -2
- package/edit/auth/__tests__/oidc.test.ts +26 -0
- package/edit/auth/github.vue +5 -0
- package/edit/auth/oidc.vue +5 -1
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
- package/edit/cloudcredential.vue +1 -1
- package/edit/configmap.vue +1 -0
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
- package/edit/fleet.cattle.io.gitrepo.vue +0 -10
- package/edit/fleet.cattle.io.helmop.vue +6 -6
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
- package/edit/logging-flow/index.vue +1 -0
- package/edit/logging.banzaicloud.io.output/index.vue +1 -0
- package/edit/management.cattle.io.fleetworkspace.vue +1 -1
- package/edit/management.cattle.io.project.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
- package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
- package/edit/monitoring.coreos.com.route.vue +1 -1
- package/edit/namespace.vue +1 -0
- package/edit/networking.istio.io.destinationrule/index.vue +1 -0
- package/edit/networking.k8s.io.ingress/index.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
- package/edit/node.vue +1 -0
- package/edit/persistentvolume/index.vue +27 -22
- package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
- package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
- package/edit/persistentvolume/plugins/azureFile.vue +15 -14
- package/edit/persistentvolume/plugins/cephfs.vue +15 -14
- package/edit/persistentvolume/plugins/cinder.vue +15 -14
- package/edit/persistentvolume/plugins/csi.vue +18 -16
- package/edit/persistentvolume/plugins/fc.vue +13 -14
- package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
- package/edit/persistentvolume/plugins/flocker.vue +1 -3
- package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
- package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
- package/edit/persistentvolume/plugins/hostPath.vue +40 -39
- package/edit/persistentvolume/plugins/iscsi.vue +13 -14
- package/edit/persistentvolume/plugins/local.vue +1 -3
- package/edit/persistentvolume/plugins/longhorn.vue +23 -22
- package/edit/persistentvolume/plugins/nfs.vue +15 -14
- package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
- package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
- package/edit/persistentvolume/plugins/quobyte.vue +15 -14
- package/edit/persistentvolume/plugins/rbd.vue +15 -14
- package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
- package/edit/persistentvolume/plugins/storageos.vue +15 -14
- package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +32 -5
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +35 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +155 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
- package/edit/provisioning.cattle.io.cluster/index.vue +28 -18
- package/edit/provisioning.cattle.io.cluster/rke2.vue +50 -16
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +107 -5
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +92 -4
- package/edit/secret/index.vue +1 -1
- package/edit/service.vue +9 -4
- package/edit/serviceaccount.vue +1 -0
- package/edit/storage.k8s.io.storageclass/index.vue +1 -0
- package/edit/workload/index.vue +2 -1
- package/edit/workload/mixins/workload.js +1 -1
- package/initialize/App.vue +4 -4
- package/initialize/install-directives.js +2 -0
- package/initialize/install-plugins.js +19 -2
- package/list/provisioning.cattle.io.cluster.vue +15 -2
- package/machine-config/amazonec2.vue +42 -135
- package/machine-config/components/EC2Networking.vue +490 -0
- package/machine-config/components/__tests__/EC2Networking.test.ts +148 -0
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +294 -0
- package/machine-config/digitalocean.vue +11 -0
- package/machine-config/google.vue +1 -1
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/__tests__/chart.test.ts +21 -0
- package/mixins/brand.js +1 -7
- package/mixins/chart.js +7 -1
- package/mixins/create-edit-view/index.js +5 -0
- package/models/__tests__/chart.test.ts +33 -4
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
- package/models/chart.js +25 -13
- package/models/cluster/node.js +13 -6
- package/models/cluster.x-k8s.io.machine.js +10 -20
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +249 -33
- package/package.json +8 -7
- package/pages/auth/login.vue +41 -5
- package/pages/auth/setup.vue +1 -1
- package/pages/auth/verify.vue +3 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
- package/pages/c/_cluster/apps/charts/chart.vue +33 -15
- package/pages/c/_cluster/apps/charts/index.vue +11 -13
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +8 -6
- package/pages/c/_cluster/manager/hostedprovider/index.vue +220 -0
- package/pages/c/_cluster/settings/brand.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
- package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
- package/pages/c/_cluster/uiplugins/index.vue +126 -184
- package/pages/home.vue +14 -4
- package/pkg/auto-import.js +3 -3
- package/pkg/dynamic-importer.lib.js +5 -1
- package/pkg/import.js +1 -1
- package/plugins/dashboard-client-init.js +3 -0
- package/plugins/dashboard-store/getters.js +19 -2
- package/plugins/dashboard-store/model-loader.js +1 -1
- package/plugins/dashboard-store/resource-class.js +10 -6
- package/plugins/dynamic-content.js +13 -0
- package/plugins/i18n.js +8 -0
- package/plugins/plugin.js +2 -2
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
- package/plugins/steve/steve-class.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +39 -20
- package/plugins/steve/subscribe.js +17 -9
- package/plugins/subscribe-events.ts +4 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -34
- package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
- package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
- package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
- package/rancher-components/Pill/types.ts +0 -1
- package/rancher-components/RcDropdown/RcDropdownItem.vue +1 -0
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
- package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
- package/rancher-components/RcIcon/RcIcon.vue +46 -0
- package/rancher-components/RcIcon/index.ts +1 -0
- package/rancher-components/RcIcon/types.ts +160 -0
- package/rancher-components/utils/status.test.ts +67 -0
- package/rancher-components/utils/status.ts +77 -0
- package/scripts/typegen.sh +1 -0
- package/store/__tests__/catalog.test.ts +1 -1
- package/store/action-menu.js +8 -0
- package/store/auth.js +4 -4
- package/store/catalog.js +6 -0
- package/store/features.js +1 -0
- package/store/i18n.js +3 -3
- package/store/index.js +40 -19
- package/store/notifications.ts +51 -4
- package/store/plugins.js +7 -3
- package/store/prefs.js +6 -6
- package/store/type-map.js +7 -7
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/notifications/index.ts +27 -3
- package/types/shell/index.d.ts +80 -4
- package/types/store/__tests__/pagination.types.spec.ts +137 -0
- package/types/store/pagination.types.ts +157 -9
- package/types/store/subscribe-events.types.ts +8 -1
- package/types/store/subscribe.types.ts +1 -0
- package/types/window-manager.ts +24 -0
- package/utils/__tests__/object.test.ts +19 -0
- package/utils/__tests__/provider.test.ts +98 -0
- package/utils/__tests__/selector-typed.test.ts +263 -0
- package/utils/__tests__/version.test.ts +19 -1
- package/utils/autoscaler-utils.ts +7 -0
- package/utils/back-off.ts +3 -3
- package/utils/brand.ts +29 -0
- package/utils/chart.js +18 -0
- package/utils/color.js +1 -1
- package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
- package/utils/dynamic-content/__tests__/info.test.ts +21 -9
- package/utils/dynamic-content/announcement.ts +142 -0
- package/utils/dynamic-content/example.json +40 -0
- package/utils/dynamic-content/index.ts +6 -2
- package/utils/dynamic-content/info.ts +44 -2
- package/utils/dynamic-content/new-release.ts +1 -1
- package/utils/dynamic-content/notification-handler.ts +48 -0
- package/utils/dynamic-content/types.d.ts +53 -1
- package/utils/dynamic-importer.js +2 -2
- package/utils/favicon.js +4 -4
- package/utils/object.js +20 -2
- package/utils/pagination-utils.ts +2 -2
- package/utils/pagination-wrapper.ts +13 -9
- package/utils/provider.ts +14 -0
- package/utils/scroll.js +7 -0
- package/utils/selector-typed.ts +6 -2
- package/utils/settings.ts +15 -0
- package/utils/unit-tests/pagination-utils.spec.ts +8 -8
- package/utils/validators/machine-pool.ts +13 -3
- package/utils/version.js +15 -0
- package/vue.config.js +3 -3
- package/assets/images/icons/document.svg +0 -3
- package/plugins/nuxt-client-init.js +0 -3
- package/store/wm.js +0 -95
- /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
- /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
- /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
package/utils/back-off.ts
CHANGED
|
@@ -137,9 +137,9 @@ class BackOff {
|
|
|
137
137
|
|
|
138
138
|
// First step is immediate (0.001s)
|
|
139
139
|
// Second and others are exponential
|
|
140
|
-
// 1,
|
|
141
|
-
// 1,
|
|
142
|
-
// 0.25s, 1s,
|
|
140
|
+
// Try: 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
141
|
+
// Multiple: 1, 4, 9, 16, 25, 36, 49, 64, 81
|
|
142
|
+
// Actual Time: 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
|
|
143
143
|
const delay = backOffTry === 0 ? 1 : Math.pow(backOffTry, 2) * 250;
|
|
144
144
|
|
|
145
145
|
this.log('info', id, `Delaying call (attempt ${ backOffTry + 1 }, delayed by ${ delay }ms)`, description);
|
package/utils/brand.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand/Theme metadata
|
|
3
|
+
*/
|
|
4
|
+
export interface BrandMeta {
|
|
5
|
+
// Does the banner have a stylesheet?
|
|
6
|
+
hasStylesheet?: string;
|
|
7
|
+
banner?: {
|
|
8
|
+
// Text alignment for the banner text overlayed on the banner image
|
|
9
|
+
textAlign?: string;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the brand/theme meta information for the specified brand
|
|
15
|
+
*
|
|
16
|
+
* @param brand - The brand identifier
|
|
17
|
+
* @returns Brand meta information or empty object if none available
|
|
18
|
+
*/
|
|
19
|
+
export function getBrandMeta(brand: string): BrandMeta {
|
|
20
|
+
let brandMeta: BrandMeta = {};
|
|
21
|
+
|
|
22
|
+
if (brand) {
|
|
23
|
+
try {
|
|
24
|
+
brandMeta = require(`~shell/assets/brand/${ brand }/metadata.json`);
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return brandMeta;
|
|
29
|
+
}
|
package/utils/chart.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { compatibleVersionsFor } from '@shell/store/catalog';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the latest chart version that is compatible with the cluster's OS and user's pre-release preference.
|
|
5
|
+
* @param {Object} chart - The chart object.
|
|
6
|
+
* @param {Array<string>} workerOSs - The list of worker OSs for the cluster.
|
|
7
|
+
* @param {boolean} showPrerelease - Whether to include pre-release versions.
|
|
8
|
+
* @returns {Object} The latest compatible chart version object.
|
|
9
|
+
*/
|
|
10
|
+
export function getLatestCompatibleVersion(chart, workerOSs, showPrerelease) {
|
|
11
|
+
if (!chart?.versions?.length) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const compatible = compatibleVersionsFor(chart, workerOSs, showPrerelease);
|
|
16
|
+
|
|
17
|
+
return (compatible.length ? compatible[0] : chart.versions[0]) || {};
|
|
18
|
+
}
|
package/utils/color.js
CHANGED
|
@@ -13,7 +13,7 @@ Primary color classes from _light.scss
|
|
|
13
13
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
import Color from 'color';
|
|
17
17
|
|
|
18
18
|
export function createCssVars(color, theme = 'light', name = 'primary') {
|
|
19
19
|
const contrastOpts = theme === 'light' ? LIGHT_CONTRAST_COLORS : DARK_CONTRAST_COLORS;
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { processAnnouncements, ANNOUNCEMENT_PREFIX } from '../announcement';
|
|
2
|
+
import { NotificationLevel, Notification } from '@shell/types/notifications';
|
|
3
|
+
import { READ_ANNOUNCEMENTS } from '@shell/store/prefs';
|
|
4
|
+
import { DynamicContentAnnouncementHandlerName } from '../notification-handler';
|
|
5
|
+
import { Context, VersionInfo, Announcement } from '../types';
|
|
6
|
+
import semver from 'semver';
|
|
7
|
+
|
|
8
|
+
jest.mock('semver', () => ({
|
|
9
|
+
...jest.requireActual('semver'),
|
|
10
|
+
satisfies: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('processAnnouncements', () => {
|
|
14
|
+
let mockDispatch: jest.Mock;
|
|
15
|
+
let mockGetters: any;
|
|
16
|
+
let mockLogger: any;
|
|
17
|
+
let mockContext: Context;
|
|
18
|
+
|
|
19
|
+
const VERSION_270 = { version: semver.coerce('v2.7.0') as semver.SemVer, isPrime: false };
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
mockDispatch = jest.fn();
|
|
23
|
+
mockGetters = {
|
|
24
|
+
'notifications/item': jest.fn(),
|
|
25
|
+
'prefs/get': jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
mockLogger = {
|
|
28
|
+
error: jest.fn(),
|
|
29
|
+
info: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
mockContext = {
|
|
32
|
+
dispatch: mockDispatch,
|
|
33
|
+
getters: mockGetters,
|
|
34
|
+
logger: mockLogger,
|
|
35
|
+
isAdmin: false, // Default to non-admin
|
|
36
|
+
} as unknown as Context;
|
|
37
|
+
|
|
38
|
+
// Reset all mocks before each test
|
|
39
|
+
jest.clearAllMocks();
|
|
40
|
+
|
|
41
|
+
// Default mock for semver.satisfies to return true
|
|
42
|
+
(semver.satisfies as jest.Mock).mockReturnValue(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// --- Early Exit Conditions ---
|
|
46
|
+
it('should return early if no announcements are provided', async() => {
|
|
47
|
+
await processAnnouncements(mockContext, undefined, VERSION_270);
|
|
48
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
49
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return early if announcements array is empty', async() => {
|
|
53
|
+
await processAnnouncements(mockContext, [], VERSION_270);
|
|
54
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
55
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return early if versionInfo is undefined', async() => {
|
|
59
|
+
await processAnnouncements(mockContext, [{
|
|
60
|
+
id: '1',
|
|
61
|
+
target: 'notification/announcement',
|
|
62
|
+
title: 'Test',
|
|
63
|
+
message: 'Msg'
|
|
64
|
+
}], undefined as any);
|
|
65
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
66
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return early if versionInfo.version is undefined', async() => {
|
|
70
|
+
await processAnnouncements(mockContext, [{
|
|
71
|
+
id: '1',
|
|
72
|
+
target: 'notification/announcement',
|
|
73
|
+
title: 'Test',
|
|
74
|
+
message: 'Msg'
|
|
75
|
+
}], { version: undefined as any, isPrime: false });
|
|
76
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
77
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// --- Version and Audience Filtering ---
|
|
81
|
+
it('should not process announcement if version does not satisfy requirement', async() => {
|
|
82
|
+
(semver.satisfies as jest.Mock).mockReturnValue(false);
|
|
83
|
+
const announcements: Announcement[] = [
|
|
84
|
+
{
|
|
85
|
+
id: '1',
|
|
86
|
+
target: 'notification/announcement',
|
|
87
|
+
title: 'Test',
|
|
88
|
+
message: 'Msg',
|
|
89
|
+
version: 'v2.8.0'
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
94
|
+
|
|
95
|
+
expect(semver.satisfies).toHaveBeenCalledWith(VERSION_270.version, announcements[0].version);
|
|
96
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
97
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should not process admin-only announcement if user is not admin', async() => {
|
|
101
|
+
mockContext.isAdmin = false;
|
|
102
|
+
const announcements: Announcement[] = [
|
|
103
|
+
{
|
|
104
|
+
id: '1',
|
|
105
|
+
target: 'notification/announcement',
|
|
106
|
+
title: 'Test',
|
|
107
|
+
message: 'Msg',
|
|
108
|
+
audience: 'admin'
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
113
|
+
|
|
114
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
115
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should process admin-only announcement if user is admin', async() => {
|
|
119
|
+
mockContext.isAdmin = true;
|
|
120
|
+
const announcements: Announcement[] = [
|
|
121
|
+
{
|
|
122
|
+
id: '1',
|
|
123
|
+
target: 'notification/announcement',
|
|
124
|
+
title: 'Test',
|
|
125
|
+
message: 'Msg',
|
|
126
|
+
audience: 'admin'
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
const versionInfo: VersionInfo = { version: VERSION_270.version };
|
|
130
|
+
|
|
131
|
+
await processAnnouncements(mockContext, announcements, versionInfo);
|
|
132
|
+
|
|
133
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.any(Object));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should process all-audience announcement regardless of admin status', async() => {
|
|
138
|
+
mockContext.isAdmin = false; // Test with non-admin
|
|
139
|
+
const announcements: Announcement[] = [
|
|
140
|
+
{
|
|
141
|
+
id: '1',
|
|
142
|
+
target: 'notification/announcement',
|
|
143
|
+
title: 'Test',
|
|
144
|
+
message: 'Msg',
|
|
145
|
+
audience: 'all'
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
150
|
+
|
|
151
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
152
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.any(Object));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// --- Target Type Handling ---
|
|
156
|
+
it('should log error for unsupported announcement target type', async() => {
|
|
157
|
+
const announcements: Announcement[] = [
|
|
158
|
+
{
|
|
159
|
+
id: '1',
|
|
160
|
+
target: 'unsupported/type',
|
|
161
|
+
title: 'Test',
|
|
162
|
+
message: 'Msg'
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
167
|
+
|
|
168
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Announcement type unsupported/type is not supported');
|
|
169
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should log error for unsupported notification sub-type', async() => {
|
|
173
|
+
const announcements: Announcement[] = [
|
|
174
|
+
{
|
|
175
|
+
id: '1',
|
|
176
|
+
target: 'notification/unsupported',
|
|
177
|
+
title: 'Test',
|
|
178
|
+
message: 'Msg'
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
183
|
+
|
|
184
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Announcement notification type unsupported is not supported');
|
|
185
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// --- Notification Creation Logic ---
|
|
189
|
+
it('should log error and not add notification if announcement has no ID', async() => {
|
|
190
|
+
const announcements: Announcement[] = [
|
|
191
|
+
{
|
|
192
|
+
target: 'notification/announcement',
|
|
193
|
+
title: 'Test',
|
|
194
|
+
message: 'Msg'
|
|
195
|
+
} as Announcement, // Missing ID
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
199
|
+
|
|
200
|
+
expect(mockLogger.error).toHaveBeenCalledWith('No ID For announcement - not going to add a notification for the announcement');
|
|
201
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should not add notification if one with the same ID already exists', async() => {
|
|
205
|
+
const announcementId = 'existing-announcement';
|
|
206
|
+
|
|
207
|
+
mockGetters['notifications/item'].mockReturnValue({ id: `${ ANNOUNCEMENT_PREFIX }${ announcementId }` });
|
|
208
|
+
const announcements: Announcement[] = [
|
|
209
|
+
{
|
|
210
|
+
id: announcementId,
|
|
211
|
+
target: 'notification/announcement',
|
|
212
|
+
title: 'Test',
|
|
213
|
+
message: 'Msg'
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
218
|
+
|
|
219
|
+
expect(mockGetters['notifications/item']).toHaveBeenCalledWith(`${ ANNOUNCEMENT_PREFIX }${ announcementId }`);
|
|
220
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Not adding announcement with ID'));
|
|
221
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should not add notification if announcement ID is in READ_ANNOUNCEMENTS preference', async() => {
|
|
225
|
+
const announcementId = 'read-announcement';
|
|
226
|
+
|
|
227
|
+
mockGetters['notifications/item'].mockReturnValue(undefined); // No existing notification
|
|
228
|
+
mockGetters['prefs/get'].mockImplementation((key: string) => {
|
|
229
|
+
if (key === READ_ANNOUNCEMENTS) {
|
|
230
|
+
return `some-other-id,${ announcementId },another-one`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return '';
|
|
234
|
+
});
|
|
235
|
+
const announcements: Announcement[] = [
|
|
236
|
+
{
|
|
237
|
+
id: announcementId,
|
|
238
|
+
target: 'notification/announcement',
|
|
239
|
+
title: 'Test',
|
|
240
|
+
message: 'Msg'
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
245
|
+
|
|
246
|
+
expect(mockGetters['prefs/get']).toHaveBeenCalledWith(READ_ANNOUNCEMENTS);
|
|
247
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Not adding announcement with ID'));
|
|
248
|
+
expect(mockDispatch).not.toHaveBeenCalled();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should add a new announcement notification with default level (announcement)', async() => {
|
|
252
|
+
const announcementId = 'new-announcement';
|
|
253
|
+
|
|
254
|
+
mockGetters['notifications/item'].mockReturnValue(undefined);
|
|
255
|
+
mockGetters['prefs/get'].mockReturnValue('');
|
|
256
|
+
const announcements: Announcement[] = [
|
|
257
|
+
{
|
|
258
|
+
id: announcementId,
|
|
259
|
+
target: 'notification/announcement',
|
|
260
|
+
title: 'New Announcement',
|
|
261
|
+
message: 'This is a new message.'
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
266
|
+
|
|
267
|
+
const expectedNotification: Notification = {
|
|
268
|
+
id: `${ ANNOUNCEMENT_PREFIX }${ announcementId }`,
|
|
269
|
+
level: NotificationLevel.Announcement,
|
|
270
|
+
title: 'New Announcement',
|
|
271
|
+
message: 'This is a new message.',
|
|
272
|
+
handlerName: DynamicContentAnnouncementHandlerName,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
276
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expectedNotification);
|
|
277
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining(`Adding announcement with ID ${ ANNOUNCEMENT_PREFIX }${ announcementId }`));
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should add a new info level notification', async() => {
|
|
281
|
+
const announcementId = 'new-info';
|
|
282
|
+
|
|
283
|
+
mockGetters['notifications/item'].mockReturnValue(undefined);
|
|
284
|
+
mockGetters['prefs/get'].mockReturnValue('');
|
|
285
|
+
const announcements: Announcement[] = [
|
|
286
|
+
{
|
|
287
|
+
id: announcementId,
|
|
288
|
+
target: 'notification/info',
|
|
289
|
+
title: 'Info Title',
|
|
290
|
+
message: 'Info Message'
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
295
|
+
|
|
296
|
+
const expectedNotification: Notification = {
|
|
297
|
+
id: `${ ANNOUNCEMENT_PREFIX }${ announcementId }`,
|
|
298
|
+
level: NotificationLevel.Info,
|
|
299
|
+
title: 'Info Title',
|
|
300
|
+
message: 'Info Message',
|
|
301
|
+
handlerName: DynamicContentAnnouncementHandlerName,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
305
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expectedNotification);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should add a new warning level notification', async() => {
|
|
309
|
+
const announcementId = 'new-warning';
|
|
310
|
+
|
|
311
|
+
mockGetters['notifications/item'].mockReturnValue(undefined);
|
|
312
|
+
mockGetters['prefs/get'].mockReturnValue('');
|
|
313
|
+
const announcements: Announcement[] = [
|
|
314
|
+
{
|
|
315
|
+
id: announcementId,
|
|
316
|
+
target: 'notification/warning',
|
|
317
|
+
title: 'Warning Title',
|
|
318
|
+
message: 'Warning Message'
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
323
|
+
|
|
324
|
+
const expectedNotification: Notification = {
|
|
325
|
+
id: `${ ANNOUNCEMENT_PREFIX }${ announcementId }`,
|
|
326
|
+
level: NotificationLevel.Warning,
|
|
327
|
+
title: 'Warning Title',
|
|
328
|
+
message: 'Warning Message',
|
|
329
|
+
handlerName: DynamicContentAnnouncementHandlerName,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
333
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expectedNotification);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// --- Call To Action (CTA) Handling ---
|
|
337
|
+
it('should add notification with primary CTA', async() => {
|
|
338
|
+
const announcementId = 'cta-primary';
|
|
339
|
+
|
|
340
|
+
mockGetters['notifications/item'].mockReturnValue(undefined);
|
|
341
|
+
mockGetters['prefs/get'].mockReturnValue('');
|
|
342
|
+
|
|
343
|
+
const announcements: Announcement[] = [
|
|
344
|
+
{
|
|
345
|
+
id: announcementId,
|
|
346
|
+
target: 'notification/announcement',
|
|
347
|
+
title: 'CTA Primary',
|
|
348
|
+
message: 'Message',
|
|
349
|
+
cta: { primary: { action: 'Click Me', link: '/some/path' } },
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
354
|
+
|
|
355
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
356
|
+
const notification = mockDispatch.mock.calls[0][1];
|
|
357
|
+
|
|
358
|
+
expect(notification.primaryAction).toStrictEqual({
|
|
359
|
+
label: 'Click Me',
|
|
360
|
+
target: '/some/path'
|
|
361
|
+
});
|
|
362
|
+
expect(notification.secondaryAction).toBeUndefined();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should add notification with secondary CTA', async() => {
|
|
366
|
+
const announcementId = 'cta-secondary';
|
|
367
|
+
|
|
368
|
+
mockGetters['notifications/item'].mockReturnValue(undefined);
|
|
369
|
+
mockGetters['prefs/get'].mockReturnValue('');
|
|
370
|
+
const announcements: Announcement[] = [
|
|
371
|
+
{
|
|
372
|
+
id: announcementId,
|
|
373
|
+
target: 'notification/announcement',
|
|
374
|
+
title: 'CTA Secondary',
|
|
375
|
+
message: 'Message',
|
|
376
|
+
cta: { secondary: { action: 'More Info', link: 'http://example.com' } },
|
|
377
|
+
},
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
381
|
+
|
|
382
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
383
|
+
const notification = mockDispatch.mock.calls[0][1];
|
|
384
|
+
|
|
385
|
+
expect(notification.secondaryAction).toStrictEqual({ label: 'More Info', target: 'http://example.com' });
|
|
386
|
+
expect(notification.primaryAction).toBeUndefined();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should add notification with both primary and secondary CTAs', async() => {
|
|
390
|
+
const announcementId = 'cta-both';
|
|
391
|
+
|
|
392
|
+
mockGetters['notifications/item'].mockReturnValue(undefined);
|
|
393
|
+
mockGetters['prefs/get'].mockReturnValue('');
|
|
394
|
+
const announcements: Announcement[] = [
|
|
395
|
+
{
|
|
396
|
+
id: announcementId,
|
|
397
|
+
target: 'notification/announcement',
|
|
398
|
+
title: 'CTA Both',
|
|
399
|
+
message: 'Message',
|
|
400
|
+
cta: {
|
|
401
|
+
primary: {
|
|
402
|
+
action: 'Primary',
|
|
403
|
+
link: '/primary'
|
|
404
|
+
},
|
|
405
|
+
secondary: {
|
|
406
|
+
action: 'Secondary',
|
|
407
|
+
link: '/secondary'
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
414
|
+
|
|
415
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
416
|
+
const notification = mockDispatch.mock.calls[0][1];
|
|
417
|
+
|
|
418
|
+
expect(notification.primaryAction).toStrictEqual({ label: 'Primary', target: '/primary' });
|
|
419
|
+
expect(notification.secondaryAction).toStrictEqual({ label: 'Secondary', target: '/secondary' });
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// --- Multiple Announcements ---
|
|
423
|
+
it('should process multiple announcements correctly, skipping invalid ones', async() => {
|
|
424
|
+
mockContext.isAdmin = true; // Ensure admin-only can be processed
|
|
425
|
+
mockGetters['notifications/item'].mockImplementation((id: string) => {
|
|
426
|
+
if (id === `${ ANNOUNCEMENT_PREFIX }existing-id`) {
|
|
427
|
+
return { id };
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return undefined;
|
|
431
|
+
});
|
|
432
|
+
mockGetters['prefs/get'].mockImplementation((key: string) => {
|
|
433
|
+
if (key === READ_ANNOUNCEMENTS) {
|
|
434
|
+
return 'read-id';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return '';
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const announcements: Announcement[] = [
|
|
441
|
+
{
|
|
442
|
+
id: 'valid-1',
|
|
443
|
+
target: 'notification/info',
|
|
444
|
+
title: 'Valid 1',
|
|
445
|
+
message: 'Msg 1',
|
|
446
|
+
audience: 'all'
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: 'existing-id',
|
|
450
|
+
target: 'notification/announcement',
|
|
451
|
+
title: 'Existing',
|
|
452
|
+
message: 'Msg Existing'
|
|
453
|
+
}, // Should be skipped
|
|
454
|
+
{
|
|
455
|
+
id: 'read-id',
|
|
456
|
+
target: 'notification/warning',
|
|
457
|
+
title: 'Read',
|
|
458
|
+
message: 'Msg Read'
|
|
459
|
+
}, // Should be skipped
|
|
460
|
+
{
|
|
461
|
+
id: 'valid-2',
|
|
462
|
+
target: 'notification/announcement',
|
|
463
|
+
title: 'Valid 2',
|
|
464
|
+
message: 'Msg 2',
|
|
465
|
+
audience: 'admin'
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 'invalid-target',
|
|
469
|
+
target: 'unsupported/type',
|
|
470
|
+
title: 'Invalid',
|
|
471
|
+
message: 'Msg Invalid'
|
|
472
|
+
}, // Should log error
|
|
473
|
+
{
|
|
474
|
+
id: 'valid-3',
|
|
475
|
+
target: 'notification/info',
|
|
476
|
+
title: 'Valid 3',
|
|
477
|
+
message: 'Msg 3',
|
|
478
|
+
version: 'v1.0.0'
|
|
479
|
+
}, // semver.satisfies is mocked to true by default
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
await processAnnouncements(mockContext, announcements, VERSION_270);
|
|
483
|
+
|
|
484
|
+
// Expect 3 notifications to be added (valid-1, valid-2, valid-3)
|
|
485
|
+
expect(mockDispatch).toHaveBeenCalledTimes(3);
|
|
486
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({ id: `${ ANNOUNCEMENT_PREFIX }valid-1` }));
|
|
487
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({ id: `${ ANNOUNCEMENT_PREFIX }valid-2` }));
|
|
488
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', expect.objectContaining({ id: `${ ANNOUNCEMENT_PREFIX }valid-3` }));
|
|
489
|
+
|
|
490
|
+
// Expect errors for invalid target
|
|
491
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Announcement type unsupported/type is not supported');
|
|
492
|
+
|
|
493
|
+
// Expect info logs for skipped announcements
|
|
494
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Not adding announcement with ID '));
|
|
495
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining(`${ ANNOUNCEMENT_PREFIX }existing-id`));
|
|
496
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining(`${ ANNOUNCEMENT_PREFIX }read-id`));
|
|
497
|
+
});
|
|
498
|
+
});
|
|
@@ -96,6 +96,8 @@ describe('systemInfoProvider', () => {
|
|
|
96
96
|
return undefined;
|
|
97
97
|
}),
|
|
98
98
|
'management/schemaFor': jest.fn(),
|
|
99
|
+
localCluster: mockClusters.find((c) => c.id === 'local') || null,
|
|
100
|
+
'features/get': jest.fn(() => 'abc'),
|
|
99
101
|
};
|
|
100
102
|
|
|
101
103
|
(version.getVersionData as jest.Mock).mockReturnValue({
|
|
@@ -128,6 +130,7 @@ describe('systemInfoProvider', () => {
|
|
|
128
130
|
expect(qs).toContain('bl=en-US');
|
|
129
131
|
expect(qs).toContain('bs=1024x768');
|
|
130
132
|
expect(qs).toContain('ss=1920x1080');
|
|
133
|
+
expect(qs).toContain('ff-usc=abc');
|
|
131
134
|
});
|
|
132
135
|
|
|
133
136
|
it('should handle missing or partial data gracefully', () => {
|
|
@@ -159,6 +162,10 @@ describe('systemInfoProvider', () => {
|
|
|
159
162
|
|
|
160
163
|
mockGetters['uiplugins/plugins'] = null; // No plugins
|
|
161
164
|
mockGetters['auth/principalId'] = null; // No user
|
|
165
|
+
mockGetters['localCluster'] = null; // No clusters
|
|
166
|
+
mockGetters['features/get'] = () => {
|
|
167
|
+
throw new Error('unknown feature');
|
|
168
|
+
};
|
|
162
169
|
|
|
163
170
|
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
164
171
|
const qs = infoProvider.buildQueryString();
|
|
@@ -175,6 +182,7 @@ describe('systemInfoProvider', () => {
|
|
|
175
182
|
expect(qs).not.toContain('lnc=');
|
|
176
183
|
expect(qs).not.toContain('xkn=');
|
|
177
184
|
expect(qs).not.toContain('xcc=');
|
|
185
|
+
expect(qs).not.toContain('ff-usc=');
|
|
178
186
|
});
|
|
179
187
|
|
|
180
188
|
it('should handle getAll returning undefined when types are not registered', () => {
|
|
@@ -191,6 +199,7 @@ describe('systemInfoProvider', () => {
|
|
|
191
199
|
|
|
192
200
|
mockGetters['auth/principalId'] = 'user-456';
|
|
193
201
|
mockGetters['uiplugins/plugins'] = []; // No plugins
|
|
202
|
+
mockGetters['localCluster'] = null; // No clusters
|
|
194
203
|
|
|
195
204
|
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
196
205
|
const qs = infoProvider.buildQueryString();
|
|
@@ -199,7 +208,6 @@ describe('systemInfoProvider', () => {
|
|
|
199
208
|
expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'install-uuid');
|
|
200
209
|
expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'server-version-type');
|
|
201
210
|
expect(mockGetters['management/typeRegistered']).toHaveBeenCalledWith(COUNT);
|
|
202
|
-
expect(mockGetters['management/typeRegistered']).toHaveBeenCalledWith(MANAGEMENT.CLUSTER);
|
|
203
211
|
expect(mockGetters['management/all']).not.toHaveBeenCalled();
|
|
204
212
|
|
|
205
213
|
// Verify the query string is built with fallback or empty values
|
|
@@ -225,6 +233,16 @@ describe('systemInfoProvider', () => {
|
|
|
225
233
|
return { id, value: '' }; // Empty values for all settings
|
|
226
234
|
}
|
|
227
235
|
});
|
|
236
|
+
|
|
237
|
+
// local cluster with missing properties
|
|
238
|
+
const localCluster = {
|
|
239
|
+
id: 'local',
|
|
240
|
+
isLocal: true,
|
|
241
|
+
status: { nodeCount: 1 },
|
|
242
|
+
// kubernetesVersionBase is missing
|
|
243
|
+
// provisioner is missing
|
|
244
|
+
};
|
|
245
|
+
|
|
228
246
|
mockGetters['management/all'].mockImplementation((type: string) => {
|
|
229
247
|
if (type === MANAGEMENT.SETTING) {
|
|
230
248
|
// Return settings, but with empty values
|
|
@@ -237,20 +255,14 @@ describe('systemInfoProvider', () => {
|
|
|
237
255
|
return [{ counts: { [MANAGEMENT.CLUSTER]: { summary: { count: 1 } } } }];
|
|
238
256
|
}
|
|
239
257
|
if (type === MANAGEMENT.CLUSTER) {
|
|
240
|
-
|
|
241
|
-
return [{
|
|
242
|
-
id: 'local',
|
|
243
|
-
isLocal: true,
|
|
244
|
-
status: { nodeCount: 1 },
|
|
245
|
-
// kubernetesVersionBase is missing
|
|
246
|
-
// provisioner is missing
|
|
247
|
-
}];
|
|
258
|
+
return [localCluster];
|
|
248
259
|
}
|
|
249
260
|
|
|
250
261
|
return [];
|
|
251
262
|
});
|
|
252
263
|
|
|
253
264
|
mockGetters['auth/principalId'] = null; // No user
|
|
265
|
+
mockGetters['localCluster'] = localCluster;
|
|
254
266
|
|
|
255
267
|
const infoProvider = new SystemInfoProvider(mockGetters, {});
|
|
256
268
|
const qs = infoProvider.buildQueryString();
|