@rancher/shell 3.0.11 → 3.0.12-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/assets/images/providers/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/styles/base/_mixins.scss +31 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -5
- package/assets/translations/en-us.yaml +24 -21
- package/assets/translations/zh-hans.yaml +4 -11
- package/chart/__tests__/S3.test.ts +10 -3
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/DetailText.vue +12 -3
- package/components/EmptyProductPage.vue +76 -0
- package/components/Resource/Detail/CopyToClipboard.vue +1 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
- package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
- package/components/Resource/Detail/TitleBar/index.vue +1 -1
- package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
- package/components/Resource/Detail/ViewOptions/index.vue +2 -1
- package/components/ResourceList/Masthead.vue +25 -2
- package/components/SelectIconGrid.vue +5 -0
- package/components/SideNav.vue +13 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/__tests__/PromptModal.test.ts +2 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/fleet/FleetClusters.vue +1 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- package/components/form/NodeScheduling.vue +17 -3
- package/components/form/PrivateRegistry.vue +69 -0
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/formatter/WorkloadHealthScale.vue +3 -1
- package/components/nav/Group.vue +33 -9
- package/components/nav/Header.vue +56 -10
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/TopLevelMenu.vue +15 -1
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +12 -25
- package/components/templates/plain.vue +13 -26
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/pagination-table-headers.js +8 -1
- package/config/product/apps.js +2 -1
- package/config/product/auth.js +1 -0
- package/config/product/backup.js +1 -0
- package/config/product/compliance.js +1 -1
- package/config/product/explorer.js +25 -6
- package/config/product/fleet.js +1 -0
- package/config/product/gatekeeper.js +1 -0
- package/config/product/istio.js +1 -0
- package/config/product/logging.js +1 -0
- package/config/product/longhorn.js +2 -1
- package/config/product/manager.js +1 -0
- package/config/product/monitoring.js +1 -0
- package/config/product/navlinks.js +1 -0
- package/config/product/neuvector.js +2 -1
- package/config/product/settings.js +1 -0
- package/config/product/uiplugins.js +1 -0
- package/config/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products-helpers.test.ts +454 -0
- package/core/__tests__/plugin-products.test.ts +3810 -0
- package/core/extension-manager-impl.js +30 -1
- package/core/plugin-products-base.ts +392 -0
- package/core/plugin-products-extending.ts +44 -0
- package/core/plugin-products-helpers.ts +263 -0
- package/core/plugin-products-top-level.ts +66 -0
- package/core/plugin-products-type-guards.ts +33 -0
- package/core/plugin-products.ts +50 -0
- package/core/plugin-types.ts +237 -0
- package/core/plugin.ts +45 -10
- package/core/productDebugger.js +48 -0
- package/core/types.ts +97 -11
- package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
- package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/fleet.cattle.io.bundle.vue +21 -34
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
- package/dialog/InstallExtensionDialog.vue +6 -27
- package/dialog/UninstallExistingExtensionDialog.vue +141 -0
- package/dialog/UninstallExtensionDialog.vue +4 -26
- package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/oidc.vue +8 -0
- package/edit/kontainerDriver.vue +1 -2
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/list/provisioning.cattle.io.cluster.vue +0 -1
- package/list/workload.vue +11 -4
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- package/mixins/resource-fetch.js +12 -3
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/fleet-application.js +1 -1
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +0 -53
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/nodedriver.js +7 -0
- package/models/pod.js +18 -0
- package/models/workload.js +20 -2
- package/package.json +13 -13
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +217 -33
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +8 -3
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/settings/brand.vue +4 -4
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
- package/pages/c/_cluster/uiplugins/index.vue +166 -62
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
- package/plugins/dashboard-store/actions.js +3 -2
- package/plugins/dashboard-store/resource-class.js +62 -6
- package/plugins/plugin.js +16 -0
- package/plugins/steve/steve-pagination-utils.ts +7 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/scripts/test-plugins-build.sh +5 -2
- package/scripts/typegen.sh +13 -1
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +161 -11
- package/store/__tests__/type-map.test.ts +84 -24
- package/store/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/store/type-map.js +42 -3
- package/tsconfig.paths.json +1 -0
- package/types/resources/pod.ts +18 -0
- package/types/shell/index.d.ts +8539 -2938
- package/types/store/dashboard-store.types.ts +5 -0
- package/types/store/pagination.types.ts +6 -0
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/axios.js +1 -4
- package/utils/dynamic-importer.js +3 -2
- package/utils/object.js +33 -2
- package/utils/pagination-utils.ts +1 -1
- package/utils/time.ts +5 -0
- package/utils/uiplugins.ts +12 -16
- package/utils/validators/__tests__/private-registry.test.ts +76 -0
- package/utils/validators/private-registry.ts +28 -0
- package/vue.config.js +0 -9
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- package/utils/ember-page.js +0 -30
|
@@ -83,6 +83,11 @@ export interface ActionFindPageArgs extends ActionCoreFindArgs {
|
|
|
83
83
|
|
|
84
84
|
saveCountAs?: string,
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* When making a supporting HTTP request include associated resource data
|
|
88
|
+
*/
|
|
89
|
+
includeAssociatedData?: boolean,
|
|
90
|
+
|
|
86
91
|
/**
|
|
87
92
|
* The target minimum revision for the resource.
|
|
88
93
|
*
|
|
@@ -607,6 +607,7 @@ export interface StorePaginationRequest {
|
|
|
607
607
|
* The single namespace to filter results by (as part of url path, not pagination params)
|
|
608
608
|
*/
|
|
609
609
|
namespace?: string,
|
|
610
|
+
|
|
610
611
|
/**
|
|
611
612
|
* The set of pagination args used to create the request
|
|
612
613
|
*/
|
|
@@ -616,6 +617,11 @@ export interface StorePaginationRequest {
|
|
|
616
617
|
* Does this request stem from a list with manual refresh?
|
|
617
618
|
*/
|
|
618
619
|
hasManualRefresh?: boolean,
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* When making a supporting HTTP request include associated resource data
|
|
623
|
+
*/
|
|
624
|
+
includeAssociatedData?: boolean,
|
|
619
625
|
}
|
|
620
626
|
|
|
621
627
|
/**
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { GitUtils, Commit } from '@shell/utils/git';
|
|
2
|
+
|
|
3
|
+
describe('git utils', () => {
|
|
4
|
+
describe('gitUtils.github.normalize.repo', () => {
|
|
5
|
+
it('maps owner fields from GitHub API response', () => {
|
|
6
|
+
const data = {
|
|
7
|
+
owner: {
|
|
8
|
+
login: 'octocat',
|
|
9
|
+
html_url: 'https://github.com/octocat',
|
|
10
|
+
avatar_url: 'https://avatars.githubusercontent.com/u/1?v=4'
|
|
11
|
+
},
|
|
12
|
+
description: 'Hello World',
|
|
13
|
+
created_at: '2021-01-01T00:00:00Z',
|
|
14
|
+
updated_at: '2021-06-01T00:00:00Z',
|
|
15
|
+
html_url: 'https://github.com/octocat/hello-world',
|
|
16
|
+
name: 'hello-world'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const result = GitUtils.github.normalize.repo(data);
|
|
20
|
+
|
|
21
|
+
expect(result.owner).toStrictEqual({
|
|
22
|
+
name: 'octocat',
|
|
23
|
+
htmlUrl: 'https://github.com/octocat',
|
|
24
|
+
avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4'
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('maps repo-level fields from GitHub API response', () => {
|
|
29
|
+
const data = {
|
|
30
|
+
owner: {
|
|
31
|
+
login: 'octocat', html_url: '', avatar_url: ''
|
|
32
|
+
},
|
|
33
|
+
description: 'A repository description',
|
|
34
|
+
created_at: '2021-01-01T00:00:00Z',
|
|
35
|
+
updated_at: '2022-03-15T12:00:00Z',
|
|
36
|
+
html_url: 'https://github.com/octocat/repo',
|
|
37
|
+
name: 'my-repo'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = GitUtils.github.normalize.repo(data);
|
|
41
|
+
|
|
42
|
+
expect(result.description).toStrictEqual('A repository description');
|
|
43
|
+
expect(result.created_at).toStrictEqual('2021-01-01T00:00:00Z');
|
|
44
|
+
expect(result.updated_at).toStrictEqual('2022-03-15T12:00:00Z');
|
|
45
|
+
expect(result.htmlUrl).toStrictEqual('https://github.com/octocat/repo');
|
|
46
|
+
expect(result.name).toStrictEqual('my-repo');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('handles missing owner gracefully', () => {
|
|
50
|
+
const data = {
|
|
51
|
+
owner: undefined,
|
|
52
|
+
description: 'No owner',
|
|
53
|
+
created_at: '2021-01-01T00:00:00Z',
|
|
54
|
+
updated_at: '2021-01-01T00:00:00Z',
|
|
55
|
+
html_url: 'https://github.com/no/owner',
|
|
56
|
+
name: 'no-owner'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = GitUtils.github.normalize.repo(data);
|
|
60
|
+
|
|
61
|
+
expect(result.owner).toStrictEqual({
|
|
62
|
+
name: undefined,
|
|
63
|
+
htmlUrl: undefined,
|
|
64
|
+
avatarUrl: undefined
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('gitUtils.github.normalize.commit', () => {
|
|
70
|
+
it('maps commit fields from GitHub API response', () => {
|
|
71
|
+
const data = {
|
|
72
|
+
commit: {
|
|
73
|
+
message: 'fix: resolve issue',
|
|
74
|
+
committer: { date: '2022-01-15T10:30:00Z' }
|
|
75
|
+
},
|
|
76
|
+
html_url: 'https://github.com/octocat/repo/commit/abc1234567890',
|
|
77
|
+
sha: 'abc1234567890abcdef',
|
|
78
|
+
author: {
|
|
79
|
+
login: 'octocat',
|
|
80
|
+
avatar_url: 'https://avatars.githubusercontent.com/u/1?v=4',
|
|
81
|
+
htmlUrl: 'https://github.com/octocat'
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const result = GitUtils.github.normalize.commit(data);
|
|
86
|
+
|
|
87
|
+
expect(result.message).toStrictEqual('fix: resolve issue');
|
|
88
|
+
expect(result.htmlUrl).toStrictEqual('https://github.com/octocat/repo/commit/abc1234567890');
|
|
89
|
+
expect(result.sha).toStrictEqual('abc1234');
|
|
90
|
+
expect(result.commitId).toStrictEqual('abc1234567890abcdef');
|
|
91
|
+
expect(result.date).toStrictEqual('2022-01-15T10:30:00Z');
|
|
92
|
+
expect(result.isChecked).toStrictEqual(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('truncates sha to 7 characters', () => {
|
|
96
|
+
const data = {
|
|
97
|
+
commit: { message: 'chore: update deps', committer: { date: '2022-01-01T00:00:00Z' } },
|
|
98
|
+
html_url: 'https://github.com/octocat/repo/commit/1234567890',
|
|
99
|
+
sha: '1234567890abcdef1234',
|
|
100
|
+
author: {
|
|
101
|
+
login: 'user', avatar_url: '', htmlUrl: ''
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = GitUtils.github.normalize.commit(data);
|
|
106
|
+
|
|
107
|
+
expect(result.sha).toStrictEqual('1234567');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('returns undefined sha when sha is empty string', () => {
|
|
111
|
+
const data = {
|
|
112
|
+
commit: { message: 'fix: bug', committer: { date: '2022-01-01T00:00:00Z' } },
|
|
113
|
+
html_url: 'https://github.com/octocat/repo/commit/x',
|
|
114
|
+
sha: '',
|
|
115
|
+
author: {
|
|
116
|
+
login: 'user', avatar_url: '', htmlUrl: ''
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result: Commit = GitUtils.github.normalize.commit(data);
|
|
121
|
+
|
|
122
|
+
expect(result.sha).toBeUndefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('maps author fields from GitHub API response', () => {
|
|
126
|
+
const data = {
|
|
127
|
+
commit: { message: 'feat: new feature', committer: { date: '2022-01-01T00:00:00Z' } },
|
|
128
|
+
html_url: 'https://github.com/octocat/repo/commit/abc',
|
|
129
|
+
sha: 'abcdef1234567890',
|
|
130
|
+
author: {
|
|
131
|
+
login: 'contributor',
|
|
132
|
+
avatar_url: 'https://avatars.githubusercontent.com/u/2?v=4',
|
|
133
|
+
htmlUrl: 'https://github.com/contributor'
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = GitUtils.github.normalize.commit(data);
|
|
138
|
+
|
|
139
|
+
expect(result.author).toStrictEqual({
|
|
140
|
+
name: 'contributor',
|
|
141
|
+
avatarUrl: 'https://avatars.githubusercontent.com/u/2?v=4',
|
|
142
|
+
htmlUrl: 'https://github.com/contributor'
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('gitUtils.gitlab.normalize.repo', () => {
|
|
148
|
+
it('maps owner fields from GitLab API response', () => {
|
|
149
|
+
const data = {
|
|
150
|
+
namespace: {
|
|
151
|
+
name: 'my-group',
|
|
152
|
+
web_url: 'https://gitlab.com/my-group',
|
|
153
|
+
avatar_url: 'https://gitlab.com/uploads/group.png'
|
|
154
|
+
},
|
|
155
|
+
description: 'GitLab repo description',
|
|
156
|
+
created_at: '2021-02-01T00:00:00Z',
|
|
157
|
+
last_activity_at: '2022-07-20T00:00:00Z',
|
|
158
|
+
web_url: 'https://gitlab.com/my-group/project',
|
|
159
|
+
name: 'project'
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = GitUtils.gitlab.normalize.repo(data);
|
|
163
|
+
|
|
164
|
+
expect(result.owner).toStrictEqual({
|
|
165
|
+
name: 'my-group',
|
|
166
|
+
htmlUrl: 'https://gitlab.com/my-group',
|
|
167
|
+
avatarUrl: 'https://gitlab.com/uploads/group.png'
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('maps repo-level fields from GitLab API response', () => {
|
|
172
|
+
const data = {
|
|
173
|
+
namespace: {
|
|
174
|
+
name: 'ns', web_url: '', avatar_url: ''
|
|
175
|
+
},
|
|
176
|
+
description: 'My GitLab project',
|
|
177
|
+
created_at: '2020-05-01T00:00:00Z',
|
|
178
|
+
last_activity_at: '2023-01-10T00:00:00Z',
|
|
179
|
+
web_url: 'https://gitlab.com/ns/myproject',
|
|
180
|
+
name: 'myproject'
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const result = GitUtils.gitlab.normalize.repo(data);
|
|
184
|
+
|
|
185
|
+
expect(result.description).toStrictEqual('My GitLab project');
|
|
186
|
+
expect(result.created_at).toStrictEqual('2020-05-01T00:00:00Z');
|
|
187
|
+
expect(result.updated_at).toStrictEqual('2023-01-10T00:00:00Z');
|
|
188
|
+
expect(result.htmlUrl).toStrictEqual('https://gitlab.com/ns/myproject');
|
|
189
|
+
expect(result.name).toStrictEqual('myproject');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('maps updated_at from last_activity_at (not updated_at)', () => {
|
|
193
|
+
const data = {
|
|
194
|
+
namespace: {
|
|
195
|
+
name: 'ns', web_url: '', avatar_url: ''
|
|
196
|
+
},
|
|
197
|
+
description: '',
|
|
198
|
+
created_at: '2020-01-01T00:00:00Z',
|
|
199
|
+
last_activity_at: '2023-06-15T08:00:00Z',
|
|
200
|
+
updated_at: 'should-be-ignored',
|
|
201
|
+
web_url: 'https://gitlab.com/ns/proj',
|
|
202
|
+
name: 'proj'
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const result = GitUtils.gitlab.normalize.repo(data);
|
|
206
|
+
|
|
207
|
+
expect(result.updated_at).toStrictEqual('2023-06-15T08:00:00Z');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('gitUtils.gitlab.normalize.commit', () => {
|
|
212
|
+
it('maps commit fields from GitLab API response', () => {
|
|
213
|
+
const data = {
|
|
214
|
+
message: 'refactor: clean up code',
|
|
215
|
+
web_url: 'https://gitlab.com/ns/proj/-/commit/abc1234',
|
|
216
|
+
short_id: 'abc1234',
|
|
217
|
+
id: 'abc1234567890abcdef',
|
|
218
|
+
author_name: 'Jane Doe',
|
|
219
|
+
avatar_url: 'https://gitlab.com/uploads/jane.png',
|
|
220
|
+
committed_date: '2022-03-10T14:00:00Z'
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const result = GitUtils.gitlab.normalize.commit(data);
|
|
224
|
+
|
|
225
|
+
expect(result.message).toStrictEqual('refactor: clean up code');
|
|
226
|
+
expect(result.htmlUrl).toStrictEqual('https://gitlab.com/ns/proj/-/commit/abc1234');
|
|
227
|
+
expect(result.sha).toStrictEqual('abc1234');
|
|
228
|
+
expect(result.commitId).toStrictEqual('abc1234567890abcdef');
|
|
229
|
+
expect(result.date).toStrictEqual('2022-03-10T14:00:00Z');
|
|
230
|
+
expect(result.isChecked).toStrictEqual(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('maps author fields from GitLab API response', () => {
|
|
234
|
+
const data = {
|
|
235
|
+
message: 'feat: add feature',
|
|
236
|
+
web_url: 'https://gitlab.com/ns/proj/-/commit/xyz',
|
|
237
|
+
short_id: 'xyz',
|
|
238
|
+
id: 'xyzabcdef',
|
|
239
|
+
author_name: 'John Smith',
|
|
240
|
+
avatar_url: 'https://gitlab.com/uploads/john.png',
|
|
241
|
+
committed_date: '2022-04-01T00:00:00Z'
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const result = GitUtils.gitlab.normalize.commit(data);
|
|
245
|
+
|
|
246
|
+
expect(result.author).toStrictEqual({
|
|
247
|
+
name: 'John Smith',
|
|
248
|
+
avatarUrl: 'https://gitlab.com/uploads/john.png',
|
|
249
|
+
htmlUrl: 'https://gitlab.com/ns/proj/-/commit/xyz'
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('uses web_url for both htmlUrl and author.htmlUrl', () => {
|
|
254
|
+
const data = {
|
|
255
|
+
message: 'fix: something',
|
|
256
|
+
web_url: 'https://gitlab.com/ns/proj/-/commit/def456',
|
|
257
|
+
short_id: 'def456',
|
|
258
|
+
id: 'def456abc',
|
|
259
|
+
author_name: 'Dev',
|
|
260
|
+
avatar_url: '',
|
|
261
|
+
committed_date: '2022-01-01T00:00:00Z'
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const result = GitUtils.gitlab.normalize.commit(data);
|
|
265
|
+
|
|
266
|
+
expect(result.htmlUrl).toStrictEqual('https://gitlab.com/ns/proj/-/commit/def456');
|
|
267
|
+
expect((result.author as any).htmlUrl).toStrictEqual('https://gitlab.com/ns/proj/-/commit/def456');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { Inactivity } from '@shell/utils/inactivity';
|
|
2
|
+
|
|
3
|
+
describe('inactivity', () => {
|
|
4
|
+
describe('inactivity class', () => {
|
|
5
|
+
let inactivity: Inactivity;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
inactivity = new Inactivity();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('getSessionTokenName', () => {
|
|
12
|
+
it('returns undefined by default', () => {
|
|
13
|
+
expect(inactivity.getSessionTokenName()).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns the token name after setSessionTokenName', () => {
|
|
17
|
+
inactivity.setSessionTokenName('my-token');
|
|
18
|
+
expect(inactivity.getSessionTokenName()).toStrictEqual('my-token');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('setSessionTokenName', () => {
|
|
23
|
+
it('sets the session token name', () => {
|
|
24
|
+
inactivity.setSessionTokenName('token-abc');
|
|
25
|
+
expect(inactivity.getSessionTokenName()).toStrictEqual('token-abc');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('overwrites a previously set token name', () => {
|
|
29
|
+
inactivity.setSessionTokenName('first');
|
|
30
|
+
inactivity.setSessionTokenName('second');
|
|
31
|
+
expect(inactivity.getSessionTokenName()).toStrictEqual('second');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('getUserActivity', () => {
|
|
36
|
+
it('dispatches management/find with correct parameters', async() => {
|
|
37
|
+
const mockResult = { status: { expiresAt: '2026-01-01T00:00:00Z' } };
|
|
38
|
+
const mockStore = { dispatch: jest.fn().mockResolvedValue(mockResult) };
|
|
39
|
+
|
|
40
|
+
const result = await inactivity.getUserActivity(mockStore, 'my-token');
|
|
41
|
+
|
|
42
|
+
expect(mockStore.dispatch).toHaveBeenCalledWith('management/find', {
|
|
43
|
+
type: 'ext.cattle.io.useractivity',
|
|
44
|
+
id: 'my-token',
|
|
45
|
+
opt: {
|
|
46
|
+
force: true, watch: false, logoutOnError: false
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
expect(result).toStrictEqual(mockResult);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('passes force=false when specified', async() => {
|
|
53
|
+
const mockResult = { status: { expiresAt: '2026-01-01T00:00:00Z' } };
|
|
54
|
+
const mockStore = { dispatch: jest.fn().mockResolvedValue(mockResult) };
|
|
55
|
+
|
|
56
|
+
await inactivity.getUserActivity(mockStore, 'my-token', false);
|
|
57
|
+
|
|
58
|
+
expect(mockStore.dispatch).toHaveBeenCalledWith('management/find', {
|
|
59
|
+
type: 'ext.cattle.io.useractivity',
|
|
60
|
+
id: 'my-token',
|
|
61
|
+
opt: {
|
|
62
|
+
force: false, watch: false, logoutOnError: false
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('dispatches auth/logout with sessionIdle=true on 401 error', async() => {
|
|
68
|
+
const mockStore = {
|
|
69
|
+
dispatch: jest.fn()
|
|
70
|
+
.mockRejectedValueOnce({ _status: 401 })
|
|
71
|
+
.mockResolvedValue('logged out')
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const result = await inactivity.getUserActivity(mockStore, 'my-token');
|
|
75
|
+
|
|
76
|
+
expect(mockStore.dispatch).toHaveBeenCalledWith('auth/logout', { sessionIdle: true });
|
|
77
|
+
expect(result).toStrictEqual('logged out');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('re-throws non-401 errors', async() => {
|
|
81
|
+
const mockStore = { dispatch: jest.fn().mockRejectedValue({ _status: 500, message: 'Server Error' }) };
|
|
82
|
+
|
|
83
|
+
await expect(inactivity.getUserActivity(mockStore, 'my-token')).rejects.toThrow(Error);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('updateUserActivity', () => {
|
|
88
|
+
it('sets spec with tokenId and seenAt and calls save', async() => {
|
|
89
|
+
const mockSaved = { status: { expiresAt: '2026-01-01T00:00:00Z' } };
|
|
90
|
+
const mockResource: any = { save: jest.fn().mockResolvedValue(mockSaved) };
|
|
91
|
+
|
|
92
|
+
const result = await inactivity.updateUserActivity(mockResource, 'my-token', '2026-01-01T00:00:00Z');
|
|
93
|
+
|
|
94
|
+
expect(mockResource.spec).toStrictEqual({
|
|
95
|
+
tokenId: 'my-token',
|
|
96
|
+
seenAt: '2026-01-01T00:00:00Z'
|
|
97
|
+
});
|
|
98
|
+
expect(mockResource.save).toHaveBeenCalledWith({ force: true });
|
|
99
|
+
expect(result).toStrictEqual(mockSaved);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('omits seenAt from spec when empty string', async() => {
|
|
103
|
+
const mockSaved = { status: { expiresAt: '2026-01-01T00:00:00Z' } };
|
|
104
|
+
const mockResource: any = { save: jest.fn().mockResolvedValue(mockSaved) };
|
|
105
|
+
|
|
106
|
+
await inactivity.updateUserActivity(mockResource, 'my-token', '');
|
|
107
|
+
|
|
108
|
+
expect(mockResource.spec).toStrictEqual({ tokenId: 'my-token' });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('re-throws errors from save', async() => {
|
|
112
|
+
const mockResource: any = { save: jest.fn().mockRejectedValue(new Error('save failed')) };
|
|
113
|
+
|
|
114
|
+
await expect(inactivity.updateUserActivity(mockResource, 'my-token', '2026-01-01T00:00:00Z')).rejects.toThrow(Error);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('parseTTLData', () => {
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
jest.restoreAllMocks();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns expiresAt from userActivityData.status', () => {
|
|
124
|
+
const expiresAt = '2030-01-01T01:00:00.000Z';
|
|
125
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
126
|
+
|
|
127
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
128
|
+
|
|
129
|
+
const data: any = {
|
|
130
|
+
status: { expiresAt },
|
|
131
|
+
metadata: { name: 'token-1' }
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const result = inactivity.parseTTLData(data);
|
|
135
|
+
|
|
136
|
+
expect(result.expiresAt).toStrictEqual(expiresAt);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('returns sessionTokenName from userActivityData.metadata.name', () => {
|
|
140
|
+
const expiresAt = '2030-01-01T01:00:00.000Z';
|
|
141
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
142
|
+
|
|
143
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
144
|
+
|
|
145
|
+
const data: any = {
|
|
146
|
+
status: { expiresAt },
|
|
147
|
+
metadata: { name: 'token-abc' }
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const result = inactivity.parseTTLData(data);
|
|
151
|
+
|
|
152
|
+
expect(result.sessionTokenName).toStrictEqual('token-abc');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('calculates courtesyTimer as 20% of threshold, capped at 300s', () => {
|
|
156
|
+
// 1 hour = 3600 seconds; threshold = 3600 - 3 = 3597s
|
|
157
|
+
// courtesyTimerVal = floor(3597 * 0.2) = floor(719.4) = 719
|
|
158
|
+
// courtesyTimer = min(719, 300) = 300
|
|
159
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
160
|
+
const expiresAt = new Date(now + 3600 * 1000).toISOString(); // 1 hour later
|
|
161
|
+
|
|
162
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
163
|
+
|
|
164
|
+
const data: any = {
|
|
165
|
+
status: { expiresAt },
|
|
166
|
+
metadata: { name: 'token-1' }
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = inactivity.parseTTLData(data);
|
|
170
|
+
|
|
171
|
+
expect(result.courtesyTimer).toStrictEqual(300);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('calculates courtesyTimer as 20% when under 300s cap', () => {
|
|
175
|
+
// 100 seconds until expiry; threshold = 100 - 3 = 97s
|
|
176
|
+
// courtesyTimerVal = floor(97 * 0.2) = floor(19.4) = 19
|
|
177
|
+
// courtesyTimer = min(19, 300) = 19
|
|
178
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
179
|
+
const expiresAt = new Date(now + 100 * 1000).toISOString();
|
|
180
|
+
|
|
181
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
182
|
+
|
|
183
|
+
const data: any = {
|
|
184
|
+
status: { expiresAt },
|
|
185
|
+
metadata: { name: 'token-1' }
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const result = inactivity.parseTTLData(data);
|
|
189
|
+
|
|
190
|
+
expect(result.courtesyTimer).toStrictEqual(19);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('courtesyCountdown equals courtesyTimer', () => {
|
|
194
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
195
|
+
const expiresAt = new Date(now + 100 * 1000).toISOString();
|
|
196
|
+
|
|
197
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
198
|
+
|
|
199
|
+
const data: any = {
|
|
200
|
+
status: { expiresAt },
|
|
201
|
+
metadata: { name: 'token-1' }
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = inactivity.parseTTLData(data);
|
|
205
|
+
|
|
206
|
+
expect(result.courtesyCountdown).toStrictEqual(result.courtesyTimer);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('calculates showModalAfter = thresholdSeconds - courtesyTimer', () => {
|
|
210
|
+
// 100s; threshold = 97s; courtesyTimer = 19s; showModalAfter = 97 - 19 = 78
|
|
211
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
212
|
+
const expiresAt = new Date(now + 100 * 1000).toISOString();
|
|
213
|
+
|
|
214
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
215
|
+
|
|
216
|
+
const data: any = {
|
|
217
|
+
status: { expiresAt },
|
|
218
|
+
metadata: { name: 'token-1' }
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const result = inactivity.parseTTLData(data);
|
|
222
|
+
|
|
223
|
+
expect(result.showModalAfter).toStrictEqual(78);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('calculates showModalAfter correctly with large TTL (1 hour)', () => {
|
|
227
|
+
// 3600s; threshold = 3597s; courtesyTimer = 300s (capped); showModalAfter = 3597 - 300 = 3297
|
|
228
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
229
|
+
const expiresAt = new Date(now + 3600 * 1000).toISOString();
|
|
230
|
+
|
|
231
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
232
|
+
|
|
233
|
+
const data: any = {
|
|
234
|
+
status: { expiresAt },
|
|
235
|
+
metadata: { name: 'token-1' }
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const result = inactivity.parseTTLData(data);
|
|
239
|
+
|
|
240
|
+
expect(result.showModalAfter).toStrictEqual(3297);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('handles expired session (negative thresholdSeconds)', () => {
|
|
244
|
+
// Already expired by 10 seconds; threshold = -10 - 3 = -13s
|
|
245
|
+
// courtesyTimerVal = floor(-13 * 0.2) = floor(-2.6) = -3
|
|
246
|
+
// courtesyTimer = min(-3, 300) = -3
|
|
247
|
+
// showModalAfter = -13 - (-3) = -10
|
|
248
|
+
const now = new Date('2030-01-01T00:00:10.000Z').getTime();
|
|
249
|
+
const expiresAt = new Date('2030-01-01T00:00:00.000Z').toISOString();
|
|
250
|
+
|
|
251
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
252
|
+
|
|
253
|
+
const data: any = {
|
|
254
|
+
status: { expiresAt },
|
|
255
|
+
metadata: { name: 'token-1' }
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const result = inactivity.parseTTLData(data);
|
|
259
|
+
|
|
260
|
+
expect(result.courtesyTimer).toStrictEqual(-3);
|
|
261
|
+
expect(result.showModalAfter).toStrictEqual(-10);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('returns undefined for expiresAt when status.expiresAt is absent', () => {
|
|
265
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
266
|
+
|
|
267
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
268
|
+
|
|
269
|
+
const data: any = {
|
|
270
|
+
status: {},
|
|
271
|
+
metadata: { name: 'token-1' }
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const result = inactivity.parseTTLData(data);
|
|
275
|
+
|
|
276
|
+
expect(result.expiresAt).toBeUndefined();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('returns undefined for sessionTokenName when metadata.name is absent', () => {
|
|
280
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
281
|
+
const expiresAt = new Date(now + 100 * 1000).toISOString();
|
|
282
|
+
|
|
283
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
284
|
+
|
|
285
|
+
const data: any = {
|
|
286
|
+
status: { expiresAt },
|
|
287
|
+
metadata: {}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const result = inactivity.parseTTLData(data);
|
|
291
|
+
|
|
292
|
+
expect(result.sessionTokenName).toBeUndefined();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('handles exactly 5 minute threshold (boundary: courtesyTimer not capped)', () => {
|
|
296
|
+
// 300s; threshold = 300 - 3 = 297s
|
|
297
|
+
// courtesyTimerVal = floor(297 * 0.2) = floor(59.4) = 59
|
|
298
|
+
// courtesyTimer = min(59, 300) = 59
|
|
299
|
+
const now = new Date('2030-01-01T00:00:00.000Z').getTime();
|
|
300
|
+
const expiresAt = new Date(now + 300 * 1000).toISOString();
|
|
301
|
+
|
|
302
|
+
jest.spyOn(Date, 'now').mockReturnValue(now);
|
|
303
|
+
|
|
304
|
+
const data: any = {
|
|
305
|
+
status: { expiresAt },
|
|
306
|
+
metadata: { name: 'token-1' }
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const result = inactivity.parseTTLData(data);
|
|
310
|
+
|
|
311
|
+
expect(result.courtesyTimer).toStrictEqual(59);
|
|
312
|
+
expect(result.showModalAfter).toStrictEqual(238);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|