mirror of
https://github.com/ansible/awx.git
synced 2026-03-14 23:47:28 -02:30
Fixes for various prompt related ui issues
Fixes bug where Forks showed up in both default values and prompted values in launch summary Fixes prompting IGs with defaults on launch Make job tags and skip tags full width on workflow form Fixes bug where we attempted to fetch instance groups for workflows Fetch default instance groups from jt/schedule for schedule form prompt Grab default IGs when adding a node that prompts for them Adds support for saving labels on a new wf node Fix linting errors Fixes for various prompt on launch related issues Adds support for saving instance groups on a new node Adds support for saving instance groups when editing an existing node Fix workflowReducer test Updates useSelected to handle a non-empty starting state Fixes visualizerNode tests Fix visualizer test Second batch of prompt related ui issues: Fixes bug saving existing node when instance groups is not promptable Fixes bug removing newly added label Adds onError function to label prompt Fixes tooltips on the other prompts step Properly fetch all labels to show on schedule details
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import Base from '../Base';
|
import Base from '../Base';
|
||||||
|
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
||||||
|
import LabelsMixin from '../mixins/Labels.mixin';
|
||||||
|
|
||||||
class WorkflowJobTemplateNodes extends Base {
|
class WorkflowJobTemplateNodes extends LabelsMixin(InstanceGroupsMixin(Base)) {
|
||||||
constructor(http) {
|
constructor(http) {
|
||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = 'api/v2/workflow_job_template_nodes/';
|
this.baseUrl = 'api/v2/workflow_job_template_nodes/';
|
||||||
|
|||||||
@@ -91,11 +91,8 @@ function LabelSelect({ value, placeholder, onChange, onError, createText }) {
|
|||||||
<Chip
|
<Chip
|
||||||
isReadOnly={currentChip.isReadOnly}
|
isReadOnly={currentChip.isReadOnly}
|
||||||
key={currentChip.name}
|
key={currentChip.name}
|
||||||
onClick={(e, item) => {
|
onClick={(e) => {
|
||||||
if (typeof item === 'string') {
|
onSelect(e, currentChip);
|
||||||
item = { id: item, name: item };
|
|
||||||
}
|
|
||||||
onSelect(e, item);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentChip.name}
|
{currentChip.name}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function LaunchButton({ resource, children }) {
|
|||||||
const [launchConfig, setLaunchConfig] = useState(null);
|
const [launchConfig, setLaunchConfig] = useState(null);
|
||||||
const [surveyConfig, setSurveyConfig] = useState(null);
|
const [surveyConfig, setSurveyConfig] = useState(null);
|
||||||
const [labels, setLabels] = useState([]);
|
const [labels, setLabels] = useState([]);
|
||||||
|
const [instanceGroups, setInstanceGroups] = useState([]);
|
||||||
const [isLaunching, setIsLaunching] = useState(false);
|
const [isLaunching, setIsLaunching] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
@@ -83,6 +84,14 @@ function LaunchButton({ resource, children }) {
|
|||||||
setLabels(allLabels);
|
setLabels(allLabels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (launch.ask_instance_groups_on_launch) {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await JobTemplatesAPI.readInstanceGroups(resource.id);
|
||||||
|
|
||||||
|
setInstanceGroups(results);
|
||||||
|
}
|
||||||
|
|
||||||
if (canLaunchWithoutPrompt(launch)) {
|
if (canLaunchWithoutPrompt(launch)) {
|
||||||
await launchWithParams({});
|
await launchWithParams({});
|
||||||
} else {
|
} else {
|
||||||
@@ -197,6 +206,7 @@ function LaunchButton({ resource, children }) {
|
|||||||
labels={labels}
|
labels={labels}
|
||||||
onLaunch={launchWithParams}
|
onLaunch={launchWithParams}
|
||||||
onCancel={() => setShowLaunchPrompt(false)}
|
onCancel={() => setShowLaunchPrompt(false)}
|
||||||
|
instanceGroups={instanceGroups}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function PromptModalForm({
|
|||||||
resource,
|
resource,
|
||||||
labels,
|
labels,
|
||||||
surveyConfig,
|
surveyConfig,
|
||||||
|
instanceGroups,
|
||||||
}) {
|
}) {
|
||||||
const { setFieldTouched, values } = useFormikContext();
|
const { setFieldTouched, values } = useFormikContext();
|
||||||
const [showDescription, setShowDescription] = useState(false);
|
const [showDescription, setShowDescription] = useState(false);
|
||||||
@@ -29,7 +30,13 @@ function PromptModalForm({
|
|||||||
visitStep,
|
visitStep,
|
||||||
visitAllSteps,
|
visitAllSteps,
|
||||||
contentError,
|
contentError,
|
||||||
} = useLaunchSteps(launchConfig, surveyConfig, resource, labels);
|
} = useLaunchSteps(
|
||||||
|
launchConfig,
|
||||||
|
surveyConfig,
|
||||||
|
resource,
|
||||||
|
labels,
|
||||||
|
instanceGroups
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const postValues = {};
|
const postValues = {};
|
||||||
@@ -197,6 +204,7 @@ function LaunchPrompt({
|
|||||||
labels = [],
|
labels = [],
|
||||||
surveyConfig,
|
surveyConfig,
|
||||||
resourceDefaultCredentials = [],
|
resourceDefaultCredentials = [],
|
||||||
|
instanceGroups = [],
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Formik initialValues={{}} onSubmit={(values) => onLaunch(values)}>
|
<Formik initialValues={{}} onSubmit={(values) => onLaunch(values)}>
|
||||||
@@ -208,6 +216,7 @@ function LaunchPrompt({
|
|||||||
resource={resource}
|
resource={resource}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
resourceDefaultCredentials={resourceDefaultCredentials}
|
resourceDefaultCredentials={resourceDefaultCredentials}
|
||||||
|
instanceGroups={instanceGroups}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const QS_CONFIG = getQSConfig('instance-groups', {
|
|||||||
|
|
||||||
function InstanceGroupsStep() {
|
function InstanceGroupsStep() {
|
||||||
const [field, , helpers] = useField('instance_groups');
|
const [field, , helpers] = useField('instance_groups');
|
||||||
const { selected, handleSelect, setSelected } = useSelected([]);
|
const { selected, handleSelect, setSelected } = useSelected([], field.value);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ function InstanceGroupsStep() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsList
|
<OptionsList
|
||||||
value={field.value}
|
value={selected}
|
||||||
options={instance_groups}
|
options={instance_groups}
|
||||||
optionCount={count}
|
optionCount={count}
|
||||||
searchColumns={[
|
searchColumns={[
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import AnsibleSelect from '../../AnsibleSelect';
|
|||||||
import { VariablesField } from '../../CodeEditor';
|
import { VariablesField } from '../../CodeEditor';
|
||||||
import Popover from '../../Popover';
|
import Popover from '../../Popover';
|
||||||
import { VerbositySelectField } from '../../VerbositySelectField';
|
import { VerbositySelectField } from '../../VerbositySelectField';
|
||||||
|
import jobHelpText from '../../../screens/Job/Job.helptext';
|
||||||
|
import workflowHelpText from '../../../screens/Template/shared/WorkflowJobTemplate.helptext';
|
||||||
|
|
||||||
const FieldHeader = styled.div`
|
const FieldHeader = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -22,22 +24,29 @@ const FieldHeader = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
||||||
|
const helpTextSource = launchConfig.job_template_data
|
||||||
|
? jobHelpText
|
||||||
|
: workflowHelpText;
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{launchConfig.ask_job_type_on_launch && <JobTypeField />}
|
{launchConfig.ask_job_type_on_launch && (
|
||||||
|
<JobTypeField helpTextSource={helpTextSource} />
|
||||||
|
)}
|
||||||
{launchConfig.ask_scm_branch_on_launch && (
|
{launchConfig.ask_scm_branch_on_launch && (
|
||||||
<FormField
|
<FormField
|
||||||
id="prompt-scm-branch"
|
id="prompt-scm-branch"
|
||||||
name="scm_branch"
|
name="scm_branch"
|
||||||
label={t`Source Control Branch`}
|
label={t`Source Control Branch`}
|
||||||
tooltip={t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch`}
|
tooltip={helpTextSource.sourceControlBranch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_labels_on_launch && <LabelsField />}
|
{launchConfig.ask_labels_on_launch && (
|
||||||
|
<LabelsField helpTextSource={helpTextSource} />
|
||||||
|
)}
|
||||||
{launchConfig.ask_forks_on_launch && (
|
{launchConfig.ask_forks_on_launch && (
|
||||||
<FormField
|
<FormField
|
||||||
id="prompt-forks"
|
id="prompt-forks"
|
||||||
@@ -45,6 +54,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
label={t`Forks`}
|
label={t`Forks`}
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
|
tooltip={helpTextSource.forks}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_limit_on_launch && (
|
{launchConfig.ask_limit_on_launch && (
|
||||||
@@ -52,13 +62,12 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
id="prompt-limit"
|
id="prompt-limit"
|
||||||
name="limit"
|
name="limit"
|
||||||
label={t`Limit`}
|
label={t`Limit`}
|
||||||
tooltip={t`Provide a host pattern to further constrain the list
|
tooltip={helpTextSource.limit}
|
||||||
of hosts that will be managed or affected by the playbook. Multiple
|
|
||||||
patterns are allowed. Refer to Ansible documentation for more
|
|
||||||
information and examples on patterns.`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_verbosity_on_launch && <VerbosityField />}
|
{launchConfig.ask_verbosity_on_launch && (
|
||||||
|
<VerbosityField helpTextSource={helpTextSource} />
|
||||||
|
)}
|
||||||
{launchConfig.ask_job_slice_count_on_launch && (
|
{launchConfig.ask_job_slice_count_on_launch && (
|
||||||
<FormField
|
<FormField
|
||||||
id="prompt-job-slicing"
|
id="prompt-job-slicing"
|
||||||
@@ -66,6 +75,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
label={t`Job Slicing`}
|
label={t`Job Slicing`}
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
|
tooltip={helpTextSource.jobSlicing}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_timeout_on_launch && (
|
{launchConfig.ask_timeout_on_launch && (
|
||||||
@@ -75,6 +85,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
label={t`Timeout`}
|
label={t`Timeout`}
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
|
tooltip={helpTextSource.timeout}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_diff_mode_on_launch && <ShowChangesToggle />}
|
{launchConfig.ask_diff_mode_on_launch && <ShowChangesToggle />}
|
||||||
@@ -84,10 +95,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
name="job_tags"
|
name="job_tags"
|
||||||
label={t`Job Tags`}
|
label={t`Job Tags`}
|
||||||
aria-label={t`Job Tags`}
|
aria-label={t`Job Tags`}
|
||||||
tooltip={t`Tags are useful when you have a large
|
tooltip={helpTextSource.jobTags}
|
||||||
playbook, and you want to run a specific part of a play or task.
|
|
||||||
Use commas to separate multiple tags. Refer to Ansible Controller
|
|
||||||
documentation for details on the usage of tags.`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_skip_tags_on_launch && (
|
{launchConfig.ask_skip_tags_on_launch && (
|
||||||
@@ -96,10 +104,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
name="skip_tags"
|
name="skip_tags"
|
||||||
label={t`Skip Tags`}
|
label={t`Skip Tags`}
|
||||||
aria-label={t`Skip Tags`}
|
aria-label={t`Skip Tags`}
|
||||||
tooltip={t`Skip tags are useful when you have a large
|
tooltip={helpTextSource.skipTags}
|
||||||
playbook, and you want to skip specific parts of a play or task.
|
|
||||||
Use commas to separate multiple tags. Refer to Ansible Controller
|
|
||||||
documentation for details on the usage of tags.`}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{launchConfig.ask_variables_on_launch && (
|
{launchConfig.ask_variables_on_launch && (
|
||||||
@@ -115,7 +120,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function JobTypeField() {
|
function JobTypeField({ helpTextSource }) {
|
||||||
const [field, meta, helpers] = useField('job_type');
|
const [field, meta, helpers] = useField('job_type');
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
@@ -135,15 +140,9 @@ function JobTypeField() {
|
|||||||
const isValid = !(meta.touched && meta.error);
|
const isValid = !(meta.touched && meta.error);
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="propmt-job-type"
|
fieldId="prompt-job-type"
|
||||||
label={t`Job Type`}
|
label={t`Job Type`}
|
||||||
labelIcon={
|
labelIcon={<Popover content={helpTextSource.jobType} />}
|
||||||
<Popover
|
|
||||||
content={t`For job templates, select run to execute the playbook.
|
|
||||||
Select check to only check playbook syntax, test environment setup,
|
|
||||||
and report problems without executing the playbook.`}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
isRequired
|
isRequired
|
||||||
validated={isValid ? 'default' : 'error'}
|
validated={isValid ? 'default' : 'error'}
|
||||||
>
|
>
|
||||||
@@ -157,15 +156,14 @@ function JobTypeField() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function VerbosityField() {
|
function VerbosityField({ helpTextSource }) {
|
||||||
const [, meta] = useField('verbosity');
|
const [, meta] = useField('verbosity');
|
||||||
const isValid = !(meta.touched && meta.error);
|
const isValid = !(meta.touched && meta.error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VerbositySelectField
|
<VerbositySelectField
|
||||||
fieldId="prompt-verbosity"
|
fieldId="prompt-verbosity"
|
||||||
tooltip={t`Control the level of output ansible
|
tooltip={helpTextSource.verbosity}
|
||||||
will produce as the playbook executes.`}
|
|
||||||
isValid={isValid ? 'default' : 'error'}
|
isValid={isValid ? 'default' : 'error'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -214,24 +212,22 @@ function TagField({ id, name, label, tooltip }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LabelsField() {
|
function LabelsField({ helpTextSource }) {
|
||||||
const [field, , helpers] = useField('labels');
|
const [field, meta, helpers] = useField('labels');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="propmt-labels"
|
fieldId="prompt-labels"
|
||||||
label={t`Labels`}
|
label={t`Labels`}
|
||||||
labelIcon={
|
labelIcon={<Popover content={helpTextSource.labels} />}
|
||||||
<Popover
|
validated={!meta.touched || !meta.error ? 'default' : 'error'}
|
||||||
content={t`Optional labels that describe this job, such as 'dev' or 'test'. Labels can be used to group and filter completed jobs.`}
|
helperTextInvalid={meta.error}
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<LabelSelect
|
<LabelSelect
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={(labels) => helpers.setValue(labels)}
|
onChange={(labels) => helpers.setValue(labels)}
|
||||||
createText={t`Create`}
|
createText={t`Create`}
|
||||||
onError={() => {}}
|
onError={(err) => helpers.setError(err)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ describe('OtherPromptsStep', () => {
|
|||||||
<OtherPromptsStep
|
<OtherPromptsStep
|
||||||
launchConfig={{
|
launchConfig={{
|
||||||
ask_job_type_on_launch: true,
|
ask_job_type_on_launch: true,
|
||||||
|
job_template_data: {
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
@@ -36,6 +41,11 @@ describe('OtherPromptsStep', () => {
|
|||||||
<OtherPromptsStep
|
<OtherPromptsStep
|
||||||
launchConfig={{
|
launchConfig={{
|
||||||
ask_limit_on_launch: true,
|
ask_limit_on_launch: true,
|
||||||
|
job_template_data: {
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
@@ -56,6 +66,11 @@ describe('OtherPromptsStep', () => {
|
|||||||
<OtherPromptsStep
|
<OtherPromptsStep
|
||||||
launchConfig={{
|
launchConfig={{
|
||||||
ask_scm_branch_on_launch: true,
|
ask_scm_branch_on_launch: true,
|
||||||
|
job_template_data: {
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
@@ -76,6 +91,11 @@ describe('OtherPromptsStep', () => {
|
|||||||
<OtherPromptsStep
|
<OtherPromptsStep
|
||||||
launchConfig={{
|
launchConfig={{
|
||||||
ask_verbosity_on_launch: true,
|
ask_verbosity_on_launch: true,
|
||||||
|
job_template_data: {
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
@@ -96,6 +116,11 @@ describe('OtherPromptsStep', () => {
|
|||||||
<OtherPromptsStep
|
<OtherPromptsStep
|
||||||
launchConfig={{
|
launchConfig={{
|
||||||
ask_diff_mode_on_launch: true,
|
ask_diff_mode_on_launch: true,
|
||||||
|
job_template_data: {
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
@@ -119,6 +144,11 @@ describe('OtherPromptsStep', () => {
|
|||||||
onVarModeChange={onModeChange}
|
onVarModeChange={onModeChange}
|
||||||
launchConfig={{
|
launchConfig={{
|
||||||
ask_variables_on_launch: true,
|
ask_variables_on_launch: true,
|
||||||
|
job_template_data: {
|
||||||
|
name: 'Demo Job Template',
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ export default function useLaunchSteps(
|
|||||||
launchConfig,
|
launchConfig,
|
||||||
surveyConfig,
|
surveyConfig,
|
||||||
resource,
|
resource,
|
||||||
labels
|
labels,
|
||||||
|
instanceGroups
|
||||||
) {
|
) {
|
||||||
const [visited, setVisited] = useState({});
|
const [visited, setVisited] = useState({});
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
@@ -64,7 +65,7 @@ export default function useLaunchSteps(
|
|||||||
visited
|
visited
|
||||||
),
|
),
|
||||||
useExecutionEnvironmentStep(launchConfig, resource),
|
useExecutionEnvironmentStep(launchConfig, resource),
|
||||||
useInstanceGroupsStep(launchConfig, resource),
|
useInstanceGroupsStep(launchConfig, resource, instanceGroups),
|
||||||
useOtherPromptsStep(launchConfig, resource, labels),
|
useOtherPromptsStep(launchConfig, resource, labels),
|
||||||
useSurveyStep(launchConfig, surveyConfig, resource, visited),
|
useSurveyStep(launchConfig, surveyConfig, resource, visited),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function useSyncedSelectValue(value, onChange) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newOptions = [];
|
const newOptions = [];
|
||||||
if (value !== selections && options.length) {
|
if (value && value !== selections && options.length) {
|
||||||
const syncedValue = value.map((item) => {
|
const syncedValue = value.map((item) => {
|
||||||
const match = options.find((i) => i.id === item.id);
|
const match = options.find((i) => i.id === item.id);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
|||||||
@@ -146,7 +146,10 @@ function PromptJobTemplateDetail({ resource }) {
|
|||||||
/>
|
/>
|
||||||
<Detail label={t`Source Control Branch`} value={scm_branch} />
|
<Detail label={t`Source Control Branch`} value={scm_branch} />
|
||||||
<Detail label={t`Playbook`} value={playbook} />
|
<Detail label={t`Playbook`} value={playbook} />
|
||||||
<Detail label={t`Forks`} value={forks || '0'} />
|
<Detail
|
||||||
|
label={t`Forks`}
|
||||||
|
value={typeof forks === 'number' ? forks.toString() : forks}
|
||||||
|
/>
|
||||||
<Detail label={t`Limit`} value={limit} />
|
<Detail label={t`Limit`} value={limit} />
|
||||||
<Detail label={t`Verbosity`} value={VERBOSITY()[verbosity]} />
|
<Detail label={t`Verbosity`} value={VERBOSITY()[verbosity]} />
|
||||||
{typeof diff_mode === 'boolean' && (
|
{typeof diff_mode === 'boolean' && (
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ import { VariablesDetail } from '../../CodeEditor';
|
|||||||
import { VERBOSITY } from '../../VerbositySelectField';
|
import { VERBOSITY } from '../../VerbositySelectField';
|
||||||
import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext';
|
import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext';
|
||||||
|
|
||||||
|
const buildLinkURL = (instance) =>
|
||||||
|
instance.is_container_group
|
||||||
|
? '/instance_groups/container_group/'
|
||||||
|
: '/instance_groups/';
|
||||||
|
|
||||||
const PromptDivider = styled(Divider)`
|
const PromptDivider = styled(Divider)`
|
||||||
margin-top: var(--pf-global--spacer--lg);
|
margin-top: var(--pf-global--spacer--lg);
|
||||||
margin-bottom: var(--pf-global--spacer--lg);
|
margin-bottom: var(--pf-global--spacer--lg);
|
||||||
@@ -80,7 +85,6 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
job_slice_count,
|
job_slice_count,
|
||||||
job_tags,
|
job_tags,
|
||||||
job_type,
|
job_type,
|
||||||
labels,
|
|
||||||
limit,
|
limit,
|
||||||
modified,
|
modified,
|
||||||
name,
|
name,
|
||||||
@@ -113,7 +117,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
const { error, dismissError } = useDismissableError(deleteError);
|
const { error, dismissError } = useDismissableError(deleteError);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
result: [credentials, preview, launchData],
|
result: [credentials, preview, launchData, labels, instanceGroups],
|
||||||
isLoading,
|
isLoading,
|
||||||
error: readContentError,
|
error: readContentError,
|
||||||
request: fetchCredentialsAndPreview,
|
request: fetchCredentialsAndPreview,
|
||||||
@@ -133,7 +137,9 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
promises.push(
|
promises.push(
|
||||||
JobTemplatesAPI.readLaunch(
|
JobTemplatesAPI.readLaunch(
|
||||||
schedule.summary_fields.unified_job_template.id
|
schedule.summary_fields.unified_job_template.id
|
||||||
)
|
),
|
||||||
|
SchedulesAPI.readAllLabels(id),
|
||||||
|
SchedulesAPI.readInstanceGroups(id)
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
schedule?.summary_fields?.unified_job_template?.unified_job_type ===
|
schedule?.summary_fields?.unified_job_template?.unified_job_type ===
|
||||||
@@ -142,17 +148,28 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
promises.push(
|
promises.push(
|
||||||
WorkflowJobTemplatesAPI.readLaunch(
|
WorkflowJobTemplatesAPI.readLaunch(
|
||||||
schedule.summary_fields.unified_job_template.id
|
schedule.summary_fields.unified_job_template.id
|
||||||
)
|
),
|
||||||
|
SchedulesAPI.readAllLabels(id)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
promises.push(Promise.resolve());
|
promises.push(Promise.resolve());
|
||||||
}
|
}
|
||||||
|
|
||||||
const [{ data }, { data: schedulePreview }, launch] = await Promise.all(
|
const [
|
||||||
promises
|
{ data },
|
||||||
);
|
{ data: schedulePreview },
|
||||||
|
launch,
|
||||||
|
allLabelsResults,
|
||||||
|
instanceGroupsResults,
|
||||||
|
] = await Promise.all(promises);
|
||||||
|
|
||||||
return [data.results, schedulePreview, launch?.data];
|
return [
|
||||||
|
data.results,
|
||||||
|
schedulePreview,
|
||||||
|
launch?.data,
|
||||||
|
allLabelsResults?.data?.results,
|
||||||
|
instanceGroupsResults?.data?.results,
|
||||||
|
];
|
||||||
}, [id, schedule, rrule]),
|
}, [id, schedule, rrule]),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -195,6 +212,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
ask_forks_on_launch,
|
ask_forks_on_launch,
|
||||||
ask_job_slice_count_on_launch,
|
ask_job_slice_count_on_launch,
|
||||||
ask_timeout_on_launch,
|
ask_timeout_on_launch,
|
||||||
|
ask_instance_groups_on_launch,
|
||||||
survey_enabled,
|
survey_enabled,
|
||||||
} = launchData || {};
|
} = launchData || {};
|
||||||
|
|
||||||
@@ -255,6 +273,8 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
const showForksDetail = ask_forks_on_launch;
|
const showForksDetail = ask_forks_on_launch;
|
||||||
const showJobSlicingDetail = ask_job_slice_count_on_launch;
|
const showJobSlicingDetail = ask_job_slice_count_on_launch;
|
||||||
const showTimeoutDetail = ask_timeout_on_launch;
|
const showTimeoutDetail = ask_timeout_on_launch;
|
||||||
|
const showInstanceGroupsDetail =
|
||||||
|
ask_instance_groups_on_launch && instanceGroups.length > 0;
|
||||||
|
|
||||||
const showPromptedFields =
|
const showPromptedFields =
|
||||||
showCredentialsDetail ||
|
showCredentialsDetail ||
|
||||||
@@ -271,7 +291,8 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
showLabelsDetail ||
|
showLabelsDetail ||
|
||||||
showForksDetail ||
|
showForksDetail ||
|
||||||
showJobSlicingDetail ||
|
showJobSlicingDetail ||
|
||||||
showTimeoutDetail;
|
showTimeoutDetail ||
|
||||||
|
showInstanceGroupsDetail;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
@@ -471,6 +492,35 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
|||||||
{ask_job_slice_count_on_launch && (
|
{ask_job_slice_count_on_launch && (
|
||||||
<Detail label={t`Job Slicing`} value={job_slice_count} />
|
<Detail label={t`Job Slicing`} value={job_slice_count} />
|
||||||
)}
|
)}
|
||||||
|
{showInstanceGroupsDetail && (
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={t`Instance Groups`}
|
||||||
|
value={
|
||||||
|
<ChipGroup
|
||||||
|
numChips={5}
|
||||||
|
totalChips={instanceGroups.length}
|
||||||
|
ouiaId="instance-group-chips"
|
||||||
|
>
|
||||||
|
{instanceGroups.map((ig) => (
|
||||||
|
<Link
|
||||||
|
to={`${buildLinkURL(ig)}${ig.id}/details`}
|
||||||
|
key={ig.id}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
key={ig.id}
|
||||||
|
ouiaId={`instance-group-${ig.id}-chip`}
|
||||||
|
isReadOnly
|
||||||
|
>
|
||||||
|
{ig.name}
|
||||||
|
</Chip>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</ChipGroup>
|
||||||
|
}
|
||||||
|
isEmpty={instanceGroups.length === 0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{showCredentialsDetail && (
|
{showCredentialsDetail && (
|
||||||
<Detail
|
<Detail
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ function ScheduleForm({
|
|||||||
request: loadScheduleData,
|
request: loadScheduleData,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
isLoading: contentLoading,
|
isLoading: contentLoading,
|
||||||
result: { zoneOptions, zoneLinks, credentials, labels },
|
result: { zoneOptions, zoneLinks, credentials, labels, instanceGroups },
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const { data } = await SchedulesAPI.readZoneInfo();
|
const { data } = await SchedulesAPI.readZoneInfo();
|
||||||
|
|
||||||
let creds = [];
|
let creds = [];
|
||||||
let allLabels;
|
let allLabels = [];
|
||||||
|
let allInstanceGroups = [];
|
||||||
if (schedule.id) {
|
if (schedule.id) {
|
||||||
if (
|
if (
|
||||||
resource.type === 'job_template' &&
|
resource.type === 'job_template' &&
|
||||||
@@ -77,15 +78,30 @@ function ScheduleForm({
|
|||||||
} = await SchedulesAPI.readAllLabels(schedule.id);
|
} = await SchedulesAPI.readAllLabels(schedule.id);
|
||||||
allLabels = results;
|
allLabels = results;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (
|
if (
|
||||||
resource.type === 'job_template' &&
|
resource.type === 'job_template' &&
|
||||||
launchConfig.ask_labels_on_launch
|
launchConfig.ask_instance_groups_on_launch
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
data: { results },
|
data: { results },
|
||||||
} = await JobTemplatesAPI.readAllLabels(resource.id);
|
} = await SchedulesAPI.readInstanceGroups(schedule.id);
|
||||||
allLabels = results;
|
allInstanceGroups = results;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (resource.type === 'job_template') {
|
||||||
|
if (launchConfig.ask_labels_on_launch) {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await JobTemplatesAPI.readAllLabels(resource.id);
|
||||||
|
allLabels = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (launchConfig.ask_instance_groups_on_launch) {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await JobTemplatesAPI.readInstanceGroups(resource.id);
|
||||||
|
allInstanceGroups = results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
resource.type === 'workflow_job_template' &&
|
resource.type === 'workflow_job_template' &&
|
||||||
@@ -108,13 +124,15 @@ function ScheduleForm({
|
|||||||
zoneOptions: zones,
|
zoneOptions: zones,
|
||||||
zoneLinks: data.links,
|
zoneLinks: data.links,
|
||||||
credentials: creds,
|
credentials: creds,
|
||||||
labels: allLabels || [],
|
labels: allLabels,
|
||||||
|
instanceGroups: allInstanceGroups,
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
schedule,
|
schedule,
|
||||||
resource.id,
|
resource.id,
|
||||||
resource.type,
|
resource.type,
|
||||||
launchConfig.ask_labels_on_launch,
|
launchConfig.ask_labels_on_launch,
|
||||||
|
launchConfig.ask_instance_groups_on_launch,
|
||||||
launchConfig.ask_credential_on_launch,
|
launchConfig.ask_credential_on_launch,
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
@@ -123,6 +141,7 @@ function ScheduleForm({
|
|||||||
credentials: [],
|
credentials: [],
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
labels: [],
|
labels: [],
|
||||||
|
instanceGroups: [],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -501,6 +520,7 @@ function ScheduleForm({
|
|||||||
}}
|
}}
|
||||||
resourceDefaultCredentials={resourceDefaultCredentials}
|
resourceDefaultCredentials={resourceDefaultCredentials}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
|
instanceGroups={instanceGroups}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function SchedulePromptableFields({
|
|||||||
resource,
|
resource,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
labels,
|
labels,
|
||||||
|
instanceGroups,
|
||||||
}) {
|
}) {
|
||||||
const { setFieldTouched, values, initialValues, resetForm } =
|
const { setFieldTouched, values, initialValues, resetForm } =
|
||||||
useFormikContext();
|
useFormikContext();
|
||||||
@@ -35,7 +36,8 @@ function SchedulePromptableFields({
|
|||||||
resource,
|
resource,
|
||||||
credentials,
|
credentials,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
labels
|
labels,
|
||||||
|
instanceGroups
|
||||||
);
|
);
|
||||||
const [showDescription, setShowDescription] = useState(false);
|
const [showDescription, setShowDescription] = useState(false);
|
||||||
const { error, dismissError } = useDismissableError(contentError);
|
const { error, dismissError } = useDismissableError(contentError);
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export default function useSchedulePromptSteps(
|
|||||||
resource,
|
resource,
|
||||||
scheduleCredentials,
|
scheduleCredentials,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
labels
|
labels,
|
||||||
|
instanceGroups
|
||||||
) {
|
) {
|
||||||
const sourceOfValues =
|
const sourceOfValues =
|
||||||
(Object.keys(schedule).length > 0 && schedule) || resource;
|
(Object.keys(schedule).length > 0 && schedule) || resource;
|
||||||
@@ -31,7 +32,7 @@ export default function useSchedulePromptSteps(
|
|||||||
resourceDefaultCredentials
|
resourceDefaultCredentials
|
||||||
),
|
),
|
||||||
useExecutionEnvironmentStep(launchConfig, resource),
|
useExecutionEnvironmentStep(launchConfig, resource),
|
||||||
useInstanceGroupsStep(launchConfig, resource),
|
useInstanceGroupsStep(launchConfig, resource, instanceGroups),
|
||||||
useOtherPromptsStep(launchConfig, sourceOfValues, labels),
|
useOtherPromptsStep(launchConfig, sourceOfValues, labels),
|
||||||
useSurveyStep(launchConfig, surveyConfig, sourceOfValues, visited),
|
useSurveyStep(launchConfig, surveyConfig, sourceOfValues, visited),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export function initReducer() {
|
|||||||
addNodeTarget: null,
|
addNodeTarget: null,
|
||||||
addingLink: false,
|
addingLink: false,
|
||||||
contentError: null,
|
contentError: null,
|
||||||
|
defaultOrganization: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
linkToDelete: null,
|
linkToDelete: null,
|
||||||
linkToEdit: null,
|
linkToEdit: null,
|
||||||
@@ -64,6 +65,11 @@ export default function visualizerReducer(state, action) {
|
|||||||
...state,
|
...state,
|
||||||
contentError: action.value,
|
contentError: action.value,
|
||||||
};
|
};
|
||||||
|
case 'SET_DEFAULT_ORGANIZATION':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
defaultOrganization: action.value,
|
||||||
|
};
|
||||||
case 'SET_IS_LOADING':
|
case 'SET_IS_LOADING':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const defaultState = {
|
|||||||
addNodeTarget: null,
|
addNodeTarget: null,
|
||||||
addingLink: false,
|
addingLink: false,
|
||||||
contentError: null,
|
contentError: null,
|
||||||
|
defaultOrganization: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
linkToDelete: null,
|
linkToDelete: null,
|
||||||
linkToEdit: null,
|
linkToEdit: null,
|
||||||
@@ -1281,6 +1282,18 @@ describe('Workflow reducer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('SET_DEFAULT_ORGANIZATION', () => {
|
||||||
|
it('should set the state variable', () => {
|
||||||
|
const result = workflowReducer(defaultState, {
|
||||||
|
type: 'SET_DEFAULT_ORGANIZATION',
|
||||||
|
value: 1,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
...defaultState,
|
||||||
|
defaultOrganization: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('SET_IS_LOADING', () => {
|
describe('SET_IS_LOADING', () => {
|
||||||
it('should set the state variable', () => {
|
it('should set the state variable', () => {
|
||||||
const result = workflowReducer(defaultState, {
|
const result = workflowReducer(defaultState, {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { useState, useCallback } from 'react';
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function useSelected(list = []) {
|
export default function useSelected(list = [], defaultSelected = []) {
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState(defaultSelected);
|
||||||
const isAllSelected = selected.length > 0 && selected.length === list.length;
|
const isAllSelected = selected.length > 0 && selected.length === list.length;
|
||||||
|
|
||||||
const handleSelect = (row) => {
|
const handleSelect = (row) => {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ function NodeModalForm({
|
|||||||
isLaunchLoading,
|
isLaunchLoading,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
labels,
|
labels,
|
||||||
|
instanceGroups,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useContext(WorkflowDispatchContext);
|
const dispatch = useContext(WorkflowDispatchContext);
|
||||||
@@ -68,7 +69,8 @@ function NodeModalForm({
|
|||||||
values.nodeResource,
|
values.nodeResource,
|
||||||
askLinkType,
|
askLinkType,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
labels
|
labels,
|
||||||
|
instanceGroups
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveNode = () => {
|
const handleSaveNode = () => {
|
||||||
@@ -243,7 +245,13 @@ const NodeModalInner = ({ title, ...rest }) => {
|
|||||||
const {
|
const {
|
||||||
request: readLaunchConfigs,
|
request: readLaunchConfigs,
|
||||||
error: launchConfigError,
|
error: launchConfigError,
|
||||||
result: { launchConfig, surveyConfig, resourceDefaultCredentials, labels },
|
result: {
|
||||||
|
launchConfig,
|
||||||
|
surveyConfig,
|
||||||
|
resourceDefaultCredentials,
|
||||||
|
labels,
|
||||||
|
instanceGroups,
|
||||||
|
},
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
@@ -263,6 +271,7 @@ const NodeModalInner = ({ title, ...rest }) => {
|
|||||||
surveyConfig: {},
|
surveyConfig: {},
|
||||||
resourceDefaultCredentials: [],
|
resourceDefaultCredentials: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
|
instanceGroups: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,11 +318,27 @@ const NodeModalInner = ({ title, ...rest }) => {
|
|||||||
defaultLabels = results;
|
defaultLabels = results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let defaultInstanceGroups = [];
|
||||||
|
|
||||||
|
if (launch.ask_instance_groups_on_launch) {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await await JobTemplatesAPI.readInstanceGroups(
|
||||||
|
values?.nodeResource?.id,
|
||||||
|
{
|
||||||
|
page_size: 200,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
defaultInstanceGroups = results;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
launchConfig: launch,
|
launchConfig: launch,
|
||||||
surveyConfig: survey,
|
surveyConfig: survey,
|
||||||
resourceDefaultCredentials: defaultCredentials,
|
resourceDefaultCredentials: defaultCredentials,
|
||||||
labels: defaultLabels,
|
labels: defaultLabels,
|
||||||
|
instanceGroups: defaultInstanceGroups,
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -367,11 +392,12 @@ const NodeModalInner = ({ title, ...rest }) => {
|
|||||||
isLaunchLoading={isLoading}
|
isLaunchLoading={isLoading}
|
||||||
title={wizardTitle}
|
title={wizardTitle}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
|
instanceGroups={instanceGroups}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NodeModal = ({ onSave, askLinkType, title, labels }) => {
|
const NodeModal = ({ onSave, askLinkType, title }) => {
|
||||||
const { nodeToEdit } = useContext(WorkflowStateContext);
|
const { nodeToEdit } = useContext(WorkflowStateContext);
|
||||||
const onSaveForm = (values, config) => {
|
const onSaveForm = (values, config) => {
|
||||||
onSave(values, config);
|
onSave(values, config);
|
||||||
@@ -398,7 +424,6 @@ const NodeModal = ({ onSave, askLinkType, title, labels }) => {
|
|||||||
onSave={onSaveForm}
|
onSave={onSaveForm}
|
||||||
title={title}
|
title={title}
|
||||||
askLinkType={askLinkType}
|
askLinkType={askLinkType}
|
||||||
labels={labels}
|
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -228,6 +228,12 @@ const getNodeToEditDefaultValues = (
|
|||||||
if (launchConfig.ask_timeout_on_launch) {
|
if (launchConfig.ask_timeout_on_launch) {
|
||||||
initialValues.timeout = sourceOfValues?.timeout || 0;
|
initialValues.timeout = sourceOfValues?.timeout || 0;
|
||||||
}
|
}
|
||||||
|
if (launchConfig.ask_labels_on_launch) {
|
||||||
|
initialValues.labels = sourceOfValues?.labels || [];
|
||||||
|
}
|
||||||
|
if (launchConfig.ask_instance_groups_on_launch) {
|
||||||
|
initialValues.instance_groups = sourceOfValues?.instance_groups || [];
|
||||||
|
}
|
||||||
|
|
||||||
if (launchConfig.ask_variables_on_launch) {
|
if (launchConfig.ask_variables_on_launch) {
|
||||||
const newExtraData = { ...sourceOfValues.extra_data };
|
const newExtraData = { ...sourceOfValues.extra_data };
|
||||||
@@ -274,7 +280,8 @@ export default function useWorkflowNodeSteps(
|
|||||||
resource,
|
resource,
|
||||||
askLinkType,
|
askLinkType,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
labels
|
labels,
|
||||||
|
instanceGroups
|
||||||
) {
|
) {
|
||||||
const { nodeToEdit } = useContext(WorkflowStateContext);
|
const { nodeToEdit } = useContext(WorkflowStateContext);
|
||||||
const {
|
const {
|
||||||
@@ -291,7 +298,7 @@ export default function useWorkflowNodeSteps(
|
|||||||
useInventoryStep(launchConfig, resource, visited),
|
useInventoryStep(launchConfig, resource, visited),
|
||||||
useCredentialsStep(launchConfig, resource, resourceDefaultCredentials),
|
useCredentialsStep(launchConfig, resource, resourceDefaultCredentials),
|
||||||
useExecutionEnvironmentStep(launchConfig, resource),
|
useExecutionEnvironmentStep(launchConfig, resource),
|
||||||
useInstanceGroupsStep(launchConfig, resource),
|
useInstanceGroupsStep(launchConfig, resource, instanceGroups),
|
||||||
useOtherPromptsStep(launchConfig, resource, labels),
|
useOtherPromptsStep(launchConfig, resource, labels),
|
||||||
useSurveyStep(launchConfig, surveyConfig, resource, visited),
|
useSurveyStep(launchConfig, surveyConfig, resource, visited),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useReducer } from 'react';
|
import React, { useCallback, useEffect, useReducer } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { shape } from 'prop-types';
|
import { shape } from 'prop-types';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
@@ -18,6 +17,7 @@ import ContentLoading from 'components/ContentLoading';
|
|||||||
import workflowReducer from 'components/Workflow/workflowReducer';
|
import workflowReducer from 'components/Workflow/workflowReducer';
|
||||||
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
||||||
import {
|
import {
|
||||||
|
OrganizationsAPI,
|
||||||
WorkflowApprovalTemplatesAPI,
|
WorkflowApprovalTemplatesAPI,
|
||||||
WorkflowJobTemplateNodesAPI,
|
WorkflowJobTemplateNodesAPI,
|
||||||
WorkflowJobTemplatesAPI,
|
WorkflowJobTemplatesAPI,
|
||||||
@@ -53,7 +53,18 @@ const Wrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const replaceIdentifier = (node) => {
|
const replaceIdentifier = (node) => {
|
||||||
if (stringIsUUID(node.originalNodeObject.identifier) || node.identifier) {
|
if (
|
||||||
|
stringIsUUID(node.originalNodeObject.identifier) &&
|
||||||
|
typeof node.identifier === 'string' &&
|
||||||
|
node.identifier !== ''
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!stringIsUUID(node.originalNodeObject.identifier) &&
|
||||||
|
node.originalNodeObject.identifier !== node.identifier
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +137,7 @@ function Visualizer({ template }) {
|
|||||||
addNodeTarget: null,
|
addNodeTarget: null,
|
||||||
addingLink: false,
|
addingLink: false,
|
||||||
contentError: null,
|
contentError: null,
|
||||||
|
defaultOrganization: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
linkToDelete: null,
|
linkToDelete: null,
|
||||||
linkToEdit: null,
|
linkToEdit: null,
|
||||||
@@ -148,6 +160,7 @@ function Visualizer({ template }) {
|
|||||||
addLinkTargetNode,
|
addLinkTargetNode,
|
||||||
addNodeSource,
|
addNodeSource,
|
||||||
contentError,
|
contentError,
|
||||||
|
defaultOrganization,
|
||||||
isLoading,
|
isLoading,
|
||||||
linkToDelete,
|
linkToDelete,
|
||||||
linkToEdit,
|
linkToEdit,
|
||||||
@@ -261,6 +274,14 @@ function Visualizer({ template }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await OrganizationsAPI.read({ page_size: 1, page: 1 });
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_DEFAULT_ORGANIZATION',
|
||||||
|
value: results[0]?.id,
|
||||||
|
});
|
||||||
|
|
||||||
const workflowNodes = await fetchWorkflowNodes(template.id);
|
const workflowNodes = await fetchWorkflowNodes(template.id);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'GENERATE_NODES_AND_LINKS',
|
type: 'GENERATE_NODES_AND_LINKS',
|
||||||
@@ -302,6 +323,9 @@ function Visualizer({ template }) {
|
|||||||
const deletedNodeIds = [];
|
const deletedNodeIds = [];
|
||||||
const associateCredentialRequests = [];
|
const associateCredentialRequests = [];
|
||||||
const disassociateCredentialRequests = [];
|
const disassociateCredentialRequests = [];
|
||||||
|
const associateLabelRequests = [];
|
||||||
|
const disassociateLabelRequests = [];
|
||||||
|
const instanceGroupRequests = [];
|
||||||
|
|
||||||
const generateLinkMapAndNewLinks = () => {
|
const generateLinkMapAndNewLinks = () => {
|
||||||
const linkMap = {};
|
const linkMap = {};
|
||||||
@@ -400,6 +424,8 @@ function Visualizer({ template }) {
|
|||||||
nodeRequests.push(
|
nodeRequests.push(
|
||||||
WorkflowJobTemplatesAPI.createNode(template.id, {
|
WorkflowJobTemplatesAPI.createNode(template.id, {
|
||||||
...node.promptValues,
|
...node.promptValues,
|
||||||
|
execution_environment:
|
||||||
|
node.promptValues?.execution_environment?.id || null,
|
||||||
inventory: node.promptValues?.inventory?.id || null,
|
inventory: node.promptValues?.inventory?.id || null,
|
||||||
unified_job_template: node.fullUnifiedJobTemplate.id,
|
unified_job_template: node.fullUnifiedJobTemplate.id,
|
||||||
all_parents_must_converge: node.all_parents_must_converge,
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
@@ -423,6 +449,29 @@ function Visualizer({ template }) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.promptValues?.labels?.length > 0) {
|
||||||
|
node.promptValues.labels.forEach((label) => {
|
||||||
|
associateLabelRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.associateLabel(
|
||||||
|
data.id,
|
||||||
|
label,
|
||||||
|
node.fullUnifiedJobTemplate.organization ||
|
||||||
|
defaultOrganization
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (node.promptValues?.instance_groups?.length > 0)
|
||||||
|
/* eslint-disable no-await-in-loop, no-restricted-syntax */
|
||||||
|
for (const group of node.promptValues.instance_groups) {
|
||||||
|
instanceGroupRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.associateInstanceGroup(
|
||||||
|
data.id,
|
||||||
|
group.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -487,6 +536,8 @@ function Visualizer({ template }) {
|
|||||||
nodeRequests.push(
|
nodeRequests.push(
|
||||||
WorkflowJobTemplateNodesAPI.replace(node.originalNodeObject.id, {
|
WorkflowJobTemplateNodesAPI.replace(node.originalNodeObject.id, {
|
||||||
...node.promptValues,
|
...node.promptValues,
|
||||||
|
execution_environment:
|
||||||
|
node.promptValues?.execution_environment?.id || null,
|
||||||
inventory: node.promptValues?.inventory?.id || null,
|
inventory: node.promptValues?.inventory?.id || null,
|
||||||
unified_job_template: node.fullUnifiedJobTemplate.id,
|
unified_job_template: node.fullUnifiedJobTemplate.id,
|
||||||
all_parents_must_converge: node.all_parents_must_converge,
|
all_parents_must_converge: node.all_parents_must_converge,
|
||||||
@@ -503,6 +554,12 @@ function Visualizer({ template }) {
|
|||||||
node.promptValues?.credentials
|
node.promptValues?.credentials
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { added: addedLabels, removed: removedLabels } =
|
||||||
|
getAddedAndRemoved(
|
||||||
|
node?.originalNodeLabels,
|
||||||
|
node.promptValues?.labels
|
||||||
|
);
|
||||||
|
|
||||||
if (addedCredentials.length > 0) {
|
if (addedCredentials.length > 0) {
|
||||||
addedCredentials.forEach((cred) => {
|
addedCredentials.forEach((cred) => {
|
||||||
associateCredentialRequests.push(
|
associateCredentialRequests.push(
|
||||||
@@ -523,6 +580,41 @@ function Visualizer({ template }) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addedLabels.length > 0) {
|
||||||
|
addedLabels.forEach((label) => {
|
||||||
|
associateLabelRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.associateLabel(
|
||||||
|
node.originalNodeObject.id,
|
||||||
|
label,
|
||||||
|
node.fullUnifiedJobTemplate.organization ||
|
||||||
|
defaultOrganization
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (removedLabels?.length > 0) {
|
||||||
|
removedLabels.forEach((label) =>
|
||||||
|
disassociateLabelRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.disassociateLabel(
|
||||||
|
node.originalNodeObject.id,
|
||||||
|
label,
|
||||||
|
node.fullUnifiedJobTemplate.organization ||
|
||||||
|
defaultOrganization
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.promptValues?.instance_groups) {
|
||||||
|
instanceGroupRequests.push(
|
||||||
|
WorkflowJobTemplateNodesAPI.orderInstanceGroups(
|
||||||
|
node.originalNodeObject.id,
|
||||||
|
node.promptValues?.instance_groups,
|
||||||
|
node?.originalNodeInstanceGroups || []
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -539,11 +631,18 @@ function Visualizer({ template }) {
|
|||||||
);
|
);
|
||||||
await Promise.all(associateNodes(newLinks, originalLinkMap));
|
await Promise.all(associateNodes(newLinks, originalLinkMap));
|
||||||
|
|
||||||
await Promise.all(disassociateCredentialRequests);
|
await Promise.all([
|
||||||
await Promise.all(associateCredentialRequests);
|
...disassociateCredentialRequests,
|
||||||
|
...disassociateLabelRequests,
|
||||||
|
]);
|
||||||
|
await Promise.all([
|
||||||
|
...associateCredentialRequests,
|
||||||
|
...associateLabelRequests,
|
||||||
|
...instanceGroupRequests,
|
||||||
|
]);
|
||||||
|
|
||||||
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
||||||
}, [links, nodes, history, template.id]),
|
}, [links, nodes, history, defaultOrganization, template.id]),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import {
|
import {
|
||||||
|
OrganizationsAPI,
|
||||||
WorkflowApprovalTemplatesAPI,
|
WorkflowApprovalTemplatesAPI,
|
||||||
WorkflowJobTemplateNodesAPI,
|
WorkflowJobTemplateNodesAPI,
|
||||||
WorkflowJobTemplatesAPI,
|
WorkflowJobTemplatesAPI,
|
||||||
@@ -104,6 +105,12 @@ const mockWorkflowNodes = [
|
|||||||
describe('Visualizer', () => {
|
describe('Visualizer', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
OrganizationsAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 1,
|
||||||
|
results: [{ id: 1, name: 'Default' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
|
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
count: mockWorkflowNodes.length,
|
count: mockWorkflowNodes.length,
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ function VisualizerNode({
|
|||||||
}) {
|
}) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
const [credentialsError, setCredentialsError] = useState(null);
|
|
||||||
const [detailError, setDetailError] = useState(null);
|
const [detailError, setDetailError] = useState(null);
|
||||||
const dispatch = useContext(WorkflowDispatchContext);
|
const dispatch = useContext(WorkflowDispatchContext);
|
||||||
const { addingLink, addLinkSourceNode, nodePositions, nodes } =
|
const { addingLink, addLinkSourceNode, nodePositions, nodes } =
|
||||||
@@ -72,7 +71,6 @@ function VisualizerNode({
|
|||||||
const isAddLinkSourceNode =
|
const isAddLinkSourceNode =
|
||||||
addLinkSourceNode && addLinkSourceNode.id === node.id;
|
addLinkSourceNode && addLinkSourceNode.id === node.id;
|
||||||
|
|
||||||
const handleCredentialsErrorClose = () => setCredentialsError(null);
|
|
||||||
const handleDetailErrorClose = () => setDetailError(null);
|
const handleDetailErrorClose = () => setDetailError(null);
|
||||||
|
|
||||||
const updateNode = async () => {
|
const updateNode = async () => {
|
||||||
@@ -98,18 +96,47 @@ function VisualizerNode({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
node?.originalNodeObject?.summary_fields?.unified_job_template
|
node?.originalNodeObject?.summary_fields?.unified_job_template
|
||||||
?.unified_job_type === 'job' &&
|
?.unified_job_type === 'job' ||
|
||||||
!node?.originalNodeCredentials
|
node?.originalNodeObject?.summary_fields?.unified_job_template
|
||||||
|
?.unified_job_type === 'workflow_job'
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const {
|
if (
|
||||||
data: { results },
|
node?.originalNodeObject?.summary_fields?.unified_job_template
|
||||||
} = await WorkflowJobTemplateNodesAPI.readCredentials(
|
?.unified_job_type === 'job' &&
|
||||||
node.originalNodeObject.id
|
!node?.originalNodeCredentials
|
||||||
);
|
) {
|
||||||
updatedNode.originalNodeCredentials = results;
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await WorkflowJobTemplateNodesAPI.readCredentials(
|
||||||
|
node.originalNodeObject.id
|
||||||
|
);
|
||||||
|
updatedNode.originalNodeCredentials = results;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node?.originalNodeObject?.summary_fields?.unified_job_template
|
||||||
|
?.unified_job_type === 'job' &&
|
||||||
|
!node.originalNodeLabels
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await WorkflowJobTemplateNodesAPI.readAllLabels(
|
||||||
|
node.originalNodeObject.id
|
||||||
|
);
|
||||||
|
updatedNode.originalNodeLabels = results;
|
||||||
|
updatedNode.originalNodeObject.labels = results;
|
||||||
|
}
|
||||||
|
if (!node.originalNodeInstanceGroups) {
|
||||||
|
const {
|
||||||
|
data: { results },
|
||||||
|
} = await WorkflowJobTemplateNodesAPI.readInstanceGroups(
|
||||||
|
node.originalNodeObject.id
|
||||||
|
);
|
||||||
|
updatedNode.originalNodeInstanceGroups = results;
|
||||||
|
updatedNode.originalNodeObject.instance_groups = results;
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setCredentialsError(err);
|
setDetailError(err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -350,17 +377,6 @@ function VisualizerNode({
|
|||||||
<ErrorDetail error={detailError} />
|
<ErrorDetail error={detailError} />
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
)}
|
)}
|
||||||
{credentialsError && (
|
|
||||||
<AlertModal
|
|
||||||
isOpen={credentialsError}
|
|
||||||
variant="error"
|
|
||||||
title={t`Error!`}
|
|
||||||
onClose={handleCredentialsErrorClose}
|
|
||||||
>
|
|
||||||
{t`Failed to retrieve node credentials.`}
|
|
||||||
<ErrorDetail error={credentialsError} />
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ const wfHelpTextStrings = () => ({
|
|||||||
playbook. Multiple patterns are allowed. Refer to Ansible
|
playbook. Multiple patterns are allowed. Refer to Ansible
|
||||||
documentation for more information and examples on patterns.`,
|
documentation for more information and examples on patterns.`,
|
||||||
sourceControlBranch: t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`,
|
sourceControlBranch: t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`,
|
||||||
labels: t`Optional labels that describe this job template,
|
labels: t`Optional labels that describe this workflow job template,
|
||||||
such as 'dev' or 'test'. Labels can be used to group and filter
|
such as 'dev' or 'test'. Labels can be used to group and filter
|
||||||
job templates and completed jobs.`,
|
workflow job templates and completed jobs.`,
|
||||||
variables: t`Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Controller documentation for example syntax.`,
|
variables: t`Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Controller documentation for example syntax.`,
|
||||||
enableWebhook: t`Enable Webhook for this workflow job template.`,
|
enableWebhook: t`Enable Webhook for this workflow job template.`,
|
||||||
enableConcurrentJobs: t`If enabled, simultaneous runs of this workflow job template will be allowed.`,
|
enableConcurrentJobs: t`If enabled, simultaneous runs of this workflow job template will be allowed.`,
|
||||||
@@ -18,6 +18,7 @@ const wfHelpTextStrings = () => ({
|
|||||||
webhookKey: t`Webhook services can use this as a shared secret.`,
|
webhookKey: t`Webhook services can use this as a shared secret.`,
|
||||||
webhookCredential: t`Optionally select the credential to use to send status updates back to the webhook service.`,
|
webhookCredential: t`Optionally select the credential to use to send status updates back to the webhook service.`,
|
||||||
webhookService: t`Select a webhook service.`,
|
webhookService: t`Select a webhook service.`,
|
||||||
|
jobTags: t`Tags are useful when you have a large playbook, and you want to run a specific part of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags.`,
|
||||||
skipTags: t`Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags.`,
|
skipTags: t`Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags.`,
|
||||||
enabledOptions: (
|
enabledOptions: (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -211,8 +211,6 @@ function WorkflowJobTemplateForm({
|
|||||||
promptId="template-ask-variables-on-launch"
|
promptId="template-ask-variables-on-launch"
|
||||||
tooltip={helpText.variables}
|
tooltip={helpText.variables}
|
||||||
/>
|
/>
|
||||||
</FormFullWidthLayout>
|
|
||||||
<FormColumnLayout>
|
|
||||||
<FieldWithPrompt
|
<FieldWithPrompt
|
||||||
fieldId="template-tags"
|
fieldId="template-tags"
|
||||||
label={t`Job Tags`}
|
label={t`Job Tags`}
|
||||||
@@ -237,7 +235,7 @@ function WorkflowJobTemplateForm({
|
|||||||
onChange={(value) => skipTagsHelpers.setValue(value)}
|
onChange={(value) => skipTagsHelpers.setValue(value)}
|
||||||
/>
|
/>
|
||||||
</FieldWithPrompt>
|
</FieldWithPrompt>
|
||||||
</FormColumnLayout>
|
</FormFullWidthLayout>
|
||||||
<FormGroup fieldId="options" label={t`Options`}>
|
<FormGroup fieldId="options" label={t`Options`}>
|
||||||
<FormCheckboxLayout isInline>
|
<FormCheckboxLayout isInline>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
Reference in New Issue
Block a user