mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 04:17:36 -02:30
Fix job relaunch where credentials are needed
This commit is contained in:
@@ -102,17 +102,20 @@ class LaunchButton extends React.Component {
|
|||||||
async launchWithParams(params) {
|
async launchWithParams(params) {
|
||||||
try {
|
try {
|
||||||
const { history, resource } = this.props;
|
const { history, resource } = this.props;
|
||||||
const jobPromise =
|
let jobPromise;
|
||||||
resource.type === 'workflow_job_template'
|
|
||||||
? WorkflowJobTemplatesAPI.launch(resource.id, params || {})
|
if (resource.type === 'job_template') {
|
||||||
: JobTemplatesAPI.launch(resource.id, params || {});
|
jobPromise = JobTemplatesAPI.launch(resource.id, params || {});
|
||||||
|
} else if (resource.type === 'workflow_job_template') {
|
||||||
|
jobPromise = WorkflowJobTemplatesAPI.launch(resource.id, params || {});
|
||||||
|
} else if (resource.type === 'job') {
|
||||||
|
jobPromise = JobsAPI.relaunch(resource.id, params || {});
|
||||||
|
} else if (resource.type === 'workflow_job') {
|
||||||
|
jobPromise = WorkflowJobsAPI.relaunch(resource.id, params || {});
|
||||||
|
}
|
||||||
|
|
||||||
const { data: job } = await jobPromise;
|
const { data: job } = await jobPromise;
|
||||||
history.push(
|
history.push(`/jobs/${job.id}/output`);
|
||||||
`/${
|
|
||||||
resource.type === 'workflow_job_template' ? 'jobs/workflow' : 'jobs'
|
|
||||||
}/${job.id}/output`
|
|
||||||
);
|
|
||||||
} catch (launchError) {
|
} catch (launchError) {
|
||||||
this.setState({ launchError });
|
this.setState({ launchError });
|
||||||
}
|
}
|
||||||
@@ -129,20 +132,15 @@ class LaunchButton extends React.Component {
|
|||||||
readRelaunch = InventorySourcesAPI.readLaunchUpdate(
|
readRelaunch = InventorySourcesAPI.readLaunchUpdate(
|
||||||
resource.inventory_source
|
resource.inventory_source
|
||||||
);
|
);
|
||||||
relaunch = InventorySourcesAPI.launchUpdate(resource.inventory_source);
|
|
||||||
} else if (resource.type === 'project_update') {
|
} else if (resource.type === 'project_update') {
|
||||||
// We'll need to handle the scenario where the project no longer exists
|
// We'll need to handle the scenario where the project no longer exists
|
||||||
readRelaunch = ProjectsAPI.readLaunchUpdate(resource.project);
|
readRelaunch = ProjectsAPI.readLaunchUpdate(resource.project);
|
||||||
relaunch = ProjectsAPI.launchUpdate(resource.project);
|
|
||||||
} else if (resource.type === 'workflow_job') {
|
} else if (resource.type === 'workflow_job') {
|
||||||
readRelaunch = WorkflowJobsAPI.readRelaunch(resource.id);
|
readRelaunch = WorkflowJobsAPI.readRelaunch(resource.id);
|
||||||
relaunch = WorkflowJobsAPI.relaunch(resource.id);
|
|
||||||
} else if (resource.type === 'ad_hoc_command') {
|
} else if (resource.type === 'ad_hoc_command') {
|
||||||
readRelaunch = AdHocCommandsAPI.readRelaunch(resource.id);
|
readRelaunch = AdHocCommandsAPI.readRelaunch(resource.id);
|
||||||
relaunch = AdHocCommandsAPI.relaunch(resource.id);
|
|
||||||
} else if (resource.type === 'job') {
|
} else if (resource.type === 'job') {
|
||||||
readRelaunch = JobsAPI.readRelaunch(resource.id);
|
readRelaunch = JobsAPI.readRelaunch(resource.id);
|
||||||
relaunch = JobsAPI.relaunch(resource.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -151,11 +149,22 @@ class LaunchButton extends React.Component {
|
|||||||
!relaunchConfig.passwords_needed_to_start ||
|
!relaunchConfig.passwords_needed_to_start ||
|
||||||
relaunchConfig.passwords_needed_to_start.length === 0
|
relaunchConfig.passwords_needed_to_start.length === 0
|
||||||
) {
|
) {
|
||||||
|
if (resource.type === 'inventory_update') {
|
||||||
|
relaunch = InventorySourcesAPI.launchUpdate(
|
||||||
|
resource.inventory_source
|
||||||
|
);
|
||||||
|
} else if (resource.type === 'project_update') {
|
||||||
|
relaunch = ProjectsAPI.launchUpdate(resource.project);
|
||||||
|
} else if (resource.type === 'workflow_job') {
|
||||||
|
relaunch = WorkflowJobsAPI.relaunch(resource.id);
|
||||||
|
} else if (resource.type === 'ad_hoc_command') {
|
||||||
|
relaunch = AdHocCommandsAPI.relaunch(resource.id);
|
||||||
|
} else if (resource.type === 'job') {
|
||||||
|
relaunch = JobsAPI.relaunch(resource.id);
|
||||||
|
}
|
||||||
const { data: job } = await relaunch;
|
const { data: job } = await relaunch;
|
||||||
history.push(`/jobs/${job.id}/output`);
|
history.push(`/jobs/${job.id}/output`);
|
||||||
} else {
|
} else {
|
||||||
// TODO: restructure (async?) to send launch command after prompts
|
|
||||||
// TODO: does relaunch need different prompt treatment than launch?
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showLaunchPrompt: true,
|
showLaunchPrompt: true,
|
||||||
launchConfig: relaunchConfig,
|
launchConfig: relaunchConfig,
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
|||||||
import { sleep } from '../../../testUtils/testUtils';
|
import { sleep } from '../../../testUtils/testUtils';
|
||||||
|
|
||||||
import LaunchButton from './LaunchButton';
|
import LaunchButton from './LaunchButton';
|
||||||
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../api';
|
import {
|
||||||
|
InventorySourcesAPI,
|
||||||
|
JobsAPI,
|
||||||
|
JobTemplatesAPI,
|
||||||
|
ProjectsAPI,
|
||||||
|
WorkflowJobsAPI,
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
} from '../../api';
|
||||||
|
|
||||||
jest.mock('../../api/models/WorkflowJobTemplates');
|
jest.mock('../../api');
|
||||||
jest.mock('../../api/models/JobTemplates');
|
|
||||||
|
|
||||||
describe('LaunchButton', () => {
|
describe('LaunchButton', () => {
|
||||||
JobTemplatesAPI.readLaunch.mockResolvedValue({
|
JobTemplatesAPI.readLaunch.mockResolvedValue({
|
||||||
@@ -22,10 +28,14 @@ describe('LaunchButton', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const children = ({ handleLaunch }) => (
|
const launchButton = ({ handleLaunch }) => (
|
||||||
<button type="submit" onClick={() => handleLaunch()} />
|
<button type="submit" onClick={() => handleLaunch()} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const relaunchButton = ({ handleRelaunch }) => (
|
||||||
|
<button type="submit" onClick={() => handleRelaunch()} />
|
||||||
|
);
|
||||||
|
|
||||||
const resource = {
|
const resource = {
|
||||||
id: 1,
|
id: 1,
|
||||||
type: 'job_template',
|
type: 'job_template',
|
||||||
@@ -35,7 +45,7 @@ describe('LaunchButton', () => {
|
|||||||
|
|
||||||
test('renders the expected content', () => {
|
test('renders the expected content', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<LaunchButton resource={resource}>{children}</LaunchButton>
|
<LaunchButton resource={resource}>{launchButton}</LaunchButton>
|
||||||
);
|
);
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
});
|
});
|
||||||
@@ -51,7 +61,7 @@ describe('LaunchButton', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<LaunchButton resource={resource}>{children}</LaunchButton>,
|
<LaunchButton resource={resource}>{launchButton}</LaunchButton>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
router: { history },
|
router: { history },
|
||||||
@@ -87,7 +97,7 @@ describe('LaunchButton', () => {
|
|||||||
type: 'workflow_job_template',
|
type: 'workflow_job_template',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{launchButton}
|
||||||
</LaunchButton>,
|
</LaunchButton>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
@@ -100,12 +110,162 @@ describe('LaunchButton', () => {
|
|||||||
expect(WorkflowJobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
|
expect(WorkflowJobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
expect(WorkflowJobTemplatesAPI.launch).toHaveBeenCalledWith(1, {});
|
expect(WorkflowJobTemplatesAPI.launch).toHaveBeenCalledWith(1, {});
|
||||||
expect(history.location.pathname).toEqual('/jobs/workflow/9000/output');
|
expect(history.location.pathname).toEqual('/jobs/9000/output');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should relaunch job correctly', async () => {
|
||||||
|
JobsAPI.readRelaunch.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
can_start_without_user_input: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/jobs/9000'],
|
||||||
|
});
|
||||||
|
JobsAPI.relaunch.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 9000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<LaunchButton
|
||||||
|
resource={{
|
||||||
|
id: 1,
|
||||||
|
type: 'job',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{relaunchButton}
|
||||||
|
</LaunchButton>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: { history },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const button = wrapper.find('button');
|
||||||
|
button.prop('onClick')();
|
||||||
|
expect(JobsAPI.readRelaunch).toHaveBeenCalledWith(1);
|
||||||
|
await sleep(0);
|
||||||
|
expect(JobsAPI.relaunch).toHaveBeenCalledWith(1);
|
||||||
|
expect(history.location.pathname).toEqual('/jobs/9000/output');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should relaunch workflow job correctly', async () => {
|
||||||
|
WorkflowJobsAPI.readRelaunch.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
can_start_without_user_input: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/jobs/9000'],
|
||||||
|
});
|
||||||
|
WorkflowJobsAPI.relaunch.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 9000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<LaunchButton
|
||||||
|
resource={{
|
||||||
|
id: 1,
|
||||||
|
type: 'workflow_job',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{relaunchButton}
|
||||||
|
</LaunchButton>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: { history },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const button = wrapper.find('button');
|
||||||
|
button.prop('onClick')();
|
||||||
|
expect(WorkflowJobsAPI.readRelaunch).toHaveBeenCalledWith(1);
|
||||||
|
await sleep(0);
|
||||||
|
expect(WorkflowJobsAPI.relaunch).toHaveBeenCalledWith(1);
|
||||||
|
expect(history.location.pathname).toEqual('/jobs/9000/output');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should relaunch project sync correctly', async () => {
|
||||||
|
ProjectsAPI.readLaunchUpdate.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
can_start_without_user_input: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/jobs/9000'],
|
||||||
|
});
|
||||||
|
ProjectsAPI.launchUpdate.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 9000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<LaunchButton
|
||||||
|
resource={{
|
||||||
|
id: 1,
|
||||||
|
project: 5,
|
||||||
|
type: 'project_update',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{relaunchButton}
|
||||||
|
</LaunchButton>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: { history },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const button = wrapper.find('button');
|
||||||
|
button.prop('onClick')();
|
||||||
|
expect(ProjectsAPI.readLaunchUpdate).toHaveBeenCalledWith(5);
|
||||||
|
await sleep(0);
|
||||||
|
expect(ProjectsAPI.launchUpdate).toHaveBeenCalledWith(5);
|
||||||
|
expect(history.location.pathname).toEqual('/jobs/9000/output');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should relaunch project sync correctly', async () => {
|
||||||
|
InventorySourcesAPI.readLaunchUpdate.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
can_start_without_user_input: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/jobs/9000'],
|
||||||
|
});
|
||||||
|
InventorySourcesAPI.launchUpdate.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 9000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<LaunchButton
|
||||||
|
resource={{
|
||||||
|
id: 1,
|
||||||
|
inventory_source: 5,
|
||||||
|
type: 'inventory_update',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{relaunchButton}
|
||||||
|
</LaunchButton>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: { history },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const button = wrapper.find('button');
|
||||||
|
button.prop('onClick')();
|
||||||
|
expect(InventorySourcesAPI.readLaunchUpdate).toHaveBeenCalledWith(5);
|
||||||
|
await sleep(0);
|
||||||
|
expect(InventorySourcesAPI.launchUpdate).toHaveBeenCalledWith(5);
|
||||||
|
expect(history.location.pathname).toEqual('/jobs/9000/output');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('displays error modal after unsuccessful launch', async () => {
|
test('displays error modal after unsuccessful launch', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<LaunchButton resource={resource}>{children}</LaunchButton>
|
<LaunchButton resource={resource}>{launchButton}</LaunchButton>
|
||||||
);
|
);
|
||||||
JobTemplatesAPI.launch.mockRejectedValue(
|
JobTemplatesAPI.launch.mockRejectedValue(
|
||||||
new Error({
|
new Error({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { t } from '@lingui/macro';
|
|||||||
import useRequest from '../../util/useRequest';
|
import useRequest from '../../util/useRequest';
|
||||||
import { UnifiedJobsAPI } from '../../api';
|
import { UnifiedJobsAPI } from '../../api';
|
||||||
import ContentError from '../../components/ContentError';
|
import ContentError from '../../components/ContentError';
|
||||||
|
import ContentLoading from '../../components/ContentLoading';
|
||||||
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
|
||||||
|
|
||||||
const NOT_FOUND = 'not found';
|
const NOT_FOUND = 'not found';
|
||||||
@@ -46,8 +47,13 @@ function JobTypeRedirect({ id, path, view, i18n }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isLoading || !job?.id) {
|
if (isLoading || !job?.id) {
|
||||||
// TODO show loading state
|
return (
|
||||||
return <div>Loading...</div>;
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<ContentLoading />
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const type = JOB_TYPE_URL_SEGMENTS[job.type];
|
const type = JOB_TYPE_URL_SEGMENTS[job.type];
|
||||||
return <Redirect from={path} to={`/jobs/${type}/${job.id}/${view}`} />;
|
return <Redirect from={path} to={`/jobs/${type}/${job.id}/${view}`} />;
|
||||||
|
|||||||
Reference in New Issue
Block a user