@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.5
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/styles/global/_button.scss +1 -1
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +183 -51
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +212 -16
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/ResourceTable.vue +1 -0
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +23 -5
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/FileSelector.vue +39 -1
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +12 -1
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +16 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/config/features.js +1 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +3 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +8 -0
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +20 -6
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -4
- package/config/table-headers.js +3 -4
- package/config/types.js +16 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +109 -7
- package/detail/workload/index.vue +12 -55
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +34 -9
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +128 -95
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +542 -141
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +39 -5
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +46 -3
- package/models/provisioning.cattle.io.cluster.js +64 -14
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +120 -20
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +236 -144
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +114 -19
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/catalog.js +57 -0
- package/store/features.js +4 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +166 -70
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/object.js +22 -2
- package/utils/operation-cr.js +19 -0
- package/utils/provider.ts +12 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Stub for extension library builds — prevents require.context() from bundling
|
|
2
|
+
// all shell images into every extension. At runtime, delegates to the host
|
|
3
|
+
// dashboard's asset resolver (exposed on window by the real require-asset.ts).
|
|
4
|
+
|
|
5
|
+
export function toContextKey(path) {
|
|
6
|
+
return `./${ path.replace(/^[~@]shell\/assets\//, '') }`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function requireAsset(path) {
|
|
10
|
+
if (typeof window !== 'undefined' && window.__shell_requireAsset) {
|
|
11
|
+
return window.__shell_requireAsset(path);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
throw new Error(`Asset context not available for: ${ path }`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function requireJson(path) {
|
|
18
|
+
if (typeof window !== 'undefined' && window.__shell_requireJson) {
|
|
19
|
+
return window.__shell_requireJson(path);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
throw new Error(`JSON context not available for: ${ path }`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function _setContexts() {}
|
package/pkg/vue.config.js
CHANGED
|
@@ -72,11 +72,18 @@ module.exports = function(dir) {
|
|
|
72
72
|
resource.request = fs.existsSync(pkgModelLoaderRequire) ? pkgModelLoaderRequire : path.join(__dirname, fileName);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// Prevent require.context('@shell/assets') from bundling all shell images into extensions.
|
|
76
|
+
// The stub delegates to the host dashboard's asset resolver at runtime via window.__shell_requireAsset.
|
|
77
|
+
const requireAssetOverride = new webpack.NormalModuleReplacementPlugin(/require-asset$/, (resource) => {
|
|
78
|
+
resource.request = path.join(__dirname, 'require-asset.lib.js');
|
|
79
|
+
});
|
|
80
|
+
|
|
75
81
|
// Auto-generate module to import the types (model, detail, edit etc)
|
|
76
82
|
const autoImportPlugin = new VirtualModulesPlugin({ 'node_modules/@rancher/auto-import': generateTypeImport('@pkg', dir) });
|
|
77
83
|
|
|
78
84
|
config.plugins.unshift(dynamicImporterOverride);
|
|
79
85
|
config.plugins.unshift(modelLoaderImporterOverride);
|
|
86
|
+
config.plugins.unshift(requireAssetOverride);
|
|
80
87
|
config.plugins.unshift(autoImportPlugin);
|
|
81
88
|
config.plugins.unshift(new NodePolyfillPlugin()); // required from Webpack 5 to polyfill node modules
|
|
82
89
|
// config.plugins.unshift(debug);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Config } from 'dompurify';
|
|
2
|
+
|
|
3
|
+
export function purifyHTML(value: string, options?: Config): string;
|
|
4
|
+
|
|
5
|
+
export function addLinkInterceptor(fn: (link: string) => string | undefined | void, name?: string): void;
|
|
6
|
+
|
|
7
|
+
export function removeLinkInterceptor(fn: (link: string) => string | undefined | void): void;
|
|
8
|
+
|
|
9
|
+
export function processLink(link: string): string;
|
|
@@ -403,6 +403,99 @@ describe('class: Resource', () => {
|
|
|
403
403
|
|
|
404
404
|
expect(cards).toHaveLength(0);
|
|
405
405
|
});
|
|
406
|
+
|
|
407
|
+
it('should include the resources card when relationships exist', () => {
|
|
408
|
+
const resource = new Resource({
|
|
409
|
+
type: 'test',
|
|
410
|
+
metadata: {
|
|
411
|
+
relationships: [
|
|
412
|
+
{
|
|
413
|
+
rel: 'uses', toType: 'svc', toId: 'a'
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
rel: 'uses', fromType: 'pod', fromId: 'b'
|
|
417
|
+
},
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
}, {
|
|
421
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
422
|
+
dispatch: jest.fn(),
|
|
423
|
+
rootGetters: {
|
|
424
|
+
'i18n/t': (key: string) => key,
|
|
425
|
+
'cluster/all': () => []
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const cards = resource.cards;
|
|
430
|
+
|
|
431
|
+
expect(cards).toHaveLength(1);
|
|
432
|
+
expect(cards[0].props.title).toBe('component.resource.detail.card.resourcesCard.title');
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('getter: resourcesCard', () => {
|
|
437
|
+
it('should return null when there are no relationships', () => {
|
|
438
|
+
const resource = new Resource({ type: 'test' }, {
|
|
439
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
440
|
+
dispatch: jest.fn(),
|
|
441
|
+
rootGetters: { 'i18n/t': (key: string) => key },
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
expect(resource.resourcesCard).toBeNull();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should return rows for both referredToBy and refersTo when relationships exist in both directions', () => {
|
|
448
|
+
const resource = new Resource({
|
|
449
|
+
type: 'test',
|
|
450
|
+
metadata: {
|
|
451
|
+
relationships: [
|
|
452
|
+
{
|
|
453
|
+
rel: 'owner', fromType: 'rs', fromId: 'r-1'
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
rel: 'uses', toType: 'svc', toId: 's-1'
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
rel: 'uses', toType: 'svc', toId: 's-2'
|
|
460
|
+
},
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
}, {
|
|
464
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
465
|
+
dispatch: jest.fn(),
|
|
466
|
+
rootGetters: { 'i18n/t': (key: string) => key },
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const rows = resource.resourcesCardRows;
|
|
470
|
+
|
|
471
|
+
expect(rows).toHaveLength(2);
|
|
472
|
+
expect(rows[0].label).toBe('component.resource.detail.card.resourcesCard.rows.referredToBy');
|
|
473
|
+
expect(rows[0].counts[0].count).toBe(1);
|
|
474
|
+
expect(rows[1].label).toBe('component.resource.detail.card.resourcesCard.rows.refersTo');
|
|
475
|
+
expect(rows[1].counts[0].count).toBe(2);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should omit a direction with no relationships', () => {
|
|
479
|
+
const resource = new Resource({
|
|
480
|
+
type: 'test',
|
|
481
|
+
metadata: {
|
|
482
|
+
relationships: [
|
|
483
|
+
{
|
|
484
|
+
rel: 'uses', toType: 'svc', toId: 's-1'
|
|
485
|
+
},
|
|
486
|
+
]
|
|
487
|
+
}
|
|
488
|
+
}, {
|
|
489
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
490
|
+
dispatch: jest.fn(),
|
|
491
|
+
rootGetters: { 'i18n/t': (key: string) => key },
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const rows = resource.resourcesCardRows;
|
|
495
|
+
|
|
496
|
+
expect(rows).toHaveLength(1);
|
|
497
|
+
expect(rows[0].label).toBe('component.resource.detail.card.resourcesCard.rows.refersTo');
|
|
498
|
+
});
|
|
406
499
|
});
|
|
407
500
|
|
|
408
501
|
describe('getter: insightCardProps', () => {
|
|
@@ -585,4 +678,88 @@ describe('class: Resource', () => {
|
|
|
585
678
|
expect(viewYaml.enabled).toBe(true);
|
|
586
679
|
});
|
|
587
680
|
});
|
|
681
|
+
|
|
682
|
+
describe('method: dryRunCreate', () => {
|
|
683
|
+
const collectionUrl = '/v1/test.resources';
|
|
684
|
+
|
|
685
|
+
it('should dispatch a request with dryRun=All query param', async() => {
|
|
686
|
+
const dispatch = jest.fn().mockResolvedValue({});
|
|
687
|
+
const resource = new Resource({
|
|
688
|
+
type: 'test.resource',
|
|
689
|
+
metadata: {
|
|
690
|
+
name: 'my-resource',
|
|
691
|
+
namespace: 'my-ns',
|
|
692
|
+
},
|
|
693
|
+
}, {
|
|
694
|
+
getters: {
|
|
695
|
+
schemaFor: () => ({
|
|
696
|
+
linkFor: (link: string) => (link === 'collection' ? collectionUrl : ''),
|
|
697
|
+
attributes: { namespaced: true },
|
|
698
|
+
})
|
|
699
|
+
},
|
|
700
|
+
dispatch,
|
|
701
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
await resource.dryRunCreate();
|
|
705
|
+
|
|
706
|
+
expect(dispatch).toHaveBeenCalledWith('request', {
|
|
707
|
+
opt: expect.objectContaining({
|
|
708
|
+
method: 'post',
|
|
709
|
+
url: `${ collectionUrl }/my-ns?dryRun=All`,
|
|
710
|
+
}),
|
|
711
|
+
type: 'test.resource'
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('should use provided data instead of resource state when given', async() => {
|
|
716
|
+
const dispatch = jest.fn().mockResolvedValue({});
|
|
717
|
+
const resource = new Resource({
|
|
718
|
+
type: 'test.resource',
|
|
719
|
+
metadata: { name: 'original', namespace: 'ns' },
|
|
720
|
+
}, {
|
|
721
|
+
getters: {
|
|
722
|
+
schemaFor: () => ({
|
|
723
|
+
linkFor: () => collectionUrl,
|
|
724
|
+
attributes: { namespaced: true },
|
|
725
|
+
})
|
|
726
|
+
},
|
|
727
|
+
dispatch,
|
|
728
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
const customData = {
|
|
732
|
+
type: 'test.resource',
|
|
733
|
+
metadata: { name: 'custom' },
|
|
734
|
+
spec: {}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
await resource.dryRunCreate(customData);
|
|
738
|
+
|
|
739
|
+
expect(dispatch).toHaveBeenCalledWith('request', {
|
|
740
|
+
opt: expect.objectContaining({ data: customData }),
|
|
741
|
+
type: 'test.resource'
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('should propagate API errors', async() => {
|
|
746
|
+
const apiError = { _status: 409, message: 'already exists' };
|
|
747
|
+
const dispatch = jest.fn().mockRejectedValue(apiError);
|
|
748
|
+
const resource = new Resource({
|
|
749
|
+
type: 'test.resource',
|
|
750
|
+
metadata: { name: 'dup', namespace: 'ns' },
|
|
751
|
+
}, {
|
|
752
|
+
getters: {
|
|
753
|
+
schemaFor: () => ({
|
|
754
|
+
linkFor: () => collectionUrl,
|
|
755
|
+
attributes: { namespaced: true },
|
|
756
|
+
})
|
|
757
|
+
},
|
|
758
|
+
dispatch,
|
|
759
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
await expect(resource.dryRunCreate()).rejects.toStrictEqual(apiError);
|
|
763
|
+
});
|
|
764
|
+
});
|
|
588
765
|
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
AS,
|
|
13
13
|
MODE
|
|
14
14
|
} from '@shell/config/query-params';
|
|
15
|
+
import { EVENT } from '@shell/config/types';
|
|
15
16
|
import { VIEW_IN_API, DEV } from '@shell/store/prefs';
|
|
16
17
|
import { addObject, addObjects, findBy, removeAt } from '@shell/utils/array';
|
|
17
18
|
import CustomValidators from '@shell/utils/custom-validators';
|
|
@@ -39,8 +40,7 @@ import { handleConflict } from '@shell/plugins/dashboard-store/normalize';
|
|
|
39
40
|
import { ExtensionPoint, ActionLocation } from '@shell/core/types';
|
|
40
41
|
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
41
42
|
import { parse } from '@shell/utils/selector';
|
|
42
|
-
import {
|
|
43
|
-
import { useResourceCardRow } from '@shell/components/Resource/Detail/Card/StateCard/composables';
|
|
43
|
+
import { useResourceCardRow, useResourceCardRowFromRelationships } from '@shell/components/Resource/Detail/Card/StateCard/composables';
|
|
44
44
|
|
|
45
45
|
export const DNS_LIKE_TYPES = ['dnsLabel', 'dnsLabelRestricted', 'hostname'];
|
|
46
46
|
|
|
@@ -79,6 +79,7 @@ export const STATES_ENUM = {
|
|
|
79
79
|
BUILDING: 'building',
|
|
80
80
|
COMPLETED: 'completed',
|
|
81
81
|
CORDONED: 'cordoned',
|
|
82
|
+
CANCELLED: 'cancelled',
|
|
82
83
|
COUNT: 'count',
|
|
83
84
|
CREATED: 'created',
|
|
84
85
|
CREATING: 'creating',
|
|
@@ -207,6 +208,9 @@ export const STATES = {
|
|
|
207
208
|
[STATES_ENUM.CORDONED]: {
|
|
208
209
|
color: 'info', icon: 'tag', label: 'Cordoned', compoundIcon: 'info'
|
|
209
210
|
},
|
|
211
|
+
[STATES_ENUM.CANCELLED]: {
|
|
212
|
+
color: 'warning', icon: 'error', label: 'Cancelled', compoundIcon: 'warning'
|
|
213
|
+
},
|
|
210
214
|
[STATES_ENUM.COUNT]: {
|
|
211
215
|
color: 'success', icon: 'dot-open', label: 'Count', compoundIcon: 'checkmark'
|
|
212
216
|
},
|
|
@@ -507,7 +511,11 @@ export function colorForState(state, isError, isTransitioning) {
|
|
|
507
511
|
return `text-${ color }`;
|
|
508
512
|
}
|
|
509
513
|
|
|
510
|
-
export function
|
|
514
|
+
export function simpleColorForState(state, isError = false, isTransitioning = false) {
|
|
515
|
+
return colorForState(state, isError, isTransitioning).replace('text-', '') || 'disabled';
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function stateDisplay(state, preserveOriginal = false) {
|
|
511
519
|
// @TODO use translations
|
|
512
520
|
const key = (state || 'active').toLowerCase();
|
|
513
521
|
|
|
@@ -515,6 +523,11 @@ export function stateDisplay(state) {
|
|
|
515
523
|
return REMAP_STATE[key];
|
|
516
524
|
}
|
|
517
525
|
|
|
526
|
+
// Preserves the original state name returned by the
|
|
527
|
+
if ( preserveOriginal ) {
|
|
528
|
+
return ucFirst(state);
|
|
529
|
+
}
|
|
530
|
+
|
|
518
531
|
return key.split(/-/).map(ucFirst).join('-');
|
|
519
532
|
}
|
|
520
533
|
|
|
@@ -754,7 +767,7 @@ export default class Resource {
|
|
|
754
767
|
}
|
|
755
768
|
|
|
756
769
|
get stateSimpleColor() {
|
|
757
|
-
return this.
|
|
770
|
+
return simpleColorForState(this.state, this.stateObj?.error, this.stateObj?.transitioning);
|
|
758
771
|
}
|
|
759
772
|
|
|
760
773
|
get stateBackground() {
|
|
@@ -1182,6 +1195,46 @@ export default class Resource {
|
|
|
1182
1195
|
return this._save(...arguments);
|
|
1183
1196
|
}
|
|
1184
1197
|
|
|
1198
|
+
_collectionUrl() {
|
|
1199
|
+
const schema = this.$getters['schemaFor'](this.type);
|
|
1200
|
+
|
|
1201
|
+
if ( !schema ) {
|
|
1202
|
+
// Schema not found - likely due to lack of permissions to view this resource type
|
|
1203
|
+
throw new Error(`${ this.type }: ${ this.t('validation.createResourceFailed', { type: this.typeDisplay }, true) }`);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
let url = schema.linkFor('collection');
|
|
1207
|
+
|
|
1208
|
+
if ( schema.attributes && schema.attributes.namespaced && this.metadata && this.metadata.namespace ) {
|
|
1209
|
+
url += `/${ this.metadata.namespace }`;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
return url;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async dryRunCreate(data) {
|
|
1216
|
+
try {
|
|
1217
|
+
const url = this._collectionUrl();
|
|
1218
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
1219
|
+
const body = data || this.cleanForSave(this.toSave() || JSON.parse(JSON.stringify(this)), true);
|
|
1220
|
+
|
|
1221
|
+
return this.$dispatch('request', {
|
|
1222
|
+
opt: {
|
|
1223
|
+
method: 'post',
|
|
1224
|
+
url: `${ url }${ separator }dryRun=All`,
|
|
1225
|
+
data: body,
|
|
1226
|
+
headers: {
|
|
1227
|
+
'content-type': 'application/json',
|
|
1228
|
+
accept: 'application/json'
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
type: this.type
|
|
1232
|
+
});
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
return Promise.reject(e);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1185
1238
|
/**
|
|
1186
1239
|
* Remove any unwanted properties from the object that will be saved
|
|
1187
1240
|
*/
|
|
@@ -1210,20 +1263,16 @@ export default class Resource {
|
|
|
1210
1263
|
if ( this.metadata?.resourceVersion ) {
|
|
1211
1264
|
this.metadata.resourceVersion = `${ this.metadata.resourceVersion }`;
|
|
1212
1265
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
if ( schema.attributes && schema.attributes.namespaced && this.metadata && this.metadata.namespace ) {
|
|
1220
|
-
url += `/${ this.metadata.namespace }`;
|
|
1266
|
+
try {
|
|
1267
|
+
if ( !opt.url ) {
|
|
1268
|
+
if ( forNew ) {
|
|
1269
|
+
opt.url = this._collectionUrl();
|
|
1270
|
+
} else {
|
|
1271
|
+
opt.url = this.linkFor('update') || this.linkFor('self');
|
|
1221
1272
|
}
|
|
1222
|
-
|
|
1223
|
-
opt.url = url;
|
|
1224
|
-
} else {
|
|
1225
|
-
opt.url = this.linkFor('update') || this.linkFor('self');
|
|
1226
1273
|
}
|
|
1274
|
+
} catch (e) {
|
|
1275
|
+
return Promise.reject(e);
|
|
1227
1276
|
}
|
|
1228
1277
|
|
|
1229
1278
|
if ( !opt.method ) {
|
|
@@ -2073,7 +2122,7 @@ export default class Resource {
|
|
|
2073
2122
|
|
|
2074
2123
|
if ( r.selector ) {
|
|
2075
2124
|
// A selector is a stringified version of a matchLabel (https://github.com/kubernetes/apimachinery/blob/master/pkg/labels/selector.go#L1010)
|
|
2076
|
-
|
|
2125
|
+
addObject(out.selectors, {
|
|
2077
2126
|
type: r.toType,
|
|
2078
2127
|
namespace: r.toNamespace,
|
|
2079
2128
|
selector: r.selector
|
|
@@ -2083,7 +2132,7 @@ export default class Resource {
|
|
|
2083
2132
|
let namespace = r[`${ direction }Namespace`];
|
|
2084
2133
|
let name = r[`${ direction }Id`];
|
|
2085
2134
|
|
|
2086
|
-
if ( !namespace && name
|
|
2135
|
+
if ( !namespace && name?.includes('/') ) {
|
|
2087
2136
|
const idx = name.indexOf('/');
|
|
2088
2137
|
|
|
2089
2138
|
namespace = name.substr(0, idx);
|
|
@@ -2245,12 +2294,58 @@ export default class Resource {
|
|
|
2245
2294
|
};
|
|
2246
2295
|
}
|
|
2247
2296
|
|
|
2297
|
+
get _resourcesCardRows() {
|
|
2298
|
+
const rows = [];
|
|
2299
|
+
const relationships = this.metadata?.relationships || [];
|
|
2300
|
+
|
|
2301
|
+
const referredToByRels = relationships.filter((r) => r.fromType && r.fromId && !r.selector);
|
|
2302
|
+
const refersToRels = relationships.filter((r) => r.toType && r.toId && !r.selector && !r.fromType);
|
|
2303
|
+
|
|
2304
|
+
if (referredToByRels.length) {
|
|
2305
|
+
rows.push(useResourceCardRowFromRelationships(
|
|
2306
|
+
this.t('component.resource.detail.card.resourcesCard.rows.referredToBy'),
|
|
2307
|
+
referredToByRels,
|
|
2308
|
+
{ hash: '#related' }
|
|
2309
|
+
));
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
if (refersToRels.length) {
|
|
2313
|
+
rows.push(useResourceCardRowFromRelationships(
|
|
2314
|
+
this.t('component.resource.detail.card.resourcesCard.rows.refersTo'),
|
|
2315
|
+
refersToRels,
|
|
2316
|
+
{ hash: '#related' }
|
|
2317
|
+
));
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
return rows;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
get resourcesCardRows() {
|
|
2324
|
+
return this._resourcesCardRows;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
get resourcesCard() {
|
|
2328
|
+
const rows = this.resourcesCardRows;
|
|
2329
|
+
|
|
2330
|
+
if (!rows.length) {
|
|
2331
|
+
return null;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
return {
|
|
2335
|
+
component: markRaw(defineAsyncComponent(() => import('@shell/components/Resource/Detail/Card/StateCard/index.vue'))),
|
|
2336
|
+
props: {
|
|
2337
|
+
title: this.t('component.resource.detail.card.resourcesCard.title'),
|
|
2338
|
+
rows
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2248
2343
|
get _cards() {
|
|
2249
2344
|
// All cards are opt in, we're leaving the insights card as part of the base resource since it should proliferate to most resources
|
|
2250
2345
|
return [];
|
|
2251
2346
|
}
|
|
2252
2347
|
|
|
2253
2348
|
get cards() {
|
|
2254
|
-
return this._cards;
|
|
2349
|
+
return [this.resourcesCard, ...this._cards].filter((c) => c);
|
|
2255
2350
|
}
|
|
2256
2351
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import actions from '@shell/plugins/steve/actions';
|
|
2
|
+
import paginationUtils from '@shell/utils/pagination-utils';
|
|
3
|
+
import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
|
|
4
|
+
import { PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
5
|
+
|
|
6
|
+
const { fetchResourceSummary } = actions;
|
|
7
|
+
|
|
8
|
+
describe('steve: actions:', () => {
|
|
9
|
+
describe('fetchResourceSummary', () => {
|
|
10
|
+
const schema = {
|
|
11
|
+
id: 'pod',
|
|
12
|
+
links: { collection: '/v1/pods' },
|
|
13
|
+
attributes: { namespaced: true },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const baseCtx = () => ({
|
|
17
|
+
getters: {
|
|
18
|
+
normalizeType: (type: string) => type,
|
|
19
|
+
schemaFor: (type: string) => (type === 'pod' ? schema : undefined),
|
|
20
|
+
},
|
|
21
|
+
dispatch: jest.fn(),
|
|
22
|
+
rootGetters: {},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
let warnSpy: jest.SpyInstance;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
29
|
+
jest.spyOn(paginationUtils, 'isSteveCacheEnabled').mockReturnValue(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
jest.restoreAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return undefined and warn when schema is not found', async() => {
|
|
37
|
+
const ctx = baseCtx();
|
|
38
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'nonexistent', opt: { summaryField: 'metadata.state.name' } });
|
|
39
|
+
|
|
40
|
+
expect(result).toBeUndefined();
|
|
41
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('no schema found'));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return undefined and warn when VAI is not enabled', async() => {
|
|
45
|
+
jest.spyOn(paginationUtils, 'isSteveCacheEnabled').mockReturnValue(false);
|
|
46
|
+
const ctx = baseCtx();
|
|
47
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name' } });
|
|
48
|
+
|
|
49
|
+
expect(result).toBeUndefined();
|
|
50
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('VAI is not enabled'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return undefined and warn when summaryField is missing', async() => {
|
|
54
|
+
const ctx = baseCtx();
|
|
55
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: {} });
|
|
56
|
+
|
|
57
|
+
expect(result).toBeUndefined();
|
|
58
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('summaryField is required'));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should construct the correct URL with summary and summaryonly params', async() => {
|
|
62
|
+
const ctx = baseCtx();
|
|
63
|
+
|
|
64
|
+
ctx.dispatch.mockResolvedValue({ count: 5, summary: [{ property: 'metadata.state.name', counts: { running: { total: 5 } } }] });
|
|
65
|
+
|
|
66
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name' } });
|
|
67
|
+
|
|
68
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
69
|
+
|
|
70
|
+
expect(requestUrl).toContain('summary=metadata.state.name');
|
|
71
|
+
expect(requestUrl).toContain('summaryonly=');
|
|
72
|
+
expect(requestUrl).not.toContain('summarynamespaced');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should not include summaryonly when summaryOnly is false', async() => {
|
|
76
|
+
const ctx = baseCtx();
|
|
77
|
+
|
|
78
|
+
ctx.dispatch.mockResolvedValue({ count: 5, summary: [{ property: 'metadata.state.name', counts: { running: { total: 5 } } }] });
|
|
79
|
+
|
|
80
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name', summaryOnly: false } });
|
|
81
|
+
|
|
82
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
83
|
+
|
|
84
|
+
expect(requestUrl).not.toContain('summaryonly');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should include summarynamespaced param when namespaceCounts is true', async() => {
|
|
88
|
+
const ctx = baseCtx();
|
|
89
|
+
|
|
90
|
+
ctx.dispatch.mockResolvedValue({ count: 5, summary: [{ property: 'metadata.state.name', counts: { running: { total: 5 } } }] });
|
|
91
|
+
|
|
92
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name', namespaceCounts: true } });
|
|
93
|
+
|
|
94
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
95
|
+
|
|
96
|
+
expect(requestUrl).toContain('summarynamespaced=');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should append namespace to path for namespaced resources', async() => {
|
|
100
|
+
const ctx = baseCtx();
|
|
101
|
+
|
|
102
|
+
ctx.dispatch.mockResolvedValue({ count: 2, summary: null });
|
|
103
|
+
|
|
104
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name', namespace: 'cattle-system' } });
|
|
105
|
+
|
|
106
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
107
|
+
|
|
108
|
+
expect(requestUrl).toMatch(/\/v1\/pods\/cattle-system\?/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should not append namespace when schema is not namespaced', async() => {
|
|
112
|
+
const nonNsSchema = { ...schema, attributes: { namespaced: false } };
|
|
113
|
+
const ctx = baseCtx();
|
|
114
|
+
|
|
115
|
+
ctx.getters.schemaFor = () => nonNsSchema;
|
|
116
|
+
ctx.dispatch.mockResolvedValue({ count: 1, summary: null });
|
|
117
|
+
|
|
118
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name', namespace: 'default' } });
|
|
119
|
+
|
|
120
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
121
|
+
|
|
122
|
+
expect(requestUrl).not.toContain('/default');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should append filter params when filters are provided', async() => {
|
|
126
|
+
const ctx = baseCtx();
|
|
127
|
+
const filters = [PaginationParamFilter.createSingleField({ field: 'metadata.namespace', value: 'default' })];
|
|
128
|
+
|
|
129
|
+
jest.spyOn(stevePaginationUtils, 'convertPaginationParams').mockReturnValue('filter=metadata.namespace%3Ddefault');
|
|
130
|
+
ctx.dispatch.mockResolvedValue({ count: 3, summary: null });
|
|
131
|
+
|
|
132
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name', filters } });
|
|
133
|
+
|
|
134
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
135
|
+
|
|
136
|
+
expect(requestUrl).toContain('filter=');
|
|
137
|
+
expect(stevePaginationUtils.convertPaginationParams).toHaveBeenCalledWith(expect.objectContaining({ filters }));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return count and summary from the response', async() => {
|
|
141
|
+
const ctx = baseCtx();
|
|
142
|
+
const apiResponse = {
|
|
143
|
+
count: 10,
|
|
144
|
+
summary: [{ property: 'metadata.state.name', counts: { running: { total: 7 }, error: { total: 3 } } }]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
ctx.dispatch.mockResolvedValue(apiResponse);
|
|
148
|
+
|
|
149
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name' } });
|
|
150
|
+
|
|
151
|
+
expect(result).toStrictEqual(apiResponse);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should pass through object-style counts as-is', async() => {
|
|
155
|
+
const ctx = baseCtx();
|
|
156
|
+
const counts = { running: { total: 7 }, error: { total: 3 } };
|
|
157
|
+
const apiResponse = {
|
|
158
|
+
count: 10,
|
|
159
|
+
summary: [{ property: 'metadata.state.name', counts }]
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
ctx.dispatch.mockResolvedValue(apiResponse);
|
|
163
|
+
|
|
164
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name' } });
|
|
165
|
+
|
|
166
|
+
expect(result).toStrictEqual({
|
|
167
|
+
count: 10,
|
|
168
|
+
summary: [{ property: 'metadata.state.name', counts }]
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should default count to 0 and summary to null when response is empty', async() => {
|
|
173
|
+
const ctx = baseCtx();
|
|
174
|
+
|
|
175
|
+
ctx.dispatch.mockResolvedValue({});
|
|
176
|
+
|
|
177
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name' } });
|
|
178
|
+
|
|
179
|
+
expect(result).toStrictEqual({ count: 0, summary: null });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should append label selector params when labelSelector is provided', async() => {
|
|
183
|
+
const ctx = baseCtx();
|
|
184
|
+
const labelSelector = {
|
|
185
|
+
matchExpressions: [{
|
|
186
|
+
key: 'app', operator: 'In', values: ['nginx']
|
|
187
|
+
}]
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
jest.spyOn(stevePaginationUtils, 'convertLabelSelectorPaginationParams').mockReturnValue('filter=metadata.labels[app] IN (nginx)');
|
|
191
|
+
ctx.dispatch.mockResolvedValue({ count: 2, summary: null });
|
|
192
|
+
|
|
193
|
+
await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name', labelSelector } });
|
|
194
|
+
|
|
195
|
+
const requestUrl = ctx.dispatch.mock.calls[0][1].opt.url;
|
|
196
|
+
|
|
197
|
+
expect(requestUrl).toContain('filter=');
|
|
198
|
+
expect(stevePaginationUtils.convertLabelSelectorPaginationParams).toHaveBeenCalledWith({ labelSelector });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should return undefined and warn when the request fails', async() => {
|
|
202
|
+
const ctx = baseCtx();
|
|
203
|
+
|
|
204
|
+
ctx.dispatch.mockRejectedValue(new Error('network error'));
|
|
205
|
+
|
|
206
|
+
const result = await fetchResourceSummary.call({}, ctx, { type: 'pod', opt: { summaryField: 'metadata.state.name' } });
|
|
207
|
+
|
|
208
|
+
expect(result).toBeUndefined();
|
|
209
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('summary API request failed'), expect.any(Error));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|