Merge pull request #10572 from mabashian/790-mgt-jobs-workflow

Add UI support for management jobs in workflows
This commit is contained in:
Tiago Góes 2021-07-28 16:15:38 -03:00 committed by GitHub
commit b349774f92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 633 additions and 133 deletions

View File

@ -10,12 +10,6 @@ class SystemJobTemplates extends Mixins {
this.baseUrl = '/api/v2/system_job_templates/';
}
readDetail(id) {
const path = `${this.baseUrl}${id}/`;
return this.http.get(path).then(({ data }) => data);
}
launch(id, data) {
return this.http.post(`${this.baseUrl}${id}/launch/`, data);
}

View File

@ -1,18 +1,15 @@
import 'styled-components/macro';
import React from 'react';
import { shape } from 'prop-types';
import { t, Trans } from '@lingui/macro';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Chip, Divider, Title } from '@patternfly/react-core';
import { toTitleCase } from 'util/strings';
import CredentialChip from '../CredentialChip';
import ChipGroup from '../ChipGroup';
import { DetailList, Detail, UserDateDetail } from '../DetailList';
import { VariablesDetail } from '../CodeEditor';
import PromptProjectDetail from './PromptProjectDetail';
import PromptInventorySourceDetail from './PromptInventorySourceDetail';
import PromptJobTemplateDetail from './PromptJobTemplateDetail';
@ -139,123 +136,136 @@ function PromptDetail({ resource, launchConfig = {}, overrides = {} }) {
user={details?.summary_fields?.modified_by}
/>
)}
{details?.type === 'system_job_template' && (
<VariablesDetail
label={t`Variables`}
rows={4}
value={overrides.extra_vars}
name="extra_vars"
/>
)}
</DetailList>
{hasPromptData(launchConfig) && hasOverrides && (
<>
<PromptTitle headingLevel="h2">{t`Prompted Values`}</PromptTitle>
<PromptDivider />
<PromptDetailList aria-label={t`Prompt Overrides`}>
{launchConfig.ask_job_type_on_launch && (
<Detail
label={t`Job Type`}
value={toTitleCase(overrides.job_type)}
/>
)}
{launchConfig.ask_credential_on_launch && (
<Detail
fullWidth
label={t`Credentials`}
rows={4}
value={
<ChipGroup
numChips={5}
totalChips={overrides.credentials.length}
>
{overrides.credentials.map((cred) => (
<CredentialChip
key={cred.id}
credential={cred}
isReadOnly
/>
))}
</ChipGroup>
}
/>
)}
{launchConfig.ask_inventory_on_launch && (
<Detail label={t`Inventory`} value={overrides.inventory?.name} />
)}
{launchConfig.ask_scm_branch_on_launch && (
<Detail
label={t`Source Control Branch`}
value={overrides.scm_branch}
/>
)}
{launchConfig.ask_limit_on_launch && (
<Detail label={t`Limit`} value={overrides.limit} />
)}
{Object.prototype.hasOwnProperty.call(overrides, 'verbosity') &&
launchConfig.ask_verbosity_on_launch ? (
<Detail
label={t`Verbosity`}
value={VERBOSITY[overrides.verbosity]}
/>
) : null}
{launchConfig.ask_tags_on_launch && (
<Detail
fullWidth
label={t`Job Tags`}
value={
<ChipGroup
numChips={5}
totalChips={
!overrides.job_tags || overrides.job_tags === ''
? 0
: overrides.job_tags.split(',').length
}
>
{overrides.job_tags.length > 0 &&
overrides.job_tags.split(',').map((jobTag) => (
<Chip key={jobTag} isReadOnly>
{jobTag}
</Chip>
{details?.type !== 'system_job_template' &&
hasPromptData(launchConfig) &&
hasOverrides && (
<>
<PromptTitle headingLevel="h2">{t`Prompted Values`}</PromptTitle>
<PromptDivider />
<PromptDetailList aria-label={t`Prompt Overrides`}>
{launchConfig.ask_job_type_on_launch && (
<Detail
label={t`Job Type`}
value={toTitleCase(overrides.job_type)}
/>
)}
{launchConfig.ask_credential_on_launch && (
<Detail
fullWidth
label={t`Credentials`}
rows={4}
value={
<ChipGroup
numChips={5}
totalChips={overrides.credentials.length}
>
{overrides.credentials.map((cred) => (
<CredentialChip
key={cred.id}
credential={cred}
isReadOnly
/>
))}
</ChipGroup>
}
/>
)}
{launchConfig.ask_skip_tags_on_launch && (
<Detail
fullWidth
label={t`Skip Tags`}
value={
<ChipGroup
numChips={5}
totalChips={
!overrides.skip_tags || overrides.skip_tags === ''
? 0
: overrides.skip_tags.split(',').length
}
>
{overrides.skip_tags.length > 0 &&
overrides.skip_tags.split(',').map((skipTag) => (
<Chip key={skipTag} isReadOnly>
{skipTag}
</Chip>
))}
</ChipGroup>
}
/>
)}
{launchConfig.ask_diff_mode_on_launch && (
<Detail
label={t`Show Changes`}
value={overrides.diff_mode === true ? t`On` : t`Off`}
/>
)}
{(launchConfig.survey_enabled ||
launchConfig.ask_variables_on_launch) && (
<VariablesDetail
label={t`Variables`}
rows={4}
value={overrides.extra_vars}
name="extra_vars"
/>
)}
</PromptDetailList>
</>
)}
</ChipGroup>
}
/>
)}
{launchConfig.ask_inventory_on_launch && (
<Detail
label={t`Inventory`}
value={overrides.inventory?.name}
/>
)}
{launchConfig.ask_scm_branch_on_launch && (
<Detail
label={t`Source Control Branch`}
value={overrides.scm_branch}
/>
)}
{launchConfig.ask_limit_on_launch && (
<Detail label={t`Limit`} value={overrides.limit} />
)}
{Object.prototype.hasOwnProperty.call(overrides, 'verbosity') &&
launchConfig.ask_verbosity_on_launch ? (
<Detail
label={t`Verbosity`}
value={VERBOSITY[overrides.verbosity]}
/>
) : null}
{launchConfig.ask_tags_on_launch && (
<Detail
fullWidth
label={t`Job Tags`}
value={
<ChipGroup
numChips={5}
totalChips={
!overrides.job_tags || overrides.job_tags === ''
? 0
: overrides.job_tags.split(',').length
}
>
{overrides.job_tags.length > 0 &&
overrides.job_tags.split(',').map((jobTag) => (
<Chip key={jobTag} isReadOnly>
{jobTag}
</Chip>
))}
</ChipGroup>
}
/>
)}
{launchConfig.ask_skip_tags_on_launch && (
<Detail
fullWidth
label={t`Skip Tags`}
value={
<ChipGroup
numChips={5}
totalChips={
!overrides.skip_tags || overrides.skip_tags === ''
? 0
: overrides.skip_tags.split(',').length
}
>
{overrides.skip_tags.length > 0 &&
overrides.skip_tags.split(',').map((skipTag) => (
<Chip key={skipTag} isReadOnly>
{skipTag}
</Chip>
))}
</ChipGroup>
}
/>
)}
{launchConfig.ask_diff_mode_on_launch && (
<Detail
label={t`Show Changes`}
value={overrides.diff_mode === true ? t`On` : t`Off`}
/>
)}
{(launchConfig.survey_enabled ||
launchConfig.ask_variables_on_launch) && (
<VariablesDetail
label={t`Variables`}
rows={4}
value={overrides.extra_vars}
name="extra_vars"
/>
)}
</PromptDetailList>
</>
)}
</>
);
}

View File

@ -103,6 +103,10 @@ function WorkflowLegend() {
<NodeTypeLetter>P</NodeTypeLetter>
<span>{t`Project Sync`}</span>
</li>
<li>
<NodeTypeLetter>M</NodeTypeLetter>
<span>{t`Management Job`}</span>
</li>
<li>
<NodeTypeLetter>
<PauseIcon />

View File

@ -62,6 +62,10 @@ function WorkflowNodeHelp({ node }) {
case 'workflow_approval':
nodeType = t`Workflow Approval`;
break;
case 'system_job_template':
case 'system_job':
nodeType = t`Management Job`;
break;
default:
nodeType = '';
}

View File

@ -53,6 +53,10 @@ function WorkflowNodeTypeLetter({ node }) {
case 'inventory_update':
nodeTypeLetter = 'I';
break;
case 'system_job_template':
case 'system_job':
nodeTypeLetter = 'M';
break;
case 'workflow_job_template':
case 'workflow_job':
nodeTypeLetter = 'W';

View File

@ -41,7 +41,7 @@ function ManagementJob({ setBreadcrumb }) {
page_size: 1,
role_level: 'notification_admin_role',
}),
]).then(([systemJobTemplate, notificationRoles]) => ({
]).then(([{ data: systemJobTemplate }, notificationRoles]) => ({
systemJobTemplate,
notificationRoles,
})),

View File

@ -0,0 +1,34 @@
import React from 'react';
import { t } from '@lingui/macro';
import { Form } from '@patternfly/react-core';
import { useField } from 'formik';
import {
required,
minMaxValue,
integer,
combine,
} from '../../../../../util/validators';
import FormField from '../../../../../components/FormField';
function DaysToKeepStep() {
const [, meta] = useField('daysToKeep');
const validators = [required(null), minMaxValue(0), integer()];
return (
<Form
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormField
name="daysToKeep"
id="days-to-keep"
isRequired
validate={combine(validators)}
validated={!(meta.touched && meta.error) ? 'default' : 'error'}
label={t`Days of data to be retained`}
/>
</Form>
);
}
export default DaysToKeepStep;

View File

@ -0,0 +1,28 @@
import React from 'react';
import { Formik } from 'formik';
import { mountWithContexts } from '../../../../../../testUtils/enzymeHelpers';
import DaysToKeepStep from './DaysToKeepStep';
let wrapper;
describe('DaysToKeepStep', () => {
beforeAll(() => {
wrapper = mountWithContexts(
<Formik initialValues={{ daysToKeep: 30 }}>
<DaysToKeepStep />
</Formik>
);
});
afterAll(() => {
wrapper.unmount();
});
test('Days to keep field rendered correctly', () => {
expect(wrapper.find('FormField#days-to-keep').length).toBe(1);
expect(wrapper.find('FormField#days-to-keep').prop('isRequired')).toBe(
true
);
expect(wrapper.find('input#days-to-keep').prop('value')).toBe(30);
});
});

View File

@ -56,7 +56,13 @@ function NodeAddModal() {
) {
node.promptValues = values;
}
if (values?.nodeType === 'system_job_template') {
node.promptValues = {
extra_data: values?.extra_data,
};
}
}
dispatch({
type: 'CREATE_NODE',
node,

View File

@ -44,6 +44,11 @@ function NodeEditModal() {
node.launchConfig = config;
}
if (nodeType === 'system_job_template') {
node.promptValues = {
extra_data: values?.extra_data,
};
}
}
dispatch({

View File

@ -1,7 +1,6 @@
import 'styled-components/macro';
import React, { useContext, useState, useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Formik, useFormikContext } from 'formik';
import yaml from 'js-yaml';
@ -27,12 +26,10 @@ import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from 'api';
import Wizard from 'components/Wizard';
import AlertModal from 'components/AlertModal';
import useWorkflowNodeSteps from './useWorkflowNodeSteps';
import NodeNextButton from './NodeNextButton';
function NodeModalForm({
askLinkType,
onSave,
title,
credentialError,
@ -66,7 +63,6 @@ function NodeModalForm({
} = useWorkflowNodeSteps(
launchConfig,
surveyConfig,
values.nodeResource,
askLinkType,
resourceDefaultCredentials
@ -98,7 +94,18 @@ function NodeModalForm({
}
values.extra_data = extraVars && parseVariableField(extraVars);
delete values.extra_vars;
} else if (
values.nodeType === 'system_job_template' &&
['cleanup_activitystream', 'cleanup_jobs'].includes(
values?.nodeResource?.job_type
)
) {
values.extra_data = {
days: parseInt(values?.daysToKeep, 10),
};
}
delete values.daysToKeep;
onSave(values, launchConfig);
};
@ -351,6 +358,7 @@ const NodeModal = ({ onSave, askLinkType, title }) => {
initialValues={{
approvalName: '',
approvalDescription: '',
daysToKeep: 30,
timeoutMinutes: 0,
timeoutSeconds: 0,
convergence: 'any',

View File

@ -1,6 +1,5 @@
import 'styled-components/macro';
import React, { useState } from 'react';
import { t, Trans } from '@lingui/macro';
import styled from 'styled-components';
import { useField } from 'formik';
@ -24,6 +23,7 @@ import { useConfig } from 'contexts/Config';
import InventorySourcesList from './InventorySourcesList';
import JobTemplatesList from './JobTemplatesList';
import ProjectsList from './ProjectsList';
import SystemJobTemplatesList from './SystemJobTemplatesList';
import WorkflowJobTemplatesList from './WorkflowJobTemplatesList';
const NodeTypeErrorAlert = styled(Alert)`
@ -99,6 +99,12 @@ function NodeTypeStep() {
label: t`Project Sync`,
isDisabled: false,
},
{
key: 'system_job_template',
value: 'system_job_template',
label: t`Management Job`,
isDisabled: false,
},
{
key: 'workflow_job_template',
value: 'workflow_job_template',
@ -137,6 +143,12 @@ function NodeTypeStep() {
onUpdateNodeResource={nodeResourceHelpers.setValue}
/>
)}
{nodeTypeField.value === 'system_job_template' && (
<SystemJobTemplatesList
nodeResource={nodeResourceField.value}
onUpdateNodeResource={nodeResourceHelpers.setValue}
/>
)}
{nodeTypeField.value === 'workflow_job_template' && (
<WorkflowJobTemplatesList
nodeResource={nodeResourceField.value}

View File

@ -0,0 +1,115 @@
import React, { useEffect, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import { func, shape } from 'prop-types';
import { SystemJobTemplatesAPI } from 'api';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import DataListToolbar from 'components/DataListToolbar';
import CheckboxListItem from 'components/CheckboxListItem';
import PaginatedTable, {
HeaderCell,
HeaderRow,
} from 'components/PaginatedTable';
const QS_CONFIG = getQSConfig('system-job-templates', {
page: 1,
page_size: 5,
order_by: 'name',
});
function SystemJobTemplatesList({ nodeResource, onUpdateNodeResource }) {
const location = useLocation();
const {
result: {
systemJobTemplates,
count,
relatedSearchableKeys,
searchableKeys,
},
error,
isLoading,
request: fetchWorkflowJobTemplates,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const [response, actionsResponse] = await Promise.all([
SystemJobTemplatesAPI.read(params, {
role_level: 'execute_role',
}),
SystemJobTemplatesAPI.readOptions(),
]);
return {
systemJobTemplates: response.data.results,
count: response.data.count,
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
};
}, [location]),
{
systemJobTemplates: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
useEffect(() => {
fetchWorkflowJobTemplates();
}, [fetchWorkflowJobTemplates]);
return (
<PaginatedTable
contentError={error}
hasContentLoading={isLoading}
itemCount={count}
items={systemJobTemplates}
qsConfig={QS_CONFIG}
headerRow={
<HeaderRow isExpandable={false} qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
</HeaderRow>
}
renderRow={(item, index) => (
<CheckboxListItem
rowIndex={index}
isSelected={!!(nodeResource && nodeResource.id === item.id)}
itemId={item.id}
key={item.id}
name={item.name}
label={item.name}
onSelect={() => onUpdateNodeResource(item)}
onDeselect={() => onUpdateNodeResource(null)}
isRadio
/>
)}
renderToolbar={(props) => <DataListToolbar {...props} fillWidth />}
showPageSizeOptions={false}
toolbarSearchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
/>
);
}
SystemJobTemplatesList.propTypes = {
nodeResource: shape(),
onUpdateNodeResource: func.isRequired,
};
SystemJobTemplatesList.defaultProps = {
nodeResource: null,
};
export default SystemJobTemplatesList;

View File

@ -0,0 +1,186 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../../../../../testUtils/enzymeHelpers';
import { SystemJobTemplatesAPI } from '../../../../../../api';
import SystemJobTemplatesList from './SystemJobTemplatesList';
jest.mock('../../../../../../api/models/SystemJobTemplates');
const nodeResource = {
id: 1,
type: 'system_job_template',
url: '/api/v2/system_job_templates/1/',
related: {
next_schedule: '/api/v2/schedules/1/',
jobs: '/api/v2/system_job_templates/1/jobs/',
schedules: '/api/v2/system_job_templates/1/schedules/',
launch: '/api/v2/system_job_templates/1/launch/',
notification_templates_started:
'/api/v2/system_job_templates/1/notification_templates_started/',
notification_templates_success:
'/api/v2/system_job_templates/1/notification_templates_success/',
notification_templates_error:
'/api/v2/system_job_templates/1/notification_templates_error/',
},
summary_fields: {},
created: '2021-06-29T18:58:47.571901Z',
modified: '2021-06-29T18:58:47.571901Z',
name: 'Cleanup Job Details',
description: 'Remove job history',
last_job_run: null,
last_job_failed: false,
next_job_run: '2021-07-04T18:58:47Z',
status: 'ok',
execution_environment: null,
job_type: 'cleanup_jobs',
};
const onUpdateNodeResource = jest.fn();
describe('SystemJobTemplatesList', () => {
let wrapper;
afterEach(() => {
wrapper.unmount();
});
test('Row selected when nodeResource id matches row id and clicking new row makes expected callback', async () => {
SystemJobTemplatesAPI.read.mockResolvedValueOnce({
data: {
count: 2,
results: [
nodeResource,
{
id: 2,
type: 'system_job_template',
url: '/api/v2/system_job_templates/2/',
related: {
last_job: '/api/v2/system_jobs/3/',
next_schedule: '/api/v2/schedules/2/',
jobs: '/api/v2/system_job_templates/2/jobs/',
schedules: '/api/v2/system_job_templates/2/schedules/',
launch: '/api/v2/system_job_templates/2/launch/',
notification_templates_started:
'/api/v2/system_job_templates/2/notification_templates_started/',
notification_templates_success:
'/api/v2/system_job_templates/2/notification_templates_success/',
notification_templates_error:
'/api/v2/system_job_templates/2/notification_templates_error/',
},
summary_fields: {
last_job: {
id: 3,
name: 'Cleanup Activity Stream',
description: 'Remove activity stream history',
finished: '2021-06-29T20:38:22.770364Z',
status: 'successful',
failed: false,
},
last_update: {
id: 3,
name: 'Cleanup Activity Stream',
description: 'Remove activity stream history',
status: 'successful',
failed: false,
},
},
created: '2021-06-29T18:58:47.571901Z',
modified: '2021-06-29T18:58:47.571901Z',
name: 'Cleanup Activity Stream',
description: 'Remove activity stream history',
last_job_run: '2021-06-29T20:38:22.770364Z',
last_job_failed: false,
next_job_run: '2021-07-06T18:58:47Z',
status: 'successful',
execution_environment: null,
job_type: 'cleanup_activitystream',
},
],
},
});
SystemJobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<SystemJobTemplatesList
nodeResource={nodeResource}
onUpdateNodeResource={onUpdateNodeResource}
/>
);
});
wrapper.update();
expect(
wrapper.find('CheckboxListItem[name="Cleanup Job Details"]').props()
.isSelected
).toBe(true);
expect(
wrapper.find('CheckboxListItem[name="Cleanup Activity Stream"]').props()
.isSelected
).toBe(false);
wrapper
.find('CheckboxListItem[name="Cleanup Activity Stream"]')
.prop('onSelect')();
expect(onUpdateNodeResource).toHaveBeenCalledWith({
id: 2,
type: 'system_job_template',
url: '/api/v2/system_job_templates/2/',
related: {
last_job: '/api/v2/system_jobs/3/',
next_schedule: '/api/v2/schedules/2/',
jobs: '/api/v2/system_job_templates/2/jobs/',
schedules: '/api/v2/system_job_templates/2/schedules/',
launch: '/api/v2/system_job_templates/2/launch/',
notification_templates_started:
'/api/v2/system_job_templates/2/notification_templates_started/',
notification_templates_success:
'/api/v2/system_job_templates/2/notification_templates_success/',
notification_templates_error:
'/api/v2/system_job_templates/2/notification_templates_error/',
},
summary_fields: {
last_job: {
id: 3,
name: 'Cleanup Activity Stream',
description: 'Remove activity stream history',
finished: '2021-06-29T20:38:22.770364Z',
status: 'successful',
failed: false,
},
last_update: {
id: 3,
name: 'Cleanup Activity Stream',
description: 'Remove activity stream history',
status: 'successful',
failed: false,
},
},
created: '2021-06-29T18:58:47.571901Z',
modified: '2021-06-29T18:58:47.571901Z',
name: 'Cleanup Activity Stream',
description: 'Remove activity stream history',
last_job_run: '2021-06-29T20:38:22.770364Z',
last_job_failed: false,
next_job_run: '2021-07-06T18:58:47Z',
status: 'successful',
execution_environment: null,
job_type: 'cleanup_activitystream',
});
});
test('Error shown when read() request errors', async () => {
SystemJobTemplatesAPI.read.mockRejectedValue(new Error());
await act(async () => {
wrapper = mountWithContexts(
<SystemJobTemplatesList
nodeResource={nodeResource}
onUpdateNodeResource={onUpdateNodeResource}
/>
);
});
wrapper.update();
expect(wrapper.find('ErrorDetail').length).toBe(1);
});
});

View File

@ -136,7 +136,12 @@ function NodeViewModal({ readOnly }) {
if (promptValues) {
overrides = promptValues;
if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
if (
launchConfig.ask_variables_on_launch ||
launchConfig.survey_enabled ||
(fullUnifiedJobTemplate.type === 'system_job_template' &&
promptValues.extra_data)
) {
overrides.extra_vars = jsonToYaml(
JSON.stringify(promptValues.extra_data)
);
@ -150,7 +155,11 @@ function NodeViewModal({ readOnly }) {
if (launchConfig.ask_scm_branch_on_launch) {
overrides.scm_branch = originalNodeObject.scm_branch;
}
if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
if (
launchConfig.ask_variables_on_launch ||
launchConfig.survey_enabled ||
fullUnifiedJobTemplate.type === 'system_job_template'
) {
overrides.extra_vars = jsonToYaml(
JSON.stringify(originalNodeObject.extra_data)
);

View File

@ -0,0 +1,43 @@
import React from 'react';
import { t } from '@lingui/macro';
import { useField } from 'formik';
import DaysToKeepStep from './DaysToKeepStep';
import StepName from '../../../../../components/LaunchPrompt/steps/StepName';
const STEP_ID = 'daysToKeep';
export default function useDaysToKeepStep() {
const [, nodeResourceMeta] = useField('nodeResource');
const [, daysToKeepMeta] = useField('daysToKeep');
return {
step: getStep(nodeResourceMeta, daysToKeepMeta),
initialValues: { daysToKeep: 30 },
isReady: true,
contentError: null,
hasError: !!daysToKeepMeta.error,
setTouched: (setFieldTouched) => {
setFieldTouched('daysToKeep', true, false);
},
validate: () => {},
};
}
function getStep(nodeResourceMeta, daysToKeepMeta) {
if (
['cleanup_activitystream', 'cleanup_jobs'].includes(
nodeResourceMeta?.value?.job_type
)
) {
return {
id: STEP_ID,
name: (
<StepName hasErrors={!!daysToKeepMeta.error} id="days-to-keep-step">
{t`Days to keep`}
</StepName>
),
component: <DaysToKeepStep />,
enableNext: !daysToKeepMeta.error,
};
}
return null;
}

View File

@ -9,6 +9,7 @@ import usePreviewStep from 'components/LaunchPrompt/steps/usePreviewStep';
import { WorkflowStateContext } from 'contexts/Workflow';
import { jsonToYaml } from 'util/yaml';
import useNodeTypeStep from './NodeTypeStep/useNodeTypeStep';
import useDaysToKeepStep from './useDaysToKeepStep';
import useRunTypeStep from './useRunTypeStep';
function showPreviewStep(nodeType, launchConfig) {
@ -59,6 +60,34 @@ const getNodeToEditDefaultValues = (
return initialValues;
}
if (nodeToEdit?.fullUnifiedJobTemplate?.type === 'system_job_template') {
if (
nodeToEdit?.promptValues?.extra_data &&
Object.prototype.hasOwnProperty.call(
nodeToEdit.promptValues.extra_data,
'days'
)
) {
initialValues.daysToKeep = parseInt(
nodeToEdit.promptValues.extra_data.days,
10
);
} else if (
nodeToEdit?.originalNodeObject?.extra_data &&
Object.prototype.hasOwnProperty.call(
nodeToEdit.originalNodeObject.extra_data,
'days'
)
) {
initialValues.daysToKeep = parseInt(
nodeToEdit.originalNodeObject.extra_data.days,
10
);
}
return initialValues;
}
if (!launchConfig || launchConfig === {}) {
return initialValues;
}
@ -186,7 +215,6 @@ const getNodeToEditDefaultValues = (
export default function useWorkflowNodeSteps(
launchConfig,
surveyConfig,
resource,
askLinkType,
resourceDefaultCredentials
@ -202,6 +230,7 @@ export default function useWorkflowNodeSteps(
const steps = [
useRunTypeStep(askLinkType),
useNodeTypeStep(launchConfig),
useDaysToKeepStep(),
useInventoryStep(launchConfig, resource, visited),
useCredentialsStep(launchConfig, resource, resourceDefaultCredentials),
useOtherPromptsStep(launchConfig, resource),
@ -213,7 +242,6 @@ export default function useWorkflowNodeSteps(
steps.push(
usePreviewStep(
launchConfig,
resource,
surveyConfig,
hasErrors,

View File

@ -2,6 +2,7 @@ import {
InventorySourcesAPI,
JobTemplatesAPI,
ProjectsAPI,
SystemJobTemplatesAPI,
WorkflowJobTemplatesAPI,
} from 'api';
@ -23,6 +24,9 @@ export default function getNodeType(node) {
case 'workflow_approval_template':
case 'workflow_approval':
return ['approval', null];
case 'system_job_template':
case 'system_job':
return ['system_job_template', SystemJobTemplatesAPI];
default:
return [null, null];
}

View File

@ -57,6 +57,12 @@ export function minLength(min) {
export function minMaxValue(min, max) {
return (value) => {
if (!Number.isFinite(min) && value > max) {
return t`This field must be a number and have a value less than ${max}`;
}
if (!Number.isFinite(max) && value < min) {
return t`This field must be a number and have a value greater than ${min}`;
}
if (value < min || value > max) {
return t`This field must be a number and have a value between ${min} and ${max}`;
}