@rancher/shell 3.0.8-rc.12 → 3.0.8-rc.13
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/impl/apis.ts +61 -0
- package/apis/index.ts +40 -0
- package/apis/intf/modal.ts +90 -0
- package/apis/intf/shell.ts +36 -0
- package/apis/intf/slide-in.ts +98 -0
- package/apis/intf/system.ts +34 -0
- package/apis/shell/__tests__/modal.test.ts +80 -0
- package/apis/shell/__tests__/notifications.test.ts +71 -0
- package/apis/shell/__tests__/slide-in.test.ts +54 -0
- package/apis/shell/__tests__/system.test.ts +129 -0
- package/apis/shell/index.ts +38 -0
- package/apis/shell/modal.ts +41 -0
- package/apis/shell/notifications.ts +65 -0
- package/apis/shell/slide-in.ts +33 -0
- package/apis/shell/system.ts +65 -0
- package/apis/vue-shim.d.ts +11 -0
- package/components/CruResource.vue +8 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
- package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
- package/components/ModalManager.vue +11 -1
- package/components/ResourceDetail/index.vue +3 -0
- package/components/ResourceTable.vue +53 -20
- package/components/SlideInPanelManager.vue +16 -11
- package/components/SortableTable/index.vue +19 -1
- package/components/Tabbed/index.vue +37 -2
- package/components/form/ResourceTabs/composable.ts +2 -2
- package/composables/cruResource.ts +27 -0
- package/composables/focusTrap.ts +3 -1
- package/composables/resourceDetail.ts +15 -0
- package/core/__tests__/extension-manager-impl.test.js +437 -0
- package/core/extension-manager-impl.js +1 -22
- package/core/plugin.ts +9 -1
- package/core/types.ts +35 -0
- package/detail/provisioning.cattle.io.cluster.vue +2 -0
- package/edit/workload/index.vue +1 -1
- package/initialize/install-plugins.js +4 -5
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +33 -0
- package/pages/c/_cluster/fleet/index.vue +4 -7
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
- package/plugins/steve/steve-pagination-utils.ts +108 -43
- package/scripts/publish-shell.sh +25 -0
- package/store/__tests__/type-map.test.ts +164 -2
- package/store/notifications.ts +2 -0
- package/store/type-map.js +10 -1
- package/types/internal-api/shell/modal.d.ts +6 -6
- package/types/notifications/index.ts +126 -15
- package/types/rancher/index.d.ts +9 -0
- package/types/vue-shim.d.ts +5 -4
- package/composables/useExtensionManager.ts +0 -17
- package/core/__test__/extension-manager-impl.test.js +0 -236
- package/core/plugins.js +0 -38
- package/plugins/internal-api/index.ts +0 -37
- package/plugins/internal-api/shared/base-api.ts +0 -13
- package/plugins/internal-api/shell/shell.api.ts +0 -108
- package/types/internal-api/shell/growl.d.ts +0 -25
- package/types/internal-api/shell/slideIn.d.ts +0 -15
|
@@ -37,20 +37,35 @@ interface Namespace extends ModelNamespace {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
interface NamespaceProjectFilterResult {
|
|
41
|
+
/**
|
|
42
|
+
* True if the ns should be filtered IN. False if filtered OUT.
|
|
43
|
+
*/
|
|
44
|
+
[nsName: string]: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Helper class, contains namespace / project filter specific functions
|
|
49
|
+
*/
|
|
40
50
|
class NamespaceProjectFilters {
|
|
41
51
|
/**
|
|
42
52
|
* User needs all resources.... except if there's some settings which should remove resources in specific circumstances
|
|
43
53
|
*/
|
|
44
54
|
protected handlePrefAndSettingFilter(args: {
|
|
45
55
|
allNamespaces: Namespace[],
|
|
56
|
+
/**
|
|
57
|
+
* Reserved / Obscure namespaces are ones used to support clusters and users. By default these are hidden
|
|
58
|
+
*/
|
|
46
59
|
showReservedRancherNamespaces: boolean,
|
|
60
|
+
/**
|
|
61
|
+
* Has product config disabled system projects and namespaces
|
|
62
|
+
*/
|
|
47
63
|
productHidesSystemNamespaces: boolean,
|
|
48
|
-
}):
|
|
64
|
+
}): NamespaceProjectFilterResult {
|
|
49
65
|
const { allNamespaces, showReservedRancherNamespaces, productHidesSystemNamespaces } = args;
|
|
50
66
|
|
|
51
|
-
// These are AND'd together
|
|
52
67
|
// Not ns 1 AND ns 2
|
|
53
|
-
|
|
68
|
+
return allNamespaces.reduce((res, ns) => {
|
|
54
69
|
// Links to ns.isObscure and covers things like `c-`, `user-`, etc (see OBSCURE_NAMESPACE_PREFIX)
|
|
55
70
|
const hideObscure = showReservedRancherNamespaces ? false : ns.isObscure;
|
|
56
71
|
|
|
@@ -58,23 +73,11 @@ class NamespaceProjectFilters {
|
|
|
58
73
|
const hideSystem = productHidesSystemNamespaces ? ns.isSystem : false;
|
|
59
74
|
|
|
60
75
|
if (hideObscure || hideSystem) {
|
|
61
|
-
res
|
|
76
|
+
res[ns.name] = false;
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
return res;
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
if (filterNamespaces.length) {
|
|
68
|
-
return [new PaginationParamFilter({
|
|
69
|
-
fields: [{
|
|
70
|
-
value: filterNamespaces.join(','),
|
|
71
|
-
equality: PaginationFilterEquality.NOT_IN,
|
|
72
|
-
field: 'metadata.namespace',
|
|
73
|
-
}],
|
|
74
|
-
})];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return [];
|
|
80
|
+
}, {} as NamespaceProjectFilterResult);
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/**
|
|
@@ -88,31 +91,88 @@ class NamespaceProjectFilters {
|
|
|
88
91
|
allNamespaces: Namespace[],
|
|
89
92
|
isAllSystem: boolean,
|
|
90
93
|
isAllUser: boolean,
|
|
91
|
-
}) {
|
|
92
|
-
const { allNamespaces, isAllSystem } = args;
|
|
94
|
+
}): NamespaceProjectFilterResult {
|
|
95
|
+
const { allNamespaces, isAllSystem, isAllUser } = args;
|
|
93
96
|
const allSystem = allNamespaces.filter((ns) => ns.isSystem);
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
return allSystem.reduce((res, ns) => {
|
|
99
|
+
if (isAllSystem) {
|
|
100
|
+
// We want to filter IN system namespaces
|
|
101
|
+
res[ns.name] = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isAllUser) {
|
|
105
|
+
// We want to filter OUT system namespaces
|
|
106
|
+
res[ns.name] = false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return res;
|
|
110
|
+
}, {} as NamespaceProjectFilterResult);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Combine result `b` into `a` and return result
|
|
115
|
+
*/
|
|
116
|
+
protected combineNsProjectFilterResults(a: NamespaceProjectFilterResult, b: NamespaceProjectFilterResult): NamespaceProjectFilterResult {
|
|
117
|
+
// Start with `a`
|
|
118
|
+
const res = { ...a };
|
|
119
|
+
|
|
120
|
+
// Merge entries from `b` into `a` if they don't exist in `a`. This maintains a hierarchy
|
|
121
|
+
// 1. if something has been excluded in `a` ignore requests to include given `b`
|
|
122
|
+
// 2. if something has been included in `a` ignore requests to exclude given `b`
|
|
123
|
+
Object.entries(b).forEach(([ns, include]) => {
|
|
124
|
+
if (res[ns] === undefined) {
|
|
125
|
+
res[ns] = include;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return res;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convert @NamespaceProjectFilterResult into @PaginationParamFilter
|
|
134
|
+
*/
|
|
135
|
+
protected createFiltersFromNamespaceProjectFilterResult(filterResult: NamespaceProjectFilterResult): PaginationParamFilter[] {
|
|
136
|
+
const inList: string[] = [];
|
|
137
|
+
const outList: string[] = [];
|
|
138
|
+
|
|
139
|
+
Object.entries(filterResult).forEach(([ns, include]) => {
|
|
140
|
+
if (include) {
|
|
141
|
+
inList.push(ns);
|
|
142
|
+
} else {
|
|
143
|
+
outList.push(ns);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const res: PaginationParamFilter[] = [];
|
|
148
|
+
|
|
149
|
+
// There's no point having both IN and OUT lists together, so prefer the IN list
|
|
150
|
+
if (inList.length) {
|
|
151
|
+
res.push(new PaginationParamFilter({
|
|
152
|
+
fields: [{
|
|
153
|
+
value: inList.join(','),
|
|
154
|
+
equality: PaginationFilterEquality.IN,
|
|
155
|
+
field: 'metadata.namespace',
|
|
156
|
+
}],
|
|
157
|
+
}));
|
|
158
|
+
} else if (outList.length) {
|
|
159
|
+
res.push(new PaginationParamFilter({
|
|
160
|
+
fields: [{
|
|
161
|
+
value: outList.join(','),
|
|
162
|
+
equality: PaginationFilterEquality.NOT_IN,
|
|
163
|
+
field: 'metadata.namespace',
|
|
164
|
+
}],
|
|
110
165
|
}));
|
|
111
166
|
}
|
|
167
|
+
|
|
168
|
+
return res;
|
|
112
169
|
}
|
|
113
170
|
|
|
114
171
|
/**
|
|
115
172
|
* User needs resources in a set of projects or namespaces
|
|
173
|
+
*
|
|
174
|
+
* Mainly deals with the projectornamespaces filter, also ensures namespace in local cluster matching target project's aren't included
|
|
175
|
+
*
|
|
116
176
|
*/
|
|
117
177
|
protected handleSelectionFilter(neu: string[], isLocalCluster: boolean) {
|
|
118
178
|
// User has one or more projects or namespaces. We can pass this straight through to projectsornamespaces
|
|
@@ -124,12 +184,12 @@ class NamespaceProjectFilters {
|
|
|
124
184
|
];
|
|
125
185
|
|
|
126
186
|
if (isLocalCluster) {
|
|
127
|
-
//
|
|
128
|
-
//
|
|
187
|
+
// We need to be careful of the local cluster where there's namespaces related to projects with the same id
|
|
188
|
+
// In this case
|
|
129
189
|
// - We're including resources in the project and it's related namespace (via projectsornamespaces)
|
|
130
190
|
// - We're also then excluding resources in the related namespace (via below `filter`)
|
|
131
191
|
|
|
132
|
-
// Exclude resources NOT in
|
|
192
|
+
// Exclude resources NOT in project's backing namespace 1 AND not in project's backing namespace 2
|
|
133
193
|
// &filter=metadata.namespace!=pn1&filter=metadata.namespace!=pn2
|
|
134
194
|
return {
|
|
135
195
|
projectsOrNamespaces,
|
|
@@ -352,24 +412,29 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
352
412
|
let projectsOrNamespaces: PaginationParamProjectOrNamespace[] = [];
|
|
353
413
|
// used to return resources in / not in namespaces
|
|
354
414
|
// &filter=metadata.namespace=abc
|
|
355
|
-
|
|
415
|
+
const filters: PaginationParamFilter[] = [];
|
|
416
|
+
let nsProjectFilterResults = {};
|
|
356
417
|
|
|
357
418
|
if (!showReservedRancherNamespaces || productHidesSystemNamespaces) {
|
|
358
|
-
// We need to hide reserved namespaces ('c-', 'user-', etc) OR system namespaces
|
|
359
|
-
|
|
419
|
+
// We need to hide reserved namespaces ('c-', 'user-', etc) OR system namespaces (given product may hide them)
|
|
420
|
+
nsProjectFilterResults = this.combineNsProjectFilterResults(nsProjectFilterResults, this.handlePrefAndSettingFilter({
|
|
360
421
|
allNamespaces, showReservedRancherNamespaces, productHidesSystemNamespaces
|
|
361
|
-
});
|
|
422
|
+
}));
|
|
362
423
|
}
|
|
363
424
|
|
|
364
425
|
const isAllSystem = selection[0] === NAMESPACE_FILTER_ALL_SYSTEM;
|
|
365
426
|
const isAllUser = selection[0] === NAMESPACE_FILTER_ALL_USER;
|
|
366
427
|
|
|
367
428
|
if (selection.length === 1 && (isAllSystem || isAllUser)) {
|
|
368
|
-
// Filter by resources either in or not in system namespaces
|
|
369
|
-
|
|
429
|
+
// Filter by resources either in or not in system namespaces (given user selection)
|
|
430
|
+
nsProjectFilterResults = this.combineNsProjectFilterResults(nsProjectFilterResults, this.handleSystemOrUserFilter({
|
|
370
431
|
allNamespaces, isAllSystem, isAllUser
|
|
371
432
|
}));
|
|
433
|
+
|
|
434
|
+
filters.push(...this.createFiltersFromNamespaceProjectFilterResult(nsProjectFilterResults));
|
|
372
435
|
} else {
|
|
436
|
+
filters.push(...this.createFiltersFromNamespaceProjectFilterResult(nsProjectFilterResults));
|
|
437
|
+
|
|
373
438
|
// User has one or more projects or namespaces
|
|
374
439
|
const res = this.handleSelectionFilter(selection, isLocalCluster);
|
|
375
440
|
|
package/scripts/publish-shell.sh
CHANGED
|
@@ -8,6 +8,7 @@ SHELL_DIR=$BASE_DIR/shell/
|
|
|
8
8
|
CREATORS_DIR=$BASE_DIR/creators/extension
|
|
9
9
|
FORCE_PUBLISH_TO_NPM="false"
|
|
10
10
|
DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
|
|
11
|
+
DUMMY_VERSION="99.99.99"
|
|
11
12
|
|
|
12
13
|
# if TAG doesn't exist, we can exit as it's needed for any type of publish.
|
|
13
14
|
if [ -z "$TAG" ]; then
|
|
@@ -84,6 +85,14 @@ function publish() {
|
|
|
84
85
|
fi
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
update_version_in_package_json() {
|
|
89
|
+
local package_json_path="$1"
|
|
90
|
+
local version="$2"
|
|
91
|
+
|
|
92
|
+
sed -i.bak -e "s/\"version\": \"[0-9]*.[0-9]*.[0-9]*\(-alpha\.[0-9]*\|-release[0-9]*.[0-9]*.[0-9]*\|-rc\.[0-9]*\)\{0,1\}\",/\"version\": \"${version}\",/g" "$package_json_path"
|
|
93
|
+
rm "${package_json_path}.bak"
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
echo "TAG ${TAG}"
|
|
88
97
|
|
|
89
98
|
# let's get the package name and version from the tag
|
|
@@ -102,10 +111,26 @@ fi
|
|
|
102
111
|
case $PKG_NAME in
|
|
103
112
|
"shell")
|
|
104
113
|
echo "Publishing only Shell pkg via tagged release"
|
|
114
|
+
|
|
115
|
+
# with the changes in https://github.com/rancher/dashboard/pull/16166/files#diff-d954ab41ef46f7fdbaaf6d8c2bc715ad2cc823a829317b6ff93a3c94a92811f1
|
|
116
|
+
# with NPM 11.3 --dry--run now does additional checks, one of them is the version number
|
|
117
|
+
# so for dry runs we need to provide a valid version number here (not something published before)
|
|
118
|
+
if [ ${DRY_RUN} == "true" ]; then
|
|
119
|
+
update_version_in_package_json "${SHELL_DIR}/package.json" "${DUMMY_VERSION}"
|
|
120
|
+
fi
|
|
121
|
+
|
|
105
122
|
publish "Shell" ${SHELL_DIR} ${PKG_V}
|
|
106
123
|
;;
|
|
107
124
|
"creators")
|
|
108
125
|
echo "Publishing only Creators pkg via tagged release"
|
|
126
|
+
|
|
127
|
+
# with the changes in https://github.com/rancher/dashboard/pull/16166/files#diff-d954ab41ef46f7fdbaaf6d8c2bc715ad2cc823a829317b6ff93a3c94a92811f1
|
|
128
|
+
# with NPM 11.3 --dry--run now does additional checks, one of them is the version number
|
|
129
|
+
# so for dry runs we need to provide a valid version number here (not something published before)
|
|
130
|
+
if [ ${DRY_RUN} == "true" ]; then
|
|
131
|
+
update_version_in_package_json "${CREATORS_DIR}/package.json" "${DUMMY_VERSION}"
|
|
132
|
+
fi
|
|
133
|
+
|
|
109
134
|
publish "Extension creator" ${CREATORS_DIR} ${PKG_V}
|
|
110
135
|
;;
|
|
111
136
|
*)
|
|
@@ -1116,7 +1116,169 @@ describe('type-map', () => {
|
|
|
1116
1116
|
});
|
|
1117
1117
|
});
|
|
1118
1118
|
});
|
|
1119
|
+
|
|
1120
|
+
describe('activeProducts', () => {
|
|
1121
|
+
// Basic schemas for product filtering tests
|
|
1122
|
+
const productSchemas = {
|
|
1123
|
+
myType: {
|
|
1124
|
+
id: 'mytype',
|
|
1125
|
+
_id: 'mytype',
|
|
1126
|
+
type: SCHEMA,
|
|
1127
|
+
_group: 'mygroup',
|
|
1128
|
+
},
|
|
1129
|
+
anotherType: {
|
|
1130
|
+
id: 'anothertype',
|
|
1131
|
+
_id: 'anothertype',
|
|
1132
|
+
type: SCHEMA,
|
|
1133
|
+
_group: 'anothergroup',
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const createProductState = (products) => ({
|
|
1138
|
+
products,
|
|
1139
|
+
schemaGeneration: 1,
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
const createProductRootGetters = (moduleSchemas = [], moduleName = 'cluster') => ({
|
|
1143
|
+
'prefs/get': () => false,
|
|
1144
|
+
[`${ moduleName }/all`]: (resource) => {
|
|
1145
|
+
if (resource === SCHEMA) {
|
|
1146
|
+
return moduleSchemas;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return [];
|
|
1150
|
+
},
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
describe('ifHaveType', () => {
|
|
1154
|
+
it('should show product when matching type exists', () => {
|
|
1155
|
+
const state = createProductState([{
|
|
1156
|
+
name: 'test-product',
|
|
1157
|
+
inStore: 'cluster',
|
|
1158
|
+
ifHaveType: 'mytype',
|
|
1159
|
+
}]);
|
|
1160
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1161
|
+
|
|
1162
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1163
|
+
|
|
1164
|
+
expect(active).toHaveLength(1);
|
|
1165
|
+
expect(active[0].name).toBe('test-product');
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
it('should hide product when matching type does not exist', () => {
|
|
1169
|
+
const state = createProductState([{
|
|
1170
|
+
name: 'test-product',
|
|
1171
|
+
inStore: 'cluster',
|
|
1172
|
+
ifHaveType: 'missingtype',
|
|
1173
|
+
}]);
|
|
1174
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1175
|
+
|
|
1176
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1177
|
+
|
|
1178
|
+
expect(active).toHaveLength(0);
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
describe('ifNotHaveType', () => {
|
|
1183
|
+
it('should show product when matching type does NOT exist', () => {
|
|
1184
|
+
const state = createProductState([{
|
|
1185
|
+
name: 'test-product',
|
|
1186
|
+
inStore: 'cluster',
|
|
1187
|
+
ifNotHaveType: 'missingtype',
|
|
1188
|
+
}]);
|
|
1189
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1190
|
+
|
|
1191
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1192
|
+
|
|
1193
|
+
expect(active).toHaveLength(1);
|
|
1194
|
+
expect(active[0].name).toBe('test-product');
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it('should hide product when matching type exists', () => {
|
|
1198
|
+
const state = createProductState([{
|
|
1199
|
+
name: 'test-product',
|
|
1200
|
+
inStore: 'cluster',
|
|
1201
|
+
ifNotHaveType: 'mytype',
|
|
1202
|
+
}]);
|
|
1203
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1204
|
+
|
|
1205
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1206
|
+
|
|
1207
|
+
expect(active).toHaveLength(0);
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
it('should support regex pattern in ifNotHaveType', () => {
|
|
1211
|
+
const state = createProductState([{
|
|
1212
|
+
name: 'test-product',
|
|
1213
|
+
inStore: 'cluster',
|
|
1214
|
+
ifNotHaveType: 'my.*',
|
|
1215
|
+
}]);
|
|
1216
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1217
|
+
|
|
1218
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1219
|
+
|
|
1220
|
+
expect(active).toHaveLength(0);
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
it('should show product when regex pattern does not match any type', () => {
|
|
1224
|
+
const state = createProductState([{
|
|
1225
|
+
name: 'test-product',
|
|
1226
|
+
inStore: 'cluster',
|
|
1227
|
+
ifNotHaveType: 'nomatch.*',
|
|
1228
|
+
}]);
|
|
1229
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1230
|
+
|
|
1231
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1232
|
+
|
|
1233
|
+
expect(active).toHaveLength(1);
|
|
1234
|
+
expect(active[0].name).toBe('test-product');
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
describe('combined ifHaveType and ifNotHaveType', () => {
|
|
1239
|
+
it('should show product when ifHaveType matches and ifNotHaveType does not match', () => {
|
|
1240
|
+
const state = createProductState([{
|
|
1241
|
+
name: 'test-product',
|
|
1242
|
+
inStore: 'cluster',
|
|
1243
|
+
ifHaveType: 'mytype',
|
|
1244
|
+
ifNotHaveType: 'missingtype',
|
|
1245
|
+
}]);
|
|
1246
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1247
|
+
|
|
1248
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1249
|
+
|
|
1250
|
+
expect(active).toHaveLength(1);
|
|
1251
|
+
expect(active[0].name).toBe('test-product');
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it('should hide product when ifHaveType matches but ifNotHaveType also matches', () => {
|
|
1255
|
+
const state = createProductState([{
|
|
1256
|
+
name: 'test-product',
|
|
1257
|
+
inStore: 'cluster',
|
|
1258
|
+
ifHaveType: 'mytype',
|
|
1259
|
+
ifNotHaveType: 'anothertype',
|
|
1260
|
+
}]);
|
|
1261
|
+
const rootGetters = createProductRootGetters([productSchemas.myType, productSchemas.anotherType]);
|
|
1262
|
+
|
|
1263
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1264
|
+
|
|
1265
|
+
expect(active).toHaveLength(0);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it('should hide product when ifHaveType does not match', () => {
|
|
1269
|
+
const state = createProductState([{
|
|
1270
|
+
name: 'test-product',
|
|
1271
|
+
inStore: 'cluster',
|
|
1272
|
+
ifHaveType: 'missingtype',
|
|
1273
|
+
ifNotHaveType: 'anothermissingtype',
|
|
1274
|
+
}]);
|
|
1275
|
+
const rootGetters = createProductRootGetters([productSchemas.myType]);
|
|
1276
|
+
|
|
1277
|
+
const active = getters.activeProducts(state, {}, {}, rootGetters);
|
|
1278
|
+
|
|
1279
|
+
expect(active).toHaveLength(0);
|
|
1280
|
+
});
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1119
1283
|
});
|
|
1120
1284
|
});
|
|
1121
|
-
|
|
1122
|
-
// getTree - Remove ignored schemas, not-applicable to ns filter
|
package/store/notifications.ts
CHANGED
|
@@ -299,6 +299,8 @@ export const actions = {
|
|
|
299
299
|
|
|
300
300
|
// Show a growl for the notification if necessary
|
|
301
301
|
dispatch('growl/notification', notification, { root: true });
|
|
302
|
+
|
|
303
|
+
return notification.id;
|
|
302
304
|
},
|
|
303
305
|
|
|
304
306
|
async fromGrowl( { commit, getters }: any, notification: Notification) {
|
package/store/type-map.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
// ifHave, -- Show this product only if the given capability is available
|
|
36
36
|
// ifHaveGroup, -- Show this product only if the given group exists in the store [inStore]
|
|
37
37
|
// ifHaveType, -- Show this product only if the given type exists in the store [inStore], This can also be specified as an object { type: TYPE, store: 'management' } if the type isn't in the current [inStore]
|
|
38
|
+
// ifNotHaveType, -- Hide this product if the given type exists in the store [inStore] (opposite of ifHaveType)
|
|
38
39
|
// ifHaveVerb, -- In combination with ifHaveTYpe, show it only if the type also has this collectionMethod
|
|
39
40
|
// inStore, -- Which store to look at for if* above and the left-nav, defaults to "cluster"
|
|
40
41
|
// rootProduct, -- Optional root (parent) product - if set, used to optimize navigation when product changes stays within root product
|
|
@@ -238,7 +239,7 @@ export function DSL(store, product, module = 'type-map') {
|
|
|
238
239
|
};
|
|
239
240
|
|
|
240
241
|
// Convert strings to regex's - we do this once here for efficiency
|
|
241
|
-
for ( const k of ['ifHaveGroup', 'ifHaveType'] ) {
|
|
242
|
+
for ( const k of ['ifHaveGroup', 'ifHaveType', 'ifNotHaveType'] ) {
|
|
242
243
|
if ( opt[k] ) {
|
|
243
244
|
if (Array.isArray(opt[k])) {
|
|
244
245
|
opt[k] = opt[k].map((r) => regexToString(ensureRegex(r)));
|
|
@@ -1419,6 +1420,14 @@ export const getters = {
|
|
|
1419
1420
|
}
|
|
1420
1421
|
}
|
|
1421
1422
|
|
|
1423
|
+
if ( p.ifNotHaveType ) {
|
|
1424
|
+
const haveIds = knownTypes[module].filter((t) => t.match(stringToRegex(p.ifNotHaveType)) );
|
|
1425
|
+
|
|
1426
|
+
if ( haveIds.length ) {
|
|
1427
|
+
return false;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1422
1431
|
if ( p.ifHaveGroup && !knownGroups[module].find((t) => t.match(stringToRegex(p.ifHaveGroup)) ) ) {
|
|
1423
1432
|
return false;
|
|
1424
1433
|
}
|
|
@@ -14,7 +14,7 @@ export interface ModalConfig {
|
|
|
14
14
|
*
|
|
15
15
|
* this.$shell.modal({
|
|
16
16
|
* component: MyCustomModal,
|
|
17
|
-
*
|
|
17
|
+
* props: { title: 'Hello Modal' }
|
|
18
18
|
* });
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
@@ -25,10 +25,10 @@ export interface ModalConfig {
|
|
|
25
25
|
*
|
|
26
26
|
* Example:
|
|
27
27
|
* ```ts
|
|
28
|
-
*
|
|
28
|
+
* props: { title: 'Hello Modal', isVisible: true }
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
|
|
31
|
+
props?: Record<string, any>;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Optional array of resources that the modal component might need.
|
|
@@ -47,11 +47,11 @@ export interface ModalConfig {
|
|
|
47
47
|
*
|
|
48
48
|
* Examples:
|
|
49
49
|
* ```ts
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* width: '800px' // Width in pixels
|
|
51
|
+
* width: '75%' // Width as a percentage
|
|
52
52
|
* ```
|
|
53
53
|
*/
|
|
54
|
-
|
|
54
|
+
width?: string;
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Determines if clicking outside the modal will close it. Defaults to `true`.
|