@rancher/shell 3.0.7 → 3.0.8-rc.10
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/images/vendor/githubapp.svg +13 -0
- package/assets/styles/base/_typography.scss +2 -1
- 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 +16 -8
- package/assets/styles/themes/_suse.scss +116 -24
- package/assets/translations/en-us.yaml +185 -21
- package/assets/translations/zh-hans.yaml +0 -4
- 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/Inactivity.vue +222 -106
- package/components/InstallHelmCharts.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 +5 -2
- package/components/ResourceList/Masthead.vue +1 -1
- package/components/SortableTable/index.vue +18 -2
- 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/fleet/FleetConfigMapSelector.vue +117 -0
- package/components/fleet/FleetSecretSelector.vue +127 -0
- package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
- package/components/form/FileImageSelector.vue +13 -4
- package/components/form/FileSelector.vue +11 -2
- package/components/form/ResourceLabeledSelect.vue +1 -0
- package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
- 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 +12 -3
- package/components/nav/Header.vue +37 -16
- 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/{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/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/auth.js +1 -0
- package/config/product/explorer.js +3 -1
- package/config/product/manager.js +20 -9
- package/config/query-params.js +1 -0
- package/config/router/routes.js +10 -2
- package/config/settings.ts +10 -2
- package/config/store.js +4 -2
- package/config/table-headers.js +8 -0
- package/config/types.js +11 -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 +23 -6
- package/core/plugin-helpers.ts +2 -0
- package/core/types-provisioning.ts +4 -1
- package/detail/pod.vue +1 -0
- package/detail/provisioning.cattle.io.cluster.vue +13 -1
- package/dialog/AddonConfigConfirmationDialog.vue +45 -1
- package/dialog/DeveloperLoadExtensionDialog.vue +12 -3
- package/dialog/RollbackWorkloadDialog.vue +2 -5
- package/directives/ui-context.ts +103 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
- package/edit/auth/AuthProviderWarningBanners.vue +14 -1
- package/edit/auth/__tests__/oidc.test.ts +26 -0
- package/edit/auth/github-app-steps.vue +97 -0
- package/edit/auth/github-steps.vue +75 -0
- package/edit/auth/github.vue +99 -65
- 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 +51 -2
- 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/PolicyRuleTarget.vue +15 -5
- 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 +11 -9
- package/edit/provisioning.cattle.io.cluster/index.vue +25 -15
- package/edit/provisioning.cattle.io.cluster/rke2.vue +98 -17
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
- 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/projectsecret.vue +1 -1
- package/list/provisioning.cattle.io.cluster.vue +15 -2
- package/machine-config/amazonec2.vue +42 -135
- package/machine-config/azure.vue +1 -1
- 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 +8 -2
- package/mixins/create-edit-view/index.js +5 -0
- package/models/__tests__/chart.test.ts +49 -12
- package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/chart.js +28 -14
- 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/compliance.cattle.io.clusterscanprofile.js +1 -1
- package/models/management.cattle.io.authconfig.js +1 -0
- 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 +6 -5
- package/pages/auth/login.vue +43 -4
- package/pages/auth/verify.vue +1 -1
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
- package/pages/c/_cluster/apps/charts/chart.vue +35 -17
- 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/EventsTable.vue +89 -3
- package/pages/c/_cluster/explorer/index.vue +8 -6
- package/pages/c/_cluster/explorer/tools/index.vue +3 -3
- package/pages/c/_cluster/manager/hostedprovider/index.vue +220 -0
- package/pages/c/_cluster/settings/brand.vue +1 -1
- package/pages/c/_cluster/settings/performance.vue +12 -25
- 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 +327 -16
- package/pkg/dynamic-importer.lib.js +4 -0
- package/plugins/axios.js +2 -1
- package/plugins/dashboard-client-init.js +3 -0
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/getters.js +18 -1
- package/plugins/dashboard-store/resource-class.js +21 -6
- package/plugins/dynamic-content.js +13 -0
- package/plugins/i18n.js +8 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
- package/plugins/steve/steve-pagination-utils.ts +41 -22
- 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/extension/publish +1 -1
- package/scripts/typegen.sh +1 -0
- package/store/action-menu.js +8 -0
- package/store/auth.js +11 -6
- package/store/aws.js +8 -6
- package/store/catalog.js +6 -0
- package/store/features.js +2 -0
- package/store/index.js +45 -20
- package/store/notifications.ts +51 -4
- package/store/plugins.js +7 -3
- package/store/prefs.js +12 -6
- package/store/type-map.js +3 -3
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/kube/kube-api.ts +2 -1
- package/types/notifications/index.ts +27 -3
- package/types/rancher/index.d.ts +1 -0
- package/types/resources/settings.d.ts +29 -7
- package/types/shell/index.d.ts +138 -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__/cluster.test.ts +379 -1
- 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/cluster.js +157 -3
- package/utils/color.js +1 -1
- package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
- package/utils/dynamic-content/__tests__/config.test.ts +187 -0
- package/utils/dynamic-content/__tests__/index.test.ts +390 -0
- package/utils/dynamic-content/__tests__/info.test.ts +275 -0
- package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
- package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
- package/utils/dynamic-content/__tests__/util.test.ts +235 -0
- package/utils/dynamic-content/announcement.ts +142 -0
- package/utils/dynamic-content/config.ts +55 -0
- package/utils/dynamic-content/example.json +40 -0
- package/utils/dynamic-content/index.ts +277 -0
- package/utils/dynamic-content/info.ts +261 -0
- package/utils/dynamic-content/new-release.ts +126 -0
- package/utils/dynamic-content/notification-handler.ts +48 -0
- package/utils/dynamic-content/support-notice.ts +169 -0
- package/utils/dynamic-content/types.d.ts +153 -0
- package/utils/dynamic-content/util.ts +122 -0
- package/utils/dynamic-importer.js +2 -2
- package/utils/favicon.js +4 -4
- package/utils/inactivity.ts +104 -0
- package/utils/object.js +20 -2
- package/utils/pagination-utils.ts +19 -4
- package/utils/pagination-wrapper.ts +12 -8
- package/utils/provider.ts +14 -0
- package/utils/release-notes.ts +1 -1
- package/utils/scroll.js +7 -0
- package/utils/selector-typed.ts +6 -2
- package/utils/settings.ts +15 -0
- package/utils/validators/machine-pool.ts +13 -3
- package/utils/version.js +15 -0
- 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
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { getConfig } from '../config';
|
|
2
|
+
import { SETTING } from '@shell/config/settings';
|
|
3
|
+
import * as version from '@shell/config/version';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ENDPOINT = 'https://updates.rancher.io/rancher/$dist/updates';
|
|
6
|
+
|
|
7
|
+
// Mock dependencies
|
|
8
|
+
jest.mock('@shell/config/version', () => ({ getVersionData: jest.fn(), isRancherPrime: jest.fn() }));
|
|
9
|
+
|
|
10
|
+
describe('getConfig', () => {
|
|
11
|
+
let mockGetters: any;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Reset mocks before each test
|
|
15
|
+
(version.getVersionData as jest.Mock).mockClear();
|
|
16
|
+
(version.isRancherPrime as jest.Mock).mockClear();
|
|
17
|
+
|
|
18
|
+
// Default mock for getters
|
|
19
|
+
mockGetters = { 'management/byId': jest.fn() };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('community distribution', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
(version.getVersionData as jest.Mock).mockReturnValue({ RancherPrime: 'false' });
|
|
25
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return default community config when no settings are present', () => {
|
|
29
|
+
mockGetters['management/byId'].mockReturnValue(null);
|
|
30
|
+
const config = getConfig(mockGetters);
|
|
31
|
+
|
|
32
|
+
expect(config.prime).toBe(false);
|
|
33
|
+
expect(config.distribution).toBe('community');
|
|
34
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
35
|
+
expect(config.enabled).toBe(true);
|
|
36
|
+
expect(config.debug).toBe(false);
|
|
37
|
+
expect(config.log).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should enable debug and log modes when enabled setting is "debug"', () => {
|
|
41
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
42
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
43
|
+
return { value: 'debug' };
|
|
44
|
+
}
|
|
45
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
46
|
+
return { value: 'https://test.endpoint' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const config = getConfig(mockGetters);
|
|
53
|
+
|
|
54
|
+
expect(config.enabled).toBe(true);
|
|
55
|
+
expect(config.debug).toBe(true);
|
|
56
|
+
expect(config.log).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should enable log mode when enabled setting is "log"', () => {
|
|
60
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
61
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
62
|
+
return { value: 'log' };
|
|
63
|
+
}
|
|
64
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
65
|
+
return { value: 'https://test.endpoint' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const config = getConfig(mockGetters);
|
|
72
|
+
|
|
73
|
+
expect(config.enabled).toBe(true);
|
|
74
|
+
expect(config.debug).toBe(false);
|
|
75
|
+
expect(config.log).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should not use the endpoint from settings when community', () => {
|
|
79
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
80
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
81
|
+
return { value: 'https://custom.endpoint/rancher/$dist' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const config = getConfig(mockGetters);
|
|
88
|
+
|
|
89
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
90
|
+
expect(config.enabled).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('prime distribution', () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
(version.getVersionData as jest.Mock).mockReturnValue({ RancherPrime: 'true' });
|
|
97
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should return default prime config when no settings are present', () => {
|
|
101
|
+
mockGetters['management/byId'].mockReturnValue(null);
|
|
102
|
+
const config = getConfig(mockGetters);
|
|
103
|
+
|
|
104
|
+
expect(config.prime).toBe(true);
|
|
105
|
+
expect(config.distribution).toBe('prime');
|
|
106
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
107
|
+
expect(config.enabled).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should be disabled when the setting is "false"', () => {
|
|
111
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
112
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
113
|
+
return { value: 'false' };
|
|
114
|
+
}
|
|
115
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
116
|
+
return { value: 'https://test.endpoint' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return null;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const config = getConfig(mockGetters);
|
|
123
|
+
|
|
124
|
+
expect(config.enabled).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should be enabled when the setting is "true"', () => {
|
|
128
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
129
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENABLED) {
|
|
130
|
+
return { value: 'true' };
|
|
131
|
+
}
|
|
132
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
133
|
+
return { value: 'https://test.endpoint' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const config = getConfig(mockGetters);
|
|
140
|
+
|
|
141
|
+
expect(config.enabled).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should NOT use the endpoint from settings if it is not a valid HTTPS URL', () => {
|
|
145
|
+
mockGetters['management/byId'].mockImplementation((type: string, id: string) => {
|
|
146
|
+
if (id === SETTING.DYNAMIC_CONTENT_ENDPOINT) {
|
|
147
|
+
return { value: 'http://insecure.endpoint/rancher/$dist' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const config = getConfig(mockGetters);
|
|
154
|
+
|
|
155
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT); // Falls back to default
|
|
156
|
+
expect(config.enabled).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('general', () => {
|
|
161
|
+
it('should handle exceptions when reading settings and return defaults', () => {
|
|
162
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
163
|
+
const testError = new Error('Something went wrong');
|
|
164
|
+
|
|
165
|
+
(version.getVersionData as jest.Mock).mockReturnValue({ RancherPrime: 'false' });
|
|
166
|
+
(version.isRancherPrime as jest.Mock).mockReturnValue(false);
|
|
167
|
+
mockGetters['management/byId'].mockImplementation(() => {
|
|
168
|
+
throw testError;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const config = getConfig(mockGetters);
|
|
172
|
+
|
|
173
|
+
// It should fall back to the default configuration
|
|
174
|
+
expect(config.prime).toBe(false);
|
|
175
|
+
expect(config.distribution).toBe('community');
|
|
176
|
+
expect(config.endpoint).toBe(DEFAULT_ENDPOINT);
|
|
177
|
+
expect(config.enabled).toBe(true);
|
|
178
|
+
expect(config.debug).toBe(false);
|
|
179
|
+
expect(config.log).toBe(false);
|
|
180
|
+
|
|
181
|
+
// And it should log the error
|
|
182
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error reading dynamic content settings', testError);
|
|
183
|
+
|
|
184
|
+
consoleErrorSpy.mockRestore();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|