Change ask_job_slicing_on_launch to ask_job_slice_count_on_launch to match api

Adds support for prompting labels on launch in the UI

Fix execution environment prompting in UI

Round out support for prompting all the things on JT launch

Adds timeout to job details

Adds fetchAllLabels to JT/WFJT data models

Moves labels methods out to a mixin so they can be shared across JTs/WFJTs/Schedules

Fixes bug where ee was not being sent on launch

Adds the ability to prompt for ee's, ig's, labels, timeout and job slicing to schedules

Fixes bug where saving schedule form without opening the prompt would throw errors

Adds support for IGs and labels to workflow node prompting

Adds support for label prompting to node modal

Fix job template form tests
This commit is contained in:
mabashian 2022-08-17 10:57:30 -04:00 committed by Alan Rominger
parent 33c0fb79d6
commit 4e665ca77f
No known key found for this signature in database
GPG Key ID: C2D7EAAA12B63559
27 changed files with 556 additions and 69 deletions

View File

@ -0,0 +1,35 @@
const LabelsMixin = (parent) =>
class extends parent {
readLabels(id, params) {
return this.http.get(`${this.baseUrl}${id}/labels/`, {
params,
});
}
readAllLabels(id) {
const fetchLabels = async (pageNo = 1, labels = []) => {
try {
const { data } = await this.http.get(`${this.baseUrl}${id}/labels/`, {
params: {
page: pageNo,
page_size: 200,
},
});
if (data?.next) {
return fetchLabels(pageNo + 1, labels.concat(data.results));
}
return Promise.resolve({
data: {
results: labels.concat(data.results),
},
});
} catch (error) {
return Promise.reject(error);
}
};
return fetchLabels();
}
};
export default LabelsMixin;

View File

