Fix job relaunch where credentials are needed

This commit is contained in:
mabashian 2021-01-19 11:34:19 -05:00
parent fb62e0ec2c
commit 7f78018386
3 changed files with 202 additions and 27 deletions

View File

@ -102,17 +102,20 @@ class LaunchButton extends React.Component {
async launchWithParams(params) {
try {
const { history, resource } = this.props;
const jobPromise =
resource.type === 'workflow_job_template'
? WorkflowJobTemplatesAPI.launch(resource.id, params || {})
: JobTemplatesAPI.launch(resource.id, params || {});
let jobPromise;
if (resource.type === 'job_template') {
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;
history.push(
`/${
resource.type === 'workflow_job_template' ? 'jobs/workflow' : 'jobs'
}/${job.id}/output`
);
history.push(`/jobs/${job.id}/output`);
} catch (launchError) {
this.setState({ launchError });
}
@ -129,20 +132,15 @@ class LaunchButton extends React.Component {
readRelaunch = InventorySourcesAPI.readLaunchUpdate(
resource.inventory_source
);
relaunch = InventorySourcesAPI.launchUpdate(resource.inventory_source);
} else if (resource.type === 'project_update') {
// We'll need to handle the scenario where the project no longer exists
readRelaunch = ProjectsAPI.readLaunchUpdate(resource.project);
relaunch = ProjectsAPI.launchUpdate(resource.project);
} else if (resource.type === 'workflow_job') {
readRelaunch = WorkflowJobsAPI.readRelaunch(resource.id);
relaunch = WorkflowJobsAPI.relaunch(resource.id);
} else if (resource.type === 'ad_hoc_command') {
readRelaunch = AdHocCommandsAPI.readRelaunch(resource.id);
relaunch = AdHocCommandsAPI.relaunch(resource.id);
} else if (resource.type === 'job') {
readRelaunch = JobsAPI.readRelaunch(resource.id);
relaunch = JobsAPI.relaunch(resource.id);
}
try {
@ -151,11 +149,22 @@ class LaunchButton extends React.Component {
!relaunchConfig.passwords_needed_to_start ||
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;
history.push(`/jobs/${job.id}/output`);
} else {
// TODO: restructure (async?) to send launch command after prompts
// TODO: does relaunch need different prompt treatment than launch?
this.setState({
showLaunchPrompt: true,
launchConfig: relaunchConfig,

View File

@ -4,10 +4,16 @@ import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import { sleep } from '../../../testUtils/testUtils';
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/models/JobTemplates');
jest.mock('../../api');
describe('LaunchButton', () => {
JobTemplatesAPI.readLaunch.mockResolvedValue({
@ -22,10 +28,14 @@ describe('LaunchButton', () => {
},
});
const children = ({ handleLaunch }) => (
const launchButton = ({ handleLaunch }) => (
<button type="submit" onClick={() => handleLaunch()} />
);
const relaunchButton = ({ handleRelaunch }) => (
<button type="submit" onClick={() => handleRelaunch()} />
);
const resource = {
id: 1,
type: 'job_template',
@ -35,7 +45,7 @@ describe('LaunchButton', () => {
test('renders the expected content', () => {
const wrapper = mountWithContexts(
<LaunchButton resource={resource}>{children}</LaunchButton>
<LaunchButton resource={resource}>{launchButton}</LaunchButton>
);
expect(wrapper).toHaveLength(1);
});
@ -51,7 +61,7 @@ describe('LaunchButton', () => {
},
});
const wrapper = mountWithContexts(
<LaunchButton resource={resource}>{children}</LaunchButton>,
<LaunchButton resource={resource}>{launchButton}</LaunchButton>,
{
context: {
router: { history },
@ -87,7 +97,7 @@ describe('LaunchButton', () => {
type: 'workflow_job_template',
}}
>
{children}
{launchButton}
</LaunchButton>,
{
context: {
@ -100,12 +110,162 @@ describe('LaunchButton', () => {
expect(WorkflowJobTemplatesAPI.readLaunch).toHaveBeenCalledWith(1);
await sleep(0);
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 () => {
const wrapper = mountWithContexts(
<LaunchButton resource={resource}>{children}</LaunchButton>
<LaunchButton resource={resource}>{launchButton}</LaunchButton>
);
JobTemplatesAPI.launch.mockRejectedValue(
new Error({

View File

@ -6,6 +6,7 @@ import { t } from '@lingui/macro';
import useRequest from '../../util/useRequest';
import { UnifiedJobsAPI } from '../../api';
import ContentError from '../../components/ContentError';
import ContentLoading from '../../components/ContentLoading';
import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
const NOT_FOUND = 'not found';
@ -46,8 +47,13 @@ function JobTypeRedirect({ id, path, view, i18n }) {
);
}
if (isLoading || !job?.id) {
// TODO show loading state
return <div>Loading...</div>;
return (
<PageSection>
<Card>
<ContentLoading />
</Card>
</PageSection>
);
}
const type = JOB_TYPE_URL_SEGMENTS[job.type];
return <Redirect from={path} to={`/jobs/${type}/${job.id}/${view}`} />;