katello 4.4.0.rc1 → 4.4.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/controllers/katello/api/v2/repositories_bulk_actions_controller.rb +10 -1
- data/app/controllers/katello/api/v2/repositories_controller.rb +3 -0
- data/app/lib/actions/katello/repository/errata_mail.rb +4 -5
- data/app/lib/actions/katello/repository/sync.rb +2 -2
- data/app/lib/actions/pulp3/abstract.rb +1 -1
- data/app/lib/actions/pulp3/content_view/delete_repository_references.rb +14 -4
- data/app/lib/actions/pulp3/content_view_version/create_import_history.rb +1 -2
- data/app/lib/actions/pulp3/repository/reclaim_space.rb +3 -10
- data/app/lib/katello/util/pulpcore_content_filters.rb +2 -1
- data/app/models/katello/candlepin/repository_mapper.rb +1 -0
- data/app/models/katello/concerns/audit_comment_extensions.rb +17 -0
- data/app/models/katello/concerns/host_managed_extensions.rb +11 -1
- data/app/models/katello/concerns/smart_proxy_extensions.rb +1 -0
- data/app/models/katello/content_view_version_export_history.rb +2 -1
- data/app/models/katello/content_view_version_import_history.rb +4 -4
- data/app/models/katello/host_available_module_stream.rb +10 -0
- data/app/models/katello/installed_package.rb +1 -0
- data/app/models/katello/root_repository.rb +14 -2
- data/app/models/setting/content.rb +9 -2
- data/app/services/katello/pulp3/api/yum.rb +4 -0
- data/app/services/katello/pulp3/repository/yum.rb +11 -4
- data/app/services/katello/pulp3/repository.rb +4 -2
- data/app/views/foreman/job_templates/remove_packages_by_search_query.erb +19 -0
- data/app/views/foreman/job_templates/update_packages_by_search_query.erb +19 -0
- data/app/views/katello/api/v2/content_views/base.json.rabl +8 -4
- data/app/views/katello/api/v2/host_module_streams/base.json.rabl +1 -0
- data/db/migrate/20210119162528_delete_puppet_and_ostree_repos.rb +2 -0
- data/db/migrate/20211019192121_create_cdn_configuration.katello.rb +11 -2
- data/db/migrate/20220209205137_expand_sync_timeout_settings.rb +23 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/errata/apply-errata.controller.js +10 -3
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details-info.controller.js +4 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/new-repository.controller.js +17 -12
- data/lib/katello/plugin.rb +2 -0
- data/lib/katello/repository_types/ostree.rb +0 -6
- data/lib/katello/version.rb +1 -1
- data/webpack/components/Errata/index.js +57 -57
- data/webpack/components/ErratumTypeLabel.js +16 -16
- data/webpack/components/MultiSelect/index.js +2 -2
- data/webpack/components/Select/Select.js +1 -1
- data/webpack/components/SelectOrg/SelectOrgReducer.js +15 -15
- data/webpack/components/SelectOrg/SetOrganization.js +1 -1
- data/webpack/components/Table/TableHooks.js +1 -0
- data/webpack/components/Table/TableWrapper.js +4 -1
- data/webpack/components/TypeAhead/helpers/commonPropTypes.js +1 -1
- data/webpack/components/TypeAhead/helpers/helpers.js +14 -14
- data/webpack/components/TypeAhead/pf3Search/TypeAheadSearch.js +1 -1
- data/webpack/components/WithOrganization/withOrganization.js +3 -3
- data/webpack/components/extensions/HostDetails/HostPackages/HostPackagesActions.js +32 -1
- data/webpack/components/extensions/HostDetails/HostPackages/HostPackagesConstants.js +3 -2
- data/webpack/components/extensions/HostDetails/Tabs/ContentTab/SecondaryTabsRoutes.js +5 -3
- data/webpack/components/extensions/HostDetails/Tabs/ContentTab/constants.js +1 -0
- data/webpack/components/extensions/HostDetails/Tabs/ErrataTab/ErratumExpansionContents.js +3 -3
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/ModuleStreamsActions.js +16 -0
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/ModuleStreamsConstants.js +3 -0
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/ModuleStreamsSelectors.js +19 -0
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/ModuleStreamsTab.js +241 -0
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/__tests__/moduleStreamsTab.test.js +108 -0
- data/webpack/components/extensions/HostDetails/Tabs/ModuleStreamsTab/__tests__/modules.fixtures.json +34 -0
- data/webpack/components/extensions/HostDetails/Tabs/PackageInstallModal.js +5 -5
- data/webpack/components/extensions/HostDetails/Tabs/PackagesTab.js +255 -79
- data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionActions.js +76 -0
- data/webpack/components/extensions/HostDetails/Tabs/RemoteExecutionConstants.js +4 -0
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/packagesTab.test.js +259 -9
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/tracesTab.test.js +22 -26
- data/webpack/components/extensions/HostDetails/Tabs/customizedRexUrlHelpers.js +13 -0
- data/webpack/components/extensions/RegistrationCommands/__tests__/__snapshots__/ActivationKeys.test.js.snap +4 -0
- data/webpack/components/extensions/RegistrationCommands/fields/ActivationKeys.js +1 -1
- data/webpack/components/extensions/RegistrationCommands/fields/LifecycleEnvironment.js +1 -1
- data/webpack/components/extensions/about/SystemStatuses.js +1 -1
- data/webpack/components/extensions/about/SystemStatusesReducer.js +10 -10
- data/webpack/components/pf3Table/components/Table.js +2 -2
- data/webpack/components/pf3Table/components/TableBody.js +2 -2
- data/webpack/redux/OrganizationProducts/OrganizationProductsReducer.js +15 -15
- data/webpack/redux/reducers/RedHatRepositories/enabled.js +43 -43
- data/webpack/redux/reducers/RedHatRepositories/repositorySetRepositories.js +43 -43
- data/webpack/redux/reducers/RedHatRepositories/sets.js +31 -31
- data/webpack/scenes/AnsibleCollections/AnsibleCollectionsReducer.js +26 -26
- data/webpack/scenes/AnsibleCollections/Details/AnsibleCollectionDetailsReducer.js +19 -19
- data/webpack/scenes/Content/Table/ContentTable.js +1 -1
- data/webpack/scenes/ContentViews/Create/CreateContentViewForm.js +2 -1
- data/webpack/scenes/ContentViews/Delete/Steps/CVDeletionReassignHostsForm.js +2 -2
- data/webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js +1 -1
- data/webpack/scenes/ContentViews/Details/ContentViewDetailActions.js +1 -1
- data/webpack/scenes/ContentViews/Details/ContentViewDetailReducer.js +8 -8
- data/webpack/scenes/ContentViews/Details/Filters/AffectedRepositories/AffectedRepositoryTable.js +1 -1
- data/webpack/scenes/ContentViews/Details/Filters/ArtifactsWithNoErrata.js +8 -8
- data/webpack/scenes/ContentViews/Details/Filters/CVContainerImageFilterContent.js +1 -1
- data/webpack/scenes/ContentViews/Details/Filters/CVErrataDateFilterContent.js +1 -1
- data/webpack/scenes/ContentViews/Details/Filters/CVErrataIDFilterContent.js +1 -1
- data/webpack/scenes/ContentViews/Details/Filters/CVFilterDetailType.js +46 -46
- data/webpack/scenes/ContentViews/Details/Filters/CVModuleStreamFilterContent.js +14 -14
- data/webpack/scenes/ContentViews/Details/Filters/CVPackageGroupFilterContent.js +14 -14
- data/webpack/scenes/ContentViews/Details/Filters/CVRpmFilterContent.js +1 -1
- data/webpack/scenes/ContentViews/Details/Filters/ContentViewFilters.js +1 -1
- data/webpack/scenes/ContentViews/Details/Filters/Rules/Package/AddEditPackageRuleModal.js +17 -17
- data/webpack/scenes/ContentViews/Details/Repositories/ContentViewRepositories.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/ContentViewVersionContent.js +19 -18
- data/webpack/scenes/ContentViews/Details/Versions/Delete/RemoveSteps/CVEnvironmentSelectionForm.js +18 -18
- data/webpack/scenes/ContentViews/Details/Versions/Delete/RemoveSteps/CVReassignActivationKeysForm.js +3 -3
- data/webpack/scenes/ContentViews/Details/Versions/Delete/RemoveSteps/CVReassignHostsForm.js +3 -3
- data/webpack/scenes/ContentViews/Details/Versions/Delete/affectedActivationKeys.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/Delete/affectedHosts.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionDetailConfig.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionDetailsTable.js +46 -34
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/ContentViewVersionRepositoryCell.js +65 -48
- data/webpack/scenes/ContentViews/Details/Versions/VersionDetails/__tests__/ContentViewVersionDetails.test.js +1 -1
- data/webpack/scenes/ContentViews/Publish/CVPublishFinish.js +2 -2
- data/webpack/scenes/ContentViews/Table/ContentViewsTable.js +4 -4
- data/webpack/scenes/ContentViews/components/EnvironmentLabels.js +18 -18
- data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.js +10 -10
- data/webpack/scenes/ContentViews/expansions/DetailsExpansion.js +2 -2
- data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +7 -7
- data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceTemplate.js +7 -7
- data/webpack/scenes/Hosts/ChangeContentSource/components/FormField.js +3 -3
- data/webpack/scenes/ModuleStreams/Details/ModuleStreamDetailsReducer.js +18 -18
- data/webpack/scenes/ModuleStreams/ModuleStreamsReducer.js +26 -26
- data/webpack/scenes/Organizations/OrganizationReducer.js +8 -8
- data/webpack/scenes/RedHatRepositories/components/EnabledRepository/EnabledRepositoryContent.js +4 -4
- data/webpack/scenes/RedHatRepositories/components/RepositorySetRepositories.js +1 -1
- data/webpack/scenes/Settings/SettingsReducer.js +14 -14
- data/webpack/scenes/Settings/Tables/TableReducer.js +23 -23
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailInfo.js +2 -2
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProductContent.js +15 -15
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProducts.js +1 -1
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +34 -34
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +13 -13
- data/webpack/scenes/Subscriptions/Manifest/CdnConfigurationTab/AirGappedTypeForm.js +3 -3
- data/webpack/scenes/Subscriptions/Manifest/CdnConfigurationTab/CdnTypeForm.js +4 -4
- data/webpack/scenes/Subscriptions/Manifest/CdnConfigurationTab/UpstreamServerTypeForm.js +3 -3
- data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +5 -5
- data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +16 -16
- data/webpack/scenes/Subscriptions/SubscriptionReducer.js +149 -149
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsReducer.js +41 -41
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/components/Dialogs/DeleteDialog.js +6 -6
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/components/Table.js +12 -12
- data/webpack/services/index.js +36 -36
- data/webpack/utils/helpers.js +5 -5
- metadata +16 -6
@@ -2,9 +2,10 @@ import React from 'react';
|
|
2
2
|
import { renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
|
3
3
|
import { nockInstance, assertNockRequest, mockForemanAutocomplete, mockSetting } from '../../../../../test-utils/nockWrapper';
|
4
4
|
import { foremanApi } from '../../../../../services/api';
|
5
|
-
import { HOST_PACKAGES_KEY } from '../../HostPackages/HostPackagesConstants';
|
5
|
+
import { HOST_PACKAGES_KEY, PACKAGES_SEARCH_QUERY } from '../../HostPackages/HostPackagesConstants';
|
6
6
|
import { PackagesTab } from '../PackagesTab';
|
7
7
|
import mockPackagesData from './packages.fixtures.json';
|
8
|
+
import { REX_FEATURES } from '../RemoteExecutionConstants';
|
8
9
|
|
9
10
|
const contentFacetAttributes = {
|
10
11
|
id: 11,
|
@@ -13,6 +14,7 @@ const contentFacetAttributes = {
|
|
13
14
|
lifecycle_environment_library: false,
|
14
15
|
};
|
15
16
|
|
17
|
+
const hostname = 'test-host.example.com';
|
16
18
|
const renderOptions = (facetAttributes = contentFacetAttributes) => ({
|
17
19
|
apiNamespace: HOST_PACKAGES_KEY,
|
18
20
|
initialState: {
|
@@ -20,7 +22,7 @@ const renderOptions = (facetAttributes = contentFacetAttributes) => ({
|
|
20
22
|
HOST_DETAILS: {
|
21
23
|
response: {
|
22
24
|
id: 1,
|
23
|
-
name:
|
25
|
+
name: hostname,
|
24
26
|
content_facet_attributes: { ...facetAttributes },
|
25
27
|
},
|
26
28
|
status: 'RESOLVED',
|
@@ -30,6 +32,7 @@ const renderOptions = (facetAttributes = contentFacetAttributes) => ({
|
|
30
32
|
});
|
31
33
|
|
32
34
|
const hostPackages = foremanApi.getApiUrl('/hosts/1/packages');
|
35
|
+
const jobInvocations = foremanApi.getApiUrl('/job_invocations');
|
33
36
|
const autocompleteUrl = '/hosts/1/packages/auto_complete_search';
|
34
37
|
const defaultQueryWithoutSearch = {
|
35
38
|
include_latest_upgradable: true,
|
@@ -38,13 +41,14 @@ const defaultQueryWithoutSearch = {
|
|
38
41
|
};
|
39
42
|
const defaultQuery = { ...defaultQueryWithoutSearch, search: '' };
|
40
43
|
|
41
|
-
let
|
44
|
+
let firstPackage;
|
45
|
+
let secondPackage;
|
42
46
|
let searchDelayScope;
|
43
47
|
let autoSearchScope;
|
44
48
|
|
45
49
|
beforeEach(() => {
|
46
50
|
const { results } = mockPackagesData;
|
47
|
-
[
|
51
|
+
[firstPackage, secondPackage] = results;
|
48
52
|
searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
|
49
53
|
autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
|
50
54
|
});
|
@@ -65,7 +69,7 @@ test('Can call API for packages and show on screen on page load', async (done) =
|
|
65
69
|
const { getAllByText } = renderWithRedux(<PackagesTab />, renderOptions());
|
66
70
|
|
67
71
|
// Assert that the packages are now showing on the screen, but wait for them to appear.
|
68
|
-
await patientlyWaitFor(() => expect(getAllByText(
|
72
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
69
73
|
// Assert request was made and completed, see helper function
|
70
74
|
assertNockRequest(autocompleteScope);
|
71
75
|
assertNockRequest(scope, done); // Pass jest callback to confirm test is done
|
@@ -107,7 +111,7 @@ test('Can filter by package status', async (done) => {
|
|
107
111
|
const scope2 = nockInstance
|
108
112
|
.get(hostPackages)
|
109
113
|
.query({ ...defaultQuery, status: 'upgradable' })
|
110
|
-
.reply(200, { ...mockPackagesData, results: [
|
114
|
+
.reply(200, { ...mockPackagesData, results: [firstPackage] });
|
111
115
|
|
112
116
|
const {
|
113
117
|
queryByText,
|
@@ -116,8 +120,7 @@ test('Can filter by package status', async (done) => {
|
|
116
120
|
getByText,
|
117
121
|
} = renderWithRedux(<PackagesTab />, renderOptions());
|
118
122
|
|
119
|
-
|
120
|
-
await patientlyWaitFor(() => expect(getAllByText(firstPackages.name)[0]).toBeInTheDocument());
|
123
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
121
124
|
// the Upgradable text in the table is just a text node, while the dropdown is a button
|
122
125
|
expect(getByText('Up-to date', { ignore: ['button', 'title'] })).toBeInTheDocument();
|
123
126
|
expect(getByText('coreutils', { ignore: ['button', 'title'] })).toBeInTheDocument();
|
@@ -133,10 +136,257 @@ test('Can filter by package status', async (done) => {
|
|
133
136
|
expect(queryByText('acl')).not.toBeInTheDocument();
|
134
137
|
});
|
135
138
|
|
136
|
-
|
137
139
|
assertNockRequest(autocompleteScope);
|
138
140
|
assertNockRequest(scope);
|
139
141
|
assertNockRequest(searchDelayScope);
|
140
142
|
assertNockRequest(autoSearchScope);
|
141
143
|
assertNockRequest(scope2, done); // Pass jest callback to confirm test is done
|
142
144
|
});
|
145
|
+
|
146
|
+
test('Can upgrade a package via remote execution', async (done) => {
|
147
|
+
const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl);
|
148
|
+
|
149
|
+
const scope = nockInstance
|
150
|
+
.get(hostPackages)
|
151
|
+
.query(defaultQuery)
|
152
|
+
.reply(200, mockPackagesData);
|
153
|
+
|
154
|
+
const statusScope = nockInstance
|
155
|
+
.get(hostPackages)
|
156
|
+
.query({ ...defaultQuery, status: 'upgradable' })
|
157
|
+
.reply(200, { ...mockPackagesData, results: [firstPackage] });
|
158
|
+
|
159
|
+
const upgradeScope = nockInstance
|
160
|
+
.post(jobInvocations, {
|
161
|
+
job_invocation: {
|
162
|
+
inputs: {
|
163
|
+
package: firstPackage.upgradable_version,
|
164
|
+
},
|
165
|
+
search_query: `name ^ (${hostname})`,
|
166
|
+
feature: REX_FEATURES.KATELLO_PACKAGE_UPDATE,
|
167
|
+
},
|
168
|
+
})
|
169
|
+
.reply(201);
|
170
|
+
|
171
|
+
const {
|
172
|
+
getByRole,
|
173
|
+
getAllByText,
|
174
|
+
getByLabelText,
|
175
|
+
getByText,
|
176
|
+
} = renderWithRedux(<PackagesTab />, renderOptions());
|
177
|
+
|
178
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
179
|
+
|
180
|
+
const statusDropdown = getByText('Status', { ignore: 'th' });
|
181
|
+
expect(statusDropdown).toBeInTheDocument();
|
182
|
+
fireEvent.click(statusDropdown);
|
183
|
+
const upgradable = getByRole('option', { name: 'select Upgradable' });
|
184
|
+
fireEvent.click(upgradable);
|
185
|
+
await patientlyWaitFor(() => {
|
186
|
+
expect(getByText('coreutils')).toBeInTheDocument();
|
187
|
+
});
|
188
|
+
|
189
|
+
const kebabDropdown = getByLabelText('Actions');
|
190
|
+
kebabDropdown.click();
|
191
|
+
|
192
|
+
const rexAction = getByText('Upgrade via remote execution');
|
193
|
+
await patientlyWaitFor(() => expect(rexAction).toBeInTheDocument());
|
194
|
+
fireEvent.click(rexAction);
|
195
|
+
|
196
|
+
assertNockRequest(autocompleteScope);
|
197
|
+
assertNockRequest(scope);
|
198
|
+
assertNockRequest(statusScope);
|
199
|
+
assertNockRequest(upgradeScope, done);
|
200
|
+
});
|
201
|
+
|
202
|
+
test('Can upgrade a package via customized remote execution', async (done) => {
|
203
|
+
const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl);
|
204
|
+
|
205
|
+
const scope = nockInstance
|
206
|
+
.get(hostPackages)
|
207
|
+
.query(defaultQuery)
|
208
|
+
.reply(200, mockPackagesData);
|
209
|
+
|
210
|
+
const statusScope = nockInstance
|
211
|
+
.get(hostPackages)
|
212
|
+
.query({ ...defaultQuery, status: 'upgradable' })
|
213
|
+
.reply(200, { ...mockPackagesData, results: [firstPackage] });
|
214
|
+
|
215
|
+
const {
|
216
|
+
getByRole,
|
217
|
+
getAllByText,
|
218
|
+
getByLabelText,
|
219
|
+
getByText,
|
220
|
+
} = renderWithRedux(<PackagesTab />, renderOptions());
|
221
|
+
|
222
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
223
|
+
|
224
|
+
const statusDropdown = getByText('Status', { ignore: 'th' });
|
225
|
+
expect(statusDropdown).toBeInTheDocument();
|
226
|
+
fireEvent.click(statusDropdown);
|
227
|
+
const upgradable = getByRole('option', { name: 'select Upgradable' });
|
228
|
+
fireEvent.click(upgradable);
|
229
|
+
await patientlyWaitFor(() => {
|
230
|
+
expect(getByText('coreutils')).toBeInTheDocument();
|
231
|
+
});
|
232
|
+
|
233
|
+
const kebabDropdown = getByLabelText('Actions');
|
234
|
+
kebabDropdown.click();
|
235
|
+
|
236
|
+
const rexAction = getByText('Upgrade via customized remote execution');
|
237
|
+
const feature = REX_FEATURES.KATELLO_PACKAGE_UPDATE;
|
238
|
+
const packageName = firstPackage.upgradable_version;
|
239
|
+
|
240
|
+
expect(rexAction).toBeInTheDocument();
|
241
|
+
expect(rexAction).toHaveAttribute(
|
242
|
+
'href',
|
243
|
+
`/job_invocations/new?feature=${feature}&host_ids=name%20%5E%20(${hostname})&inputs%5Bpackage%5D=${packageName}`,
|
244
|
+
);
|
245
|
+
|
246
|
+
fireEvent.click(rexAction);
|
247
|
+
|
248
|
+
assertNockRequest(autocompleteScope);
|
249
|
+
assertNockRequest(scope);
|
250
|
+
assertNockRequest(statusScope, done);
|
251
|
+
});
|
252
|
+
|
253
|
+
test('Can bulk upgrade via remote execution', async (done) => {
|
254
|
+
const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl);
|
255
|
+
|
256
|
+
const scope = nockInstance
|
257
|
+
.get(hostPackages)
|
258
|
+
.query(defaultQuery)
|
259
|
+
.reply(200, mockPackagesData);
|
260
|
+
|
261
|
+
const upgradeScope = nockInstance
|
262
|
+
.post(jobInvocations, {
|
263
|
+
job_invocation: {
|
264
|
+
inputs: {
|
265
|
+
[PACKAGES_SEARCH_QUERY]: `id ^ (${firstPackage.id},${secondPackage.id})`,
|
266
|
+
},
|
267
|
+
search_query: `name ^ (${hostname})`,
|
268
|
+
feature: REX_FEATURES.KATELLO_PACKAGES_UPDATE_BY_SEARCH,
|
269
|
+
},
|
270
|
+
})
|
271
|
+
.reply(201);
|
272
|
+
|
273
|
+
const {
|
274
|
+
getAllByRole,
|
275
|
+
getAllByText,
|
276
|
+
getByRole,
|
277
|
+
getByLabelText,
|
278
|
+
} = renderWithRedux(<PackagesTab />, renderOptions());
|
279
|
+
|
280
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
281
|
+
|
282
|
+
getByRole('checkbox', { name: 'Select row 0' }).click();
|
283
|
+
expect(getByLabelText('Select row 0').checked).toEqual(true);
|
284
|
+
getByRole('checkbox', { name: 'Select row 1' }).click();
|
285
|
+
expect(getByLabelText('Select row 1').checked).toEqual(true);
|
286
|
+
|
287
|
+
const upgradeDropdown = getAllByRole('button', { name: 'Select' })[1];
|
288
|
+
fireEvent.click(upgradeDropdown);
|
289
|
+
|
290
|
+
const rexAction = getByLabelText('bulk_upgrade_rex');
|
291
|
+
expect(rexAction).toBeInTheDocument();
|
292
|
+
fireEvent.click(rexAction);
|
293
|
+
|
294
|
+
assertNockRequest(autocompleteScope);
|
295
|
+
assertNockRequest(scope);
|
296
|
+
assertNockRequest(upgradeScope, done);
|
297
|
+
});
|
298
|
+
|
299
|
+
test('Can bulk upgrade via customized remote execution', async (done) => {
|
300
|
+
const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl);
|
301
|
+
|
302
|
+
const scope = nockInstance
|
303
|
+
.get(hostPackages)
|
304
|
+
.query(defaultQuery)
|
305
|
+
.reply(200, mockPackagesData);
|
306
|
+
|
307
|
+
const {
|
308
|
+
getAllByRole,
|
309
|
+
getAllByText,
|
310
|
+
getByRole,
|
311
|
+
getByLabelText,
|
312
|
+
} = renderWithRedux(<PackagesTab />, renderOptions());
|
313
|
+
|
314
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
315
|
+
|
316
|
+
const feature = REX_FEATURES.KATELLO_PACKAGES_UPDATE_BY_SEARCH;
|
317
|
+
const packages = `${firstPackage.id},${secondPackage.id}`;
|
318
|
+
const job =
|
319
|
+
`/job_invocations/new?feature=${feature}&host_ids=name%20%5E%20(${hostname})&inputs%5BPackages%20search%20query%5D=id%20%5E%20(${packages})`;
|
320
|
+
|
321
|
+
getByRole('checkbox', { name: 'Select row 0' }).click();
|
322
|
+
expect(getByLabelText('Select row 0').checked).toEqual(true);
|
323
|
+
getByRole('checkbox', { name: 'Select row 1' }).click();
|
324
|
+
expect(getByLabelText('Select row 1').checked).toEqual(true);
|
325
|
+
|
326
|
+
const upgradeDropdown = getAllByRole('button', { name: 'Select' })[1];
|
327
|
+
fireEvent.click(upgradeDropdown);
|
328
|
+
expect(upgradeDropdown).not.toHaveAttribute('disabled');
|
329
|
+
|
330
|
+
const rexAction = getByLabelText('bulk_upgrade_customized_rex');
|
331
|
+
expect(rexAction).toBeInTheDocument();
|
332
|
+
expect(rexAction).toHaveAttribute('href', job);
|
333
|
+
|
334
|
+
assertNockRequest(autocompleteScope);
|
335
|
+
assertNockRequest(scope, done);
|
336
|
+
});
|
337
|
+
|
338
|
+
test('Upgrade is disabled when there are non-upgradable packages selected', async (done) => {
|
339
|
+
const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl);
|
340
|
+
|
341
|
+
const scope = nockInstance
|
342
|
+
.get(hostPackages)
|
343
|
+
.query(defaultQuery)
|
344
|
+
.reply(200, mockPackagesData);
|
345
|
+
|
346
|
+
const {
|
347
|
+
getAllByRole,
|
348
|
+
getAllByText,
|
349
|
+
getByLabelText,
|
350
|
+
getByRole,
|
351
|
+
} = renderWithRedux(<PackagesTab />, renderOptions());
|
352
|
+
|
353
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
354
|
+
|
355
|
+
// select an upgradable package
|
356
|
+
getByRole('checkbox', { name: 'Select row 0' }).click();
|
357
|
+
// select an up-to-date package
|
358
|
+
getByRole('checkbox', { name: 'Select row 2' }).click();
|
359
|
+
expect(getByLabelText('Select row 2').checked).toEqual(true);
|
360
|
+
|
361
|
+
const upgradeDropdown = getAllByRole('button', { name: 'Select' })[1];
|
362
|
+
expect(upgradeDropdown).toHaveAttribute('disabled');
|
363
|
+
|
364
|
+
assertNockRequest(autocompleteScope);
|
365
|
+
assertNockRequest(scope, done);
|
366
|
+
});
|
367
|
+
|
368
|
+
test('Remove is disabled when in select all mode', async (done) => {
|
369
|
+
const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl);
|
370
|
+
const scope = nockInstance
|
371
|
+
.get(hostPackages)
|
372
|
+
.query(defaultQuery)
|
373
|
+
.reply(200, mockPackagesData);
|
374
|
+
|
375
|
+
const {
|
376
|
+
getAllByText, getByRole,
|
377
|
+
} = renderWithRedux(<PackagesTab />, renderOptions());
|
378
|
+
|
379
|
+
await patientlyWaitFor(() => expect(getAllByText(firstPackage.name)[0]).toBeInTheDocument());
|
380
|
+
|
381
|
+
// find and click the select all checkbox
|
382
|
+
const selectAllCheckbox = getByRole('checkbox', { name: 'Select all' });
|
383
|
+
fireEvent.click(selectAllCheckbox);
|
384
|
+
getByRole('button', { name: 'bulk_actions' }).click();
|
385
|
+
|
386
|
+
const removeButton = getByRole('button', { name: 'bulk_remove' });
|
387
|
+
await patientlyWaitFor(() => expect(removeButton).toBeInTheDocument());
|
388
|
+
expect(removeButton).toHaveAttribute('aria-disabled', 'true');
|
389
|
+
|
390
|
+
assertNockRequest(autocompleteScope);
|
391
|
+
assertNockRequest(scope, done);
|
392
|
+
});
|
@@ -40,10 +40,6 @@ const renderOptions = isTracerInstalled => ({ // sets initial Redux state
|
|
40
40
|
},
|
41
41
|
});
|
42
42
|
|
43
|
-
// Find the checkbox to the left of the trace.
|
44
|
-
// (We could also have just found the checkbox by its aria-label "Select row 0",
|
45
|
-
// but this is closer to how the user would do it)
|
46
|
-
const checkboxToTheLeftOf = node => node.previousElementSibling.firstElementChild;
|
47
43
|
const actionMenuToTheRightOf = node => node.nextElementSibling.firstElementChild.firstElementChild;
|
48
44
|
|
49
45
|
const hostTraces = foremanApi.getApiUrl('/hosts/1/traces?per_page=20&page=1');
|
@@ -51,14 +47,13 @@ const autocompleteUrl = '/hosts/1/traces/auto_complete_search';
|
|
51
47
|
const jobInvocations = foremanApi.getApiUrl('/job_invocations');
|
52
48
|
|
53
49
|
let firstTrace;
|
54
|
-
let secondTrace;
|
55
50
|
let searchDelayScope;
|
56
51
|
let autoSearchScope;
|
57
52
|
|
58
53
|
describe('With tracer installed', () => {
|
59
54
|
beforeEach(() => {
|
60
55
|
const { results } = mockTraceData;
|
61
|
-
[firstTrace
|
56
|
+
[firstTrace] = results;
|
62
57
|
searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 0);
|
63
58
|
autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing');
|
64
59
|
});
|
@@ -119,17 +114,17 @@ describe('With tracer installed', () => {
|
|
119
114
|
.reply(201, mockResolveTraceTask);
|
120
115
|
|
121
116
|
|
122
|
-
const { getByText } = renderWithRedux(
|
117
|
+
const { getByText, getByLabelText } = renderWithRedux(
|
123
118
|
<TracesTab />,
|
124
119
|
renderOptions(true),
|
125
120
|
);
|
121
|
+
|
126
122
|
let traceCheckbox;
|
127
|
-
// Find the trace.
|
123
|
+
// Find the trace checkbox.
|
128
124
|
await patientlyWaitFor(() => {
|
129
|
-
|
130
|
-
traceCheckbox = checkboxToTheLeftOf(traceNameNode);
|
125
|
+
traceCheckbox = getByLabelText('Select row 0');
|
131
126
|
});
|
132
|
-
|
127
|
+
fireEvent.click(traceCheckbox);
|
133
128
|
expect(traceCheckbox.checked).toEqual(true);
|
134
129
|
|
135
130
|
const restartAppButton = getByText('Restart app');
|
@@ -158,16 +153,17 @@ describe('With tracer installed', () => {
|
|
158
153
|
.reply(201, mockResolveTraceTask);
|
159
154
|
|
160
155
|
|
161
|
-
const {
|
156
|
+
const { getByLabelText, queryByText } = renderWithRedux(
|
162
157
|
<TracesTab />,
|
163
158
|
renderOptions(true),
|
164
159
|
);
|
165
160
|
let traceCheckbox;
|
161
|
+
|
162
|
+
// Find the trace.
|
166
163
|
await patientlyWaitFor(() => {
|
167
|
-
|
168
|
-
traceCheckbox = checkboxToTheLeftOf(traceNameNode);
|
164
|
+
traceCheckbox = getByLabelText('Select row 0');
|
169
165
|
});
|
170
|
-
|
166
|
+
fireEvent.click(traceCheckbox);
|
171
167
|
expect(traceCheckbox.checked).toEqual(true);
|
172
168
|
const actionMenu = getByLabelText('bulk_actions');
|
173
169
|
actionMenu.click();
|
@@ -204,14 +200,16 @@ describe('With tracer installed', () => {
|
|
204
200
|
);
|
205
201
|
|
206
202
|
let traceCheckbox;
|
207
|
-
//
|
203
|
+
// Find the trace.
|
208
204
|
await patientlyWaitFor(() => {
|
209
|
-
|
210
|
-
traceCheckbox = checkboxToTheLeftOf(traceNameNode);
|
205
|
+
traceCheckbox = getByLabelText('Select row 0');
|
211
206
|
});
|
207
|
+
|
208
|
+
|
212
209
|
const selectAllCheckbox = getByLabelText('Select all');
|
213
210
|
fireEvent.click(selectAllCheckbox);
|
214
211
|
expect(traceCheckbox.checked).toEqual(true);
|
212
|
+
|
215
213
|
fireEvent.click(getByLabelText('Select row 0')); // de select
|
216
214
|
fireEvent.click(getByLabelText('Select row 2')); // de select
|
217
215
|
|
@@ -298,16 +296,15 @@ describe('With tracer installed', () => {
|
|
298
296
|
.get(hostTraces)
|
299
297
|
.reply(200, mockTraceData);
|
300
298
|
|
301
|
-
const {
|
299
|
+
const { getByLabelText, queryByText } = renderWithRedux(
|
302
300
|
<TracesTab />,
|
303
301
|
renderOptions(true),
|
304
302
|
);
|
305
303
|
let traceCheckbox;
|
306
304
|
await patientlyWaitFor(() => {
|
307
|
-
|
308
|
-
traceCheckbox = checkboxToTheLeftOf(traceNameNode);
|
305
|
+
traceCheckbox = getByLabelText('Select row 0');
|
309
306
|
});
|
310
|
-
|
307
|
+
fireEvent.click(traceCheckbox);
|
311
308
|
expect(traceCheckbox.checked).toEqual(true);
|
312
309
|
|
313
310
|
const actionMenu = getByLabelText('bulk_actions');
|
@@ -327,7 +324,7 @@ describe('With tracer installed', () => {
|
|
327
324
|
describe('Remote execution URL helper logic', () => {
|
328
325
|
beforeEach(() => {
|
329
326
|
const { results } = mockTraceData;
|
330
|
-
[firstTrace
|
327
|
+
[firstTrace] = results;
|
331
328
|
});
|
332
329
|
|
333
330
|
afterEach(() => {
|
@@ -341,14 +338,13 @@ describe('With tracer installed', () => {
|
|
341
338
|
.get(hostTraces)
|
342
339
|
.reply(200, mockTraceData);
|
343
340
|
|
344
|
-
const {
|
341
|
+
const { getByLabelText } = renderWithRedux(
|
345
342
|
<TracesTab />,
|
346
343
|
renderOptions(true),
|
347
344
|
);
|
348
345
|
let traceCheckbox;
|
349
346
|
await patientlyWaitFor(() => {
|
350
|
-
|
351
|
-
traceCheckbox = checkboxToTheLeftOf(traceNameNode);
|
347
|
+
traceCheckbox = getByLabelText('Select row 1');
|
352
348
|
});
|
353
349
|
expect(traceCheckbox.disabled).toEqual(true);
|
354
350
|
|
@@ -2,6 +2,7 @@ import { REX_FEATURES } from './RemoteExecutionConstants';
|
|
2
2
|
import { TRACES_SEARCH_QUERY } from './TracesTab/HostTracesConstants';
|
3
3
|
import { ERRATA_SEARCH_QUERY } from './ErrataTab/HostErrataConstants';
|
4
4
|
import { PACKAGE_SEARCH_QUERY } from '../YumInstallablePackages/YumInstallablePackagesConstants';
|
5
|
+
import { PACKAGES_SEARCH_QUERY } from '../HostPackages/HostPackagesConstants';
|
5
6
|
|
6
7
|
export const createJob = ({
|
7
8
|
hostname, feature, inputs,
|
@@ -28,6 +29,18 @@ export const katelloPackageInstallBySearchUrl = ({ hostname, search }) => create
|
|
28
29
|
inputs: { [PACKAGE_SEARCH_QUERY]: search },
|
29
30
|
});
|
30
31
|
|
32
|
+
export const katelloPackageUpdateUrl = ({ hostname, packageName }) => createJob({
|
33
|
+
hostname,
|
34
|
+
feature: REX_FEATURES.KATELLO_PACKAGE_UPDATE,
|
35
|
+
inputs: { package: packageName },
|
36
|
+
});
|
37
|
+
|
38
|
+
export const packagesUpdateUrl = ({ hostname, search }) => createJob({
|
39
|
+
hostname,
|
40
|
+
feature: REX_FEATURES.KATELLO_PACKAGES_UPDATE_BY_SEARCH,
|
41
|
+
inputs: { [PACKAGES_SEARCH_QUERY]: search },
|
42
|
+
});
|
43
|
+
|
31
44
|
export const resolveTraceUrl = ({ hostname, search }) => createJob({
|
32
45
|
hostname,
|
33
46
|
feature: REX_FEATURES.KATELLO_HOST_TRACER_RESOLVE,
|
@@ -29,12 +29,16 @@ exports[`ActivationKeys renders 1`] = `
|
|
29
29
|
favorites={Array []}
|
30
30
|
favoritesLabel="Favorites"
|
31
31
|
hasInlineFilter={false}
|
32
|
+
hasPlaceholderStyle={false}
|
32
33
|
id="reg_katello_ak"
|
33
34
|
inlineFilterPlaceholderText={null}
|
35
|
+
inputAutoComplete="off"
|
34
36
|
inputIdPrefix=""
|
35
37
|
isCreatable={false}
|
38
|
+
isCreateSelectOptionObject={false}
|
36
39
|
isDisabled={true}
|
37
40
|
isGrouped={false}
|
41
|
+
isInputFilterPersisted={false}
|
38
42
|
isInputValuePersisted={false}
|
39
43
|
isOpen={false}
|
40
44
|
isPlain={false}
|
@@ -81,7 +81,7 @@ ActivationKeys.propTypes = {
|
|
81
81
|
selectedKeys: PropTypes.array,
|
82
82
|
hostGroupActivationKeys: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
83
83
|
hostGroupId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
84
|
-
pluginValues: PropTypes.
|
84
|
+
pluginValues: PropTypes.objectOf(PropTypes.shape({})),
|
85
85
|
onChange: PropTypes.func.isRequired,
|
86
86
|
handleInvalidField: PropTypes.func.isRequired,
|
87
87
|
isLoading: PropTypes.bool,
|
@@ -12,15 +12,15 @@ const initialState = Immutable({
|
|
12
12
|
|
13
13
|
export default (state = initialState, action) => {
|
14
14
|
switch (action.type) {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
case SYSTEM_STATUSES_REQUEST:
|
16
|
+
return state.set('loaderStatus', 'PENDING');
|
17
|
+
case SYSTEM_STATUSES_SUCCESS:
|
18
|
+
return state
|
19
|
+
.set('services', action.payload.services)
|
20
|
+
.set('loaderStatus', 'RESOLVED');
|
21
|
+
case SYSTEM_STATUSES_FAILURE:
|
22
|
+
return state.set('loaderStatus', 'ERROR');
|
23
|
+
default:
|
24
|
+
return state;
|
25
25
|
}
|
26
26
|
};
|
@@ -53,8 +53,8 @@ const Table = ({
|
|
53
53
|
};
|
54
54
|
|
55
55
|
Table.propTypes = {
|
56
|
-
columns: PropTypes.arrayOf(PropTypes.
|
57
|
-
rows: PropTypes.arrayOf(PropTypes.
|
56
|
+
columns: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
57
|
+
rows: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
58
58
|
emptyState: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
59
59
|
pagination: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
60
60
|
bodyMessage: PropTypes.node,
|
@@ -15,8 +15,8 @@ const TableBody = ({
|
|
15
15
|
};
|
16
16
|
|
17
17
|
TableBody.propTypes = {
|
18
|
-
columns: PropTypes.arrayOf(PropTypes.
|
19
|
-
rows: PropTypes.arrayOf(PropTypes.
|
18
|
+
columns: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
19
|
+
rows: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
20
20
|
message: PropTypes.string,
|
21
21
|
};
|
22
22
|
|
@@ -16,23 +16,23 @@ export default (state = initialState, action) => {
|
|
16
16
|
const { type, payload } = action;
|
17
17
|
|
18
18
|
switch (type) {
|
19
|
-
|
20
|
-
|
19
|
+
case ORGANIZATION_PRODUCTS_REQUEST:
|
20
|
+
return state.set('loading', true);
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
case ORGANIZATION_PRODUCTS_SUCCESS:
|
23
|
+
return state.merge({
|
24
|
+
...payload,
|
25
|
+
loading: false,
|
26
|
+
});
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
case ORGANIZATION_PRODUCTS_FAILURE:
|
29
|
+
return state.merge({
|
30
|
+
error: payload,
|
31
|
+
loading: false,
|
32
|
+
results: [],
|
33
|
+
});
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
default:
|
36
|
+
return state;
|
37
37
|
}
|
38
38
|
};
|