@ -1,10 +1,11 @@
import Base from '../Base';
import NotificationsMixin from '../mixins/Notifications.mixin';
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
import LabelsMixin from '../mixins/Labels.mixin';
import SchedulesMixin from '../mixins/Schedules.mixin';
class JobTemplates extends SchedulesMixin(
InstanceGroupsMixin(NotificationsMixin(Base))
InstanceGroupsMixin(NotificationsMixin(LabelsMixin(Base)))
) {
constructor(http) {
super(http);

View File

@ -1,6 +1,7 @@
import Base from '../Base';
import LabelsMixin from '../mixins/Labels.mixin';
class Schedules extends Base {
class Schedules extends LabelsMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = 'api/v2/schedules/';

View File

@ -1,8 +1,11 @@
import Base from '../Base';
import SchedulesMixin from '../mixins/Schedules.mixin';
import NotificationsMixin from '../mixins/Notifications.mixin';
import LabelsMixin from '../mixins/Labels.mixin';
class WorkflowJobTemplates extends SchedulesMixin(NotificationsMixin(Base)) {
class WorkflowJobTemplates extends SchedulesMixin(
NotificationsMixin(LabelsMixin(Base))
) {
constructor(http) {
super(http);
this.baseUrl = 'api/v2/workflow_job_templates/';

View File

@ -1,9 +1,7 @@
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { number, shape } from 'prop-types';
import { t } from '@lingui/macro';
import {
AdHocCommandsAPI,
InventorySourcesAPI,
@ -27,7 +25,7 @@ function canLaunchWithoutPrompt(launchData) {
!launchData.ask_execution_environment_on_launch &&
!launchData.ask_labels_on_launch &&
!launchData.ask_forks_on_launch &&
!launchData.ask_job_slicing_on_launch &&
!launchData.ask_job_slice_count_on_launch &&
!launchData.ask_timeout_on_launch &&
!launchData.ask_instance_groups_on_launch &&
!launchData.survey_enabled &&
@ -43,6 +41,7 @@ function LaunchButton({ resource, children }) {
const [showLaunchPrompt, setShowLaunchPrompt] = useState(false);
const [launchConfig, setLaunchConfig] = useState(null);
const [surveyConfig, setSurveyConfig] = useState(null);
const [labels, setLabels] = useState([]);
const [isLaunching, setIsLaunching] = useState(false);
const [error, setError] = useState(null);
@ -56,6 +55,11 @@ function LaunchButton({ resource, children }) {
resource.type === 'workflow_job_template'
? WorkflowJobTemplatesAPI.readSurvey(resource.id)
: JobTemplatesAPI.readSurvey(resource.id);
const readLabels =
resource.type === 'workflow_job_template'
? WorkflowJobTemplatesAPI.readAllLabels(resource.id)
: JobTemplatesAPI.readAllLabels(resource.id);
try {
const { data: launch } = await readLaunch;
setLaunchConfig(launch);
@ -66,6 +70,14 @@ function LaunchButton({ resource, children }) {
setSurveyConfig(data);
}
if (launch.ask_labels_on_launch) {
const {
data: { results },
} = await readLabels;
setLabels(results);
}
if (canLaunchWithoutPrompt(launch)) {
await launchWithParams({});
} else {
@ -177,6 +189,7 @@ function LaunchButton({ resource, children }) {
launchConfig={launchConfig}
surveyConfig={surveyConfig}
resource={resource}
labels={labels}
onLaunch={launchWithParams}
onCancel={() => setShowLaunchPrompt(false)}
/>

View File

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { ExpandableSection, Wizard } from '@patternfly/react-core';
import { t } from '@lingui/macro';
import { Formik, useFormikContext } from 'formik';
import { LabelsAPI, OrganizationsAPI } from 'api';
import { useDismissableError } from 'hooks/useRequest';
import mergeExtraVars from 'util/prompt/mergeExtraVars';
import getSurveyValues from 'util/prompt/getSurveyValues';
@ -15,6 +16,7 @@ function PromptModalForm({
onCancel,
onSubmit,
resource,
labels,
surveyConfig,
}) {
const { setFieldTouched, values } = useFormikContext();
@ -27,9 +29,9 @@ function PromptModalForm({
visitStep,
visitAllSteps,
contentError,
} = useLaunchSteps(launchConfig, surveyConfig, resource);
} = useLaunchSteps(launchConfig, surveyConfig, resource, labels);
const handleSubmit = () => {
const handleSubmit = async () => {
const postValues = {};
const setValue = (key, value) => {
if (typeof value !== 'undefined' && value !== null) {
@ -53,6 +55,61 @@ function PromptModalForm({
setValue('extra_vars', mergeExtraVars(extraVars, surveyValues));
setValue('scm_branch', values.scm_branch);
setValue('verbosity', values.verbosity);
setValue('timeout', values.timeout);
setValue('forks', values.forks);
setValue('job_slice_count', values.job_slice_count);
setValue('execution_environment', values.execution_environment?.id);
if (launchConfig.ask_instance_groups_on_launch) {
const instanceGroupIds = [];
values.instance_groups.forEach((instance_group) => {
instanceGroupIds.push(instance_group.id);
});
setValue('instance_groups', instanceGroupIds);
}
if (launchConfig.ask_labels_on_launch) {
const labelIds = [];
const newLabels = [];
const labelRequests = [];
let organizationId = resource.organization;
values.labels.forEach((label) => {
if (typeof label.id !== 'number') {
newLabels.push(label);
} else {
labelIds.push(label.id);
}
});
if (newLabels.length > 0) {
if (!organizationId) {
// eslint-disable-next-line no-useless-catch
try {
const {
data: { results },
} = await OrganizationsAPI.read();
organizationId = results[0].id;
} catch (err) {
throw err;
}
}
}
newLabels.forEach((label) => {
labelRequests.push(
LabelsAPI.create({
name: label.name,
organization: organizationId,
}).then(({ data }) => {
labelIds.push(data.id);
})
);
});
await Promise.all(labelRequests);
setValue('labels', labelIds);
}
onSubmit(postValues);
};
@ -137,6 +194,7 @@ function LaunchPrompt({
onCancel,
onLaunch,
resource = {},
labels = [],
surveyConfig,
resourceDefaultCredentials = [],
}) {
@ -148,6 +206,7 @@ function LaunchPrompt({
launchConfig={launchConfig}
surveyConfig={surveyConfig}
resource={resource}
labels={labels}
resourceDefaultCredentials={resourceDefaultCredentials}
/>
</Formik>

View File

@ -108,7 +108,7 @@ function ExecutionEnvironmentStep() {
qsConfig={QS_CONFIG}
readOnly
selectItem={helpers.setValue}
deselectItem={() => field.onChange(null)}
deselectItem={() => helpers.setValue(null)}
/>
);
}

View File

@ -0,0 +1,106 @@
import React, { useCallback, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { t } from '@lingui/macro';
import { useField } from 'formik';
import { InstanceGroupsAPI } from 'api';
import { getSearchableKeys } from 'components/PaginatedTable';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import useSelected from 'hooks/useSelected';
import OptionsList from '../../OptionsList';
import ContentLoading from '../../ContentLoading';
import ContentError from '../../ContentError';
const QS_CONFIG = getQSConfig('instance-groups', {
page: 1,
page_size: 5,
order_by: 'name',
});
function InstanceGroupsStep() {
const [field, , helpers] = useField('instance_groups');
const { selected, handleSelect, setSelected } = useSelected([]);
const history = useHistory();
const {
result: { instance_groups, count, relatedSearchableKeys, searchableKeys },
request: fetchInstanceGroups,
error,
isLoading,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search);
const [{ data }, actionsResponse] = await Promise.all([
InstanceGroupsAPI.read(params),
InstanceGroupsAPI.readOptions(),
]);
return {
instance_groups: data.results,
count: data.count,
relatedSearchableKeys: (
actionsResponse?.data?.related_search_fields || []
).map((val) => val.slice(0, -8)),
searchableKeys: getSearchableKeys(actionsResponse.data.actions?.GET),
};
}, [history.location]),
{
instance_groups: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
}
);
useEffect(() => {
fetchInstanceGroups();
}, [fetchInstanceGroups]);
useEffect(() => {
helpers.setValue(selected);
}, [selected]); // eslint-disable-line react-hooks/exhaustive-deps
if (isLoading) {
return <ContentLoading />;
}
if (error) {
return <ContentError error={error} />;
}
return (
<OptionsList
value={field.value}
options={instance_groups}
optionCount={count}
searchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
{
name: t`Credential Name`,
key: 'credential__name__icontains',
},
]}
sortColumns={[
{
name: t`Name`,
key: 'name',
},
]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
multiple
header={t`Instance Groups`}
name="instanceGroups"
qsConfig={QS_CONFIG}
selectItem={handleSelect}
deselectItem={handleSelect}
sortSelectedItems={(selectedItems) => setSelected(selectedItems)}
isSelectedDraggable
/>
);
}
export default InstanceGroupsStep;

View File

@ -1,9 +1,9 @@
import React from 'react';
import { t } from '@lingui/macro';
import { useField } from 'formik';
import { Form, FormGroup, Switch } from '@patternfly/react-core';
import styled from 'styled-components';
import LabelSelect from '../../LabelSelect';
import FormField from '../../FormField';
import { TagMultiSelect } from '../../MultiSelect';
import AnsibleSelect from '../../AnsibleSelect';
@ -37,6 +37,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
tooltip={t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch`}
/>
)}
{launchConfig.ask_labels_on_launch && <LabelsField />}
{launchConfig.ask_forks_on_launch && (
<FormField
id="prompt-forks"
@ -58,7 +59,7 @@ function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
/>
)}
{launchConfig.ask_verbosity_on_launch && <VerbosityField />}
{launchConfig.ask_job_slicing_on_launch && (
{launchConfig.ask_job_slice_count_on_launch && (
<FormField
id="prompt-job-slicing"
name="job_slice_count"
@ -213,4 +214,27 @@ function TagField({ id, name, label, tooltip }) {
);
}
function LabelsField() {
const [field, , helpers] = useField('labels');
return (
<FormGroup
fieldId="propmt-labels"
label={t`Labels`}
labelIcon={
<Popover
content={t`Optional labels that describe this job, such as 'dev' or 'test'. Labels can be used to group and filter completed jobs.`}
/>
}
>
<LabelSelect
value={field.value}
onChange={(labels) => helpers.setValue(labels)}
createText={t`Create`}
onError={() => alert('error')}
/>
</FormGroup>
);
}
export default OtherPromptsStep;

View File

@ -19,7 +19,7 @@ export default function useExecutionEnvironmentStep(launchConfig, resource) {
};
}
function getStep(launchConfig) {
if (!launchConfig.ask_inventory_on_launch) {
if (!launchConfig.ask_execution_environment_on_launch) {
return null;
}
return {
@ -40,6 +40,7 @@ function getInitialValues(launchConfig, resource) {
}
return {
inventory: resource?.summary_fields?.execution_environment || null,
execution_environment:
resource?.summary_fields?.execution_environment || null,
};
}

View File

@ -0,0 +1,45 @@
import React from 'react';
import { t } from '@lingui/macro';
import InstanceGroupsStep from './InstanceGroupsStep';
import StepName from './StepName';
const STEP_ID = 'instanceGroups';
export default function useInstanceGroupsStep(
launchConfig,
resource,
instanceGroups
) {
return {
step: getStep(launchConfig, resource),
initialValues: getInitialValues(launchConfig, instanceGroups),
isReady: true,
contentError: null,
hasError: false,
setTouched: (setFieldTouched) => {
setFieldTouched('instance_groups', true, false);
},
validate: () => {},
};
}
function getStep(launchConfig) {
if (!launchConfig.ask_instance_groups_on_launch) {
return null;
}
return {
id: STEP_ID,
name: <StepName id="instance-groups-step">{t`Instance Groups`}</StepName>,
component: <InstanceGroupsStep />,
enableNext: true,
};
}
function getInitialValues(launchConfig, instanceGroups) {
if (!launchConfig.ask_instance_groups_on_launch) {
return {};
}
return {
instance_groups: instanceGroups || [],
};
}

View File

@ -31,9 +31,10 @@ const FIELD_NAMES = [
'timeout',
'job_slice_count',
'forks',
'labels',
];
export default function useOtherPromptsStep(launchConfig, resource) {
export default function useOtherPromptsStep(launchConfig, resource, labels) {
const [variablesField] = useField('extra_vars');
const [variablesMode, setVariablesMode] = useState(null);
const [isTouched, setIsTouched] = useState(false);
@ -63,7 +64,7 @@ export default function useOtherPromptsStep(launchConfig, resource) {
return {
step: getStep(launchConfig, hasError, variablesMode, handleModeChange),
initialValues: getInitialValues(launchConfig, resource),
initialValues: getInitialValues(launchConfig, resource, labels),
isReady: true,
contentError: null,
hasError,
@ -112,12 +113,12 @@ function shouldShowPrompt(launchConfig) {
launchConfig.ask_diff_mode_on_launch ||
launchConfig.ask_labels_on_launch ||
launchConfig.ask_forks_on_launch ||
launchConfig.ask_job_slicing_on_launch ||
launchConfig.ask_job_slice_count_on_launch ||
launchConfig.ask_timeout_on_launch
);
}
function getInitialValues(launchConfig, resource) {
function getInitialValues(launchConfig, resource, labels) {
const initialValues = {};
if (!launchConfig) {
@ -151,11 +152,14 @@ function getInitialValues(launchConfig, resource) {
if (launchConfig.ask_forks_on_launch) {
initialValues.forks = resource?.forks || 0;
}
if (launchConfig.ask_job_slicing_on_launch) {
if (launchConfig.ask_job_slice_count_on_launch) {
initialValues.job_slice_count = resource?.job_slice_count || 1;
}
if (launchConfig.ask_timeout_on_launch) {
initialValues.timeout = resource?.timeout || 0;
}
if (launchConfig.ask_labels_on_launch) {
initialValues.labels = labels || [];
}
return initialValues;
}

View File

@ -7,6 +7,7 @@ import useExecutionEnvironmentStep from './steps/useExecutionEnvironmentStep';
import useOtherPromptsStep from './steps/useOtherPromptsStep';
import useSurveyStep from './steps/useSurveyStep';
import usePreviewStep from './steps/usePreviewStep';
import useInstanceGroupsStep from './steps/useInstanceGroupsStep';
function showCredentialPasswordsStep(launchConfig, credentials = []) {
if (
@ -40,7 +41,12 @@ function showCredentialPasswordsStep(launchConfig, credentials = []) {
return credentialPasswordStepRequired;
}
export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
export default function useLaunchSteps(
launchConfig,
surveyConfig,
resource,
labels
) {
const [visited, setVisited] = useState({});
const [isReady, setIsReady] = useState(false);
const { touched, values: formikValues } = useFormikContext();
@ -58,7 +64,8 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
visited
),
useExecutionEnvironmentStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, resource),
useInstanceGroupsStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, resource, labels),
useSurveyStep(launchConfig, surveyConfig, resource, visited),
];
const { resetForm } = useFormikContext();
@ -146,6 +153,7 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
credentials: true,
credentialPasswords: true,
executionEnvironment: true,
instanceGroups: true,
other: true,
survey: true,
preview: true,

View File

@ -75,7 +75,7 @@ function hasPromptData(launchData) {
launchData.ask_execution_environment_on_launch ||
launchData.ask_labels_on_launch ||
launchData.ask_forks_on_launch ||
launchData.ask_job_slicing_on_launch ||
launchData.ask_job_slice_count_on_launch ||
launchData.ask_timeout_on_launch ||
launchData.ask_instance_groups_on_launch
);
@ -341,7 +341,7 @@ function PromptDetail({
{launchConfig.ask_forks_on_launch && (
<Detail label={t`Forks`} value={overrides.forks} />
)}
{launchConfig.ask_job_slicing_on_launch && (
{launchConfig.ask_job_slice_count_on_launch && (
<Detail
label={t`Job Slicing`}
value={overrides.job_slice_count}

View File

@ -1,12 +1,10 @@
import React, { useState } from 'react';
import { func, shape } from 'prop-types';
import { useHistory, useLocation } from 'react-router-dom';
import { Card } from '@patternfly/react-core';
import yaml from 'js-yaml';
import { parseVariableField } from 'util/yaml';
import { SchedulesAPI } from 'api';
import { LabelsAPI, OrganizationsAPI, SchedulesAPI } from 'api';
import mergeExtraVars from 'util/prompt/mergeExtraVars';
import getSurveyValues from 'util/prompt/getSurveyValues';
import { getAddedAndRemoved } from 'util/lists';
@ -34,6 +32,8 @@ function ScheduleAdd({
surveyConfiguration
) => {
const {
execution_environment,
instance_groups,
inventory,
frequency,
frequencyOptions,
@ -72,7 +72,60 @@ function ScheduleAdd({
submitValues.inventory = inventory.id;
}
if (execution_environment) {
submitValues.execution_environment = execution_environment.id;
}
submitValues.instance_groups = instance_groups
? instance_groups.map((s) => s.id)
: [];
try {
if (launchConfiguration?.ask_labels_on_launch) {
const labelIds = [];
const newLabels = [];
const labelRequests = [];
let organizationId = resource.organization;
if (values.labels) {
values.labels.forEach((label) => {
if (typeof label.id !== 'number') {
newLabels.push(label);
} else {
labelIds.push(label.id);
}
});
}
if (newLabels.length > 0) {
if (!organizationId) {
// eslint-disable-next-line no-useless-catch
try {
const {
data: { results },
} = await OrganizationsAPI.read();
organizationId = results[0].id;
} catch (err) {
throw err;
}
}
}
newLabels.forEach((label) => {
labelRequests.push(
LabelsAPI.create({
name: label.name,
organization: organizationId,
}).then(({ data }) => {
labelIds.push(data.id);
})
);
});
await Promise.all(labelRequests);
submitValues.labels = labelIds;
}
const ruleSet = buildRuleSet(values);
const requestData = {
...submitValues,

View File

@ -193,7 +193,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
ask_execution_environment_on_launch,
ask_labels_on_launch,
ask_forks_on_launch,
ask_job_slicing_on_launch,
ask_job_slice_count_on_launch,
ask_timeout_on_launch,
survey_enabled,
} = launchData || {};
@ -253,7 +253,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
ask_execution_environment_on_launch && execution_environment;
const showLabelsDetail = ask_labels_on_launch && labels && labels.length > 0;
const showForksDetail = ask_forks_on_launch;
const showJobSlicingDetail = ask_job_slicing_on_launch;
const showJobSlicingDetail = ask_job_slice_count_on_launch;
const showTimeoutDetail = ask_timeout_on_launch;
const showPromptedFields =
@ -468,7 +468,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
dataCy="schedule-show-changes"
/>
)}
{ask_job_slicing_on_launch && (
{ask_job_slice_count_on_launch && (
<Detail label={t`Job Slicing`} value={job_slice_count} />
)}
{showCredentialsDetail && (

View File

@ -1,12 +1,10 @@
import React, { useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { shape } from 'prop-types';
import { Card } from '@patternfly/react-core';
import yaml from 'js-yaml';
import { SchedulesAPI } from 'api';
import { LabelsAPI, OrganizationsAPI, SchedulesAPI } from 'api';
import { getAddedAndRemoved } from 'util/lists';
import { parseVariableField } from 'util/yaml';
import mergeExtraVars from 'util/prompt/mergeExtraVars';
import getSurveyValues from 'util/prompt/getSurveyValues';
@ -35,6 +33,8 @@ function ScheduleEdit({
scheduleCredentials = []
) => {
const {
execution_environment,
instance_groups,
inventory,
credentials = [],
frequency,
@ -82,7 +82,60 @@ function ScheduleEdit({
submitValues.inventory = inventory.id;
}
if (execution_environment) {
submitValues.execution_environment = execution_environment.id;
}
submitValues.instance_groups = instance_groups
? instance_groups.map((s) => s.id)
: [];
try {
if (launchConfiguration?.ask_labels_on_launch) {
const labelIds = [];
const newLabels = [];
const labelRequests = [];
let organizationId = resource.organization;
if (values.labels) {
values.labels.forEach((label) => {
if (typeof label.id !== 'number') {
newLabels.push(label);
} else {
labelIds.push(label.id);
}
});
}
if (newLabels.length > 0) {
if (!organizationId) {
// eslint-disable-next-line no-useless-catch
try {
const {
data: { results },
} = await OrganizationsAPI.read();
organizationId = results[0].id;
} catch (err) {
throw err;
}
}
}
newLabels.forEach((label) => {
labelRequests.push(
LabelsAPI.create({
name: label.name,
organization: organizationId,
}).then(({ data }) => {
labelIds.push(data.id);
})
);
});
await Promise.all(labelRequests);
submitValues.labels = labelIds;
}
const ruleSet = buildRuleSet(values);
const requestData = {
...submitValues,

View File

@ -1,13 +1,12 @@
import React, { useEffect, useCallback, useState } from 'react';
import { shape, func } from 'prop-types';
import { DateTime } from 'luxon';
import { t } from '@lingui/macro';
import { Formik } from 'formik';
import { RRule } from 'rrule';
import { Button, Form, ActionGroup } from '@patternfly/react-core';
import { Config } from 'contexts/Config';
import { SchedulesAPI } from 'api';
import { JobTemplatesAPI, SchedulesAPI, WorkflowJobTemplatesAPI } from 'api';
import { dateToInputDateTime } from 'util/dates';
import useRequest from 'hooks/useRequest';
import { parseVariableField } from 'util/yaml';
@ -31,7 +30,7 @@ const NUM_DAYS_PER_FREQUENCY = {
function ScheduleForm({
hasDaysToKeepField,
handleCancel,
handleSubmit,
handleSubmit: submitSchedule,
schedule,
submitError,
resource,
@ -55,17 +54,48 @@ function ScheduleForm({
request: loadScheduleData,
error: contentError,
isLoading: contentLoading,
result: { zoneOptions, zoneLinks, credentials },
result: { zoneOptions, zoneLinks, credentials, labels },
} = useRequest(
useCallback(async () => {
const { data } = await SchedulesAPI.readZoneInfo();
let creds;
let allLabels;
if (schedule.id) {
const {
data: { results },
} = await SchedulesAPI.readCredentials(schedule.id);
creds = results;
if (
resource.type === 'job_template' &&
launchConfig.ask_credential_on_launch
) {
const {
data: { results },
} = await SchedulesAPI.readCredentials(schedule.id);
creds = results;
}
if (launchConfig.ask_labels_on_launch) {
const {
data: { results },
} = await SchedulesAPI.readAllLabels(schedule.id);
allLabels = results;
}
} else {
if (
resource.type === 'job_template' &&
launchConfig.ask_labels_on_launch
) {
const {
data: { results },
} = await JobTemplatesAPI.readAllLabels(resource.id);
allLabels = results;
}
if (
resource.type === 'workflow_job_template' &&
launchConfig.ask_labels_on_launch
) {
const {
data: { results },
} = await WorkflowJobTemplatesAPI.readAllLabels(resource.id);
allLabels = results;
}
}
const zones = (data.zones || []).map((zone) => ({
@ -78,13 +108,21 @@ function ScheduleForm({
zoneOptions: zones,
zoneLinks: data.links,
credentials: creds || [],
labels: allLabels || [],
};
}, [schedule]),
}, [
schedule,
resource.id,
resource.type,
launchConfig.ask_labels_on_launch,
launchConfig.ask_credential_on_launch,
]),
{
zonesOptions: [],
zoneLinks: {},
credentials: [],
isLoading: true,
labels: [],
}
);
@ -228,7 +266,7 @@ function ScheduleForm({
launchConfig.ask_execution_environment_on_launch ||
launchConfig.ask_labels_on_launch ||
launchConfig.ask_forks_on_launch ||
launchConfig.ask_job_slicing_on_launch ||
launchConfig.ask_job_slice_count_on_launch ||
launchConfig.ask_timeout_on_launch ||
launchConfig.ask_instance_groups_on_launch ||
launchConfig.survey_enabled ||
@ -307,19 +345,6 @@ function ScheduleForm({
startTime: time,
timezone: schedule.timezone || now.zoneName,
};
const submitSchedule = (
values,
launchConfiguration,
surveyConfiguration,
scheduleCredentials
) => {
handleSubmit(
values,
launchConfiguration,
surveyConfiguration,
scheduleCredentials
);
};
if (hasDaysToKeepField) {
let initialDaysToKeep = 30;
@ -469,6 +494,7 @@ function ScheduleForm({
setIsSaveDisabled(false);
}}
resourceDefaultCredentials={resourceDefaultCredentials}
labels={labels}
/>
)}
<FormSubmitError error={submitError} />

View File

@ -17,6 +17,7 @@ function SchedulePromptableFields({
credentials,
resource,
resourceDefaultCredentials,
labels,
}) {
const { setFieldTouched, values, initialValues, resetForm } =
useFormikContext();
@ -33,7 +34,8 @@ function SchedulePromptableFields({
schedule,
resource,
credentials,
resourceDefaultCredentials
resourceDefaultCredentials,
labels
);
const [showDescription, setShowDescription] = useState(false);
const { error, dismissError } = useDismissableError(contentError);

View File

@ -3,6 +3,8 @@ import { useFormikContext } from 'formik';
import { t } from '@lingui/macro';
import useInventoryStep from '../../LaunchPrompt/steps/useInventoryStep';
import useCredentialsStep from '../../LaunchPrompt/steps/useCredentialsStep';
import useExecutionEnvironmentStep from '../../LaunchPrompt/steps/useExecutionEnvironmentStep';
import useInstanceGroupsStep from '../../LaunchPrompt/steps/useInstanceGroupsStep';
import useOtherPromptsStep from '../../LaunchPrompt/steps/useOtherPromptsStep';
import useSurveyStep from '../../LaunchPrompt/steps/useSurveyStep';
import usePreviewStep from '../../LaunchPrompt/steps/usePreviewStep';
@ -12,9 +14,9 @@ export default function useSchedulePromptSteps(
launchConfig,
schedule,
resource,
scheduleCredentials,
resourceDefaultCredentials
resourceDefaultCredentials,
labels
) {
const sourceOfValues =
(Object.keys(schedule).length > 0 && schedule) || resource;
@ -28,7 +30,9 @@ export default function useSchedulePromptSteps(
sourceOfValues,
resourceDefaultCredentials
),
useOtherPromptsStep(launchConfig, sourceOfValues),
useExecutionEnvironmentStep(launchConfig, resource),
useInstanceGroupsStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, sourceOfValues, labels),
useSurveyStep(launchConfig, surveyConfig, sourceOfValues, visited),
];
@ -37,7 +41,6 @@ export default function useSchedulePromptSteps(
steps.push(
usePreviewStep(
launchConfig,
resource,
surveyConfig,
hasErrors,
@ -130,6 +133,8 @@ export default function useSchedulePromptSteps(
setVisited({
inventory: true,
credentials: true,
executionEnvironment: true,
instanceGroups: true,
other: true,
survey: true,
preview: true,

View File

@ -391,6 +391,16 @@ function JobDetail({ job, inventorySourceLabels }) {
helpText={jobHelpText.forks}
/>
)}
{typeof job.timeout === 'number' && (
<Detail
dataCy="timeout"
label={t`Timeout`}
value={
job.timeout ? t`${job.timeout} seconds` : t`No timeout specified`
}
helpText={jobHelpText.timeout}
/>
)}
{credential && (
<Detail
dataCy="job-machine-credential"

View File

@ -30,6 +30,12 @@ const jobTemplateData = {
ask_tags_on_launch: false,
ask_variables_on_launch: false,
ask_verbosity_on_launch: false,
ask_execution_environment_on_launch: false,
ask_forks_on_launch: false,
ask_instance_groups_on_launch: false,
ask_job_slice_count_on_launch: false,
ask_labels_on_launch: false,
ask_timeout_on_launch: false,
become_enabled: false,
description: '',
diff_mode: false,

View File

@ -43,6 +43,12 @@ const mockJobTemplate = {
ask_verbosity_on_launch: false,
ask_inventory_on_launch: false,
ask_credential_on_launch: false,
ask_execution_environment_on_launch: false,
ask_forks_on_launch: false,
ask_instance_groups_on_launch: false,
ask_job_slice_count_on_launch: false,
ask_labels_on_launch: false,
ask_timeout_on_launch: false,
become_enabled: false,
description: 'Bar',
diff_mode: false,

View File

@ -38,6 +38,7 @@ function NodeModalForm({
surveyConfig,
isLaunchLoading,
resourceDefaultCredentials,
labels,
}) {
const history = useHistory();
const dispatch = useContext(WorkflowDispatchContext);
@ -66,7 +67,8 @@ function NodeModalForm({
surveyConfig,
values.nodeResource,
askLinkType,
resourceDefaultCredentials
resourceDefaultCredentials,
labels
);
const handleSaveNode = () => {
@ -241,7 +243,7 @@ const NodeModalInner = ({ title, ...rest }) => {
const {
request: readLaunchConfigs,
error: launchConfigError,
result: { launchConfig, surveyConfig, resourceDefaultCredentials },
result: { launchConfig, surveyConfig, resourceDefaultCredentials, labels },
isLoading,
} = useRequest(
useCallback(async () => {
@ -260,9 +262,15 @@ const NodeModalInner = ({ title, ...rest }) => {
launchConfig: {},
surveyConfig: {},
resourceDefaultCredentials: [],
labels: [],
};
}
const readLabels =
values.nodeType === 'workflow_job_template'
? WorkflowJobTemplatesAPI.readAllLabels(values.nodeResource.id)
: JobTemplatesAPI.readAllLabels(values.nodeResource.id);
const { data: launch } = await readLaunch(
values.nodeType,
values?.nodeResource?.id
@ -291,10 +299,21 @@ const NodeModalInner = ({ title, ...rest }) => {
defaultCredentials = results;
}
let defaultLabels = [];
if (launch.ask_labels_on_launch) {
const {
data: { results },
} = await readLabels;
defaultLabels = results;
}
return {
launchConfig: launch,
surveyConfig: survey,
resourceDefaultCredentials: defaultCredentials,
labels: defaultLabels,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -347,11 +366,12 @@ const NodeModalInner = ({ title, ...rest }) => {
resourceDefaultCredentials={resourceDefaultCredentials}
isLaunchLoading={isLoading}
title={wizardTitle}
labels={labels}
/>
);
};
const NodeModal = ({ onSave, askLinkType, title }) => {
const NodeModal = ({ onSave, askLinkType, title, labels }) => {
const { nodeToEdit } = useContext(WorkflowStateContext);
const onSaveForm = (values, config) => {
onSave(values, config);
@ -378,6 +398,7 @@ const NodeModal = ({ onSave, askLinkType, title }) => {
onSave={onSaveForm}
title={title}
askLinkType={askLinkType}
labels={labels}
/>
</Form>
)}

View File

@ -165,7 +165,7 @@ function NodeViewModal({ readOnly }) {
if (launchConfig.ask_forks_on_launch) {
overrides.forks = originalNodeObject.forks;
}
if (launchConfig.ask_job_slicing_on_launch) {
if (launchConfig.ask_job_slice_count_on_launch) {
overrides.job_slice_count = originalNodeObject.job_slice_count;
}
if (launchConfig.ask_timeout_on_launch) {

View File

@ -7,6 +7,7 @@ import useExecutionEnvironmentStep from 'components/LaunchPrompt/steps/useExecut
import useOtherPromptsStep from 'components/LaunchPrompt/steps/useOtherPromptsStep';
import useSurveyStep from 'components/LaunchPrompt/steps/useSurveyStep';
import usePreviewStep from 'components/LaunchPrompt/steps/usePreviewStep';
import useInstanceGroupsStep from 'components/LaunchPrompt/steps/useInstanceGroupsStep';
import { WorkflowStateContext } from 'contexts/Workflow';
import { jsonToYaml } from 'util/yaml';
import { stringIsUUID } from 'util/strings';
@ -30,7 +31,7 @@ function showPreviewStep(nodeType, launchConfig) {
launchConfig.ask_execution_environment_on_launch ||
launchConfig.ask_labels_on_launch ||
launchConfig.ask_forks_on_launch ||
launchConfig.ask_job_slicing_on_launch ||
launchConfig.ask_job_slice_count_on_launch ||
launchConfig.ask_timeout_on_launch ||
launchConfig.ask_instance_groups_on_launch ||
launchConfig.survey_enabled ||
@ -221,7 +222,7 @@ const getNodeToEditDefaultValues = (
if (launchConfig.ask_forks_on_launch) {
initialValues.forks = sourceOfValues?.forks || 0;
}
if (launchConfig.ask_job_slicing_on_launch) {
if (launchConfig.ask_job_slice_count_on_launch) {
initialValues.job_slice_count = sourceOfValues?.job_slice_count || 1;
}
if (launchConfig.ask_timeout_on_launch) {
@ -272,7 +273,8 @@ export default function useWorkflowNodeSteps(
surveyConfig,
resource,
askLinkType,
resourceDefaultCredentials
resourceDefaultCredentials,
labels
) {
const { nodeToEdit } = useContext(WorkflowStateContext);
const {
@ -289,7 +291,8 @@ export default function useWorkflowNodeSteps(
useInventoryStep(launchConfig, resource, visited),
useCredentialsStep(launchConfig, resource, resourceDefaultCredentials),
useExecutionEnvironmentStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, resource),
useInstanceGroupsStep(launchConfig, resource),
useOtherPromptsStep(launchConfig, resource, labels),
useSurveyStep(launchConfig, surveyConfig, resource, visited),
];
@ -380,6 +383,7 @@ export default function useWorkflowNodeSteps(
inventory: true,
credentials: true,
executionEnvironment: true,
instanceGroups: true,
other: true,
survey: true,
preview: true,

View File

@ -452,7 +452,7 @@ function JobTemplateForm({
fieldId="template-job-slicing"
label={t`Job Slicing`}
promptId="template-ask-job-slicing-on-launch"
promptName="ask_job_slicing_on_launch"
promptName="ask_job_slice_count_on_launch"
tooltip={helpText.jobSlicing}
>
<TextInput
@ -703,7 +703,8 @@ const FormikApp = withFormik({
ask_instance_groups_on_launch:
template.ask_instance_groups_on_launch || false,
ask_inventory_on_launch: template.ask_inventory_on_launch || false,
ask_job_slicing_on_launch: template.ask_job_slicing_on_launch || false,
ask_job_slice_count_on_launch:
template.ask_job_slice_count_on_launch || false,
ask_job_type_on_launch: template.ask_job_type_on_launch || false,
ask_labels_on_launch: template.ask_labels_on_launch || false,
ask_limit_on_launch: template.ask_limit_on_launch || false,