@rancher/shell 3.0.9-rc.6 → 3.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/base/_color.scss +4 -0
- package/assets/styles/themes/_light.scss +6 -6
- package/assets/styles/themes/_modern.scss +14 -6
- package/assets/translations/en-us.yaml +2 -5
- package/components/CopyToClipboard.vue +28 -0
- package/components/CopyToClipboardText.vue +4 -0
- package/components/CruResource.vue +1 -0
- package/components/GlobalRoleBindings.vue +1 -5
- package/components/IconOrSvg.vue +61 -42
- package/components/ResourceDetail/index.vue +0 -21
- package/components/SortableTable/index.vue +2 -2
- package/components/__tests__/CruResource.test.ts +35 -1
- package/components/form/BannerSettings.vue +2 -2
- package/components/form/NotificationSettings.vue +2 -2
- package/composables/useIsNewDetailPageEnabled.test.ts +98 -0
- package/composables/useIsNewDetailPageEnabled.ts +12 -0
- package/config/product/explorer.js +11 -1
- package/config/product/manager.js +0 -1
- package/config/table-headers.js +0 -9
- package/config/types.js +0 -1
- package/detail/fleet.cattle.io.cluster.vue +1 -1
- package/dialog/FeatureFlagListDialog.vue +1 -1
- package/edit/auth/github-app-steps.vue +2 -0
- package/edit/auth/github-steps.vue +2 -0
- package/edit/catalog.cattle.io.clusterrepo.vue +1 -1
- package/edit/management.cattle.io.user.vue +60 -35
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
- package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +14 -12
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -5
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +18 -3
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +100 -76
- package/edit/token.vue +29 -68
- package/list/provisioning.cattle.io.cluster.vue +2 -2
- package/models/__tests__/chart.test.ts +2 -2
- package/models/chart.js +3 -3
- package/models/token.js +0 -4
- package/package.json +8 -8
- package/pages/account/index.vue +67 -96
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +108 -24
- package/pages/c/_cluster/apps/charts/index.vue +1 -11
- package/pages/c/_cluster/explorer/index.vue +2 -19
- package/pages/c/_cluster/explorer/tools/index.vue +1 -1
- package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/pkg/auto-import.js +41 -0
- package/plugins/dashboard-store/resource-class.js +2 -2
- package/plugins/steve/__tests__/steve-class.test.ts +1 -1
- package/plugins/steve/steve-class.js +3 -3
- package/plugins/steve/steve-pagination-utils.ts +2 -4
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +7 -7
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +5 -2
- package/rancher-components/RcIcon/types.ts +2 -2
- package/rancher-components/RcItemCard/RcItemCard.vue +8 -1
- package/rancher-components/RcSection/RcSection.test.ts +323 -0
- package/rancher-components/RcSection/RcSection.vue +252 -0
- package/rancher-components/RcSection/RcSectionActions.test.ts +212 -0
- package/rancher-components/RcSection/RcSectionActions.vue +85 -0
- package/rancher-components/RcSection/RcSectionBadges.test.ts +149 -0
- package/rancher-components/RcSection/RcSectionBadges.vue +29 -0
- package/rancher-components/RcSection/index.ts +12 -0
- package/rancher-components/RcSection/types.ts +86 -0
- package/scripts/test-plugins-build.sh +5 -4
- package/types/shell/index.d.ts +93 -108
- package/utils/style.ts +17 -0
- package/utils/svg-filter.js +4 -3
- package/utils/units.js +14 -5
- package/models/ext.cattle.io.token.js +0 -48
|
@@ -6,7 +6,7 @@ import ResourceTabs from '@shell/components/form/ResourceTabs';
|
|
|
6
6
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
7
7
|
import { MANAGEMENT, FLEET, SCHEMA } from '@shell/config/types';
|
|
8
8
|
import { FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
|
|
9
|
-
import { allHash } from 'utils/promise';
|
|
9
|
+
import { allHash } from '@shell/utils/promise';
|
|
10
10
|
|
|
11
11
|
export default {
|
|
12
12
|
name: 'FleetDetailCluster',
|
|
@@ -134,7 +134,7 @@ export default {
|
|
|
134
134
|
this.waiting = false;
|
|
135
135
|
this.close();
|
|
136
136
|
|
|
137
|
-
await this.$store.dispatch('management/findAll', { type: this.
|
|
137
|
+
await this.$store.dispatch('management/findAll', { type: this.update.type, opt: { force: true } });
|
|
138
138
|
}
|
|
139
139
|
} catch (e) {}
|
|
140
140
|
|
|
@@ -49,6 +49,7 @@ defineProps<{
|
|
|
49
49
|
<CopyToClipboard
|
|
50
50
|
label-as="tooltip"
|
|
51
51
|
:text="tArgs.serverUrl"
|
|
52
|
+
:aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
|
|
52
53
|
class="icon-btn"
|
|
53
54
|
action-color="bg-transparent"
|
|
54
55
|
/>
|
|
@@ -67,6 +68,7 @@ defineProps<{
|
|
|
67
68
|
<CopyToClipboard
|
|
68
69
|
:text="t(`authConfig.${name}.form.callback.value`, tArgs, true)"
|
|
69
70
|
label-as="tooltip"
|
|
71
|
+
:aria-label="t('generic.copyValueToClipboard', { value: t(`authConfig.${name}.form.callback.value`, tArgs, true) })"
|
|
70
72
|
class="icon-btn"
|
|
71
73
|
action-color="bg-transparent"
|
|
72
74
|
/>
|
|
@@ -40,6 +40,7 @@ defineProps<{
|
|
|
40
40
|
<b>{{ t(`authConfig.${name}.form.homepage.label`) }}</b>: {{ tArgs.serverUrl }} <CopyToClipboard
|
|
41
41
|
label-as="tooltip"
|
|
42
42
|
:text="tArgs.serverUrl"
|
|
43
|
+
:aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
|
|
43
44
|
class="icon-btn"
|
|
44
45
|
action-color="bg-transparent"
|
|
45
46
|
/>
|
|
@@ -49,6 +50,7 @@ defineProps<{
|
|
|
49
50
|
<b>{{ t(`authConfig.${name}.form.callback.label`) }}</b>: {{ tArgs.serverUrl }} <CopyToClipboard
|
|
50
51
|
:text="tArgs.serverUrl"
|
|
51
52
|
label-as="tooltip"
|
|
53
|
+
:aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
|
|
52
54
|
class="icon-btn"
|
|
53
55
|
action-color="bg-transparent"
|
|
54
56
|
/>
|
|
@@ -16,8 +16,6 @@ export default {
|
|
|
16
16
|
ChangePassword, GlobalRoleBindings, CruResource, LabeledInput, Loading
|
|
17
17
|
},
|
|
18
18
|
|
|
19
|
-
emits: ['update:mode'],
|
|
20
|
-
|
|
21
19
|
mixins: [
|
|
22
20
|
CreateEditView
|
|
23
21
|
],
|
|
@@ -42,8 +40,7 @@ export default {
|
|
|
42
40
|
password: false,
|
|
43
41
|
roles: !showGlobalRoles,
|
|
44
42
|
rolesChanged: false,
|
|
45
|
-
}
|
|
46
|
-
watchOverride: false,
|
|
43
|
+
}
|
|
47
44
|
};
|
|
48
45
|
},
|
|
49
46
|
|
|
@@ -110,10 +107,20 @@ export default {
|
|
|
110
107
|
if (this.isCreate) {
|
|
111
108
|
const user = await this.createUser();
|
|
112
109
|
|
|
113
|
-
await this.
|
|
110
|
+
await this.createSecret(user);
|
|
111
|
+
await this.updateRoles(user);
|
|
112
|
+
|
|
113
|
+
// Show success notification only after ALL operations complete
|
|
114
|
+
// this is a "clone" of steve-class "processSaveResponse" toast/growl
|
|
115
|
+
this.$store.dispatch('growl/success', {
|
|
116
|
+
title: this.t('generic.autogeneratedCreated.title', { resource: user.kind }),
|
|
117
|
+
message: this.t('generic.autogeneratedCreated.message', { id: user.username, resource: user.kind }),
|
|
118
|
+
timeout: 3000
|
|
119
|
+
}, { root: true });
|
|
114
120
|
} else {
|
|
115
|
-
await this.editUser();
|
|
116
|
-
|
|
121
|
+
const user = await this.editUser();
|
|
122
|
+
|
|
123
|
+
await this.updateRoles(user);
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
this.$router.replace({ name: this.doneRoute });
|
|
@@ -139,21 +146,10 @@ export default {
|
|
|
139
146
|
username: this.form.username
|
|
140
147
|
});
|
|
141
148
|
|
|
142
|
-
const userSaved = await user.save(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const secret = await this.$store.dispatch('management/create', {
|
|
147
|
-
type: SECRET,
|
|
148
|
-
metadata: {
|
|
149
|
-
namespace: 'cattle-local-user-passwords',
|
|
150
|
-
name: userSaved.id
|
|
151
|
-
},
|
|
152
|
-
data: { password: base64Encode(this.form.password.password) }
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await secret.save();
|
|
156
|
-
}
|
|
149
|
+
const userSaved = await user.save({
|
|
150
|
+
// Don't show a success toast until the secret and GRB are also created
|
|
151
|
+
suppressSuccessToast: true,
|
|
152
|
+
});
|
|
157
153
|
|
|
158
154
|
return userSaved;
|
|
159
155
|
},
|
|
@@ -178,27 +174,57 @@ export default {
|
|
|
178
174
|
await wait(5000);
|
|
179
175
|
}
|
|
180
176
|
|
|
181
|
-
this.value.save();
|
|
177
|
+
const user = this.value.save();
|
|
178
|
+
|
|
179
|
+
return user;
|
|
182
180
|
},
|
|
183
181
|
|
|
184
|
-
async
|
|
182
|
+
async createSecret(user) {
|
|
183
|
+
if (this.form.password.password) {
|
|
184
|
+
try {
|
|
185
|
+
// create secret to hold user password
|
|
186
|
+
const secret = await this.$store.dispatch('management/create', {
|
|
187
|
+
type: SECRET,
|
|
188
|
+
metadata: {
|
|
189
|
+
namespace: 'cattle-local-user-passwords',
|
|
190
|
+
name: user.id
|
|
191
|
+
},
|
|
192
|
+
data: { password: base64Encode(this.form.password.password) }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await secret.save();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (this.isCreate) {
|
|
198
|
+
try {
|
|
199
|
+
// If secret creation fails, attempt to clean up the user to maintain consistency
|
|
200
|
+
await user.remove();
|
|
201
|
+
} catch (cleanupErr) {
|
|
202
|
+
// Log cleanup error but prioritize original error for user feedback
|
|
203
|
+
console.error('Failed to clean up user after secret creation failure:', cleanupErr); // eslint-disable-line no-console
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
async updateRoles(user) {
|
|
185
213
|
if (!this.$refs.grb) {
|
|
186
214
|
return;
|
|
187
215
|
}
|
|
188
216
|
|
|
189
217
|
try {
|
|
190
|
-
await this.$refs.grb.save(
|
|
218
|
+
await this.$refs.grb.save(user.id);
|
|
191
219
|
} catch (err) {
|
|
192
220
|
if (this.isCreate) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
);
|
|
221
|
+
try {
|
|
222
|
+
// If GRB creation fails, clean up the user to maintain consistency
|
|
223
|
+
await user.remove();
|
|
224
|
+
} catch (cleanupErr) {
|
|
225
|
+
// Log cleanup error but prioritize original error for user feedback
|
|
226
|
+
console.error('Failed to clean up user after GRB creation failure:', cleanupErr); // eslint-disable-line no-console
|
|
227
|
+
}
|
|
202
228
|
}
|
|
203
229
|
throw err;
|
|
204
230
|
}
|
|
@@ -274,7 +300,6 @@ export default {
|
|
|
274
300
|
:user-id="value.id || liveValue.id"
|
|
275
301
|
:mode="mode"
|
|
276
302
|
:real-mode="realMode"
|
|
277
|
-
:watch-override="watchOverride"
|
|
278
303
|
type="user"
|
|
279
304
|
@hasChanges="validation.rolesChanged = $event"
|
|
280
305
|
@canLogIn="validation.roles = $event"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import Auth from '@shell/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue';
|
|
3
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
4
|
+
import SimpleSecretSelector from '@shell/components/form/SimpleSecretSelector';
|
|
5
|
+
|
|
6
|
+
describe('component: Auth.vue', () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
mode: 'edit',
|
|
9
|
+
value: {},
|
|
10
|
+
namespace: 'test-namespace'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it('should render correctly with initial props', () => {
|
|
14
|
+
const wrapper = shallowMount(Auth, {
|
|
15
|
+
props: defaultProps,
|
|
16
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(wrapper.find('h3').text()).toBe('%monitoringReceiver.auth.label%');
|
|
20
|
+
expect(wrapper.findComponent(LabeledSelect).exists()).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should initialize with basic auth type if present', () => {
|
|
24
|
+
const wrapper = shallowMount(Auth, {
|
|
25
|
+
props: { ...defaultProps, value: { basicAuth: { username: { name: 'test' } } } },
|
|
26
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(wrapper.vm.authType).toBe('basicAuth');
|
|
30
|
+
expect(wrapper.findAllComponents(SimpleSecretSelector)).toHaveLength(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should initialize with bearer token auth type if present', () => {
|
|
34
|
+
const wrapper = shallowMount(Auth, {
|
|
35
|
+
props: { ...defaultProps, value: { bearerTokenSecret: { name: 'test' } } },
|
|
36
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(wrapper.vm.authType).toBe('bearerTokenSecret');
|
|
40
|
+
expect(wrapper.findAllComponents(SimpleSecretSelector)).toHaveLength(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should switch to basic auth and clear other types', async() => {
|
|
44
|
+
const value = { bearerTokenSecret: { name: 'b', key: 'k' } };
|
|
45
|
+
const wrapper = shallowMount(Auth, {
|
|
46
|
+
props: { ...defaultProps, value },
|
|
47
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'basicAuth');
|
|
51
|
+
expect(wrapper.vm.authType).toBe('basicAuth');
|
|
52
|
+
expect(wrapper.props('value').bearerTokenSecret).toBeUndefined();
|
|
53
|
+
expect(wrapper.props('value').basicAuth).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should switch to bearer token and clear other types', async() => {
|
|
57
|
+
const value = { basicAuth: { username: { name: 'u' } } };
|
|
58
|
+
const wrapper = shallowMount(Auth, {
|
|
59
|
+
props: { ...defaultProps, value },
|
|
60
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'bearerTokenSecret');
|
|
64
|
+
expect(wrapper.vm.authType).toBe('bearerTokenSecret');
|
|
65
|
+
expect(wrapper.props('value').basicAuth).toBeUndefined();
|
|
66
|
+
expect(wrapper.props('value').bearerTokenSecret).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should switch to none and clear other types', async() => {
|
|
70
|
+
const value = { basicAuth: { username: { name: 'u' } } };
|
|
71
|
+
const wrapper = shallowMount(Auth, {
|
|
72
|
+
props: { ...defaultProps, value },
|
|
73
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'none');
|
|
77
|
+
expect(wrapper.vm.authType).toBe('none');
|
|
78
|
+
expect(wrapper.props('value').basicAuth).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should update basic auth username and password', async() => {
|
|
82
|
+
const wrapper = shallowMount(Auth, {
|
|
83
|
+
props: { ...defaultProps, value: { basicAuth: { username: { name: 'test' } } } },
|
|
84
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await wrapper.vm.$nextTick();
|
|
88
|
+
|
|
89
|
+
const selectors = wrapper.findAllComponents(SimpleSecretSelector);
|
|
90
|
+
|
|
91
|
+
expect(selectors).toHaveLength(2);
|
|
92
|
+
|
|
93
|
+
const usernameSelector = selectors[0];
|
|
94
|
+
const passwordSelector = selectors[1];
|
|
95
|
+
|
|
96
|
+
await usernameSelector.vm.$emit('updateSecretName', 'user-secret');
|
|
97
|
+
await usernameSelector.vm.$emit('updateSecretKey', 'user-key');
|
|
98
|
+
await passwordSelector.vm.$emit('updateSecretName', 'pass-secret');
|
|
99
|
+
await passwordSelector.vm.$emit('updateSecretKey', 'pass-key');
|
|
100
|
+
|
|
101
|
+
const basicAuth = wrapper.props('value').basicAuth;
|
|
102
|
+
|
|
103
|
+
expect(basicAuth.username.name).toBe('user-secret');
|
|
104
|
+
expect(basicAuth.username.key).toBe('user-key');
|
|
105
|
+
expect(basicAuth.password.name).toBe('pass-secret');
|
|
106
|
+
expect(basicAuth.password.key).toBe('pass-key');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should update bearer token secret', async() => {
|
|
110
|
+
const wrapper = shallowMount(Auth, {
|
|
111
|
+
props: { ...defaultProps, value: { bearerTokenSecret: { name: 'test' } } },
|
|
112
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await wrapper.vm.$nextTick();
|
|
116
|
+
|
|
117
|
+
const selector = wrapper.findComponent(SimpleSecretSelector);
|
|
118
|
+
|
|
119
|
+
expect(selector.exists()).toBe(true);
|
|
120
|
+
|
|
121
|
+
await selector.vm.$emit('updateSecretName', 'bearer-name');
|
|
122
|
+
await selector.vm.$emit('updateSecretKey', 'bearer-key');
|
|
123
|
+
|
|
124
|
+
const bearerToken = wrapper.props('value').bearerTokenSecret;
|
|
125
|
+
|
|
126
|
+
expect(bearerToken.name).toBe('bearer-name');
|
|
127
|
+
expect(bearerToken.key).toBe('bearer-key');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should render in view mode', () => {
|
|
131
|
+
const wrapper = shallowMount(Auth, {
|
|
132
|
+
props: {
|
|
133
|
+
mode: 'view', value: { basicAuth: {} }, namespace: 'ns'
|
|
134
|
+
},
|
|
135
|
+
global: { mocks: { $fetchState: { pending: false, error: null } } }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(wrapper.findComponent(LabeledSelect).attributes('disabled')).toBe('true');
|
|
139
|
+
const selectors = wrapper.findAllComponents(SimpleSecretSelector);
|
|
140
|
+
|
|
141
|
+
selectors.forEach((selector) => {
|
|
142
|
+
expect(selector.props('disabled')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/* eslint-disable import/first */
|
|
2
|
+
global.PointerEvent = class PointerEvent extends Event {};
|
|
3
|
+
|
|
4
|
+
import { shallowMount } from '@vue/test-utils';
|
|
5
|
+
import { EDITOR_MODES } from '@shell/components/YamlEditor.vue';
|
|
6
|
+
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
7
|
+
import Index from '../index.vue';
|
|
8
|
+
|
|
9
|
+
describe('monitoring.coreos.com.alertmanagerconfig/index.vue', () => {
|
|
10
|
+
it('should render correctly', () => {
|
|
11
|
+
const valueMock = {
|
|
12
|
+
applyDefaults: jest.fn(),
|
|
13
|
+
spec: {
|
|
14
|
+
receivers: [
|
|
15
|
+
{ name: 'receiver1', type: 'webhook' },
|
|
16
|
+
{ name: 'receiver2', type: 'email' },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
getCreateReceiverRoute: jest.fn(() => ({ name: 'create-receiver' })),
|
|
20
|
+
getReceiverDetailLink: jest.fn((name) => `detail-${ name }`),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const wrapper = shallowMount(Index, {
|
|
24
|
+
propsData: {
|
|
25
|
+
value: valueMock,
|
|
26
|
+
mode: 'create',
|
|
27
|
+
},
|
|
28
|
+
global: {
|
|
29
|
+
mocks: {
|
|
30
|
+
$store: {
|
|
31
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
32
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
33
|
+
},
|
|
34
|
+
$router: { push: jest.fn() },
|
|
35
|
+
$route: { name: 'test-route' },
|
|
36
|
+
$fetchState: { pending: false },
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
stubs: {
|
|
40
|
+
CruResource: true,
|
|
41
|
+
NameNsDescription: true,
|
|
42
|
+
Tabbed: true,
|
|
43
|
+
Tab: true,
|
|
44
|
+
RouteConfig: true,
|
|
45
|
+
ResourceTable: true,
|
|
46
|
+
ActionMenu: true,
|
|
47
|
+
Loading: true,
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Ensure the component is rendered
|
|
52
|
+
expect(wrapper.exists()).toBe(true);
|
|
53
|
+
|
|
54
|
+
// Assert that applyDefaults is called
|
|
55
|
+
expect(valueMock.applyDefaults).toHaveBeenCalledTimes(1);
|
|
56
|
+
|
|
57
|
+
// Assert receiverOptions are correctly populated from value.spec.receivers
|
|
58
|
+
expect(wrapper.vm.receiverOptions).toStrictEqual(['receiver1', 'receiver2']);
|
|
59
|
+
|
|
60
|
+
// Assert createReceiverLink is correctly set
|
|
61
|
+
expect(wrapper.vm.createReceiverLink).toStrictEqual({ name: 'create-receiver' });
|
|
62
|
+
|
|
63
|
+
// Assert that receiverTableHeaders has the expected structure (basic check)
|
|
64
|
+
expect(wrapper.vm.receiverTableHeaders.length).toBeGreaterThan(0);
|
|
65
|
+
expect(wrapper.vm.receiverTableHeaders[0].name).toBe('name');
|
|
66
|
+
expect(wrapper.vm.receiverTableHeaders[1].name).toBe('type');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Additional test for editorMode computed property
|
|
70
|
+
it('editorMode should return VIEW_CODE when mode is _VIEW', () => {
|
|
71
|
+
const valueMock = {
|
|
72
|
+
applyDefaults: jest.fn(),
|
|
73
|
+
spec: { receivers: [] },
|
|
74
|
+
getCreateReceiverRoute: jest.fn(),
|
|
75
|
+
getReceiverDetailLink: jest.fn(),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const wrapper = shallowMount(Index, {
|
|
79
|
+
propsData: {
|
|
80
|
+
value: valueMock,
|
|
81
|
+
mode: _VIEW, // Set mode to _VIEW
|
|
82
|
+
},
|
|
83
|
+
global: {
|
|
84
|
+
mocks: {
|
|
85
|
+
$store: {
|
|
86
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
87
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
88
|
+
},
|
|
89
|
+
$router: { push: jest.fn() },
|
|
90
|
+
$route: { name: 'test-route' },
|
|
91
|
+
$fetchState: { pending: false },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
stubs: {
|
|
95
|
+
CruResource: true,
|
|
96
|
+
NameNsDescription: true,
|
|
97
|
+
Tabbed: true,
|
|
98
|
+
Tab: true,
|
|
99
|
+
RouteConfig: true,
|
|
100
|
+
ResourceTable: true,
|
|
101
|
+
ActionMenu: true,
|
|
102
|
+
Loading: true,
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(wrapper.vm.editorMode).toBe(EDITOR_MODES.VIEW_CODE);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('editorMode should return EDIT_CODE when mode is not _VIEW', () => {
|
|
110
|
+
const valueMock = {
|
|
111
|
+
applyDefaults: jest.fn(),
|
|
112
|
+
spec: { receivers: [] },
|
|
113
|
+
getCreateReceiverRoute: jest.fn(),
|
|
114
|
+
getReceiverDetailLink: jest.fn(),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const wrapper = shallowMount(Index, {
|
|
118
|
+
propsData: {
|
|
119
|
+
value: valueMock,
|
|
120
|
+
mode: _EDIT, // Set mode to _EDIT
|
|
121
|
+
},
|
|
122
|
+
global: {
|
|
123
|
+
mocks: {
|
|
124
|
+
$store: {
|
|
125
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
126
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
127
|
+
},
|
|
128
|
+
$router: { push: jest.fn() },
|
|
129
|
+
$route: { name: 'test-route' },
|
|
130
|
+
$fetchState: { pending: false },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
stubs: {
|
|
134
|
+
CruResource: true,
|
|
135
|
+
NameNsDescription: true,
|
|
136
|
+
Tabbed: true,
|
|
137
|
+
Tab: true,
|
|
138
|
+
RouteConfig: true,
|
|
139
|
+
ResourceTable: true,
|
|
140
|
+
ActionMenu: true,
|
|
141
|
+
Loading: true,
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(wrapper.vm.editorMode).toBe(EDITOR_MODES.EDIT_CODE);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('translateReceiverTypes method translates receiver types', () => {
|
|
149
|
+
const valueMock = {
|
|
150
|
+
applyDefaults: jest.fn(),
|
|
151
|
+
spec: { receivers: [] },
|
|
152
|
+
getCreateReceiverRoute: jest.fn(),
|
|
153
|
+
getReceiverDetailLink: jest.fn(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const wrapper = shallowMount(Index, {
|
|
157
|
+
propsData: {
|
|
158
|
+
value: valueMock,
|
|
159
|
+
mode: 'create',
|
|
160
|
+
},
|
|
161
|
+
global: {
|
|
162
|
+
mocks: {
|
|
163
|
+
$store: {
|
|
164
|
+
getters: { currentProduct: { inStore: 'test' } },
|
|
165
|
+
dispatch: jest.fn(() => Promise.resolve({ receiverSchema: {} })),
|
|
166
|
+
},
|
|
167
|
+
$router: { push: jest.fn() },
|
|
168
|
+
$route: { name: 'test-route' },
|
|
169
|
+
$fetchState: { pending: false },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
stubs: {
|
|
173
|
+
CruResource: true,
|
|
174
|
+
NameNsDescription: true,
|
|
175
|
+
Tabbed: true,
|
|
176
|
+
Tab: true,
|
|
177
|
+
RouteConfig: true,
|
|
178
|
+
ResourceTable: true,
|
|
179
|
+
ActionMenu: true,
|
|
180
|
+
Loading: true,
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Ensure receiverTypes is initialized, then call the method
|
|
185
|
+
const originalReceiverTypes = [
|
|
186
|
+
{ label: 'alertmanagerConfigReceiver.alerta', value: 'alerta' },
|
|
187
|
+
{ label: 'alertmanagerConfigReceiver.dingtalk', value: 'dingtalk' }
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// Manually set receiverTypes for the test (it's normally populated from an import)
|
|
191
|
+
// In a real scenario, you'd mock the import or ensure it's loaded before the test.
|
|
192
|
+
// For this test, we'll directly manipulate the vm.
|
|
193
|
+
wrapper.setData({ receiverTypes: originalReceiverTypes });
|
|
194
|
+
|
|
195
|
+
const translatedTypes = wrapper.vm.translateReceiverTypes();
|
|
196
|
+
|
|
197
|
+
expect(translatedTypes).toStrictEqual([
|
|
198
|
+
{ label: '%alertmanagerConfigReceiver.alerta%', value: 'alerta' },
|
|
199
|
+
{ label: '%alertmanagerConfigReceiver.dingtalk%', value: 'dingtalk' }
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
});
|