@rancher/shell 3.0.8 → 3.0.9-rc.2
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/apis/intf/modal.ts +38 -0
- package/apis/intf/slide-in.ts +3 -1
- package/apis/shell/__tests__/slide-in.test.ts +36 -0
- package/apis/shell/slide-in.ts +5 -1
- package/assets/styles/base/_color.scss +1 -0
- package/assets/styles/base/_typography.scss +14 -5
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +1 -1
- package/assets/translations/en-us.yaml +94 -33
- package/assets/translations/zh-hans.yaml +0 -2
- package/components/ActionMenuShell.vue +4 -4
- package/components/CodeMirror.vue +4 -3
- package/components/DetailText.vue +54 -7
- package/components/Drawer/Chrome.vue +11 -4
- package/components/Drawer/DrawerCard.vue +19 -0
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
- package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
- package/components/Drawer/types.ts +1 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
- package/components/LocaleSelector.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/PopoverCard.vue +3 -3
- package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
- package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
- package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
- package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
- package/components/Resource/Detail/Cards.vue +27 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
- package/components/Resource/Detail/Masthead/index.vue +5 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
- package/components/Resource/Detail/ResourceRow.types.ts +14 -0
- package/components/Resource/Detail/ResourceRow.vue +23 -35
- package/components/Resource/Detail/StatusRow.vue +5 -2
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +41 -6
- package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
- package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
- package/components/ResourceDetail/Masthead/index.vue +1 -0
- package/components/ResourceDetail/Masthead/latest.vue +8 -1
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/Setting.vue +1 -1
- package/components/SortableTable/index.vue +25 -0
- package/components/SortableTable/selection.js +25 -12
- package/components/SortableTable/sorting.js +1 -1
- package/components/Tabbed/Tab.vue +1 -0
- package/components/Tabbed/index.vue +29 -6
- package/components/Window/ContainerShell.vue +10 -13
- package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
- package/components/fleet/FleetClusterTargets/index.vue +82 -29
- package/components/fleet/FleetClusters.vue +26 -12
- package/components/fleet/FleetGitRepoPaths.vue +2 -2
- package/components/fleet/FleetResources.vue +14 -0
- package/components/fleet/FleetValuesFrom.vue +2 -2
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
- package/components/fleet/dashboard/ResourceDetails.vue +96 -123
- package/components/form/Conditions.vue +1 -15
- package/components/form/HookOption.vue +5 -0
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/LifecycleHooks.vue +2 -6
- package/components/form/ResourceLabeledSelect.vue +12 -1
- package/components/form/SeccompProfile.vue +113 -0
- package/components/form/Security.vue +244 -133
- package/components/form/__tests__/LabeledSelect.test.ts +1 -1
- package/components/form/__tests__/SeccompProfile.test.js +124 -0
- package/components/form/__tests__/Security.test.ts +125 -37
- package/components/formatter/Autoscaler.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +4 -1
- package/components/nav/Group.vue +5 -0
- package/components/nav/Header.vue +3 -3
- package/components/nav/HeaderPageActionMenu.vue +1 -1
- package/components/nav/NamespaceFilter.vue +6 -6
- package/components/nav/NotificationCenter/index.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +41 -16
- package/components/nav/TopLevelMenu.vue +45 -25
- package/components/nav/WorkspaceSwitcher.vue +1 -1
- package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
- package/components/templates/default.vue +0 -3
- package/components/templates/home.vue +0 -3
- package/components/templates/plain.vue +0 -3
- package/composables/useClickOutside.ts +1 -1
- package/config/product/explorer.js +1 -2
- package/config/types.js +41 -8
- package/detail/__tests__/workload.test.ts +8 -16
- package/detail/catalog.cattle.io.app.vue +6 -0
- package/detail/fleet.cattle.io.cluster.vue +6 -0
- package/detail/workload/index.vue +7 -109
- package/edit/__tests__/projectsecret.test.ts +42 -0
- package/edit/auth/__tests__/oidc.test.ts +50 -0
- package/edit/auth/oidc.vue +68 -44
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
- package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
- package/edit/projectsecret.vue +29 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
- package/edit/workload/__tests__/index.test.ts +122 -85
- package/edit/workload/index.vue +48 -29
- package/edit/workload/mixins/workload.js +85 -32
- package/list/catalog.cattle.io.clusterrepo.vue +1 -1
- package/list/projectsecret.vue +2 -2
- package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
- package/machine-config/amazonec2.vue +2 -2
- package/machine-config/vmwarevsphere.vue +58 -4
- package/mixins/__tests__/brand.spec.ts +18 -13
- package/mixins/__tests__/chart.test.ts +63 -0
- package/mixins/chart.js +56 -51
- package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
- package/models/__tests__/workload.test.ts +333 -0
- package/models/catalog.cattle.io.app.js +8 -0
- package/models/pod.js +14 -0
- package/models/secret.js +1 -1
- package/models/workload.js +93 -27
- package/package.json +4 -4
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
- package/pages/c/_cluster/apps/charts/install.vue +4 -4
- package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
- package/pages/c/_cluster/fleet/index.vue +18 -12
- package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
- package/plugins/dashboard-store/actions.js +9 -8
- package/plugins/dashboard-store/resource-class.js +97 -1
- package/plugins/steve/__tests__/revision.test.ts +84 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
- package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
- package/plugins/steve/mutations.js +9 -0
- package/plugins/steve/revision.ts +26 -0
- package/plugins/steve/steve-pagination-utils.ts +6 -5
- package/plugins/steve/subscribe.js +211 -51
- package/plugins/subscribe-events.ts +2 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
- package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
- package/rancher-components/Pill/index.ts +4 -0
- package/rancher-components/RcButton/RcButton.test.ts +53 -9
- package/rancher-components/RcButton/RcButton.vue +217 -25
- package/rancher-components/RcButton/types.ts +27 -1
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
- package/rancher-components/RcDropdown/types.ts +3 -3
- package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
- package/rancher-components/RcIcon/RcIcon.vue +9 -6
- package/rancher-components/RcIcon/types.ts +13 -9
- package/rancher-components/utils/status.test.ts +10 -15
- package/rancher-components/utils/status.ts +5 -6
- package/store/aws.js +18 -12
- package/store/index.js +4 -8
- package/store/type-map.utils.ts +1 -1
- package/types/kube/kube-api.ts +29 -3
- package/types/rancher/steve.api.ts +40 -0
- package/types/shell/index.d.ts +99 -0
- package/types/store/dashboard-store.types.ts +29 -7
- package/types/store/pagination.types.ts +1 -0
- package/types/store/subscribe-events.types.ts +1 -0
- package/utils/__tests__/azure.test.ts +56 -0
- package/utils/__tests__/back-off.test.ts +364 -245
- package/utils/__tests__/error.test.ts +44 -0
- package/utils/__tests__/fleet.test.ts +8 -1
- package/utils/__tests__/pagination-wrapper.test.ts +167 -0
- package/utils/__tests__/version.test.ts +55 -1
- package/utils/azure.js +12 -0
- package/utils/back-off.ts +302 -69
- package/utils/cspAdaptor.ts +32 -14
- package/utils/dynamic-content/__tests__/index.test.ts +1 -1
- package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
- package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
- package/utils/dynamic-content/index.ts +1 -6
- package/utils/dynamic-content/new-release.ts +5 -3
- package/utils/dynamic-content/types.d.ts +0 -1
- package/utils/error.js +9 -0
- package/utils/fleet.ts +2 -2
- package/utils/inactivity.ts +2 -3
- package/utils/pagination-wrapper.ts +101 -17
- package/utils/validators/formRules/index.ts +3 -0
- package/utils/version.js +38 -0
- package/components/auth/AzureWarning.vue +0 -77
- /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
- /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
|
@@ -33,12 +33,14 @@ import forIn from 'lodash/forIn';
|
|
|
33
33
|
import isEmpty from 'lodash/isEmpty';
|
|
34
34
|
import isFunction from 'lodash/isFunction';
|
|
35
35
|
import isString from 'lodash/isString';
|
|
36
|
-
import { markRaw } from 'vue';
|
|
36
|
+
import { defineAsyncComponent, markRaw } from 'vue';
|
|
37
37
|
|
|
38
38
|
import { handleConflict } from '@shell/plugins/dashboard-store/normalize';
|
|
39
39
|
import { ExtensionPoint, ActionLocation } from '@shell/core/types';
|
|
40
40
|
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
41
41
|
import { parse } from '@shell/utils/selector';
|
|
42
|
+
import { EVENT } from '@shell/config/types';
|
|
43
|
+
import { useResourceCardRow } from '@shell/components/Resource/Detail/Card/StateCard/composables';
|
|
42
44
|
|
|
43
45
|
export const DNS_LIKE_TYPES = ['dnsLabel', 'dnsLabelRestricted', 'hostname'];
|
|
44
46
|
|
|
@@ -1383,6 +1385,49 @@ export default class Resource {
|
|
|
1383
1385
|
return this._detailLocation;
|
|
1384
1386
|
}
|
|
1385
1387
|
|
|
1388
|
+
/**
|
|
1389
|
+
* Override this getter to provide additional action buttons or a custom component
|
|
1390
|
+
* for the detail page title bar.
|
|
1391
|
+
*
|
|
1392
|
+
* @returns {undefined|object|Array} A Vue component definition, an array of RcButton props, or undefined
|
|
1393
|
+
*
|
|
1394
|
+
* @example
|
|
1395
|
+
* // Using an array of button props with the new variant/size props
|
|
1396
|
+
* get detailPageAdditionalActions() {
|
|
1397
|
+
* return [
|
|
1398
|
+
* { label: 'Action 1', variant: 'secondary', onClick: () => this.doAction1() },
|
|
1399
|
+
* { label: 'Action 2', variant: 'primary', size: 'large', onClick: () => this.doAction2() }
|
|
1400
|
+
* ];
|
|
1401
|
+
* }
|
|
1402
|
+
*
|
|
1403
|
+
* @example
|
|
1404
|
+
* // Using defineComponent with h() render function for custom rendering
|
|
1405
|
+
* import { defineComponent, h } from 'vue';
|
|
1406
|
+
* import RcButton from '@components/RcButton/RcButton.vue';
|
|
1407
|
+
*
|
|
1408
|
+
* get detailPageAdditionalActions() {
|
|
1409
|
+
* return defineComponent({
|
|
1410
|
+
* render() {
|
|
1411
|
+
* return h(RcButton, {
|
|
1412
|
+
* variant: 'primary',
|
|
1413
|
+
* onClick: () => console.log('clicked')
|
|
1414
|
+
* }, () => 'Click Me');
|
|
1415
|
+
* }
|
|
1416
|
+
* });
|
|
1417
|
+
* }
|
|
1418
|
+
*
|
|
1419
|
+
* @example
|
|
1420
|
+
* // Using dynamic import for a custom component
|
|
1421
|
+
* import { defineAsyncComponent } from 'vue';
|
|
1422
|
+
*
|
|
1423
|
+
* get detailPageAdditionalActions() {
|
|
1424
|
+
* return defineAsyncComponent(() => import('@shell/components/MyCustomActions.vue'));
|
|
1425
|
+
* }
|
|
1426
|
+
*/
|
|
1427
|
+
get detailPageAdditionalActions() {
|
|
1428
|
+
return undefined;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1386
1431
|
goToDetail() {
|
|
1387
1432
|
this.currentRouter().push(this.detailLocation);
|
|
1388
1433
|
}
|
|
@@ -2091,4 +2136,55 @@ export default class Resource {
|
|
|
2091
2136
|
get yamlFolding() {
|
|
2092
2137
|
return [];
|
|
2093
2138
|
}
|
|
2139
|
+
|
|
2140
|
+
get resourceConditions() {
|
|
2141
|
+
return (this.status?.conditions || []).map((cond) => {
|
|
2142
|
+
let message = cond.message || '';
|
|
2143
|
+
|
|
2144
|
+
if ( cond.reason ) {
|
|
2145
|
+
message = `[${ cond.reason }] ${ message }`.trim();
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
return {
|
|
2149
|
+
condition: cond.type || 'Unknown',
|
|
2150
|
+
status: cond.status || 'Unknown',
|
|
2151
|
+
stateSimpleColor: cond.error ? 'error' : 'disabled',
|
|
2152
|
+
error: cond.error,
|
|
2153
|
+
time: cond.lastProbeTime || cond.lastUpdateTime || cond.lastTransitionTime,
|
|
2154
|
+
message,
|
|
2155
|
+
};
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
get resourceEvents() {
|
|
2160
|
+
return this.$rootGetters['cluster/all'](EVENT)
|
|
2161
|
+
.filter((e) => e.involvedObject?.uid === this.metadata?.uid);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
get insightCardProps() {
|
|
2165
|
+
const rows = [
|
|
2166
|
+
useResourceCardRow(this.t('component.resource.detail.card.insightsCard.rows.conditions'), this.resourceConditions, undefined, 'condition', '#conditions'),
|
|
2167
|
+
useResourceCardRow(this.t('component.resource.detail.card.insightsCard.rows.events'), this.resourceEvents, undefined, undefined, '#events'),
|
|
2168
|
+
];
|
|
2169
|
+
|
|
2170
|
+
return {
|
|
2171
|
+
title: this.t('component.resource.detail.card.insightsCard.title'),
|
|
2172
|
+
rows
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
get insightCard() {
|
|
2177
|
+
return {
|
|
2178
|
+
component: markRaw(defineAsyncComponent(() => import('@shell/components/Resource/Detail/Card/StateCard/index.vue'))),
|
|
2179
|
+
props: this.insightCardProps
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
get _cards() {
|
|
2184
|
+
return [this.insightCard];
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
get cards() {
|
|
2188
|
+
return this._cards;
|
|
2189
|
+
}
|
|
2094
2190
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SteveRevision } from '../revision';
|
|
2
|
+
|
|
3
|
+
describe('class: SteveRevision', () => {
|
|
4
|
+
describe('constructor', () => {
|
|
5
|
+
it('should correctly parse a numeric string', () => {
|
|
6
|
+
const rev = new SteveRevision('123');
|
|
7
|
+
|
|
8
|
+
expect(rev.asNumber).toBe(123);
|
|
9
|
+
expect(rev.isNumber).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should correctly parse a number', () => {
|
|
13
|
+
const rev = new SteveRevision(456);
|
|
14
|
+
|
|
15
|
+
expect(rev.asNumber).toBe(456);
|
|
16
|
+
expect(rev.isNumber).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should handle non-numeric strings', () => {
|
|
20
|
+
const rev = new SteveRevision('abc');
|
|
21
|
+
|
|
22
|
+
expect(rev.asNumber).toBeNaN();
|
|
23
|
+
expect(rev.isNumber).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle undefined', () => {
|
|
27
|
+
const rev = new SteveRevision(undefined);
|
|
28
|
+
|
|
29
|
+
expect(rev.asNumber).toBeNaN();
|
|
30
|
+
expect(rev.isNumber).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('method: isNewerThan', () => {
|
|
35
|
+
it('should return true if current revision is greater than provided revision', () => {
|
|
36
|
+
const r1 = new SteveRevision('10');
|
|
37
|
+
const r2 = new SteveRevision('5');
|
|
38
|
+
|
|
39
|
+
expect(r1.isNewerThan(r2)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return false if current revision is less than provided revision', () => {
|
|
43
|
+
const r1 = new SteveRevision('5');
|
|
44
|
+
const r2 = new SteveRevision('10');
|
|
45
|
+
|
|
46
|
+
expect(r1.isNewerThan(r2)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return false if revisions are equal', () => {
|
|
50
|
+
const r1 = new SteveRevision('10');
|
|
51
|
+
const r2 = new SteveRevision('10');
|
|
52
|
+
|
|
53
|
+
expect(r1.isNewerThan(r2)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return false if current revision is not a number', () => {
|
|
57
|
+
const r1 = new SteveRevision('abc');
|
|
58
|
+
const r2 = new SteveRevision('10');
|
|
59
|
+
|
|
60
|
+
expect(r1.isNewerThan(r2)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return false if provided revision is not a number', () => {
|
|
64
|
+
const r1 = new SteveRevision('10');
|
|
65
|
+
const r2 = new SteveRevision('abc');
|
|
66
|
+
|
|
67
|
+
expect(r1.isNewerThan(r2)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should return false if current revision is undefined', () => {
|
|
71
|
+
const r1 = new SteveRevision(undefined);
|
|
72
|
+
const r2 = new SteveRevision('10');
|
|
73
|
+
|
|
74
|
+
expect(r1.isNewerThan(r2)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return false if provided revision is undefined', () => {
|
|
78
|
+
const r1 = new SteveRevision('10');
|
|
79
|
+
const r2 = new SteveRevision(undefined);
|
|
80
|
+
|
|
81
|
+
expect(r1.isNewerThan(r2)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -477,6 +477,20 @@ describe('class StevePaginationUtils', () => {
|
|
|
477
477
|
expect(result).toBe('filter=spec.containers.image~nginx&filter=metadata.name!=test');
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
+
it.each([
|
|
481
|
+
['test', 'test'],
|
|
482
|
+
['-test', '"-test"'],
|
|
483
|
+
['te-st', '"te-st"'],
|
|
484
|
+
['test-', '"test-"'],
|
|
485
|
+
])('should handle filter value %s with hyphens', (x, y) => {
|
|
486
|
+
const filters = [
|
|
487
|
+
new PaginationParamFilter({ fields: [new PaginationFilterField({ field: 'metadata.name', value: x })] }),
|
|
488
|
+
];
|
|
489
|
+
const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
|
|
490
|
+
|
|
491
|
+
expect(result).toBe(`filter=metadata.name=${ y }`);
|
|
492
|
+
});
|
|
493
|
+
|
|
480
494
|
it('should handle IN and NOT_IN operators', () => {
|
|
481
495
|
const filters = [
|
|
482
496
|
new PaginationParamFilter({
|
|
@@ -502,5 +516,21 @@ describe('class StevePaginationUtils', () => {
|
|
|
502
516
|
|
|
503
517
|
expect(result).toBe('filter=metadata.name IN (test1,test2)&filter=metadata.namespace NOTIN (ns1,ns2)');
|
|
504
518
|
});
|
|
519
|
+
|
|
520
|
+
it.each([
|
|
521
|
+
[null, '""'],
|
|
522
|
+
[undefined, '""'],
|
|
523
|
+
[false, 'false'],
|
|
524
|
+
[true, 'true'],
|
|
525
|
+
[0, '0'],
|
|
526
|
+
[1, '1'],
|
|
527
|
+
])('should handle falsy filter value %s', (x: any, y) => {
|
|
528
|
+
const filters = [
|
|
529
|
+
new PaginationParamFilter({ fields: [new PaginationFilterField({ field: 'metadata.name', value: x })] }),
|
|
530
|
+
];
|
|
531
|
+
const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
|
|
532
|
+
|
|
533
|
+
expect(result).toBe(`filter=metadata.name=${ y }`);
|
|
534
|
+
});
|
|
505
535
|
});
|
|
506
536
|
});
|
|
@@ -3,6 +3,7 @@ import { REVISION_TOO_OLD } from '../../../utils/socket';
|
|
|
3
3
|
import { STEVE_WATCH_MODE } from '../../../types/store/subscribe.types';
|
|
4
4
|
import backOff from '../../../utils/back-off';
|
|
5
5
|
import { SteveWatchEventListenerManager } from '../../subscribe-events';
|
|
6
|
+
import { STEVE_RESPONSE_CODE } from '../../../types/rancher/steve.api';
|
|
6
7
|
|
|
7
8
|
describe('steve: subscribe', () => {
|
|
8
9
|
describe('actions', () => {
|
|
@@ -110,6 +111,139 @@ describe('steve: subscribe', () => {
|
|
|
110
111
|
expect(commit).toHaveBeenCalledWith('setInError', { msg, reason: 'NO_PERMS' });
|
|
111
112
|
});
|
|
112
113
|
});
|
|
114
|
+
|
|
115
|
+
describe('fetchPageResources', () => {
|
|
116
|
+
const dispatch = jest.fn();
|
|
117
|
+
const getters = {
|
|
118
|
+
backOffId: jest.fn(),
|
|
119
|
+
typeEntry: jest.fn(),
|
|
120
|
+
canBackoff: jest.fn(),
|
|
121
|
+
watchStarted: jest.fn(),
|
|
122
|
+
inError: jest.fn(),
|
|
123
|
+
};
|
|
124
|
+
let backOffSpy: any;
|
|
125
|
+
const context = { $socket: {} };
|
|
126
|
+
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
jest.clearAllMocks();
|
|
129
|
+
backOffSpy = {
|
|
130
|
+
getBackOff: jest.spyOn(backOff, 'getBackOff'),
|
|
131
|
+
reset: jest.spyOn(backOff, 'reset'),
|
|
132
|
+
recurse: jest.spyOn(backOff, 'recurse'),
|
|
133
|
+
};
|
|
134
|
+
getters.backOffId.mockReturnValue('backoff-id');
|
|
135
|
+
getters.typeEntry.mockReturnValue({ revision: '10' });
|
|
136
|
+
backOffSpy.getBackOff.mockReturnValue({ metadata: { revision: '10' } } as any);
|
|
137
|
+
backOffSpy.recurse.mockResolvedValue(undefined);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
afterEach(() => {
|
|
141
|
+
jest.restoreAllMocks();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should abort if current revision is newer than target revision', async() => {
|
|
145
|
+
// Current (10) > Target (5)
|
|
146
|
+
const params = {
|
|
147
|
+
resourceType: 'type', namespace: 'ns', revision: '5'
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
await actions.fetchPageResources.call(context, { getters, dispatch }, {
|
|
151
|
+
opt: {}, storePagination: {}, params
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(backOffSpy.recurse).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should reset backoff if target revision is newer than active revision', async() => {
|
|
158
|
+
// Active (10) < Target (15)
|
|
159
|
+
const params = {
|
|
160
|
+
resourceType: 'type', namespace: 'ns', revision: '15'
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
await actions.fetchPageResources.call(context, { getters, dispatch }, {
|
|
164
|
+
opt: {}, storePagination: {}, params
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(backOffSpy.reset).toHaveBeenCalledWith('backoff-id');
|
|
168
|
+
expect(backOffSpy.recurse).toHaveBeenCalledWith(expect.objectContaining({
|
|
169
|
+
id: 'backoff-id',
|
|
170
|
+
metadata: { revision: '15' }
|
|
171
|
+
}));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should recurse if revisions are valid', async() => {
|
|
175
|
+
const params = {
|
|
176
|
+
resourceType: 'type', namespace: 'ns', revision: '10'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
await actions.fetchPageResources.call(context, { getters, dispatch }, {
|
|
180
|
+
opt: {}, storePagination: {}, params
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(backOffSpy.recurse).toHaveBeenCalledWith(expect.objectContaining({
|
|
184
|
+
id: 'backoff-id',
|
|
185
|
+
metadata: { revision: '10' }
|
|
186
|
+
}));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('recurse options', () => {
|
|
190
|
+
const params = {
|
|
191
|
+
resourceType: 'type', namespace: 'ns', revision: '10'
|
|
192
|
+
};
|
|
193
|
+
let recurseArgs: any;
|
|
194
|
+
|
|
195
|
+
beforeEach(async() => {
|
|
196
|
+
await actions.fetchPageResources.call(context, { getters, dispatch }, {
|
|
197
|
+
opt: {}, storePagination: { request: { filter: 'foo' } }, params
|
|
198
|
+
});
|
|
199
|
+
recurseArgs = backOffSpy.recurse.mock.calls[0][0];
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('canFn should return false if socket closed', () => {
|
|
203
|
+
getters.canBackoff.mockReturnValue(false);
|
|
204
|
+
expect(recurseArgs.canFn()).toBe(false);
|
|
205
|
+
expect(getters.canBackoff).toHaveBeenCalledWith(context.$socket);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('canFn should return false if watch not started and not in REVISION_TOO_OLD error', () => {
|
|
209
|
+
getters.canBackoff.mockReturnValue(true);
|
|
210
|
+
getters.watchStarted.mockReturnValue(false);
|
|
211
|
+
getters.inError.mockReturnValue('some-other-error');
|
|
212
|
+
expect(recurseArgs.canFn()).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('canFn should return true if watch started', () => {
|
|
216
|
+
getters.canBackoff.mockReturnValue(true);
|
|
217
|
+
getters.watchStarted.mockReturnValue(true);
|
|
218
|
+
expect(recurseArgs.canFn()).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('canFn should return true if watch not started but in REVISION_TOO_OLD error', () => {
|
|
222
|
+
getters.canBackoff.mockReturnValue(true);
|
|
223
|
+
getters.watchStarted.mockReturnValue(false);
|
|
224
|
+
getters.inError.mockReturnValue(REVISION_TOO_OLD);
|
|
225
|
+
expect(recurseArgs.canFn()).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('continueOnError should return true for UNKNOWN_REVISION error', async() => {
|
|
229
|
+
const err = { status: 400, code: STEVE_RESPONSE_CODE.UNKNOWN_REVISION };
|
|
230
|
+
|
|
231
|
+
expect(await recurseArgs.continueOnError(err)).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('delayedFn should dispatch findPage', async() => {
|
|
235
|
+
await recurseArgs.delayedFn();
|
|
236
|
+
expect(dispatch).toHaveBeenCalledWith('findPage', {
|
|
237
|
+
type: 'type',
|
|
238
|
+
opt: {
|
|
239
|
+
namespaced: 'ns',
|
|
240
|
+
revision: '10',
|
|
241
|
+
filter: 'foo'
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
113
247
|
});
|
|
114
248
|
|
|
115
249
|
describe('getters', () => {
|
|
@@ -180,6 +180,15 @@ export default {
|
|
|
180
180
|
* Load multiple different types of resources
|
|
181
181
|
*/
|
|
182
182
|
loadMulti(state, { data, ctx }) {
|
|
183
|
+
const type = data[0]?.type;
|
|
184
|
+
const cache = state.types[type];
|
|
185
|
+
|
|
186
|
+
if (cache?.havePage) {
|
|
187
|
+
console.warn(`Prevented \`loadMulti\` mutation from polluting the cache for type "${ type }" (currently represents a page).`); // eslint-disable-line no-console
|
|
188
|
+
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
183
192
|
for (const entry of data) {
|
|
184
193
|
const resource = load(state, { data: entry, ctx });
|
|
185
194
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper class to handle Steve API revisions comparisons
|
|
3
|
+
*/
|
|
4
|
+
export class SteveRevision {
|
|
5
|
+
public asNumber: number;
|
|
6
|
+
public isNumber: boolean;
|
|
7
|
+
|
|
8
|
+
constructor(public revision: any) {
|
|
9
|
+
this.asNumber = Number(revision);
|
|
10
|
+
this.isNumber = !Number.isNaN(this.asNumber);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Is this provided revision newer than this revision?
|
|
15
|
+
*
|
|
16
|
+
* @param revision
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
isNewerThan(revision: SteveRevision): boolean {
|
|
20
|
+
return SteveRevision.areAllNumbers([this, revision]) && this.asNumber > revision.asNumber;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private static areAllNumbers(revisions: SteveRevision[]): boolean {
|
|
24
|
+
return revisions.every((r) => r.isNumber);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -213,9 +213,9 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
213
213
|
* Match
|
|
214
214
|
* - a-z (case insensitive)
|
|
215
215
|
* - 0-9
|
|
216
|
-
* -
|
|
216
|
+
* - `_`, `.`
|
|
217
217
|
*/
|
|
218
|
-
static VALID_FIELD_VALUE_REGEX = /^[\w
|
|
218
|
+
static VALID_FIELD_VALUE_REGEX = /^[\w.]+$/;
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
221
|
* Filtering with the vai cache supports specific fields
|
|
@@ -596,10 +596,11 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
596
596
|
if ([PaginationFilterEquality.IN, PaginationFilterEquality.NOT_IN].includes(equality)) {
|
|
597
597
|
safeValue = `(${ field.value })`;
|
|
598
598
|
} else {
|
|
599
|
-
const
|
|
599
|
+
const booleanSafeValue = typeof field.value === 'undefined' || field.value === null ? '' : field.value;
|
|
600
|
+
const encodedValue = encodeURIComponent(booleanSafeValue);
|
|
600
601
|
|
|
601
|
-
if (StevePaginationUtils.VALID_FIELD_VALUE_REGEX.test(
|
|
602
|
-
//
|
|
602
|
+
if (StevePaginationUtils.VALID_FIELD_VALUE_REGEX.test(booleanSafeValue)) {
|
|
603
|
+
// All characters safe, send as is
|
|
603
604
|
safeValue = encodedValue;
|
|
604
605
|
} else {
|
|
605
606
|
// Contains protected characters, wrap in quotes to ensure backend doesn't fail
|