@rancher/shell 0.5.1 → 0.5.3
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/translations/en-us.yaml +8 -4
- package/components/ClusterIconMenu.vue +24 -9
- package/components/CodeMirror.vue +79 -18
- package/components/FixedBanner.vue +1 -0
- package/components/ResourceDetail/index.vue +1 -4
- package/components/ResourceYaml.vue +29 -5
- package/components/SideNav.vue +42 -64
- package/components/SortableTable/index.vue +1 -1
- package/components/YamlEditor.vue +1 -0
- package/components/__tests__/CodeMirror.spec.ts +99 -0
- package/components/form/BannerSettings.vue +3 -0
- package/components/form/FileSelector.vue +1 -0
- package/components/form/KeyValue.vue +1 -0
- package/components/formatter/WorkloadDetailEndpoints.vue +12 -22
- package/components/formatter/__tests__/WorkloadDetailEndpoints.test.ts +81 -0
- package/components/nav/Header.vue +1 -0
- package/components/nav/Jump.vue +19 -9
- package/components/nav/TopLevelMenu.vue +37 -15
- package/components/nav/Type.vue +15 -4
- package/components/nav/__tests__/TopLevelMenu.test.ts +1 -1
- package/components/nav/__tests__/Type.test.ts +30 -0
- package/core/types-provisioning.ts +7 -0
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +77 -0
- package/detail/fleet.cattle.io.bundle.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +19 -4
- package/edit/management.cattle.io.setting.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/opsgenie.vue +1 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +1 -2
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/slack.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +23 -10
- package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -50
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +9 -11
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +3 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +3 -0
- package/edit/token.vue +1 -0
- package/list/catalog.cattle.io.app.vue +1 -0
- package/list/management.cattle.io.setting.vue +1 -0
- package/machine-config/amazonec2.vue +1 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +151 -0
- package/models/__tests__/secret.test.ts +37 -0
- package/models/__tests__/storage.k8s.io.storageclass.test.ts +22 -0
- package/models/management.cattle.io.kontainerdriver.js +2 -1
- package/models/provisioning.cattle.io.cluster.js +36 -1
- package/models/secret.js +9 -0
- package/models/storage.k8s.io.storageclass.js +1 -1
- package/package.json +1 -1
- package/pages/c/_cluster/settings/DefaultLinksEditor.vue +1 -0
- package/pages/c/_cluster/settings/brand.vue +3 -0
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +4 -4
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +5 -2
- package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/__tests__/SetupUIPlugins.test.ts +128 -0
- package/plugins/dashboard-store/__tests__/actions.test.ts +196 -111
- package/plugins/dashboard-store/actions.js +4 -6
- package/plugins/dashboard-store/getters.js +60 -2
- package/plugins/dashboard-store/resource-class.js +6 -2
- package/plugins/steve/__tests__/getters.spec.ts +10 -0
- package/plugins/steve/__tests__/resource-utils.test.ts +159 -0
- package/plugins/steve/actions.js +3 -37
- package/plugins/steve/getters.js +6 -0
- package/plugins/steve/resource-utils.ts +38 -0
- package/scripts/extension/parse-tag-name +3 -3
- package/store/__tests__/type-map.test.ts +1122 -0
- package/store/index.js +3 -2
- package/store/plugins.js +7 -6
- package/store/type-map.js +145 -75
- package/types/shell/index.d.ts +2 -0
- package/utils/__tests__/create-yaml.test.ts +10 -0
- package/utils/create-yaml.js +5 -1
- package/utils/object.js +10 -0
|
@@ -1,127 +1,127 @@
|
|
|
1
1
|
import _actions from '@shell/plugins/dashboard-store/actions';
|
|
2
2
|
|
|
3
|
-
const { findAll } = _actions;
|
|
3
|
+
const { findAll, findMatching } = _actions;
|
|
4
4
|
|
|
5
5
|
describe('dashboard-store: actions', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
describe('findAll', () => {
|
|
7
|
+
// Note - there are TS errors alll over this describe and should not have merged with them in.
|
|
8
|
+
const setupContext = () => {
|
|
9
|
+
const commit = jest.fn();
|
|
10
|
+
const dispatch = jest.fn((...args) => {
|
|
11
|
+
switch (args[0]) {
|
|
12
|
+
case 'request':
|
|
13
|
+
return { data: ['requestData'] };
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const state = { config: { namespace: 'unitTest' } };
|
|
17
|
+
const getters = {
|
|
18
|
+
normalizeType: jest.fn(() => 'getters.normalizeType'),
|
|
19
|
+
typeRegistered: jest.fn(() => false),
|
|
20
|
+
haveAll: jest.fn(() => false),
|
|
21
|
+
haveAllNamespace: jest.fn(() => false),
|
|
22
|
+
all: jest.fn(() => 'getters.all'),
|
|
23
|
+
urlFor: jest.fn(() => 'getters.urlFor'), // we're not testing the urlFor getter so we don't need to do anything with opt here
|
|
24
|
+
};
|
|
25
|
+
const rootGetters = {
|
|
26
|
+
'type-map/optionsFor': jest.fn(),
|
|
27
|
+
'auth/fromHeader': 'foo'
|
|
28
|
+
};
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// we're not testing function output based off of state or getter inputs here since they are dependencies and should be tested independently
|
|
31
|
+
return {
|
|
32
|
+
state,
|
|
33
|
+
getters,
|
|
34
|
+
rootGetters,
|
|
35
|
+
commit,
|
|
36
|
+
dispatch
|
|
37
|
+
};
|
|
35
38
|
};
|
|
36
|
-
};
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
40
|
+
const standardAssertions = {
|
|
41
|
+
returnsPromise: {
|
|
41
42
|
assertionLabel: 'returns a promise',
|
|
42
43
|
valueGetter: ({ findAllPromise }) => typeof findAllPromise.then,
|
|
43
44
|
valueExpected: 'function'
|
|
44
45
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
firstDispatchAction: {
|
|
56
|
-
assertionLabel: 'first dispatch should be the "request" action',
|
|
57
|
-
valueGetter: ({ dispatch }) => dispatch.mock.calls[0][0],
|
|
58
|
-
valueExpected: 'request'
|
|
59
|
-
},
|
|
60
|
-
firstDispatchParams: {
|
|
61
|
-
assertionLabel: 'first dispatch parameters should be provided a normalized type and a url, streaming, and "metadata.managedFields" excluded under opt',
|
|
62
|
-
valueGetter: ({ dispatch }) => dispatch.mock.calls[0][1],
|
|
63
|
-
valueExpected: {
|
|
64
|
-
type: 'getters.normalizeType',
|
|
65
|
-
opt: {
|
|
66
|
-
url: 'getters.urlFor',
|
|
67
|
-
stream: true
|
|
68
|
-
}
|
|
46
|
+
callsAll: {
|
|
47
|
+
assertionLabel: 'calls the "all" getter with the normalizedType',
|
|
48
|
+
valueGetter: ({ getters }) => getters.all.mock.calls[0][0],
|
|
49
|
+
valueExpected: 'getters.normalizeType'
|
|
50
|
+
},
|
|
51
|
+
returnsFromAll: {
|
|
52
|
+
assertionLabel: 'returns the value expected from the "all" getter',
|
|
53
|
+
valueGetter: ({ findAllReturnValue }) => findAllReturnValue,
|
|
54
|
+
valueExpected: 'getters.all'
|
|
69
55
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
56
|
+
firstDispatchAction: {
|
|
57
|
+
assertionLabel: 'first dispatch should be the "request" action',
|
|
58
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[0][0],
|
|
59
|
+
valueExpected: 'request'
|
|
60
|
+
},
|
|
61
|
+
firstDispatchParams: {
|
|
62
|
+
assertionLabel: 'first dispatch parameters should be provided a normalized type and a url, streaming, and "metadata.managedFields" excluded under opt',
|
|
63
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[0][1],
|
|
64
|
+
valueExpected: {
|
|
65
|
+
type: 'getters.normalizeType',
|
|
66
|
+
opt: {
|
|
67
|
+
url: 'getters.urlFor',
|
|
68
|
+
stream: true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
assertionMethod: 'toMatchObject'
|
|
72
|
+
},
|
|
73
|
+
secondDispatchAction: {
|
|
74
|
+
assertionLabel: 'second dispatch should be the "watch" action',
|
|
75
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[1][0],
|
|
76
|
+
valueExpected: 'watch'
|
|
77
|
+
},
|
|
78
|
+
secondDispatchParams: {
|
|
79
|
+
assertionLabel: 'second dispatch parameters should have a normalized type and force set to false',
|
|
80
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[1][1],
|
|
81
|
+
valueExpected: { type: 'getters.normalizeType', force: false },
|
|
82
|
+
assertionMethod: 'toMatchObject'
|
|
83
|
+
},
|
|
84
|
+
countDispatches: {
|
|
85
|
+
assertionLabel: 'should only make two dispatches',
|
|
86
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls,
|
|
87
|
+
valueExpected: 2,
|
|
88
|
+
assertionMethod: 'toHaveLength'
|
|
89
|
+
},
|
|
90
|
+
firstCommitMutation: {
|
|
91
|
+
assertionLabel: 'first commit should be the "registerType" mutation',
|
|
92
|
+
valueGetter: ({ commit }) => commit.mock.calls[0][0],
|
|
93
|
+
valueExpected: 'registerType'
|
|
94
|
+
},
|
|
95
|
+
firstCommitParams: {
|
|
96
|
+
assertionLabel: 'first commit parameter should be a normalized type',
|
|
97
|
+
valueGetter: ({ commit }) => commit.mock.calls[0][1],
|
|
98
|
+
valueExpected: 'getters.normalizeType'
|
|
99
|
+
},
|
|
100
|
+
secondCommitMutation: {
|
|
101
|
+
assertionLabel: 'second commit should be the "loadAll" mutation',
|
|
102
|
+
valueGetter: ({ commit }) => commit.mock.calls[1][0],
|
|
103
|
+
valueExpected: 'loadAll'
|
|
104
|
+
},
|
|
105
|
+
secondCommitParams: {
|
|
106
|
+
assertionLabel: 'second commit parameters should have a normalized type, ctx.state.config.namespace, data returned by request, and skipHaveAll set to false',
|
|
107
|
+
valueGetter: ({ commit }) => commit.mock.calls[1][1],
|
|
108
|
+
valueExpected: {
|
|
109
|
+
type: 'getters.normalizeType',
|
|
110
|
+
ctx: { state: { config: { namespace: 'unitTest' } } },
|
|
111
|
+
data: ['requestData'],
|
|
112
|
+
skipHaveAll: false,
|
|
113
|
+
},
|
|
114
|
+
assertionMethod: 'toMatchObject'
|
|
112
115
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
describe('dashboard-store > actions > findAll', () => {
|
|
116
|
+
countCommits: {
|
|
117
|
+
assertionLabel: 'should only make two commits',
|
|
118
|
+
valueGetter: ({ commit }) => commit.mock.calls,
|
|
119
|
+
valueExpected: 2,
|
|
120
|
+
assertionMethod: 'toHaveLength'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
};
|
|
124
|
+
|
|
125
125
|
describe('called without a cache for the type in the second param', () => {
|
|
126
126
|
const {
|
|
127
127
|
dispatch, commit, getters, rootGetters, state
|
|
@@ -162,4 +162,89 @@ describe('dashboard-store: actions', () => {
|
|
|
162
162
|
);
|
|
163
163
|
});
|
|
164
164
|
});
|
|
165
|
+
|
|
166
|
+
describe('findMatching', () => {
|
|
167
|
+
const setupContext = () => {
|
|
168
|
+
const commit = jest.fn();
|
|
169
|
+
const dispatch = jest.fn(() => 'dispatch');
|
|
170
|
+
|
|
171
|
+
const state = { config: { namespace: 'unitTest' } };
|
|
172
|
+
const getters = {
|
|
173
|
+
normalizeType: jest.fn((type) => type),
|
|
174
|
+
typeRegistered: jest.fn(() => true),
|
|
175
|
+
haveSelector: jest.fn(() => false),
|
|
176
|
+
matching: jest.fn(() => 'getters.all'),
|
|
177
|
+
urlFor: jest.fn(() => 'getters.urlFor'),
|
|
178
|
+
urlOptions: jest.fn(() => 'getters.urlOptions')
|
|
179
|
+
};
|
|
180
|
+
const rootGetters = { 'type-map/optionsFor': jest.fn() };
|
|
181
|
+
|
|
182
|
+
// we're not testing function output based off of state or getter inputs here since they are dependencies and should be tested independently
|
|
183
|
+
return {
|
|
184
|
+
state,
|
|
185
|
+
getters,
|
|
186
|
+
rootGetters,
|
|
187
|
+
commit,
|
|
188
|
+
dispatch
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const genericType = 'services';
|
|
192
|
+
const genericSelector = 'a=b';
|
|
193
|
+
const genericOpt = {};
|
|
194
|
+
|
|
195
|
+
const assertionChain = [{
|
|
196
|
+
assertionLabel: 'Basic Selector',
|
|
197
|
+
input: {
|
|
198
|
+
type: genericType,
|
|
199
|
+
selector: genericSelector,
|
|
200
|
+
opt: { ...genericOpt },
|
|
201
|
+
namespace: undefined
|
|
202
|
+
},
|
|
203
|
+
output: {
|
|
204
|
+
getters: {
|
|
205
|
+
urlFor: [
|
|
206
|
+
genericType,
|
|
207
|
+
null,
|
|
208
|
+
{
|
|
209
|
+
...genericOpt,
|
|
210
|
+
depaginate: undefined,
|
|
211
|
+
labelSelector: genericSelector,
|
|
212
|
+
url: 'getters.urlFor',
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
actions: {
|
|
217
|
+
request: {
|
|
218
|
+
opt: {},
|
|
219
|
+
type: genericType
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}];
|
|
224
|
+
|
|
225
|
+
const {
|
|
226
|
+
dispatch,
|
|
227
|
+
commit,
|
|
228
|
+
getters,
|
|
229
|
+
rootGetters,
|
|
230
|
+
state
|
|
231
|
+
} = setupContext();
|
|
232
|
+
|
|
233
|
+
it.each(assertionChain)(
|
|
234
|
+
'$assertionLabel',
|
|
235
|
+
async({ input, output }) => {
|
|
236
|
+
await findMatching(
|
|
237
|
+
{
|
|
238
|
+
dispatch,
|
|
239
|
+
getters,
|
|
240
|
+
rootGetters,
|
|
241
|
+
state,
|
|
242
|
+
commit,
|
|
243
|
+
},
|
|
244
|
+
input
|
|
245
|
+
);
|
|
246
|
+
expect(getters.urlFor).toHaveBeenCalledWith(...output.getters.urlFor);
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
});
|
|
165
250
|
});
|
|
@@ -7,6 +7,7 @@ import { classify } from '@shell/plugins/dashboard-store/classify';
|
|
|
7
7
|
import { normalizeType } from './normalize';
|
|
8
8
|
import garbageCollect from '@shell/utils/gc/gc';
|
|
9
9
|
import { addSchemaIndexFields } from '@shell/plugins/steve/schema.utils';
|
|
10
|
+
import { addParam } from '@shell/utils/url';
|
|
10
11
|
|
|
11
12
|
export const _ALL = 'all';
|
|
12
13
|
export const _MERGE = 'merge';
|
|
@@ -208,12 +209,12 @@ export default {
|
|
|
208
209
|
|
|
209
210
|
const pageFetchOpts = {
|
|
210
211
|
...opt,
|
|
211
|
-
url:
|
|
212
|
+
url: addParam(opt.url, 'limit', `${ opt.incremental }`),
|
|
212
213
|
};
|
|
213
214
|
|
|
214
215
|
// this is where we "hijack" the limit for the dispatch('request') some lines below
|
|
215
216
|
// and therefore have 2 initial requests in parallel
|
|
216
|
-
opt.url =
|
|
217
|
+
opt.url = addParam(opt.url, 'limit', '100');
|
|
217
218
|
skipHaveAll = true;
|
|
218
219
|
|
|
219
220
|
// since we are forcing a request, clear the haveAll
|
|
@@ -368,10 +369,7 @@ export default {
|
|
|
368
369
|
const typeOptions = rootGetters['type-map/optionsFor'](type);
|
|
369
370
|
|
|
370
371
|
opt = opt || {};
|
|
371
|
-
|
|
372
|
-
opt.filter = opt.filter || {};
|
|
373
|
-
opt.filter['labelSelector'] = selector;
|
|
374
|
-
|
|
372
|
+
opt.labelSelector = selector;
|
|
375
373
|
opt.url = getters.urlFor(type, null, opt);
|
|
376
374
|
opt.depaginate = typeOptions?.depaginate;
|
|
377
375
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { SCHEMA } from '@shell/config/types';
|
|
2
|
+
import { SCHEMA, COUNT } from '@shell/config/types';
|
|
3
3
|
|
|
4
4
|
import { matches } from '@shell/utils/selector';
|
|
5
5
|
import { typeMunge, typeRef, SIMPLE_TYPES } from '@shell/utils/create-yaml';
|
|
@@ -45,6 +45,29 @@ export const urlFor = (state, getters) => (type, id, opt) => {
|
|
|
45
45
|
return url;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Find the number of resources given
|
|
50
|
+
* - if the type is namespaced
|
|
51
|
+
* - if there are any counts per namespace
|
|
52
|
+
* - if there are no namespaces
|
|
53
|
+
* - if there is no total count
|
|
54
|
+
*/
|
|
55
|
+
function matchingCounts(typeObj, namespaces) {
|
|
56
|
+
// That was easy
|
|
57
|
+
if ( !typeObj.namespaced || !typeObj.byNamespace || namespaces === null || typeObj.count === null) {
|
|
58
|
+
return typeObj.count;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let out = 0;
|
|
62
|
+
|
|
63
|
+
// Otherwise start with 0 and count up
|
|
64
|
+
for ( const namespace of namespaces ) {
|
|
65
|
+
out += typeObj.byNamespace[namespace]?.count || 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
export default {
|
|
49
72
|
|
|
50
73
|
all: (state, getters, rootState) => (type) => {
|
|
@@ -345,5 +368,40 @@ export default {
|
|
|
345
368
|
|
|
346
369
|
gcIgnoreTypes: () => {
|
|
347
370
|
return {};
|
|
348
|
-
}
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* For the given type, and it's settings, find the number of resources associated with it
|
|
375
|
+
*
|
|
376
|
+
* This takes into account if the type is namespaced.
|
|
377
|
+
*
|
|
378
|
+
* @param typeObj see inners for properties. must have at least `name` (resource type)
|
|
379
|
+
*
|
|
380
|
+
*/
|
|
381
|
+
count: (state, getters, rootState, rootGetters) => (typeObj) => {
|
|
382
|
+
let _typeObj = typeObj;
|
|
383
|
+
const { name: type, count } = _typeObj;
|
|
384
|
+
|
|
385
|
+
if (!type) {
|
|
386
|
+
throw new Error(`Resource type required to calc count: ${ JSON.stringify(typeObj) }`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!count) {
|
|
390
|
+
const schema = getters.schemaFor(type);
|
|
391
|
+
const counts = getters.all(COUNT)?.[0]?.counts || {};
|
|
392
|
+
const count = counts[type];
|
|
393
|
+
|
|
394
|
+
_typeObj = {
|
|
395
|
+
count: count ? count.summary.count || 0 : null,
|
|
396
|
+
byNamespace: count ? count.namespaces : {},
|
|
397
|
+
revision: count ? count.revision : null,
|
|
398
|
+
namespaced: schema?.attributes?.namespaced
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const namespaces = Object.keys(rootGetters.activeNamespaceCache || {});
|
|
403
|
+
|
|
404
|
+
return matchingCounts(_typeObj, namespaces.length ? namespaces : null);
|
|
405
|
+
},
|
|
406
|
+
|
|
349
407
|
};
|
|
@@ -1381,7 +1381,7 @@ export default class Resource {
|
|
|
1381
1381
|
|
|
1382
1382
|
async download() {
|
|
1383
1383
|
const value = await this.followLink('view', { headers: { accept: 'application/yaml' } });
|
|
1384
|
-
const data = await this
|
|
1384
|
+
const data = await this.cleanForDownload(value.data);
|
|
1385
1385
|
|
|
1386
1386
|
downloadFile(`${ this.nameDisplay }.yaml`, data, 'application/yaml');
|
|
1387
1387
|
}
|
|
@@ -1404,7 +1404,7 @@ export default class Resource {
|
|
|
1404
1404
|
await eachLimit(items, 10, (item, idx) => {
|
|
1405
1405
|
return item.followLink('view', { headers: { accept: 'application/yaml' } } ).then(async(data) => {
|
|
1406
1406
|
const yaml = data.data || data;
|
|
1407
|
-
const cleanedYaml = await this
|
|
1407
|
+
const cleanedYaml = await this.cleanForDownload(yaml);
|
|
1408
1408
|
|
|
1409
1409
|
files[`resources/${ names[idx] }`] = cleanedYaml;
|
|
1410
1410
|
});
|
|
@@ -1481,6 +1481,10 @@ export default class Resource {
|
|
|
1481
1481
|
this.$dispatch(`cleanForDiff`, this.toJSON());
|
|
1482
1482
|
}
|
|
1483
1483
|
|
|
1484
|
+
async cleanForDownload(yaml) {
|
|
1485
|
+
return this.$dispatch(`cleanForDownload`, yaml);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1484
1488
|
yamlForSave(yaml) {
|
|
1485
1489
|
try {
|
|
1486
1490
|
const obj = jsyaml.load(yaml);
|
|
@@ -37,6 +37,7 @@ describe('steve: getters', () => {
|
|
|
37
37
|
expect(urlForGetter('typeFoo', undefined, { namespaced: ['nsBar', 'nsBaz'] })).toBe('protocol/urlFoo');
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
|
+
|
|
40
41
|
describe('steve > getters > urlOptions', () => {
|
|
41
42
|
// we're not testing function output based off of state or getter inputs here since they are dependencies
|
|
42
43
|
const state = { config: { baseUrl: 'protocol' } };
|
|
@@ -71,12 +72,21 @@ describe('steve: getters', () => {
|
|
|
71
72
|
it('returns a string with a single filter statement applied and formatted for steve if a single filter statement is applied and the url starts with "/v1"', () => {
|
|
72
73
|
expect(urlOptionsGetter('/v1/foo', { filter: { bar: 'baz' } })).toBe('/v1/foo?filter=bar=baz&exclude=metadata.managedFields');
|
|
73
74
|
});
|
|
75
|
+
it('returns a string with a single filter statement applied and formatted for steve if a single filter statement is applied and the url starts with "/k8s/clusters/c-m-n4x45x4b/v1/"', () => {
|
|
76
|
+
expect(urlOptionsGetter('/k8s/clusters/c-m-n4x45x4b/v1/foo', { filter: { bar: 'baz' } })).toBe('/k8s/clusters/c-m-n4x45x4b/v1/foo?filter=bar=baz&exclude=metadata.managedFields');
|
|
77
|
+
});
|
|
74
78
|
it('returns a string with a multiple filter statements applied if a single filter statement is applied', () => {
|
|
75
79
|
expect(urlOptionsGetter('foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('foo?bar=baz&far=faz');
|
|
76
80
|
});
|
|
77
81
|
it('returns a string with a multiple filter statements applied and formatted for steve if a single filter statement is applied and the url starts with "/v1"', () => {
|
|
78
82
|
expect(urlOptionsGetter('/v1/foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('/v1/foo?filter=bar=baz&far=faz&exclude=metadata.managedFields');
|
|
79
83
|
});
|
|
84
|
+
it('returns a string with a labelSelector and formatted for steve if the url starts with "/v1"', () => {
|
|
85
|
+
expect(urlOptionsGetter('/v1/foo', { labelSelector: 'a=b' })).toBe('/v1/foo?labelSelector=a=b&exclude=metadata.managedFields');
|
|
86
|
+
});
|
|
87
|
+
it('returns a string with a labelSelector and filter, and formatted for steve if the url starts with "/v1"', () => {
|
|
88
|
+
expect(urlOptionsGetter('/v1/foo', { labelSelector: 'a=b', filter: { bar: 'baz', far: 'faz' } })).toBe('/v1/foo?labelSelector=a=b&filter=bar=baz&far=faz&exclude=metadata.managedFields');
|
|
89
|
+
});
|
|
80
90
|
it('returns a string with an exclude statement for "bar" and "metadata.managedFields" if excludeFields is a single element array with the string "bar" and the url starts with "/v1/"', () => {
|
|
81
91
|
expect(urlOptionsGetter('/v1/foo', { excludeFields: ['bar'] })).toBe('/v1/foo?exclude=bar&exclude=metadata.managedFields');
|
|
82
92
|
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';
|
|
2
|
+
|
|
3
|
+
describe('steve: ressource-utils', () => {
|
|
4
|
+
it('should do nothing if the yaml is not passed', () => {
|
|
5
|
+
const r = steveCleanForDownload();
|
|
6
|
+
|
|
7
|
+
expect(r).toBeUndefined();
|
|
8
|
+
});
|
|
9
|
+
it('should remove all default rootKeys', () => {
|
|
10
|
+
const expectedYamlStr = `apiVersion: v1
|
|
11
|
+
kind: ConfigMap
|
|
12
|
+
metadata:
|
|
13
|
+
name: my-configmap
|
|
14
|
+
`;
|
|
15
|
+
const yamlStr = `
|
|
16
|
+
id: test_id
|
|
17
|
+
links:
|
|
18
|
+
view: https://example.com2
|
|
19
|
+
type: test_type
|
|
20
|
+
actions:
|
|
21
|
+
remove: https://example.com
|
|
22
|
+
${ expectedYamlStr }
|
|
23
|
+
`;
|
|
24
|
+
const cleanedYamlStr = steveCleanForDownload(yamlStr);
|
|
25
|
+
|
|
26
|
+
expect(cleanedYamlStr).toBe(expectedYamlStr);
|
|
27
|
+
});
|
|
28
|
+
it('should remove all the specified root keys', () => {
|
|
29
|
+
const part = `apiVersion: v1
|
|
30
|
+
kind: Secret
|
|
31
|
+
metadata:
|
|
32
|
+
name: my-secret`;
|
|
33
|
+
|
|
34
|
+
const rootKeyToYamlStringMap = {
|
|
35
|
+
id: 'id: test_id',
|
|
36
|
+
links: `links:
|
|
37
|
+
view: https://example.com`,
|
|
38
|
+
actions: `actions:
|
|
39
|
+
remove: https://example.com`,
|
|
40
|
+
type: 'type: Opaque'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const entries = Object.entries(rootKeyToYamlStringMap);
|
|
44
|
+
const yamlStr = `${ part }
|
|
45
|
+
${ entries.map(([_, str]) => str).join('\n') }`;
|
|
46
|
+
|
|
47
|
+
entries.forEach(([key, str]) => {
|
|
48
|
+
const expectedYamlStr = `${ part }
|
|
49
|
+
${ entries.filter(([k]) => k !== key).map(([_, str]) => str).join('\n') }\n`;
|
|
50
|
+
const cleanedYamlStr = steveCleanForDownload(yamlStr, { rootKeys: [key] });
|
|
51
|
+
|
|
52
|
+
expect(cleanedYamlStr).toBe(expectedYamlStr);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it('should remove all default metadata keys', () => {
|
|
56
|
+
const expectedYamlStr = `apiVersion: v1
|
|
57
|
+
kind: ConfigMap
|
|
58
|
+
metadata:
|
|
59
|
+
name: my-configmap
|
|
60
|
+
`;
|
|
61
|
+
const yamlStr = `apiVersion: v1
|
|
62
|
+
kind: ConfigMap
|
|
63
|
+
metadata:
|
|
64
|
+
name: my-configmap
|
|
65
|
+
fields:
|
|
66
|
+
- kube-root-ca.crt
|
|
67
|
+
- 1
|
|
68
|
+
- 7d23h
|
|
69
|
+
relationships:
|
|
70
|
+
- rel: 'owner'
|
|
71
|
+
state: 'active'
|
|
72
|
+
`;
|
|
73
|
+
const cleanedYamlStr = steveCleanForDownload(yamlStr);
|
|
74
|
+
|
|
75
|
+
expect(cleanedYamlStr).toBe(expectedYamlStr);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should remove all the specified metadata keys', () => {
|
|
79
|
+
const part = `apiVersion: v1
|
|
80
|
+
kind: ConfigMap
|
|
81
|
+
metadata:
|
|
82
|
+
name: my-configmap`;
|
|
83
|
+
|
|
84
|
+
const metadataKeyToYamlStringMap = {
|
|
85
|
+
fields:
|
|
86
|
+
` fields:
|
|
87
|
+
- kube-root-ca.crt
|
|
88
|
+
- 1
|
|
89
|
+
- 7d23h`,
|
|
90
|
+
relationships:
|
|
91
|
+
` relationships:
|
|
92
|
+
- rel: owner`,
|
|
93
|
+
state: ` state: active`
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const entries = Object.entries(metadataKeyToYamlStringMap);
|
|
97
|
+
const yamlStr = `${ part }
|
|
98
|
+
${ entries.map(([_, str]) => str).join('\n') }`;
|
|
99
|
+
|
|
100
|
+
entries.forEach(([key, str]) => {
|
|
101
|
+
const expectedYamlStr = `${ part }
|
|
102
|
+
${ entries.filter(([k]) => k !== key).map(([_, str]) => str).join('\n') }\n`;
|
|
103
|
+
const cleanedYamlStr = steveCleanForDownload(yamlStr, { metadataKeys: [key] });
|
|
104
|
+
|
|
105
|
+
expect(cleanedYamlStr).toBe(expectedYamlStr);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('should remove all defalut condition keys', () => {
|
|
109
|
+
const expectedYamlStr = `apiVersion: v1
|
|
110
|
+
kind: ConfigMap
|
|
111
|
+
metadata:
|
|
112
|
+
name: my-configmap
|
|
113
|
+
status:
|
|
114
|
+
conditions:
|
|
115
|
+
- {}
|
|
116
|
+
- {}
|
|
117
|
+
- message: message
|
|
118
|
+
`;
|
|
119
|
+
const yamlStr = `apiVersion: v1
|
|
120
|
+
kind: ConfigMap
|
|
121
|
+
metadata:
|
|
122
|
+
name: my-configmap
|
|
123
|
+
status:
|
|
124
|
+
conditions:
|
|
125
|
+
- error: 'error'
|
|
126
|
+
- transitioning: false
|
|
127
|
+
- message: message
|
|
128
|
+
`;
|
|
129
|
+
const cleanedYamlStr = steveCleanForDownload(yamlStr);
|
|
130
|
+
|
|
131
|
+
expect(cleanedYamlStr).toBe(expectedYamlStr);
|
|
132
|
+
});
|
|
133
|
+
it('should remove all the specified condition keys', () => {
|
|
134
|
+
const part = `apiVersion: v1
|
|
135
|
+
kind: ConfigMap
|
|
136
|
+
metadata:
|
|
137
|
+
name: my-configmap
|
|
138
|
+
status:
|
|
139
|
+
conditions:
|
|
140
|
+
- message: message`;
|
|
141
|
+
|
|
142
|
+
const conditionKeyToYamlStringMap = {
|
|
143
|
+
error: ' - error: error',
|
|
144
|
+
transitioning: ' - transitioning: false'
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const entries = Object.entries(conditionKeyToYamlStringMap);
|
|
148
|
+
const yamlStr = `${ part }
|
|
149
|
+
${ entries.map(([_, str]) => str).join('\n') }`;
|
|
150
|
+
|
|
151
|
+
entries.forEach(([key, str]) => {
|
|
152
|
+
const expectedYamlStr = `${ part }
|
|
153
|
+
${ entries.map(([k, str]) => k === key ? ' - {}' : str).join('\n') }\n`;
|
|
154
|
+
const cleanedYamlStr = steveCleanForDownload(yamlStr, { conditionKeys: [key] });
|
|
155
|
+
|
|
156
|
+
expect(cleanedYamlStr).toBe(expectedYamlStr);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|