@rancher/shell 0.3.22 → 0.3.23

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.
Files changed (30) hide show
  1. package/babel.config.js +3 -0
  2. package/components/SortableTable/paging.js +10 -0
  3. package/components/form/GitPicker.vue +16 -0
  4. package/components/form/SelectOrCreateAuthSecret.vue +9 -3
  5. package/edit/resources.cattle.io.backup.vue +3 -1
  6. package/edit/resources.cattle.io.restore.vue +3 -1
  7. package/package.json +1 -1
  8. package/rancher-components/BadgeState/BadgeState.vue +1 -5
  9. package/rancher-components/Banner/Banner.test.ts +1 -51
  10. package/rancher-components/Banner/Banner.vue +53 -134
  11. package/rancher-components/Card/Card.vue +7 -24
  12. package/rancher-components/Form/Checkbox/Checkbox.test.ts +29 -20
  13. package/rancher-components/Form/Checkbox/Checkbox.vue +20 -45
  14. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +8 -2
  15. package/rancher-components/Form/LabeledInput/LabeledInput.vue +10 -22
  16. package/rancher-components/Form/Radio/RadioButton.vue +13 -30
  17. package/rancher-components/Form/Radio/RadioGroup.vue +7 -26
  18. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
  19. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +38 -25
  20. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +11 -23
  21. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +5 -19
  22. package/rancher-components/StringList/StringList.test.ts +49 -453
  23. package/rancher-components/StringList/StringList.vue +58 -92
  24. package/types/shell/index.d.ts +15 -9
  25. package/utils/__tests__/formatter.test.ts +77 -0
  26. package/utils/formatter.js +11 -0
  27. package/vue.config.js +6 -2
  28. package/rancher-components/Card/Card.test.ts +0 -37
  29. package/rancher-components/Form/Radio/RadioButton.test.ts +0 -31
  30. package/yarn-error.log +0 -200
