@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
|
@@ -38,13 +38,13 @@ describe('component: ResourceDetailDrawer/ConfigTab', () => {
|
|
|
38
38
|
expect(component.props('name')).toStrictEqual('config-tab');
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('should render a dynamic component within
|
|
41
|
+
it('should render a dynamic component within DrawerCard and pass the correct props', () => {
|
|
42
42
|
const wrapper = mount(ConfigTab, {
|
|
43
43
|
props: { resource, component: markRaw(DynamicComponent) },
|
|
44
44
|
global
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
const component = wrapper.
|
|
47
|
+
const component = wrapper.findComponent(DynamicComponent);
|
|
48
48
|
|
|
49
49
|
expect(component.props('value')).toStrictEqual(resource);
|
|
50
50
|
expect(component.props('mode')).toStrictEqual(_VIEW);
|
|
@@ -59,7 +59,6 @@ useResourceDetailDrawerProvider();
|
|
|
59
59
|
</script>
|
|
60
60
|
<template>
|
|
61
61
|
<Drawer
|
|
62
|
-
class="resource-detail-drawer"
|
|
63
62
|
:ariaTarget="title"
|
|
64
63
|
@close="emit('close')"
|
|
65
64
|
>
|
|
@@ -72,10 +71,10 @@ useResourceDetailDrawerProvider();
|
|
|
72
71
|
</template>
|
|
73
72
|
<template #body>
|
|
74
73
|
<Tabbed
|
|
75
|
-
class="tabbed"
|
|
76
74
|
:useHash="false"
|
|
77
75
|
:showExtensionTabs="false"
|
|
78
76
|
:componentTestid="componentTestid"
|
|
77
|
+
:remove-borders="true"
|
|
79
78
|
@changed="({selectedName}) => {activeTab = selectedName;}"
|
|
80
79
|
>
|
|
81
80
|
<ConfigTab
|
|
@@ -92,7 +91,8 @@ useResourceDetailDrawerProvider();
|
|
|
92
91
|
<template #additional-actions>
|
|
93
92
|
<RcButton
|
|
94
93
|
v-if="canEdit"
|
|
95
|
-
|
|
94
|
+
variant="primary"
|
|
95
|
+
size="large"
|
|
96
96
|
:aria-label="action.ariaLabel"
|
|
97
97
|
:data-testid="editBttnDataTestId"
|
|
98
98
|
@click="action.action"
|
|
@@ -102,20 +102,3 @@ useResourceDetailDrawerProvider();
|
|
|
102
102
|
</template>
|
|
103
103
|
</Drawer>
|
|
104
104
|
</template>
|
|
105
|
-
|
|
106
|
-
<style lang="scss" scoped>
|
|
107
|
-
.resource-detail-drawer {
|
|
108
|
-
:deep() .tabbed {
|
|
109
|
-
& > .tabs {
|
|
110
|
-
border: none;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
& > .tab-container {
|
|
114
|
-
border: none;
|
|
115
|
-
border-top: 1px solid var(--border);
|
|
116
|
-
padding: 0;
|
|
117
|
-
padding-top: 24px;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
</style>
|
package/components/Markdown.vue
CHANGED
|
@@ -57,7 +57,7 @@ watch(
|
|
|
57
57
|
>
|
|
58
58
|
<slot name="default" />
|
|
59
59
|
<RcButton
|
|
60
|
-
ghost
|
|
60
|
+
variant="ghost"
|
|
61
61
|
class="focus-button"
|
|
62
62
|
:aria-label="props.showPopoverAriaLabel"
|
|
63
63
|
aria-haspopup="true"
|
|
@@ -128,7 +128,7 @@ watch(
|
|
|
128
128
|
display: inline-block;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
.focus-button {
|
|
131
|
+
.rc-button.btn.focus-button {
|
|
132
132
|
margin-left: 4px;
|
|
133
133
|
margin-right: 2px;
|
|
134
134
|
padding: 0;
|
|
@@ -160,7 +160,7 @@ watch(
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
&:deep() {
|
|
163
|
-
& > .v-popper > .btn.
|
|
163
|
+
& > .v-popper > .btn.variant-link {
|
|
164
164
|
padding: 0;
|
|
165
165
|
min-height: initial;
|
|
166
166
|
line-height: initial;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Card from '@shell/components/Resource/Detail/Card/index.vue';
|
|
3
|
+
import { useI18n } from '@shell/composables/useI18n';
|
|
4
|
+
import { useStore } from 'vuex';
|
|
5
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types';
|
|
6
|
+
</script>
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { useRouter } from 'vue-router';
|
|
9
|
+
|
|
10
|
+
const store = useStore();
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const i18n = useI18n(store);
|
|
13
|
+
|
|
14
|
+
const extensionsUrl = router.resolve({
|
|
15
|
+
name: 'c-cluster-uiplugins',
|
|
16
|
+
params: { cluster: BLANK_CLUSTER }
|
|
17
|
+
}).href;
|
|
18
|
+
|
|
19
|
+
const clusterToolsUrl = router.resolve({
|
|
20
|
+
name: 'c-cluster-apps-charts',
|
|
21
|
+
params: { cluster: BLANK_CLUSTER }
|
|
22
|
+
}).href;
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<Card :title="i18n.t('component.resource.detail.card.extrasCard.title')">
|
|
27
|
+
<p
|
|
28
|
+
v-clean-html="i18n.t('component.resource.detail.card.extrasCard.message', { extensionsUrl, clusterToolsUrl }, true)"
|
|
29
|
+
class="message text-deemphasized"
|
|
30
|
+
/>
|
|
31
|
+
</Card>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<style lang="scss" scoped>
|
|
35
|
+
.message {
|
|
36
|
+
margin: 0;
|
|
37
|
+
line-height: 1.5;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useResourceCardRow } from '@shell/components/Resource/Detail/Card/StateCard/composables';
|
|
2
|
+
|
|
3
|
+
describe('useResourceCardRow', () => {
|
|
4
|
+
describe('with default keys', () => {
|
|
5
|
+
it('should return undefined counts when resources is empty', () => {
|
|
6
|
+
const result = useResourceCardRow('Test', []);
|
|
7
|
+
|
|
8
|
+
expect(result.label).toBe('Test');
|
|
9
|
+
expect(result.color).toBeUndefined();
|
|
10
|
+
expect(result.counts).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should aggregate resources by stateDisplay', () => {
|
|
14
|
+
const resources = [
|
|
15
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
16
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
17
|
+
{ stateSimpleColor: 'error', stateDisplay: 'Failed' }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const result = useResourceCardRow('Pods', resources);
|
|
21
|
+
|
|
22
|
+
expect(result.label).toBe('Pods');
|
|
23
|
+
expect(result.counts).toHaveLength(2);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return highest alert color as the main color', () => {
|
|
27
|
+
const resources = [
|
|
28
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
29
|
+
{ stateSimpleColor: 'error', stateDisplay: 'Failed' },
|
|
30
|
+
{ stateSimpleColor: 'warning', stateDisplay: 'Pending' }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const result = useResourceCardRow('Pods', resources);
|
|
34
|
+
|
|
35
|
+
expect(result.color).toBe('error');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should sort counts by alert level (error first)', () => {
|
|
39
|
+
const resources = [
|
|
40
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
41
|
+
{ stateSimpleColor: 'error', stateDisplay: 'Failed' },
|
|
42
|
+
{ stateSimpleColor: 'warning', stateDisplay: 'Pending' }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const result = useResourceCardRow('Pods', resources);
|
|
46
|
+
|
|
47
|
+
expect(result.counts![0].color).toBe('error');
|
|
48
|
+
expect(result.counts![1].color).toBe('warning');
|
|
49
|
+
expect(result.counts![2].color).toBe('success');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should sort by count when colors are equal', () => {
|
|
53
|
+
const resources = [
|
|
54
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
55
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
56
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' },
|
|
57
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Completed' }
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const result = useResourceCardRow('Pods', resources);
|
|
61
|
+
|
|
62
|
+
expect(result.counts![0].label).toBe('running');
|
|
63
|
+
expect(result.counts![0].count).toBe(3);
|
|
64
|
+
expect(result.counts![1].label).toBe('completed');
|
|
65
|
+
expect(result.counts![1].count).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should use default color when stateSimpleColor is undefined', () => {
|
|
69
|
+
const resources = [
|
|
70
|
+
{ stateDisplay: 'Unknown' }
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const result = useResourceCardRow('Pods', resources);
|
|
74
|
+
|
|
75
|
+
expect(result.color).toBe('disabled');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should include to parameter when provided', () => {
|
|
79
|
+
const to = { hash: '#pods' };
|
|
80
|
+
const result = useResourceCardRow('Pods', [], undefined, undefined, to);
|
|
81
|
+
|
|
82
|
+
expect(result.to).toStrictEqual(to);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('with custom keys', () => {
|
|
87
|
+
it('should use custom stateColorKey', () => {
|
|
88
|
+
const resources = [
|
|
89
|
+
{ customColor: 'error', stateDisplay: 'Failed' },
|
|
90
|
+
{ customColor: 'success', stateDisplay: 'Running' }
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const result = useResourceCardRow('Conditions', resources, 'customColor');
|
|
94
|
+
|
|
95
|
+
expect(result.color).toBe('error');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should use custom stateDisplayKey', () => {
|
|
99
|
+
const resources = [
|
|
100
|
+
{ stateSimpleColor: 'success', condition: 'Available' },
|
|
101
|
+
{ stateSimpleColor: 'success', condition: 'Available' },
|
|
102
|
+
{ stateSimpleColor: 'success', condition: 'Progressing' }
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const result = useResourceCardRow('Conditions', resources, undefined, 'condition');
|
|
106
|
+
|
|
107
|
+
// Both have same color (success), so sorted by count (higher first)
|
|
108
|
+
expect(result.counts![0].label).toBe('available');
|
|
109
|
+
expect(result.counts![0].count).toBe(2);
|
|
110
|
+
expect(result.counts![1].label).toBe('progressing');
|
|
111
|
+
expect(result.counts![1].count).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should use both custom keys together', () => {
|
|
115
|
+
const resources = [
|
|
116
|
+
{ statusColor: 'error', status: 'False' },
|
|
117
|
+
{ statusColor: 'disabled', status: 'True' }
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const result = useResourceCardRow('Status', resources, 'statusColor', 'status');
|
|
121
|
+
|
|
122
|
+
expect(result.color).toBe('error');
|
|
123
|
+
expect(result.counts).toContainEqual(expect.objectContaining({ label: 'false', color: 'error' }));
|
|
124
|
+
expect(result.counts).toContainEqual(expect.objectContaining({ label: 'true', color: 'disabled' }));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('lowercase handling', () => {
|
|
129
|
+
it('should convert stateDisplay to lowercase', () => {
|
|
130
|
+
const resources = [
|
|
131
|
+
{ stateSimpleColor: 'success', stateDisplay: 'RUNNING' },
|
|
132
|
+
{ stateSimpleColor: 'success', stateDisplay: 'Running' }
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const result = useResourceCardRow('Pods', resources);
|
|
136
|
+
|
|
137
|
+
expect(result.counts).toHaveLength(1);
|
|
138
|
+
expect(result.counts![0].label).toBe('running');
|
|
139
|
+
expect(result.counts![0].count).toBe(2);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Count, Props as ResourceRowProps } from '@shell/components/Resource/Detail/ResourceRow.types';
|
|
2
2
|
import { useI18n } from '@shell/composables/useI18n';
|
|
3
3
|
import { INGRESS, SERVICE } from '@shell/config/types';
|
|
4
|
-
import {
|
|
4
|
+
import { isHigherAlert, StateColor } from '@shell/utils/style';
|
|
5
5
|
import { computed, Ref, toValue } from 'vue';
|
|
6
6
|
import { useStore } from 'vuex';
|
|
7
|
-
import { Props as StateCardProps } from '@shell/components/Resource/Detail/Card/StateCard/
|
|
7
|
+
import { Props as StateCardProps } from '@shell/components/Resource/Detail/Card/StateCard/types';
|
|
8
8
|
import { RouteLocationRaw } from 'vue-router';
|
|
9
9
|
|
|
10
|
-
export function useResourceCardRow(label: string, resources: any[], to?: RouteLocationRaw): ResourceRowProps {
|
|
11
|
-
const
|
|
12
|
-
|
|
10
|
+
export function useResourceCardRow(label: string, resources: any[], stateColorKey = 'stateSimpleColor', stateDisplayKey = 'stateDisplay', to?: RouteLocationRaw): ResourceRowProps {
|
|
11
|
+
const agg: any = {};
|
|
12
|
+
|
|
13
|
+
resources.forEach((r: any) => {
|
|
14
|
+
const state = r[stateDisplayKey]?.toLowerCase();
|
|
15
|
+
const color = r[stateColorKey] || 'disabled';
|
|
16
|
+
|
|
17
|
+
agg[state] = agg[state] || {
|
|
18
|
+
color, label: state, count: 0
|
|
19
|
+
};
|
|
20
|
+
agg[state].count++;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
interface Tuple extends Count {
|
|
24
|
+
color: StateColor;
|
|
25
|
+
}
|
|
26
|
+
const tuples: Tuple[] = Object.values(agg);
|
|
27
|
+
|
|
28
|
+
tuples.sort((left: any, right: any) => {
|
|
29
|
+
if (isHigherAlert(left.color, right.color)) {
|
|
30
|
+
return -1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (left.color !== right.color) {
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (left.count === right.count) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return left.count > right.count ? -1 : 1;
|
|
42
|
+
});
|
|
13
43
|
|
|
14
44
|
return {
|
|
15
45
|
label,
|
|
16
|
-
color:
|
|
17
|
-
counts:
|
|
46
|
+
color: tuples.length ? tuples[0].color : undefined,
|
|
47
|
+
counts: tuples.length ? tuples : undefined,
|
|
18
48
|
to
|
|
19
49
|
};
|
|
20
50
|
}
|
|
@@ -27,7 +57,7 @@ export interface Pairs {
|
|
|
27
57
|
|
|
28
58
|
export function useDefaultResources(pairs: Ref<Pairs[]>) {
|
|
29
59
|
const pairsValue = toValue(pairs);
|
|
30
|
-
const rows = computed(() => pairsValue.map(({ label, resources, to }) => useResourceCardRow(label, resources, to)));
|
|
60
|
+
const rows = computed(() => pairsValue.map(({ label, resources, to }) => useResourceCardRow(label, resources, undefined, undefined, to)));
|
|
31
61
|
|
|
32
62
|
return rows;
|
|
33
63
|
}
|
|
@@ -93,13 +123,13 @@ export function useDefaultWorkloadInsightsCardProps(): StateCardProps {
|
|
|
93
123
|
const rows: ResourceRowProps[] = [
|
|
94
124
|
{
|
|
95
125
|
label: i18n.t('component.resource.detail.card.insightsCard.rows.conditions'),
|
|
96
|
-
to: '#',
|
|
126
|
+
to: '#conditions',
|
|
97
127
|
color: 'disabled',
|
|
98
128
|
counts: [{ label: 'Available', count: 1 }, { label: 'Progressing', count: 1 }]
|
|
99
129
|
},
|
|
100
130
|
{
|
|
101
131
|
label: i18n.t('component.resource.detail.card.insightsCard.rows.events'),
|
|
102
|
-
to: '#',
|
|
132
|
+
to: '#events',
|
|
103
133
|
color: 'disabled',
|
|
104
134
|
counts: [{ label: 'Normal', count: 2 }]
|
|
105
135
|
}
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import Card from '@shell/components/Resource/Detail/Card/index.vue';
|
|
3
|
-
import ResourceRow
|
|
3
|
+
import ResourceRow from '@shell/components/Resource/Detail/ResourceRow.vue';
|
|
4
|
+
import { Props } from './types';
|
|
4
5
|
|
|
5
|
-
export interface Props {
|
|
6
|
-
title: string;
|
|
7
|
-
rows: ResourceRowProps[];
|
|
8
|
-
}
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<script setup lang="ts">
|
|
12
6
|
const { title, rows } = defineProps<Props>();
|
|
13
7
|
</script>
|
|
14
8
|
|
|
@@ -10,7 +10,8 @@ import { computed } from 'vue';
|
|
|
10
10
|
import { useStore } from 'vuex';
|
|
11
11
|
|
|
12
12
|
export interface Props {
|
|
13
|
-
|
|
13
|
+
title: string;
|
|
14
|
+
resources?: any[];
|
|
14
15
|
showScaling?: boolean;
|
|
15
16
|
}
|
|
16
17
|
</script>
|
|
@@ -19,7 +20,7 @@ export interface Props {
|
|
|
19
20
|
const store = useStore();
|
|
20
21
|
const i18n = useI18n(store);
|
|
21
22
|
|
|
22
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
+
const props = withDefaults(defineProps<Props>(), { resources: undefined, showScaling: false });
|
|
23
24
|
const emit = defineEmits(['decrease', 'increase']);
|
|
24
25
|
|
|
25
26
|
const segmentAccumulator = computed(() => {
|
|
@@ -28,8 +29,8 @@ const segmentAccumulator = computed(() => {
|
|
|
28
29
|
}
|
|
29
30
|
const accumulator: {[key in StateColor]?: Value} = {};
|
|
30
31
|
|
|
31
|
-
props.
|
|
32
|
-
const color: StateColor =
|
|
32
|
+
props.resources?.forEach((resource: any) => {
|
|
33
|
+
const color: StateColor = resource.stateSimpleColor;
|
|
33
34
|
|
|
34
35
|
accumulator[color] = accumulator[color] || { count: 0 };
|
|
35
36
|
accumulator[color].count++;
|
|
@@ -45,10 +46,10 @@ const rowAccumulator = computed(() => {
|
|
|
45
46
|
}
|
|
46
47
|
const accumulator: {[key in string]: Value} = {};
|
|
47
48
|
|
|
48
|
-
props.
|
|
49
|
-
accumulator[
|
|
50
|
-
accumulator[
|
|
51
|
-
accumulator[
|
|
49
|
+
props.resources?.forEach((resource: any) => {
|
|
50
|
+
accumulator[resource.stateDisplay] = accumulator[resource.stateDisplay] || { count: 0 };
|
|
51
|
+
accumulator[resource.stateDisplay].count++;
|
|
52
|
+
accumulator[resource.stateDisplay].color = resource.stateSimpleColor.replace('text-', '') as StateColor;
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
return accumulator;
|
|
@@ -58,7 +59,7 @@ const percent = (count: number, total: number) => {
|
|
|
58
59
|
return count / total * 100;
|
|
59
60
|
};
|
|
60
61
|
|
|
61
|
-
const count = computed(() => props.
|
|
62
|
+
const count = computed(() => props.resources?.length || 0);
|
|
62
63
|
|
|
63
64
|
const segmentColors = computed(() => Object.keys(segmentAccumulator.value) as StateColor[]);
|
|
64
65
|
const segments = computed(() => segmentColors.value.map((color: StateColor) => ({
|
|
@@ -82,7 +83,7 @@ const rows = computed(() => {
|
|
|
82
83
|
</script>
|
|
83
84
|
|
|
84
85
|
<template>
|
|
85
|
-
<Card :title="
|
|
86
|
+
<Card :title="title">
|
|
86
87
|
<template
|
|
87
88
|
v-if="props.showScaling"
|
|
88
89
|
#heading-action
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
|
-
import
|
|
3
|
-
import PodsCard from '@shell/components/Resource/Detail/Card/PodsCard/index.vue';
|
|
2
|
+
import PodsCard from '@shell/components/Resource/Detail/Card/StatusCard/index.vue';
|
|
4
3
|
import Card from '@shell/components/Resource/Detail/Card/index.vue';
|
|
5
4
|
import Scaler from '@shell/components/Resource/Detail/Card/Scaler.vue';
|
|
6
5
|
import StatusBar from '@shell/components/Resource/Detail/StatusBar.vue';
|
|
7
6
|
import StatusRow from '@shell/components/Resource/Detail/StatusRow.vue';
|
|
8
7
|
import { createStore } from 'vuex';
|
|
9
8
|
|
|
10
|
-
describe('component: Bubble', () => {
|
|
11
|
-
it('should render element with bubble class for styling', async() => {
|
|
12
|
-
const content = 'content';
|
|
13
|
-
const wrapper = mount(Bubble, { slots: { default: content } });
|
|
14
|
-
|
|
15
|
-
expect(wrapper.element.className).toStrictEqual('bubble');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should render default slot content', async() => {
|
|
19
|
-
const content = 'content';
|
|
20
|
-
const wrapper = mount(Bubble, { slots: { default: content } });
|
|
21
|
-
|
|
22
|
-
expect(wrapper.find('.bubble').element.innerHTML).toStrictEqual(content);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
9
|
describe('component: PodsCard', () => {
|
|
27
10
|
const store = createStore({});
|
|
28
11
|
|
|
@@ -30,11 +13,12 @@ describe('component: PodsCard', () => {
|
|
|
30
13
|
const podFail = { stateSimpleColor: 'error', stateDisplay: 'Error' };
|
|
31
14
|
|
|
32
15
|
it('should pass title to Card prop correctly', async() => {
|
|
33
|
-
const
|
|
16
|
+
const title = 'component.resource.detail.card.podsCard.title';
|
|
17
|
+
const wrapper = mount(PodsCard, { props: { title, showScaling: true }, global: { provide: { store } } });
|
|
34
18
|
|
|
35
19
|
const card = wrapper.findComponent(Card);
|
|
36
20
|
|
|
37
|
-
expect(card.props('title')).toStrictEqual(
|
|
21
|
+
expect(card.props('title')).toStrictEqual(title);
|
|
38
22
|
});
|
|
39
23
|
|
|
40
24
|
it('should show Scaler when showScaling is true', async() => {
|
|
@@ -50,15 +34,25 @@ describe('component: PodsCard', () => {
|
|
|
50
34
|
});
|
|
51
35
|
|
|
52
36
|
it('should pass the appropriate props to the Scaler component', async() => {
|
|
53
|
-
const wrapper = mount(PodsCard, {
|
|
37
|
+
const wrapper = mount(PodsCard, {
|
|
38
|
+
props: {
|
|
39
|
+
title: 'Test', showScaling: true, resources: [podSuccess]
|
|
40
|
+
},
|
|
41
|
+
global: { provide: { store } }
|
|
42
|
+
});
|
|
54
43
|
const scaler = wrapper.findComponent(Scaler);
|
|
55
44
|
|
|
56
45
|
expect(scaler.props('value')).toStrictEqual(1);
|
|
57
46
|
expect(scaler.props('min')).toStrictEqual(0);
|
|
58
47
|
});
|
|
59
48
|
|
|
60
|
-
it('should pass the appropriate props to the StatusBar component based on the
|
|
61
|
-
const wrapper = mount(PodsCard, {
|
|
49
|
+
it('should pass the appropriate props to the StatusBar component based on the resources input', async() => {
|
|
50
|
+
const wrapper = mount(PodsCard, {
|
|
51
|
+
props: {
|
|
52
|
+
title: 'Test', showScaling: true, resources: [podSuccess, podFail]
|
|
53
|
+
},
|
|
54
|
+
global: { provide: { store } }
|
|
55
|
+
});
|
|
62
56
|
const statusBar = wrapper.findComponent(StatusBar);
|
|
63
57
|
|
|
64
58
|
const segments = statusBar.props('segments');
|
|
@@ -70,8 +64,13 @@ describe('component: PodsCard', () => {
|
|
|
70
64
|
expect(segments[1].percent).toStrictEqual(50);
|
|
71
65
|
});
|
|
72
66
|
|
|
73
|
-
it('should pass the appropriate props to the StatusRow component based on the
|
|
74
|
-
const wrapper = mount(PodsCard, {
|
|
67
|
+
it('should pass the appropriate props to the StatusRow component based on the resources input', async() => {
|
|
68
|
+
const wrapper = mount(PodsCard, {
|
|
69
|
+
props: {
|
|
70
|
+
title: 'Test', showScaling: true, resources: [podSuccess, podFail]
|
|
71
|
+
},
|
|
72
|
+
global: { provide: { store } }
|
|
73
|
+
});
|
|
75
74
|
const rows = wrapper.findComponent(StatusRow);
|
|
76
75
|
|
|
77
76
|
expect(rows.props()).toStrictEqual({
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import SpacedRow from '@shell/components/Resource/Detail/SpacedRow.vue';
|
|
3
|
+
import ExtrasCard from '@shell/components/Resource/Detail/Card/ExtrasCard.vue';
|
|
4
|
+
import { computed } from 'vue';
|
|
5
|
+
|
|
6
|
+
export interface Props {
|
|
7
|
+
resource: any;
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
const { resource } = defineProps<Props>();
|
|
13
|
+
const cards = computed(() => resource?.cards?.filter((c: any) => c) || []);
|
|
14
|
+
const showExtrasCard = computed(() => cards.value.length >= 1 && cards.value.length < 3);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<SpacedRow v-if="cards.length > 0">
|
|
19
|
+
<component
|
|
20
|
+
:is="card.component"
|
|
21
|
+
v-for="(card, i) in cards"
|
|
22
|
+
:key="i"
|
|
23
|
+
v-bind="card.props"
|
|
24
|
+
/>
|
|
25
|
+
<ExtrasCard v-if="showExtrasCard" />
|
|
26
|
+
</SpacedRow>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import Masthead from '@shell/components/Resource/Detail/Masthead/index.vue';
|
|
3
|
+
import Cards from '@shell/components/Resource/Detail/Cards.vue';
|
|
4
|
+
|
|
5
|
+
jest.mock('@shell/utils/clipboard', () => ({ copyTextToClipboard: jest.fn() }));
|
|
6
|
+
|
|
7
|
+
describe('component: Masthead/index', () => {
|
|
8
|
+
const mockResource = {
|
|
9
|
+
name: 'test-resource',
|
|
10
|
+
cards: []
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const defaultProps = {
|
|
14
|
+
titleBarProps: {
|
|
15
|
+
resource: mockResource,
|
|
16
|
+
resourceTypeLabel: 'ConfigMap',
|
|
17
|
+
resourceName: 'test-resource'
|
|
18
|
+
},
|
|
19
|
+
metadataProps: { items: [] }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it('should render the Cards component', () => {
|
|
23
|
+
const wrapper = mount(Masthead, {
|
|
24
|
+
props: defaultProps,
|
|
25
|
+
global: {
|
|
26
|
+
stubs: {
|
|
27
|
+
TitleBar: true,
|
|
28
|
+
Metadata: true,
|
|
29
|
+
Cards: true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(wrapper.findComponent(Cards).exists()).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should pass the resource from titleBarProps to Cards', () => {
|
|
38
|
+
const wrapper = mount(Masthead, {
|
|
39
|
+
props: defaultProps,
|
|
40
|
+
global: {
|
|
41
|
+
stubs: {
|
|
42
|
+
TitleBar: true,
|
|
43
|
+
Metadata: true,
|
|
44
|
+
Cards: true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const cardsComponent = wrapper.findComponent(Cards);
|
|
50
|
+
|
|
51
|
+
expect(cardsComponent.props('resource')).toStrictEqual(mockResource);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should render Cards with mb-20 class', () => {
|
|
55
|
+
const wrapper = mount(Masthead, {
|
|
56
|
+
props: defaultProps,
|
|
57
|
+
global: {
|
|
58
|
+
stubs: {
|
|
59
|
+
TitleBar: true,
|
|
60
|
+
Metadata: true,
|
|
61
|
+
Cards: true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const cardsComponent = wrapper.findComponent(Cards);
|
|
67
|
+
|
|
68
|
+
expect(cardsComponent.classes()).toContain('mb-20');
|
|
69
|
+
});
|
|
70
|
+
});
|