diff --git a/webpack/JobInvocationDetail/JobInvocationActions.js b/webpack/JobInvocationDetail/JobInvocationActions.js index 131696068..e89bdeeb0 100644 --- a/webpack/JobInvocationDetail/JobInvocationActions.js +++ b/webpack/JobInvocationDetail/JobInvocationActions.js @@ -12,6 +12,7 @@ import { CHANGE_ENABLED_RECURRING_LOGIC, GET_TASK, JOB_INVOCATION_KEY, + POLLING_INTERVAL_MS, UPDATE_JOB, } from './JobInvocationConstants'; @@ -32,7 +33,7 @@ export const getJobInvocation = url => dispatch => { response?.data?.error?.message || 'Error', }), - 1000 + POLLING_INTERVAL_MS ); dispatch(fetchData); diff --git a/webpack/JobInvocationDetail/JobInvocationConstants.js b/webpack/JobInvocationDetail/JobInvocationConstants.js index 12d6deb11..79352913c 100644 --- a/webpack/JobInvocationDetail/JobInvocationConstants.js +++ b/webpack/JobInvocationDetail/JobInvocationConstants.js @@ -20,6 +20,13 @@ export const DIRECT_OPEN_HOST_LIMIT = 3; export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS'; export const AWAITING_STATUS_FILTER = '(job_invocation.result = N/A)'; +export const POLLING_INTERVAL_MS = 1000; +export const AUTO_REFRESH_INTERVAL_MS = 5000; +export const UNIX_TO_JS_TIMESTAMP_FACTOR = 1000; +export const DEFAULT_CHART_LEGEND_WIDTH = 270; +export const CLIPBOARD_COPIED_EXIT_DELAY_MS = 1500; +export const CLIPBOARD_DEFAULT_EXIT_DELAY_MS = 600; + export const showTemplateInvocationUrl = (hostID, jobID) => `/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`; export const LIST_TEMPLATE_INVOCATIONS = 'LIST_TEMPLATE_INVOCATIONS'; diff --git a/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js b/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js index f44f9b707..77a46e3c7 100644 --- a/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +++ b/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js @@ -23,7 +23,10 @@ import { global_palette_blue_300 as inProgressColor, global_palette_green_500 as successedColor, } from '@patternfly/react-tokens'; -import { STATUS_TITLES } from './JobInvocationConstants'; +import { + STATUS_TITLES, + DEFAULT_CHART_LEGEND_WIDTH, +} from './JobInvocationConstants'; import './JobInvocationDetail.scss'; const JobInvocationSystemStatusChart = ({ @@ -52,7 +55,7 @@ const JobInvocationSystemStatusChart = ({ return '0'; }; const chartSize = 105; - const [legendWidth, setLegendWidth] = useState(270); + const [legendWidth, setLegendWidth] = useState(DEFAULT_CHART_LEGEND_WIDTH); // Calculates chart legend width based on its content useEffect(() => { diff --git a/webpack/JobInvocationDetail/TemplateInvocation.js b/webpack/JobInvocationDetail/TemplateInvocation.js index 9045be218..faad034f8 100644 --- a/webpack/JobInvocationDetail/TemplateInvocation.js +++ b/webpack/JobInvocationDetail/TemplateInvocation.js @@ -11,6 +11,9 @@ import { showTemplateInvocationUrl, templateInvocationPageUrl, GET_TEMPLATE_INVOCATION, + CLIPBOARD_COPIED_EXIT_DELAY_MS, + CLIPBOARD_DEFAULT_EXIT_DELAY_MS, + AUTO_REFRESH_INTERVAL_MS, } from './JobInvocationConstants'; import { selectTemplateInvocationStatus, @@ -41,7 +44,11 @@ const CopyToClipboard = ({ fullOutput }) => { textId="code-content" aria-label="Copy to clipboard" onClick={e => onClick(e, fullOutput)} - exitDelay={copied ? 1500 : 600} + exitDelay={ + copied + ? CLIPBOARD_COPIED_EXIT_DELAY_MS + : CLIPBOARD_DEFAULT_EXIT_DELAY_MS + } maxWidth="110px" variant="plain" onTooltipHidden={() => setCopied(false)} @@ -111,7 +118,7 @@ export const TemplateInvocation = ({ } else if (intervalRef.current) { clearInterval(intervalRef.current); } - }, 5000); + }, AUTO_REFRESH_INTERVAL_MS); } return () => { diff --git a/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js b/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js index 759b7b280..3a4a57644 100644 --- a/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js +++ b/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js @@ -7,6 +7,7 @@ import React, { import PropTypes from 'prop-types'; import { Button } from '@patternfly/react-core'; import { translate as __ } from 'foremanReact/common/I18n'; +import { UNIX_TO_JS_TIMESTAMP_FACTOR } from '../JobInvocationConstants'; export const OutputCodeBlock = ({ code, showOutputType, scrollElement }) => { let lineCounter = 0; @@ -122,7 +123,9 @@ export const OutputCodeBlock = ({ code, showOutputType, scrollElement }) => {
{lineCounter.toString().padStart(4, '\u00A0')}:{' '} diff --git a/webpack/JobWizard/JobWizard.js b/webpack/JobWizard/JobWizard.js index 68f2a820c..8e244c628 100644 --- a/webpack/JobWizard/JobWizard.js +++ b/webpack/JobWizard/JobWizard.js @@ -18,6 +18,7 @@ import { WIZARD_TITLES, SCHEDULE_TYPES, initialScheduleState, + STARTS_ERROR_CHECK_INTERVAL_MS, } from './JobWizardConstants'; import { selectTemplateError, @@ -237,7 +238,10 @@ export const JobWizard = ({ rerunData }) => { } }; updateStartsError(); - const interval = setInterval(updateStartsError, 5000); + const interval = setInterval( + updateStartsError, + STARTS_ERROR_CHECK_INTERVAL_MS + ); return () => { interval && clearInterval(interval); diff --git a/webpack/JobWizard/JobWizardConstants.js b/webpack/JobWizard/JobWizardConstants.js index ca1bfcdc0..a1db56bca 100644 --- a/webpack/JobWizard/JobWizardConstants.js +++ b/webpack/JobWizard/JobWizardConstants.js @@ -78,3 +78,12 @@ export const HOST_IDS = 'HOST_IDS'; export const REX_FEATURE = 'REX_FEATURE'; export const JOB_API_KEY = 'JOB_API_KEY'; + +export const MINUTES_IN_HOUR = 60; +export const DAYS_IN_WEEK = 7; +export const SUNDAY_BASE_YEAR = 2017; +export const DEFAULT_MINUTE_OPTIONS = [0, 15, 30, 45]; +export const MS_PER_MINUTE = 60000; +export const STARTS_ERROR_CHECK_INTERVAL_MS = 5000; +export const DATE_PADDING_SLICE = -2; +export const DEBOUNCE_INPUT_MS = 1000; diff --git a/webpack/JobWizard/steps/Schedule/RepeatHour.js b/webpack/JobWizard/steps/Schedule/RepeatHour.js index 28e8f588e..90a7ff0a6 100644 --- a/webpack/JobWizard/steps/Schedule/RepeatHour.js +++ b/webpack/JobWizard/steps/Schedule/RepeatHour.js @@ -13,12 +13,16 @@ import { } from '@patternfly/react-core/deprecated'; import { translate as __ } from 'foremanReact/common/I18n'; import { helpLabel } from '../form/FormHelpers'; +import { + MINUTES_IN_HOUR, + DEFAULT_MINUTE_OPTIONS, +} from '../../JobWizardConstants'; export const RepeatHour = ({ repeatData, setRepeatData }) => { const isValidMinute = newMinute => Number.isInteger(parseInt(newMinute, 10)) && newMinute >= 0 && - newMinute < 60; + newMinute < MINUTES_IN_HOUR; const { minute } = repeatData; useEffect(() => { @@ -27,7 +31,7 @@ export const RepeatHour = ({ repeatData, setRepeatData }) => { } }, [minute, setRepeatData]); const [minuteOpen, setMinuteOpen] = useState(false); - const [options, setOptions] = useState([0, 15, 30, 45]); + const [options, setOptions] = useState(DEFAULT_MINUTE_OPTIONS); const [isAlertOpen, setIsAlertOpen] = useState(false); return ( { const locale = documentLocale().replace(/-/g, '_'); - const baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday + const baseDate = new Date(Date.UTC(SUNDAY_BASE_YEAR, 0, 1)); // just a Sunday const weekDays = []; const formatOptions = { weekday: 'short', timeZone: 'UTC' }; - for (let i = 0; i < 7; i++) { + for (let i = 0; i < DAYS_IN_WEEK; i++) { try { weekDays.push(baseDate.toLocaleDateString(locale, formatOptions)); } catch { diff --git a/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js b/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js index 6668a9924..d2427b896 100644 --- a/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +++ b/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js @@ -13,7 +13,7 @@ import { import { ExclamationCircleIcon } from '@patternfly/react-icons'; import { translate as __ } from 'foremanReact/common/I18n'; import { RepeatOn } from './RepeatOn'; -import { SCHEDULE_TYPES } from '../../JobWizardConstants'; +import { SCHEDULE_TYPES, MS_PER_MINUTE } from '../../JobWizardConstants'; import { PurposeField } from './PurposeField'; import { DateTimePicker } from '../form/DateTimePicker'; import { WizardTitle } from '../form/WizardTitle'; @@ -114,7 +114,7 @@ export const ScheduleRecurring = ({ setScheduleValue(current => ({ ...current, startsAt: new Date( - new Date().getTime() + 60000 + new Date().getTime() + MS_PER_MINUTE ).toISOString(), // 1 minute in the future isFuture: true, })) diff --git a/webpack/JobWizard/steps/Schedule/ScheduleType.js b/webpack/JobWizard/steps/Schedule/ScheduleType.js index 77862c1f4..07857fa40 100644 --- a/webpack/JobWizard/steps/Schedule/ScheduleType.js +++ b/webpack/JobWizard/steps/Schedule/ScheduleType.js @@ -6,6 +6,7 @@ import { WIZARD_TITLES, SCHEDULE_TYPES, repeatTypes, + MS_PER_MINUTE, } from '../../JobWizardConstants'; import { WizardTitle } from '../form/WizardTitle'; import { QueryType } from './QueryType'; @@ -49,7 +50,9 @@ export const ScheduleType = ({ onChange={() => { setScheduleValue(current => ({ ...current, - startsAt: new Date(new Date().getTime() + 60000).toISOString(), // 1 minute in the future + startsAt: new Date( + new Date().getTime() + MS_PER_MINUTE + ).toISOString(), // 1 minute in the future scheduleType: SCHEDULE_TYPES.FUTURE, repeatType: repeatTypes.noRepeat, })); diff --git a/webpack/JobWizard/steps/form/DateTimePicker.js b/webpack/JobWizard/steps/form/DateTimePicker.js index 4c12012b6..2917d968e 100644 --- a/webpack/JobWizard/steps/form/DateTimePicker.js +++ b/webpack/JobWizard/steps/form/DateTimePicker.js @@ -7,13 +7,19 @@ import { } from '@patternfly/react-core'; import { debounce } from 'lodash'; import { translate as __, documentLocale } from 'foremanReact/common/I18n'; +import { + DATE_PADDING_SLICE, + DEBOUNCE_INPUT_MS, +} from '../../JobWizardConstants'; const formatDateTime = d => `${d.getFullYear()}-${`0${d.getMonth() + 1}`.slice( - -2 - )}-${`0${d.getDate()}`.slice(-2)} ${`0${d.getHours()}`.slice( - -2 - )}:${`0${d.getMinutes()}`.slice(-2)}:${`0${d.getSeconds()}`.slice(-2)}`; + DATE_PADDING_SLICE + )}-${`0${d.getDate()}`.slice(DATE_PADDING_SLICE)} ${`0${d.getHours()}`.slice( + DATE_PADDING_SLICE + )}:${`0${d.getMinutes()}`.slice( + DATE_PADDING_SLICE + )}:${`0${d.getSeconds()}`.slice(DATE_PADDING_SLICE)}`; export const DateTimePicker = ({ dateTime, @@ -103,7 +109,7 @@ export const DateTimePicker = ({ aria-label={`${ariaLabel} datepicker`} value={formattedDate} placeholder="yyyy/mm/dd" - onChange={debounce(onDateChange, 1000, { + onChange={debounce(onDateChange, DEBOUNCE_INPUT_MS, { leading: false, trailing: true, })} @@ -123,7 +129,7 @@ export const DateTimePicker = ({ time={dateTime ? dateObject.toString() : ''} inputProps={dateTime ? {} : { value: '' }} placeholder={includeSeconds ? 'hh:mm:ss' : 'hh:mm'} - onChange={debounce(onTimeChange, 1000, { + onChange={debounce(onTimeChange, DEBOUNCE_INPUT_MS, { leading: false, trailing: true, })} diff --git a/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js b/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js index 5fd27d8c8..3642c0393 100644 --- a/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +++ b/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js @@ -1,2 +1,3 @@ export const TARGETING_HOSTS = 'TARGETING_HOSTS'; export const TARGETING_HOSTS_AUTOCOMPLETE = 'targeting_hosts_search'; +export const TARGETING_HOSTS_POLLING_INTERVAL_MS = 1000; diff --git a/webpack/react_app/components/TargetingHosts/index.js b/webpack/react_app/components/TargetingHosts/index.js index 76f1ec728..415fb660b 100644 --- a/webpack/react_app/components/TargetingHosts/index.js +++ b/webpack/react_app/components/TargetingHosts/index.js @@ -17,7 +17,10 @@ import { selectStatusFilter, } from './TargetingHostsSelectors'; import { getApiUrl } from './TargetingHostsHelpers'; -import { TARGETING_HOSTS } from './TargetingHostsConsts'; +import { + TARGETING_HOSTS, + TARGETING_HOSTS_POLLING_INTERVAL_MS, +} from './TargetingHostsConsts'; import TargetingHostsPage from './TargetingHostsPage'; import { chartFilter } from '../../redux/actions/jobInvocations'; @@ -100,7 +103,7 @@ const WrappedTargetingHosts = () => { dispatch(stopInterval(TARGETING_HOSTS)); }, }), - 1000 + TARGETING_HOSTS_POLLING_INTERVAL_MS ), [dispatch] ); diff --git a/webpack/react_app/redux/actions/jobInvocations/index.js b/webpack/react_app/redux/actions/jobInvocations/index.js index 968d83923..885712656 100644 --- a/webpack/react_app/redux/actions/jobInvocations/index.js +++ b/webpack/react_app/redux/actions/jobInvocations/index.js @@ -5,6 +5,7 @@ import { JOB_INVOCATIONS_GET_JOB_INVOCATIONS, JOB_INVOCATIONS_POLLING_STARTED, JOB_INVOCATIONS_JOB_FINISHED, + HTTP_UNAUTHORIZED, } from '../../consts'; const defaultJobInvocationsPollingInterval = 1000; @@ -32,7 +33,7 @@ const getJobInvocations = url => async (dispatch, getState) => { } function onGetJobInvocationsFailed(error) { - if (error.response.status === 401) { + if (error.response.status === HTTP_UNAUTHORIZED) { window.location.replace('/users/login'); } } diff --git a/webpack/react_app/redux/consts.js b/webpack/react_app/redux/consts.js index 02c09fc35..1cdc74474 100644 --- a/webpack/react_app/redux/consts.js +++ b/webpack/react_app/redux/consts.js @@ -3,3 +3,4 @@ export const JOB_INVOCATIONS_POLLING_STARTED = export const JOB_INVOCATIONS_GET_JOB_INVOCATIONS = 'JOB_INVOCATIONS_GET_JOB_INVOCATIONS'; export const JOB_INVOCATIONS_JOB_FINISHED = 'JOB_INVOCATIONS_JOB_FINISHED'; +export const HTTP_UNAUTHORIZED = 401;