Skip to content

Commit

Permalink
Merge pull request #9224 from jakemcdermott/add-mgmt-jobs
Browse files Browse the repository at this point in the history
Add system jobs interface w/ configurable data retention

Reviewed-by: Jake McDermott <yo@jakemcdermott.me>
             https://github.com/jakemcdermott
  • Loading branch information
softwarefactory-project-zuul[bot] committed Feb 24, 2021
2 parents fe60559 + 218b978 commit cb05b54
Show file tree
Hide file tree
Showing 22 changed files with 909 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This is a list of high-level changes for each release of AWX. A full list of com
- Playbook, credential type, and inventory file inputs now support type-ahead and manual type-in! https://github.com/ansible/awx/pull/9120
- Added ability to relaunch against failed hosts: https://github.com/ansible/awx/pull/9225
- Added pending workflow approval count to the application header https://github.com/ansible/awx/pull/9334
- Added user interface for management jobs: https://github.com/ansible/awx/pull/9224

# 17.0.1 (January 26, 2021)
- Fixed pgdocker directory permissions issue with Local Docker installer: https://github.com/ansible/awx/pull/9152
Expand Down
3 changes: 3 additions & 0 deletions awx/ui_next/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Root from './models/Root';
import Schedules from './models/Schedules';
import Settings from './models/Settings';
import SystemJobs from './models/SystemJobs';
import SystemJobTemplates from './models/SystemJobTemplates';
import Teams from './models/Teams';
import Tokens from './models/Tokens';
import UnifiedJobTemplates from './models/UnifiedJobTemplates';
Expand Down Expand Up @@ -71,6 +72,7 @@ const RootAPI = new Root();
const SchedulesAPI = new Schedules();
const SettingsAPI = new Settings();
const SystemJobsAPI = new SystemJobs();
const SystemJobTemplatesAPI = new SystemJobTemplates();
const TeamsAPI = new Teams();
const TokensAPI = new Tokens();
const UnifiedJobTemplatesAPI = new UnifiedJobTemplates();
Expand Down Expand Up @@ -114,6 +116,7 @@ export {
SchedulesAPI,
SettingsAPI,
SystemJobsAPI,
SystemJobTemplatesAPI,
TeamsAPI,
TokensAPI,
UnifiedJobTemplatesAPI,
Expand Down
4 changes: 2 additions & 2 deletions awx/ui_next/src/api/models/Jobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const getBaseURL = type => {
case 'project':
case 'project_update':
return '/project_updates/';
case 'system':
case 'system_job':
case 'management':
case 'management_job':
return '/system_jobs/';
case 'inventory':
case 'inventory_update':
Expand Down
24 changes: 24 additions & 0 deletions awx/ui_next/src/api/models/SystemJobTemplates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Base from '../Base';
import NotificationsMixin from '../mixins/Notifications.mixin';
import SchedulesMixin from '../mixins/Schedules.mixin';

const Mixins = SchedulesMixin(NotificationsMixin(Base));

class SystemJobTemplates extends Mixins {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/system_job_templates/';
}

readDetail(id) {
const path = `${this.baseUrl}${id}/`;

return this.http.get(path).then(({ data }) => data);
}

launch(id, data) {
return this.http.post(`${this.baseUrl}${id}/launch/`, data);
}
}

export default SystemJobTemplates;
2 changes: 1 addition & 1 deletion awx/ui_next/src/components/JobList/JobListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function JobListItem({
inventory_update: i18n._(t`Inventory Sync`),
job: i18n._(t`Playbook Run`),
ad_hoc_command: i18n._(t`Command`),
management_job: i18n._(t`Management Job`),
system_job: i18n._(t`Management Job`),
workflow_job: i18n._(t`Workflow Job`),
};

Expand Down
12 changes: 8 additions & 4 deletions awx/ui_next/src/components/PaginatedTable/PaginatedTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function PaginatedTable({
showPageSizeOptions,
i18n,
renderToolbar,
emptyContentMessage,
}) {
const history = useHistory();

Expand Down Expand Up @@ -73,9 +74,6 @@ function PaginatedTable({
const queryParams = parseQueryString(qsConfig, history.location.search);

const dataListLabel = i18n._(t`${pluralizedItemName} List`);
const emptyContentMessage = i18n._(
t`Please add ${pluralizedItemName} to populate this list `
);
const emptyContentTitle = i18n._(t`No ${pluralizedItemName} Found `);

let Content;
Expand All @@ -85,7 +83,13 @@ function PaginatedTable({
Content = <ContentError error={contentError} />;
} else if (items.length <= 0) {
Content = (
<ContentEmpty title={emptyContentTitle} message={emptyContentMessage} />
<ContentEmpty
title={emptyContentTitle}
message={
emptyContentMessage ||
i18n._(t`Please add ${pluralizedItemName} to populate this list `)
}
/>
);
} else {
Content = (
Expand Down
11 changes: 9 additions & 2 deletions awx/ui_next/src/components/Schedule/Schedule.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function Schedule({
resource,
launchConfig,
surveyConfig,
hasDaysToKeepField,
}) {
const { scheduleId } = useParams();

Expand Down Expand Up @@ -69,7 +70,7 @@ function Schedule({
},
];

if (isLoading) {
if (isLoading || !schedule?.summary_fields?.unified_job_template?.id) {
return <ContentLoading />;
}

Expand All @@ -95,6 +96,7 @@ function Schedule({
if (!pathname.includes('schedules/') || pathname.endsWith('edit')) {
showCardHeader = false;
}

return (
<>
{showCardHeader && <RoutedTabs tabsArray={tabsArray} />}
Expand All @@ -107,6 +109,7 @@ function Schedule({
{schedule && [
<Route key="edit" path={`${pathRoot}schedules/:id/edit`}>
<ScheduleEdit
hasDaysToKeepField={hasDaysToKeepField}
schedule={schedule}
resource={resource}
launchConfig={launchConfig}
Expand All @@ -117,7 +120,11 @@ function Schedule({
key="details"
path={`${pathRoot}schedules/:scheduleId/details`}
>
<ScheduleDetail schedule={schedule} surveyConfig={surveyConfig} />
<ScheduleDetail
hasDaysToKeepField={hasDaysToKeepField}
schedule={schedule}
surveyConfig={surveyConfig}
/>
</Route>,
]}
<Route key="not-found" path="*">
Expand Down
27 changes: 22 additions & 5 deletions awx/ui_next/src/components/Schedule/ScheduleAdd/ScheduleAdd.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import mergeExtraVars from '../../../util/prompt/mergeExtraVars';
import getSurveyValues from '../../../util/prompt/getSurveyValues';
import { getAddedAndRemoved } from '../../../util/lists';

function ScheduleAdd({ i18n, resource, apiModel, launchConfig, surveyConfig }) {
function ScheduleAdd({
i18n,
resource,
apiModel,
launchConfig,
surveyConfig,
hasDaysToKeepField,
}) {
const [formSubmitError, setFormSubmitError] = useState(null);
const history = useHistory();
const location = useLocation();
Expand Down Expand Up @@ -70,13 +77,22 @@ function ScheduleAdd({ i18n, resource, apiModel, launchConfig, surveyConfig }) {

try {
const rule = new RRule(buildRuleObj(values, i18n));
const requestData = {
...submitValues,
rrule: rule.toString().replace(/\n/g, ' '),
};

if (Object.keys(values).includes('daysToKeep')) {
if (requestData.extra_data) {
requestData.extra_data.days = values.daysToKeep;
} else {
requestData.extra_data = JSON.stringify({ days: values.daysToKeep });
}
}

const {
data: { id: scheduleId },
} = await apiModel.createSchedule(resource.id, {
...submitValues,
rrule: rule.toString().replace(/\n/g, ' '),
});
} = await apiModel.createSchedule(resource.id, requestData);
if (credentials?.length > 0) {
await Promise.all(
added.map(({ id: credentialId }) =>
Expand All @@ -94,6 +110,7 @@ function ScheduleAdd({ i18n, resource, apiModel, launchConfig, surveyConfig }) {
<Card>
<CardBody>
<ScheduleForm
hasDaysToKeepField={hasDaysToKeepField}
handleCancel={() => history.push(`${pathRoot}schedules`)}
handleSubmit={handleSubmit}
submitError={formSubmitError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import DeleteButton from '../../DeleteButton';
import ErrorDetail from '../../ErrorDetail';
import ChipGroup from '../../ChipGroup';
import { VariablesDetail } from '../../CodeMirrorInput';
import { parseVariableField } from '../../../util/yaml';

const PromptDivider = styled(Divider)`
margin-top: var(--pf-global--spacer--lg);
Expand All @@ -42,7 +43,7 @@ const PromptDetailList = styled(DetailList)`
padding: 0px 20px;
`;

function ScheduleDetail({ schedule, i18n, surveyConfig }) {
function ScheduleDetail({ hasDaysToKeepField, schedule, i18n, surveyConfig }) {
const {
id,
created,
Expand Down Expand Up @@ -233,6 +234,16 @@ function ScheduleDetail({ schedule, i18n, surveyConfig }) {
return <ContentError error={readContentError} />;
}

let daysToKeep = null;
if (hasDaysToKeepField && extra_data) {
if (typeof extra_data === 'string' && extra_data !== '') {
daysToKeep = parseVariableField(extra_data).days;
}
if (typeof extra_data === 'object') {
daysToKeep = extra_data?.days;
}
}

return (
<CardBody>
<ScheduleToggle
Expand All @@ -254,6 +265,9 @@ function ScheduleDetail({ schedule, i18n, surveyConfig }) {
<Detail label={i18n._(t`Last Run`)} value={formatDateString(dtend)} />
<Detail label={i18n._(t`Local Time Zone`)} value={timezone} />
<Detail label={i18n._(t`Repeat Frequency`)} value={repeatFrequency} />
{hasDaysToKeepField ? (
<Detail label={i18n._(t`Days of Data to Keep`)} value={daysToKeep} />
) : null}
<ScheduleOccurrences preview={preview} />
<UserDateDetail
label={i18n._(t`Created`)}
Expand Down
20 changes: 16 additions & 4 deletions awx/ui_next/src/components/Schedule/ScheduleEdit/ScheduleEdit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import getSurveyValues from '../../../util/prompt/getSurveyValues';

function ScheduleEdit({
i18n,
hasDaysToKeepField,
schedule,
resource,
launchConfig,
Expand Down Expand Up @@ -83,12 +84,22 @@ function ScheduleEdit({

try {
const rule = new RRule(buildRuleObj(values, i18n));
const {
data: { id: scheduleId },
} = await SchedulesAPI.update(schedule.id, {
const requestData = {
...submitValues,
rrule: rule.toString().replace(/\n/g, ' '),
});
};

if (Object.keys(values).includes('daysToKeep')) {
if (!requestData.extra_data) {
requestData.extra_data = JSON.stringify({ days: values.daysToKeep });
} else {
requestData.extra_data.days = values.daysToKeep;
}
}

const {
data: { id: scheduleId },
} = await SchedulesAPI.update(schedule.id, requestData);
if (values.credentials?.length > 0) {
await Promise.all([
...removed.map(({ id }) =>
Expand All @@ -111,6 +122,7 @@ function ScheduleEdit({
<CardBody>
<ScheduleForm
schedule={schedule}
hasDaysToKeepField={hasDaysToKeepField}
handleCancel={() =>
history.push(`${pathRoot}schedules/${schedule.id}/details`)
}
Expand Down
9 changes: 9 additions & 0 deletions awx/ui_next/src/components/Schedule/Schedules.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ function Schedules({
}) {
const match = useRouteMatch();

// For some management jobs that delete data, we want to provide an additional
// field on the scheduler for configuring the number of days to retain.
const hasDaysToKeepField = [
'cleanup_activitystream',
'cleanup_jobs',
].includes(resource?.job_type);

return (
<Switch>
<Route path={`${match.path}/add`}>
<ScheduleAdd
hasDaysToKeepField={hasDaysToKeepField}
apiModel={apiModel}
resource={resource}
launchConfig={launchConfig}
Expand All @@ -28,6 +36,7 @@ function Schedules({
</Route>
<Route key="details" path={`${match.path}/:scheduleId`}>
<Schedule
hasDaysToKeepField={hasDaysToKeepField}
setBreadcrumb={setBreadcrumb}
resource={resource}
launchConfig={launchConfig}
Expand Down
31 changes: 30 additions & 1 deletion awx/ui_next/src/components/Schedule/shared/ScheduleForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { dateToInputDateTime, formatDateStringUTC } from '../../../util/dates';
import useRequest from '../../../util/useRequest';
import { required } from '../../../util/validators';
import { parseVariableField } from '../../../util/yaml';
import FrequencyDetailSubform from './FrequencyDetailSubform';
import SchedulePromptableFields from './SchedulePromptableFields';

Expand Down Expand Up @@ -77,7 +78,7 @@ const generateRunOnTheDay = (days = []) => {
return null;
};

function ScheduleFormFields({ i18n, zoneOptions }) {
function ScheduleFormFields({ i18n, hasDaysToKeepField, zoneOptions }) {
const [startDateTime, startDateTimeMeta] = useField({
name: 'startDateTime',
validate: required(
Expand Down Expand Up @@ -169,6 +170,16 @@ function ScheduleFormFields({ i18n, zoneOptions }) {
{...frequency}
/>
</FormGroup>
{hasDaysToKeepField ? (
<FormField
id="schedule-days-to-keep"
label={i18n._(t`Days of Data to Keep`)}
name="daysToKeep"
type="number"
validate={required(null, i18n)}
isRequired
/>
) : null}
{frequency.value !== 'none' && (
<SubFormLayout>
<Title size="md" headingLevel="h4">
Expand All @@ -184,6 +195,7 @@ function ScheduleFormFields({ i18n, zoneOptions }) {
}

function ScheduleForm({
hasDaysToKeepField,
handleCancel,
handleSubmit,
i18n,
Expand Down Expand Up @@ -344,6 +356,22 @@ function ScheduleForm({
);
};

if (hasDaysToKeepField) {
let initialDaysToKeep = 30;
if (schedule?.extra_data) {
if (
typeof schedule?.extra_data === 'string' &&
schedule?.extra_data !== ''
) {
initialDaysToKeep = parseVariableField(schedule?.extra_data).days;
}
if (typeof schedule?.extra_data === 'object') {
initialDaysToKeep = schedule?.extra_data?.days;
}
}
initialValues.daysToKeep = initialDaysToKeep;
}

const overriddenValues = {};

if (Object.keys(schedule).length > 0) {
Expand Down Expand Up @@ -487,6 +515,7 @@ function ScheduleForm({
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<ScheduleFormFields
hasDaysToKeepField={hasDaysToKeepField}
i18n={i18n}
zoneOptions={zoneOptions}
{...rest}
Expand Down
Loading

0 comments on commit cb05b54

Please sign in to comment.