@@ -30,10 +30,10 @@ const CLASS = {
30
30
  * Manage a list of strings
31
31
  */
32
32
  export default Vue.extend({
33
-
34
- name: 'StringList',
35
33
  components: { LabeledInput },
36
34
 
35
+ name: 'StringList',
36
+
37
37
  props: {
38
38
  /**
39
39
  * The items source
@@ -85,11 +85,11 @@ export default Vue.extend({
85
85
  },
86
86
  data() {
87
87
  return {
88
- value: null as string | null,
89
- selected: null as string | null,
90
- editedItem: null as string | null,
91
- isCreateItem: false,
92
- errors: { duplicate: false } as Record<Error, boolean>
88
+ value: null as string | null,
89
+ selected: null as string | null,
90
+ isEditItem: null as string | null,
91
+ isCreateItem: false,
92
+ errors: { duplicate: false } as Record<Error, boolean>
93
93
  };
94
94
  },
95
95
 
@@ -100,8 +100,8 @@ export default Vue.extend({
100
100
  */
101
101
  errorMessagesArray(): string[] {
102
102
  return (Object.keys(this.errors) as Error[])
103
- .filter((f) => this.errors[f] && this.errorMessages[f])
104
- .map((k) => this.errorMessages[k]);
103
+ .filter(f => !!(this.errors)[f])
104
+ .map(k => this.errorMessages[k]);
105
105
  },
106
106
  },
107
107
 
@@ -113,35 +113,23 @@ export default Vue.extend({
113
113
  this.toggleEditMode(false);
114
114
  this.toggleCreateMode(false);
115
115
  },
116
- value(val) {
117
- this.$emit('type:item', val);
118
- },
119
- errors: {
120
- handler(val) {
121
- this.$emit('errors', val);
122
- },
123
- deep: true
124
- }
125
116
  },
126
117
 
127
118
  methods: {
128
119
  onChange(value: string) {
129
120
  this.value = value;
130
-
131
- const items = [
132
- ...this.items,
133
- this.value
134
- ];
135
-
121
+ /**
122
+ * Remove duplicate error when a new value is typed
123
+ */
136
124
  this.toggleError(
137
125
  'duplicate',
138
- hasDuplicatedStrings(items, this.caseSensitive),
139
- this.isCreateItem ? INPUT.create : INPUT.edit
126
+ false,
127
+ this.isCreateItem ? INPUT.create : INPUT.edit,
140
128
  );
141
129
  },
142
130
 
143
131
  onSelect(item: string) {
144
- if (this.readonly || this.isCreateItem || this.editedItem === item) {
132
+ if (this.isCreateItem || this.isEditItem === item) {
145
133
  return;
146
134
  }
147
135
  this.selected = item;
@@ -172,7 +160,7 @@ export default Vue.extend({
172
160
  },
173
161
 
174
162
  onClickEmptyBody() {
175
- if (!this.isCreateItem && !this.editedItem) {
163
+ if (!this.isCreateItem && !this.isEditItem) {
176
164
  this.toggleCreateMode(true);
177
165
  }
178
166
  },
@@ -188,43 +176,38 @@ export default Vue.extend({
188
176
 
189
177
  return;
190
178
  }
191
- if (this.editedItem) {
192
- this.deleteAndSelectNext(this.editedItem);
179
+ if (this.isEditItem) {
193
180
  this.toggleEditMode(false);
194
181
 
195
182
  return;
196
183
  }
197
184
  if (this.selected) {
198
- this.deleteAndSelectNext(this.selected);
199
- }
200
- },
201
-
202
- deleteAndSelectNext(currItem: string) {
203
- const index = findStringIndex(this.items, currItem, false);
185
+ const index = findStringIndex(this.items, this.selected, false);
204
186
 
205
- if (index !== -1) {
206
- /**
207
- * Select the next item in the list.
208
- */
209
- const item = (this.items[index + 1] || this.items[index - 1]);
187
+ if (index !== -1) {
188
+ /**
189
+ * Select the next item in the list when an item is to be deleted.
190
+ */
191
+ const item = (this.items[index + 1] || this.items[index - 1]);
210
192
 
211
- this.onSelect(item);
212
- this.setFocus(item);
193
+ this.onSelect(item);
194
+ this.setFocus(item);
213
195
 
214
- this.deleteItem(this.items[index]);
196
+ this.deleteItem(this.items[index]);
197
+ }
215
198
  }
216
199
  },
217
200
 
218
201
  setFocus(refId: string) {
219
- this.$nextTick(() => (this.getElemByRef(refId) as Vue & HTMLElement)?.focus());
202
+ this.$nextTick(() => this.getElemByRef(refId)?.focus());
220
203
  },
221
204
 
222
205
  /**
223
206
  * Move scrollbar when the selected item is over the top or bottom side of the box
224
207
  */
225
208
  moveScrollbar(arrow: Arrow, value?: number) {
226
- const box = this.getElemByRef(BOX) as HTMLElement;
227
- const item = this.getElemByRef(this.selected || '') as HTMLElement;
209
+ const box = this.getElemByRef(BOX);
210
+ const item = this.getElemByRef(this.selected || '');
228
211
 
229
212
  if (box && item && item.className.includes(CLASS.item)) {
230
213
  const boxRect = box.getClientRects()[0];
@@ -246,14 +229,13 @@ export default Vue.extend({
246
229
  */
247
230
  toggleError(type: Error, val: boolean, refId?: string) {
248
231
  this.errors[type] = val;
249
-
250
232
  if (refId) {
251
233
  this.toggleErrorClass(refId, val);
252
234
  }
253
235
  },
254
236
 
255
237
  toggleErrorClass(refId: string, val: boolean) {
256
- const input = (this.getElemByRef(refId) as Vue)?.$el;
238
+ const input = this.getElemByRef(refId)?.$el;
257
239
 
258
240
  if (input) {
259
241
  if (val) {
@@ -268,11 +250,7 @@ export default Vue.extend({
268
250
  * Show/Hide the input line to create new item
269
251
  */
270
252
  toggleCreateMode(show: boolean) {
271
- if (this.readonly) {
272
- return;
273
- }
274
253
  if (show) {
275
- this.toggleEditMode(false);
276
254
  this.value = '';
277
255
 
278
256
  this.isCreateItem = true;
@@ -290,34 +268,31 @@ export default Vue.extend({
290
268
  * Show/Hide the in-line editing to edit an existing item
291
269
  */
292
270
  toggleEditMode(show: boolean, item?: string) {
293
- if (this.readonly) {
294
- return;
295
- }
296
271
  if (show) {
297
272
  this.toggleCreateMode(false);
298
- this.value = this.editedItem;
273
+ this.value = this.isEditItem;
299
274
 
300
- this.editedItem = item || '';
275
+ this.isEditItem = item || '';
301
276
  this.setFocus(INPUT.edit);
302
277
  } else {
303
278
  this.value = null;
304
279
  this.toggleError('duplicate', false);
305
280
  this.onSelectLeave();
306
281
 
307
- this.editedItem = null;
282
+ this.isEditItem = null;
308
283
  }
309
284
  },
310
285
 
311
286
  getElemByRef(id: string) {
312
287
  const ref = this.$refs[id];
313
288
 
314
- return Array.isArray(ref) ? ref[0] : ref;
289
+ return (Array.isArray(ref) ? ref[0] : ref) as any;
315
290
  },
316
291
 
317
292
  /**
318
293
  * Create a new item and insert in the items list
319
294
  */
320
- saveItem(closeInput = true) {
295
+ saveItem() {
321
296
  const value = this.value?.trim();
322
297
 
323
298
  if (value) {
@@ -326,20 +301,21 @@ export default Vue.extend({
326
301
  value,
327
302
  ];
328
303
 
329
- if (!hasDuplicatedStrings(items, this.caseSensitive)) {
330
- this.updateItems(items);
304
+ if (hasDuplicatedStrings(items, this.caseSensitive)) {
305
+ this.toggleError('duplicate', true, INPUT.create);
306
+
307
+ return;
331
308
  }
332
- }
333
309
 
334
- if (closeInput) {
335
- this.toggleCreateMode(false);
310
+ this.updateItems(items);
336
311
  }
312
+ this.toggleCreateMode(false);
337
313
  },
338
314
 
339
315
  /**
340
316
  * Update an existing item in the items list
341
317
  */
342
- updateItem(item: string, closeInput = true) {
318
+ updateItem(item: string) {
343
319
  const value = this.value?.trim();
344
320
 
345
321
  if (value) {
@@ -350,21 +326,22 @@ export default Vue.extend({
350
326
  items[index] = value;
351
327
  }
352
328
 
353
- if (!hasDuplicatedStrings(items, this.caseSensitive)) {
354
- this.updateItems(items);
329
+ if (hasDuplicatedStrings(items, this.caseSensitive)) {
330
+ this.toggleError('duplicate', true, INPUT.edit);
331
+
332
+ return;
355
333
  }
356
- }
357
334
 
358
- if (closeInput) {
359
- this.toggleEditMode(false);
335
+ this.updateItems(items);
360
336
  }
337
+ this.toggleEditMode(false);
361
338
  },
362
339
 
363
340
  /**
364
341
  * Remove an item from items list
365
342
  */
366
343
  deleteItem(item?: string) {
367
- const items = this.items.filter((f) => f !== item);
344
+ const items = this.items.filter(f => f !== item);
368
345
 
369
346
  this.updateItems(items);
370
347
  },
@@ -410,20 +387,19 @@ export default Vue.extend({
410
387
  @blur="onSelectLeave(item)"
411
388
  >
412
389
  <span
413
- v-if="!editedItem || editedItem !== item"
390
+ v-if="!isEditItem || isEditItem !== item"
414
391
  class="label static"
415
392
  >
416
393
  {{ item }}
417
394
  </span>
418
395
  <LabeledInput
419
- v-if="editedItem && editedItem === item"
396
+ v-if="isEditItem && isEditItem === item"
420
397
  ref="item-edit"
421
- :data-testid="`item-edit-${item}`"
422
398
  class="edit-input static"
423
399
  :value="value != null ? value : item"
424
400
  @input="onChange($event)"
425
- @blur.prevent="updateItem(item)"
426
- @keydown.native.enter="updateItem(item, !errors.duplicate)"
401
+ @blur.prevent="toggleEditMode(false)"
402
+ @keydown.native.enter="updateItem(item)"
427
403
  />
428
404
  </div>
429
405
  <div
@@ -432,14 +408,12 @@ export default Vue.extend({
432
408
  >
433
409
  <LabeledInput
434
410
  ref="item-create"
435
- data-testid="item-create"
436
411
  class="create-input static"
437
412
  type="text"
438
413
  :value="value"
439
414
  :placeholder="placeholder"
440
415
  @input="onChange($event)"
441
- @blur.prevent="saveItem"
442
- @keydown.native.enter="saveItem(!errors.duplicate)"
416
+ @keydown.native.enter="saveItem"
443
417
  />
444
418
  </div>
445
419
  </div>
@@ -453,32 +427,25 @@ export default Vue.extend({
453
427
  class="action-buttons"
454
428
  >
455
429
  <button
456
- data-testid="button-remove"
457
430
  class="btn btn-sm role-tertiary remove-button"
458
- :disabled="!selected && !isCreateItem && !editedItem"
431
+ :disabled="!selected && !isCreateItem && !isEditItem"
459
432
  @mousedown.prevent="onClickMinusButton"
460
433
  >
461
434
  <span class="icon icon-minus icon-sm" />
462
435
  </button>
463
436
  <button
464
- data-testid="button-add"
465
437
  class="btn btn-sm role-tertiary add-button"
466
- :disabled="isCreateItem || editedItem"
438
+ :disabled="isCreateItem"
467
439
  @click.prevent="onClickPlusButton"
468
440
  >
469
441
  <span class="icon icon-plus icon-sm" />
470
442
  </button>
471
443
  </div>
472
444
  <div class="messages">
473
- <i
474
- v-if="errorMessagesArray.length > 0"
475
- data-testid="i-warning-icon"
476
- class="icon icon-warning icon-lg"
477
- />
445
+ <i v-if="errorMessagesArray.length > 0" class="icon icon-warning icon-lg" />
478
446
  <span
479
447
  v-for="(msg, idx) in errorMessagesArray"
480
448
  :key="idx"
481
- :data-testid="`span-error-message-${msg}`"
482
449
  class="error"
483
450
  >
484
451
  {{ idx > 0 ? '; ' : '' }}
@@ -532,7 +499,6 @@ export default Vue.extend({
532
499
  width: auto;
533
500
  user-select: none;
534
501
  overflow: hidden;
535
- white-space: no-wrap;
536
502
  text-overflow: ellipsis;
537
503
  padding-top: 1px;
538
504
  }
@@ -3376,6 +3376,12 @@ export function haveSetFavIcon(): boolean;
3376
3376
  export function setFavIcon(store: any): void;
3377
3377
  }
3378
3378
 
3379
+ // @shell/utils/formatter
3380
+
3381
+ declare module '@shell/utils/formatter' {
3382
+ export function formatEncryptionSecretNames(secrets: any, chartNamespace: any): any;
3383
+ }
3384
+
3379
3385
  // @shell/utils/grafana
3380
3386
 
3381
3387
  declare module '@shell/utils/grafana' {
@@ -3599,35 +3605,35 @@ export namespace KEY {
3599
3605
  }
3600
3606
  }
3601
3607
 
3602
- // @shell/utils/poller-sequential
3608
+ // @shell/utils/poller
3603
3609
 
3604
- declare module '@shell/utils/poller-sequential' {
3605
- export default class PollerSequential {
3610
+ declare module '@shell/utils/poller' {
3611
+ export default class Poller {
3606
3612
  constructor(fn: any, pollRateMs: any, maxRetries?: number);
3607
3613
  fn: any;
3608
3614
  pollRateMs: any;
3609
3615
  maxRetries: number;
3610
- timeoutId: any;
3616
+ intervalId: any;
3611
3617
  tryCount: number;
3612
3618
  start(): void;
3613
3619
  stop(): void;
3614
- _poll(): void;
3615
3620
  _intervalMethod(): Promise<void>;
3616
3621
  }
3617
3622
  }
3618
3623
 
3619
- // @shell/utils/poller
3624
+ // @shell/utils/poller-sequential
3620
3625
 
3621
- declare module '@shell/utils/poller' {
3622
- export default class Poller {
3626
+ declare module '@shell/utils/poller-sequential' {
3627
+ export default class PollerSequential {
3623
3628
  constructor(fn: any, pollRateMs: any, maxRetries?: number);
3624
3629
  fn: any;
3625
3630
  pollRateMs: any;
3626
3631
  maxRetries: number;
3627
- intervalId: any;
3632
+ timeoutId: any;
3628
3633
  tryCount: number;
3629
3634
  start(): void;
3630
3635
  stop(): void;
3636
+ _poll(): void;
3631
3637
  _intervalMethod(): Promise<void>;
3632
3638
  }
3633
3639
  }
@@ -0,0 +1,77 @@
1
+ import { formatEncryptionSecretNames } from '@shell/utils/formatter';
2
+
3
+ describe('formatter', () => {
4
+ const secrets = [
5
+ {
6
+ id: 'test5',
7
+ _type: 'Opaque',
8
+ data: { hash: 'test5', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
9
+ metadata: {
10
+ name: 'test5',
11
+ namespace: 'test',
12
+ state: {
13
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
14
+ }
15
+ }
16
+ },
17
+ {
18
+ id: 'test2',
19
+ _type: 'Opaque',
20
+ data: { hash: 'test', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
21
+ metadata: {
22
+ name: 'test2',
23
+ namespace: 'test',
24
+ state: {
25
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
26
+ }
27
+ }
28
+ },
29
+ {
30
+ id: 'test4',
31
+ _type: 'Opaque',
32
+ data: { hash: 'test4' },
33
+ metadata: {
34
+ name: 'test4',
35
+ namespace: 'test',
36
+ state: {
37
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
38
+ }
39
+ }
40
+ },
41
+ {
42
+ id: 'test1',
43
+ _type: 'Custom',
44
+ data: { hash: 'test1', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
45
+ metadata: {
46
+ name: 'test1',
47
+ namespace: 'test',
48
+ state: {
49
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
50
+ },
51
+ }
52
+ },
53
+ {
54
+ id: 'test6',
55
+ _type: 'Opaque',
56
+ data: { hash: 'test5', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
57
+ metadata: {
58
+ name: 'test5',
59
+ namespace: 'test',
60
+ state: {
61
+ error: true, message: 'Failed', name: 'active', transitioning: true
62
+ }
63
+ }
64
+ }];
65
+ const chart = 'test';
66
+
67
+ it.each([[chart, 2], ['test1', 0]])('should show correct number of secrets', (chartVal: string, result: number) => {
68
+ const res = formatEncryptionSecretNames(secrets, chartVal);
69
+
70
+ expect(res).toHaveLength(result);
71
+ });
72
+ it('should return correct results in a correct order', () => {
73
+ const res = formatEncryptionSecretNames(secrets, chart);
74
+
75
+ expect(res).toStrictEqual(['test2', 'test5']);
76
+ });
77
+ });
@@ -0,0 +1,11 @@
1
+
2
+ import { SECRET_TYPES } from '@shell/config/secret';
3
+
4
+ export function formatEncryptionSecretNames(secrets, chartNamespace) {
5
+ return secrets.filter(
6
+ (secret) => (secret.data || {})['encryption-provider-config.yaml'] &&
7
+ secret.metadata.namespace === chartNamespace &&
8
+ !secret.metadata?.state?.error &&
9
+ secret._type === SECRET_TYPES.OPAQUE
10
+ ).map((secret) => secret.metadata.name).sort();
11
+ }
package/vue.config.js CHANGED
@@ -72,7 +72,9 @@ module.exports = function(dir, _appConfig) {
72
72
  ];
73
73
 
74
74
  if (instrumentCode) {
75
- babelPlugins.push('babel-plugin-istanbul');
75
+ babelPlugins.push([
76
+ 'babel-plugin-istanbul', { extension: ['.js', '.vue'] }, 'add-vue'
77
+ ]);
76
78
 
77
79
  console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
78
80
  }
@@ -451,7 +453,9 @@ module.exports = function(dir, _appConfig) {
451
453
  ];
452
454
 
453
455
  if (instrumentCode) {
454
- babelPlugins.push('babel-plugin-istanbul');
456
+ babelPlugins.push([
457
+ 'babel-plugin-istanbul', { extension: ['.js', '.vue'] }, 'add-vue'
458
+ ]);
455
459
 
456
460
  console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
457
461
  }
@@ -1,37 +0,0 @@
1
- import { mount } from '@vue/test-utils';
2
- import { Card } from './index';
3
-
4
- describe('component: Card', () => {
5
- const title = 'Card title';
6
- const body = 'Card body';
7
-
8
- it('should have a card title', () => {
9
- const wrapper = mount(Card, {
10
- propsData: { title },
11
- slots: { title: '<div>Card title</div>' }
12
- });
13
-
14
- const element = wrapper.find('[data-testid="card-title-slot"]');
15
-
16
- expect(element.exists()).toBe(true);
17
- expect(element.text()).toBe(title);
18
- });
19
-
20
- it('should have a card body', () => {
21
- const wrapper = mount(Card, {
22
- propsData: { body },
23
- slots: { body: '<div>Card body</div>' }
24
- });
25
- const element = wrapper.find('[data-testid="card-body-slot"]');
26
-
27
- expect(element.exists()).toBe(true);
28
- expect(element.text()).toBe(body);
29
- });
30
-
31
- it('should display the default card actions', () => {
32
- const wrapper = mount(Card);
33
- const element = wrapper.find('[data-testid="card-actions-slot"]');
34
-
35
- expect(element.exists()).toBe(true);
36
- });
37
- });
@@ -1,31 +0,0 @@
1
- import { shallowMount } from '@vue/test-utils';
2
- import { RadioButton } from './index';
3
- import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
4
-
5
- describe('radioButton.vue', () => {
6
- it('renders label slot contents', () => {
7
- const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' } });
8
-
9
- expect(wrapper.find('.radio-label').text()).toBe('Test Label');
10
- });
11
-
12
- it('renders label prop contents', () => {
13
- const wrapper = shallowMount(
14
- RadioButton,
15
- {
16
- directives: { cleanHtmlDirective },
17
- propsData: { label: 'Test Label' }
18
- });
19
-
20
- expect(wrapper.find('.radio-label').text()).toBe('Test Label');
21
- });
22
-
23
- it('renders slot contents when both slot and label prop are provided', () => {
24
- const wrapper = shallowMount(RadioButton, {
25
- slots: { label: 'Test Label - Slot' },
26
- propsData: { label: 'Test Label - Props' },
27
- });
28
-
29
- expect(wrapper.find('.radio-label').text()).toBe('Test Label - Slot');
30
- });
31
- });