Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add system jobs interface w/ configurable data retention #9224

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆒

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

woahh


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`)}
jakemcdermott marked this conversation as resolved.
Show resolved Hide resolved
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