|
|
- {inventory.kind !== 'smart' && }
+ {inventory.kind !== 'smart' && (
+
+ )}
|
{inventory.kind === 'smart'
diff --git a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx
index 6be246df39..179a089e52 100644
--- a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx
+++ b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryListItem.test.jsx
@@ -7,24 +7,33 @@ import InventoryListItem from './InventoryListItem';
jest.mock('../../../api/models/Inventories');
describe('', () => {
- test('initially renders succesfully', () => {
+ const inventory = {
+ id: 1,
+ name: 'Inventory',
+ kind: '',
+ has_active_failures: true,
+ total_hosts: 10,
+ hosts_with_active_failures: 4,
+ has_inventory_sources: true,
+ total_inventory_sources: 4,
+ inventory_sources_with_failures: 5,
+ summary_fields: {
+ organization: {
+ id: 1,
+ name: 'Default',
+ },
+ user_capabilities: {
+ edit: true,
+ },
+ },
+ };
+
+ test('initially renders successfully', () => {
mountWithContexts(
{}}
@@ -34,25 +43,50 @@ describe('', () => {
);
});
+ test('should render not configured tooltip', () => {
+ const wrapper = mountWithContexts(
+
+ );
+
+ expect(wrapper.find('StatusLabel').prop('tooltipContent')).toBe(
+ 'Not configured for inventory sync.'
+ );
+ });
+
+ test('should render success tooltip', () => {
+ const wrapper = mountWithContexts(
+
+ );
+
+ expect(wrapper.find('StatusLabel').prop('tooltipContent')).toBe(
+ 'No inventory sync failures.'
+ );
+ });
+
test('should render prompt list item data', () => {
const wrapper = mountWithContexts(
{}}
@@ -61,6 +95,9 @@ describe('', () => {
);
expect(wrapper.find('StatusLabel').length).toBe(1);
+ expect(wrapper.find('StatusLabel').prop('tooltipContent')).toBe(
+ `${inventory.inventory_sources_with_failures} sources with sync failures.`
+ );
expect(
wrapper
.find('Td')
@@ -72,7 +109,7 @@ describe('', () => {
.find('Td')
.at(2)
.text()
- ).toBe('Disabled');
+ ).toBe('Error');
expect(
wrapper
.find('Td')
@@ -92,19 +129,7 @@ describe('', () => {
{}}
diff --git a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx
index 9ee8e2a94e..d41632008b 100644
--- a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx
+++ b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx
@@ -281,6 +281,42 @@ function JobDetail({ job, i18n }) {
}
/>
)}
+ {job.job_tags && job.job_tags.length > 0 && (
+
+ {job.job_tags.split(',').map(jobTag => (
+
+ {jobTag}
+
+ ))}
+
+ }
+ />
+ )}
+ {job.skip_tags && job.skip_tags.length > 0 && (
+
+ {job.skip_tags.split(',').map(skipTag => (
+
+ {skipTag}
+
+ ))}
+
+ }
+ />
+ )}
', () => {
mockJobData.summary_fields.credentials[0]
);
+ expect(
+ wrapper
+ .find('Detail[label="Job Tags"]')
+ .containsAnyMatchingElements([a, b])
+ ).toEqual(true);
+
+ expect(
+ wrapper
+ .find('Detail[label="Skip Tags"]')
+ .containsAnyMatchingElements([c, d])
+ ).toEqual(true);
+
const statusDetail = wrapper.find('Detail[label="Status"]');
expect(statusDetail.find('StatusIcon SuccessfulTop')).toHaveLength(1);
expect(statusDetail.find('StatusIcon SuccessfulBottom')).toHaveLength(1);
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 97c6220d0c..ac974c44ff 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
-import { withI18n } from '@lingui/react';
+import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
import {
@@ -578,7 +578,7 @@ class JobOutput extends Component {
}
render() {
- const { job, i18n } = this.props;
+ const { job } = this.props;
const {
contentError,
@@ -666,64 +666,72 @@ class JobOutput extends Component {
{showCancelPrompt &&
['pending', 'waiting', 'running'].includes(jobStatus) && (
-
+ {({ i18n }) => (
+
+ {i18n._(t`Cancel job`)}
+ ,
+ ,
+ ]}
>
- {i18n._(t`Cancel job`)}
- ,
- ,
- ]}
- >
- {i18n._(
- t`Are you sure you want to submit the request to cancel this job?`
+ {i18n._(
+ t`Are you sure you want to submit the request to cancel this job?`
+ )}
+
)}
-
+
)}
{cancelError && (
- <>
- this.setState({ cancelError: null })}
- title={i18n._(t`Job Cancel Error`)}
- label={i18n._(t`Job Cancel Error`)}
- >
-
-
- >
+
+ {({ i18n }) => (
+ this.setState({ cancelError: null })}
+ title={i18n._(t`Job Cancel Error`)}
+ label={i18n._(t`Job Cancel Error`)}
+ >
+
+
+ )}
+
)}
{deletionError && (
- <>
- this.setState({ deletionError: null })}
- title={i18n._(t`Job Delete Error`)}
- label={i18n._(t`Job Delete Error`)}
- >
-
-
- >
+
+ {({ i18n }) => (
+ this.setState({ deletionError: null })}
+ title={i18n._(t`Job Delete Error`)}
+ label={i18n._(t`Job Delete Error`)}
+ >
+
+
+ )}
+
)}
);
@@ -731,4 +739,4 @@ class JobOutput extends Component {
}
export { JobOutput as _JobOutput };
-export default withI18n()(withRouter(JobOutput));
+export default withRouter(JobOutput);
diff --git a/awx/ui_next/src/screens/Job/shared/data.job.json b/awx/ui_next/src/screens/Job/shared/data.job.json
index 98d071c876..778c2fcc84 100644
--- a/awx/ui_next/src/screens/Job/shared/data.job.json
+++ b/awx/ui_next/src/screens/Job/shared/data.job.json
@@ -101,9 +101,9 @@
"limit": "",
"verbosity": 0,
"extra_vars": "{\"num_messages\": 94}",
- "job_tags": "",
+ "job_tags": "a,b",
"force_handlers": false,
- "skip_tags": "",
+ "skip_tags": "c,d",
"start_at_task": "",
"timeout": 0,
"use_fact_cache": false,
diff --git a/awx/ui_next/src/util/validators.jsx b/awx/ui_next/src/util/validators.jsx
index 561f3051ca..c7b3c7ab6b 100644
--- a/awx/ui_next/src/util/validators.jsx
+++ b/awx/ui_next/src/util/validators.jsx
@@ -50,10 +50,23 @@ export function requiredEmail(i18n) {
if (!value) {
return i18n._(t`This field must not be blank`);
}
- if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
- return i18n._(t`Invalid email address`);
+
+ // This isn't a perfect validator. It's likely to let a few
+ // invalid (though unlikely) email addresses through.
+
+ // This is ok, because the server will always do strict validation for us.
+
+ const splitVals = value.split('@');
+
+ if (splitVals.length >= 2) {
+ if (splitVals[0] && splitVals[1]) {
+ // We get here if the string has an '@' that is enclosed by
+ // non-empty substrings
+ return undefined;
+ }
}
- return undefined;
+
+ return i18n._(t`Invalid email address`);
};
}
diff --git a/awx/ui_next/src/util/validators.test.js b/awx/ui_next/src/util/validators.test.js
index a660c8ea5c..db1285bb21 100644
--- a/awx/ui_next/src/util/validators.test.js
+++ b/awx/ui_next/src/util/validators.test.js
@@ -8,6 +8,7 @@ import {
url,
combine,
regExp,
+ requiredEmail,
} from './validators';
const i18n = { _: val => val };
@@ -187,4 +188,14 @@ describe('validators', () => {
expect(regExp(i18n)('ok')).toBeUndefined();
expect(regExp(i18n)('[^a-zA-Z]')).toBeUndefined();
});
+
+ test('email validator rejects obviously invalid email ', () => {
+ expect(requiredEmail(i18n)('foobar321')).toEqual({
+ id: 'Invalid email address',
+ });
+ });
+
+ test('bob has email', () => {
+ expect(requiredEmail(i18n)('bob@localhost')).toBeUndefined();
+ });
});
diff --git a/awx_collection/plugins/modules/tower_instance_group.py b/awx_collection/plugins/modules/tower_instance_group.py
index 076610f7c0..f32b60aebf 100644
--- a/awx_collection/plugins/modules/tower_instance_group.py
+++ b/awx_collection/plugins/modules/tower_instance_group.py
@@ -31,7 +31,6 @@ options:
new_name:
description:
- Setting this option will change the existing name (looked up via the name field.
- required: True
type: str
credential:
description:
diff --git a/awx_collection/test/awx/test_inventory_source.py b/awx_collection/test/awx/test_inventory_source.py
index 3e65feaddb..fa01b16ddb 100644
--- a/awx_collection/test/awx/test_inventory_source.py
+++ b/awx_collection/test/awx/test_inventory_source.py
@@ -169,7 +169,7 @@ def test_falsy_value(run_module, admin_user, base_inventory):
result = run_module('tower_inventory_source', dict(
name='falsy-test',
inventory=base_inventory.name,
- # source='ec2',
+ source='ec2',
update_on_launch=False
), admin_user)
diff --git a/installer/roles/kubernetes/templates/deployment.yml.j2 b/installer/roles/kubernetes/templates/deployment.yml.j2
index 5902d8d3ae..da329d23de 100644
--- a/installer/roles/kubernetes/templates/deployment.yml.j2
+++ b/installer/roles/kubernetes/templates/deployment.yml.j2
@@ -150,7 +150,8 @@ spec:
virtualenv -p {{ custom_venv.python | default(custom_venvs_python) }} \
{{ custom_venvs_path }}/{{ custom_venv.name }} &&
source {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/activate &&
- {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U psutil \
+ {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U pip &&
+ {{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U psutil \
"ansible=={{ custom_venv.python_ansible_version }}" &&
{% if custom_venv.python_modules is defined %}
{{ custom_venvs_path }}/{{ custom_venv.name }}/bin/pip install {{ trusted_hosts }} -U \
diff --git a/requirements/requirements.in b/requirements/requirements.in
index 75d1b7afd0..263b95dfbe 100644
--- a/requirements/requirements.in
+++ b/requirements/requirements.in
@@ -1,5 +1,5 @@
aiohttp
-ansible-runner>=1.4.6
+ansible-runner>=1.4.7
ansiconv==1.0.0 # UPGRADE BLOCKER: from 2013, consider replacing instead of upgrading
asciichartpy
autobahn>=20.12.3 # CVE-2020-35678
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index 9668f2d0fb..fd1591dc29 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -1,7 +1,7 @@
adal==1.2.2 # via msrestazure
aiohttp==3.6.2 # via -r /awx_devel/requirements/requirements.in
aioredis==1.3.1 # via channels-redis
-ansible-runner==1.4.6 # via -r /awx_devel/requirements/requirements.in
+ansible-runner==1.4.7 # via -r /awx_devel/requirements/requirements.in
ansiconv==1.0.0 # via -r /awx_devel/requirements/requirements.in
asciichartpy==1.5.25 # via -r /awx_devel/requirements/requirements.in
asgiref==3.2.5 # via channels, channels-redis, daphne
|