diff --git a/.env.development b/.env.development index 0f7b7c0409..df332e2b2d 100644 --- a/.env.development +++ b/.env.development @@ -45,3 +45,4 @@ MAINTENANCE_ALERT_MESSAGE='' MAINTENANCE_ALERT_START_TIMESTAMP='' TABLEAU_URL='https://enterprise-tableau.edx.org' USE_API_CACHE='true' +FEATURE_SSO_SETTINGS_TAB='true' diff --git a/src/components/FileInput/index.jsx b/src/components/FileInput/index.jsx index d3f892b4ee..ec38d04bad 100644 --- a/src/components/FileInput/index.jsx +++ b/src/components/FileInput/index.jsx @@ -2,7 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { clearFields } from 'redux-form'; -import { Button, Icon, ValidationFormGroup } from '@edx/paragon'; +import { Button, Icon, Form } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; class FileInput extends React.Component { constructor(props) { @@ -68,12 +69,10 @@ class FileInput extends React.Component { } = this.props; const hasError = !!(touched && error); return ( -
)} + {description} + {hasError && ( + }> + {error} + + )}
-
+ ); } } diff --git a/src/components/LmsConfigurations/BlackboardIntegrationConfigForm.jsx b/src/components/LmsConfigurations/BlackboardIntegrationConfigForm.jsx index a756370a8f..d276b744f4 100644 --- a/src/components/LmsConfigurations/BlackboardIntegrationConfigForm.jsx +++ b/src/components/LmsConfigurations/BlackboardIntegrationConfigForm.jsx @@ -2,12 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, - Input, + Form, StatefulButton, Icon, Hyperlink, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; import { snakeCaseFormData } from '../../utils'; import LmsApiService from '../../data/services/LmsApiService'; import StatusAlert from '../StatusAlert'; @@ -20,6 +20,33 @@ export const REQUIRED_BLACKBOARD_CONFIG_FIELDS = [ 'clientSecret', ]; +const BLACKBOARD_FIELDS = [ + { + key: 'blackboardBaseUrl', + invalidMessage: 'Blackboard Instance URL is required.', + helpText: 'Your Blackboard instance URL. Make sure to include the protocol (ie https/http)', + label: 'Blackboard Instance URL', + }, + { + key: 'clientId', + invalidMessage: 'Blackboard client ID is required.', + helpText: 'This should match the API Client ID found on Blackboard.', + label: 'Blackboard Client ID', + }, + { + key: 'clientSecret', + invalidMessage: 'Blackboard client secret is required.', + helpText: 'This should match the API Client secret found on Blackboard.', + label: 'Blackboard Client Secret', + }, + { + key: 'refreshToken', + helpText: "The Blackboard API's refresh token token. This should be automatically propagated once you visit the oauth complete endpoint.", + label: 'Blackboard API Refresh Token', + + }, +]; + class BlackboardIntegrationConfigForm extends React.Component { state = { invalidFields: {}, @@ -126,95 +153,42 @@ class BlackboardIntegrationConfigForm extends React.Component { >
- - - + this.setState(prevState => ({ active: !prevState.active }))} - /> - -
-
-
-
- - - - + floatLabelLeft + > + Active + +
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - + {BLACKBOARD_FIELDS.map(backgroundField => ( +
+
+ + {backgroundField.label} + + {backgroundField.helpText} + {invalidFields[backgroundField.key] && backgroundField.invalidMessage && ( + }> + {backgroundField.invalidMessage} + + )} + +
-
+ ))}
diff --git a/src/components/LmsConfigurations/CanvasIntegrationConfigForm.jsx b/src/components/LmsConfigurations/CanvasIntegrationConfigForm.jsx index 23973bed6f..f6d87c3363 100644 --- a/src/components/LmsConfigurations/CanvasIntegrationConfigForm.jsx +++ b/src/components/LmsConfigurations/CanvasIntegrationConfigForm.jsx @@ -2,8 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, Input, StatefulButton, Icon, + Form, StatefulButton, Icon, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; import { snakeCaseFormData } from '../../utils'; import StatusAlert from '../StatusAlert'; import LmsApiService from '../../data/services/LmsApiService'; @@ -17,6 +18,28 @@ export const REQUIRED_CANVAS_CONFIG_FIELDS = [ 'canvasBaseUrl', ]; +const CANVAS_FIELDS = [ + { + key: 'clientId', + invalidMessage: 'Required. Client Id must not be blank', + label: 'API Client Id', + }, + { + key: 'clientSecret', + invalidMessage: 'Required. Client Secret must not be blank', + label: 'API Client Secret', + }, + { + key: 'canvasAccountId', + invalidMessage: 'Required. Canvas Account Id must not be blank', + label: 'Canvas Account Id', + }, + { + key: 'canvasBaseUrl', + invalidMessage: 'Required. Canvas Base URL must not be blank', + label: 'Canvas Base URL', + }, +]; class CanvasIntegrationConfigForm extends React.Component { state = { invalidFields: {}, @@ -117,102 +140,44 @@ class CanvasIntegrationConfigForm extends React.Component { >
- - - + this.setState(prevState => ({ active: !prevState.active }))} - data-hj-suppress - /> - -
-
-
-
- - - this.setState(prevState => ({ clientId: !prevState.clientId }))} - data-hj-suppress - /> - -
-
-
-
- - - this.setState(prevState => ({ clientSecret: !prevState.clientSecret }))} - data-hj-suppress - /> - -
-
-
-
- - - this.setState(prevState => ({ canvasAccountId: !prevState.canvasAccountId }))} - data-hj-suppress - /> - + floatLabelLeft + > + Active + +
-
-
- - - this.setState(prevState => ({ canvasBaseUrl: !prevState.canvasBaseUrl }))} - data-hj-suppress - /> - + {CANVAS_FIELDS.map(canvasField => ( +
+
+ + {canvasField.label} + this.setState( + prevState => ({ [canvasField.key]: !prevState[canvasField.key] }), + )} + data-hj-suppress + /> + {invalidFields[canvasField.key] && canvasField.invalidMessage && ( + }> + {canvasField.invalidMessage} + + )} + +
-
+ ))}
diff --git a/src/components/LmsConfigurations/CornerstoneIntegrationConfigForm.jsx b/src/components/LmsConfigurations/CornerstoneIntegrationConfigForm.jsx index 3c5729d369..d6515b3cf7 100644 --- a/src/components/LmsConfigurations/CornerstoneIntegrationConfigForm.jsx +++ b/src/components/LmsConfigurations/CornerstoneIntegrationConfigForm.jsx @@ -2,8 +2,9 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, Input, StatefulButton, Icon, + Form, StatefulButton, Icon, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; import { snakeCaseFormData } from '../../utils'; import LmsApiService from '../../data/services/LmsApiService'; import StatusAlert from '../StatusAlert'; @@ -179,38 +180,38 @@ function CornerstoneIntegrationConfigForm({ enterpriseId, config }) {
- - - + setActive(prevActive => (!prevActive))} - /> - + floatLabelLeft + > + Active + +
- - - Cornerstone Instance URL + - + Your Cornerstone instance URL. Make sure to include the protocol (ie https/http) + {state.invalidFields.cornerstoneBaseUrl && ( + }> + Cornerstone Instance URL is required. + + )} +
diff --git a/src/components/LmsConfigurations/DegreedIntegrationConfigForm.jsx b/src/components/LmsConfigurations/DegreedIntegrationConfigForm.jsx index 9ba5208a45..fec960bcb7 100644 --- a/src/components/LmsConfigurations/DegreedIntegrationConfigForm.jsx +++ b/src/components/LmsConfigurations/DegreedIntegrationConfigForm.jsx @@ -2,8 +2,9 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, Input, StatefulButton, Icon, + Form, StatefulButton, Icon, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; import { snakeCaseFormData } from '../../utils'; import LmsApiService from '../../data/services/LmsApiService'; import StatusAlert from '../StatusAlert'; @@ -19,6 +20,46 @@ export const REQUIRED_DEGREED_CONFIG_FIELDS = [ 'key', ]; +const DEGREED_FIELDS = [ + { + key: 'degreedCompanyId', + invalidMessage: 'Degreed Organization Code is required.', + helpText: 'The organization code provided to you by Degreed.', + label: 'Degreed Organization Code', + }, + { + key: 'degreedBaseUrl', + invalidMessage: 'Degreed Instance URL is required.', + helpText: 'Your Degreed instance URL. Make sure to include the protocol (ie https/http)', + label: 'Degreed Instance URL', + }, + { + key: 'key', + invalidMessage: 'Degreed API Client ID required.', + helpText: 'The API Client ID used to make calls to Degreed on behalf of the customer.', + label: 'API Client ID', + }, + { + key: 'secret', + invalidMessage: 'Degreed API Client Secret required.', + helpText: 'The API Client Secret used to make calls to Degreed on behalf of the customer.', + label: 'API Client Secret', + }, + { + key: 'degreedUserId', + invalidMessage: 'The Degreed User ID is required to access Degreed via Oauth.', + helpText: 'The Degreed User ID provided to the content provider by Degreed.', + label: 'Degreed User ID', + }, + { + key: 'degreedUserPassword', + invalidMessage: 'The Degreed User Password is required to access Degreed via Oauth.', + helpText: 'The Degreed User Password provided to the content provider by Degreed.', + label: 'Degreed User Password', + type: 'password', + }, +]; + function configFormReducer(state, action) { switch (action.type) { case 'PENDING': { @@ -184,135 +225,42 @@ function DegreedIntegrationConfigForm({ enterpriseId, config }) {
- - - + setActive(prevActive => (!prevActive))} - /> - -
-
-
-
- - - - + floatLabelLeft + > + Active + +
-
-
- - - - + {DEGREED_FIELDS.map(degreedField => ( +
+
+ + {degreedField.label} + + {degreedField.helpText} + {state.invalidFields[degreedField.key] && degreedField.invalidMessage && ( + }> + {degreedField.invalidMessage} + + )} + +
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
+ ))}
diff --git a/src/components/LmsConfigurations/MoodleIntegrationConfigForm.jsx b/src/components/LmsConfigurations/MoodleIntegrationConfigForm.jsx index f7495ae560..c4ccc3105d 100644 --- a/src/components/LmsConfigurations/MoodleIntegrationConfigForm.jsx +++ b/src/components/LmsConfigurations/MoodleIntegrationConfigForm.jsx @@ -2,8 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, Input, StatefulButton, Icon, + Form, StatefulButton, Icon, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; import { snakeCaseFormData } from '../../utils'; import LmsApiService from '../../data/services/LmsApiService'; import StatusAlert from '../StatusAlert'; @@ -15,6 +16,48 @@ export const REQUIRED_MOODLE_CONFIG_FIELDS = [ 'serviceShortName', ]; +const MOODLE_FIELDS = [ + { + key: 'moodleBaseUrl', + invalidMessage: 'Moodle Instance URL is required.', + helpText: 'Your Moodle instance URL. Make sure to include the protocol (ie https/http)', + label: 'Moodle Instance URL', + }, + { + key: 'serviceShortName', + invalidMessage: 'Webservice name is required.', + helpText: 'This should match the webservice\'s short name in Moodle.', + label: 'Webservice\'s Short Name', + }, + { + key: 'categoryId', + helpText: 'The category id all edX courses will be added under. Default is 1 (Miscellaneous)', + type: 'number', + label: 'Moodle Category ID', + }, + { + key: 'username', + invalidMessage: 'A username and password must be provided when a token is not. However, you should not provide both user credentials and token.', + helpText: 'The Webservice\'s username in Moodle. You must provide this and password or a token', + label: 'Webservice Username', + invalidAdditionalCondition: 'duplicateCreds', + }, + { + key: 'password', + invalidMessage: 'A username and password must be provided when a token is not. However, you should not provide both user credentials and token.', + helpText: 'The Webservice\'s password in Moodle. You must provide this and username or a token', + label: 'Webservice Password', + invalidAdditionalCondition: 'duplicateCreds', + }, + { + key: 'token', + invalidMessage: 'A token must be provided when username/password is not. However, you should not provide both user credentials and token.', + helpText: 'The Webservice user\'s auth token. Use this in place of a username/password.', + label: 'Webservice Auth Token', + invalidAdditionalCondition: 'duplicateCreds', + }, +]; + class MoodleIntegrationConfigForm extends React.Component { state = { invalidFields: {}, @@ -137,133 +180,45 @@ class MoodleIntegrationConfigForm extends React.Component { >
- - - + this.setState(prevState => ({ active: !prevState.active }))} - /> - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - + floatLabelLeft + > + Active + +
-
-
- - - - + + {MOODLE_FIELDS.map(moodleField => ( +
+
+ + {moodleField.label} + + {moodleField.helpText} + {(invalidFields[moodleField.key] || invalidFields[moodleField.invalidAdditionalCondition]) + && moodleField.invalidMessage && ( + }> + {moodleField.invalidMessage} + + )} + +
-
+ ))}
diff --git a/src/components/LmsConfigurations/SuccessFactorsIntegrationConfigForm.jsx b/src/components/LmsConfigurations/SuccessFactorsIntegrationConfigForm.jsx index 807a76bfbc..65d71dc2e1 100644 --- a/src/components/LmsConfigurations/SuccessFactorsIntegrationConfigForm.jsx +++ b/src/components/LmsConfigurations/SuccessFactorsIntegrationConfigForm.jsx @@ -2,8 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, Input, StatefulButton, Icon, + Form, StatefulButton, Icon, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; + import { snakeCaseFormData } from '../../utils'; import LmsApiService from '../../data/services/LmsApiService'; import StatusAlert from '../StatusAlert'; @@ -19,6 +21,40 @@ export const REQUIRED_SUCCESS_FACTOR_CONFIG_FIELDS = [ 'userType', ]; +const SUCCESS_FACTOR_FIELDS = [ + { + key: 'sapsfBaseUrl', + invalidMessage: 'SAP Success Factors Instance URL is required.', + helpText: 'Your SAP Success Factors instance URL. Make sure to include the protocol (ie https/http)', + label: 'SAP Success Factors Instance URL', + }, + { + key: 'sapsfCompanyId', + invalidMessage: 'SAP Success Factors Company Id field is required.', + helpText: 'This should match the Company Id as found in SAP Success Factors.', + label: 'SAP Success Factors Company Id', + }, + { + key: 'key', + invalidMessage: 'Success Factors\' Client Id is required.', + helpText: 'Oauth client identifier.', + label: 'Client Id', + }, + { + key: 'secret', + invalidMessage: 'Success Factors\' Client Secret is required.', + helpText: 'OAuth client secret.', + label: 'Client Secret', + type: 'password', + }, + { + key: 'sapsfUserId', + invalidMessage: 'Success Factors\' User Id is required.', + helpText: 'Success Factors user identifier', + label: 'SAP Success Factors User Id', + }, +]; + class SuccessFactorsIntegrationConfigForm extends React.Component { state = { invalidFields: {}, @@ -99,6 +135,36 @@ class SuccessFactorsIntegrationConfigForm extends React.Component { } } + renderField = data => { + const { invalidFields } = this.state; + const { config } = this.props; + return ( +
+
+ + {data.label} + + {data.helpText} + {invalidFields[data.key] && data.invalidMessage && ( + }> + {data.invalidMessage} + + )} + +
+
+ ); + } + render() { const { invalidFields, @@ -109,6 +175,16 @@ class SuccessFactorsIntegrationConfigForm extends React.Component { } = this.state; const { config } = this.props; + const userTypeOptions = [ + { value: 'admin', label: 'Admin' }, + { value: 'user', label: 'User' }, + { value: null, label: 'blank', hidden: true }, + ].map(userType => ( + + )); + return (
{ @@ -120,174 +196,64 @@ class SuccessFactorsIntegrationConfigForm extends React.Component { >
- - - + this.setState(prevState => ({ active: !prevState.active }))} - /> - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - -
-
-
-
- - - - + floatLabelLeft + > + Active + +
+ + {SUCCESS_FACTOR_FIELDS.map(this.renderField)} +
- - - SAP Success Factors User Type + - + > + {userTypeOptions} + + Type of SAP User (admin or user). + {invalidFields.userType && ( + }> + Success Factors' User Type is required. + + )} +
- - - + this.setState(prevState => ({ transmitTotalHours: !prevState.transmitTotalHours }))} - data-hj-suppress - /> - -
-
-
-
- - - - + floatLabelLeft + > + Transmit Total Hours? + + Include totalHours in the transmitted completion data. +
+ {this.renderField({ + key: 'additionalLocales', + helpText: "A comma separated list of any additional locales used in SAP (such as 'Dutch' or 'English Canadian'). See SAP's documentation for more examples.", + label: 'Additional Locales', + })}
diff --git a/src/components/MultipleFileInputField/MultipleFileInputField.jsx b/src/components/MultipleFileInputField/MultipleFileInputField.jsx index 47f7fe8eaf..6571ba9b5f 100644 --- a/src/components/MultipleFileInputField/MultipleFileInputField.jsx +++ b/src/components/MultipleFileInputField/MultipleFileInputField.jsx @@ -71,7 +71,6 @@ const MultipleFileInputField = ({ 'm-0', 'ml-2', )} - htmlFor="multiple-file-input-control" >Select files renders checked correctly 1`] = `
renders checked correctly 1`] = ` className="form-check-input position-static" defaultChecked={false} disabled={false} - id="id" type="checkbox" />
@@ -21,7 +20,7 @@ exports[` renders checked correctly 1`] = ` exports[` renders unchecked correctly 1`] = `
renders unchecked correctly 1`] = ` className="form-check-input position-static" defaultChecked={false} disabled={false} - id="id" type="checkbox" />
diff --git a/src/components/ReduxFormCheckbox/index.jsx b/src/components/ReduxFormCheckbox/index.jsx index f6971d15d9..108fa1d941 100644 --- a/src/components/ReduxFormCheckbox/index.jsx +++ b/src/components/ReduxFormCheckbox/index.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Form, ValidationFormGroup } from '@edx/paragon'; +import { Form } from '@edx/paragon'; const ReduxFormCheckbox = (props) => { const { @@ -12,19 +12,16 @@ const ReduxFormCheckbox = (props) => { } = props; return ( - + - + {helptext && {helptext}} + ); }; diff --git a/src/components/ReportingConfig/EmailDeliveryMethodForm.jsx b/src/components/ReportingConfig/EmailDeliveryMethodForm.jsx index 19ca516ed7..cfa2f1573f 100644 --- a/src/components/ReportingConfig/EmailDeliveryMethodForm.jsx +++ b/src/components/ReportingConfig/EmailDeliveryMethodForm.jsx @@ -1,6 +1,8 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Input, ValidationFormGroup } from '@edx/paragon'; +import { Form } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; + import isEmpty from 'lodash/isEmpty'; import isEmail from 'validator/lib/isEmail'; @@ -10,16 +12,13 @@ const EmailDeliveryMethodForm = ({ invalidFields, config, handleBlur }) => { return (
- - - Email(s) + handleBlur(e, () => { @@ -28,36 +27,48 @@ const EmailDeliveryMethodForm = ({ invalidFields, config, handleBlur }) => { return !isEmpty(emails); })} data-hj-suppress + autoResize /> - + The email(s), one per line, where the report should be sent + {invalidFields.emailRaw && ( + }> + Required. One email per line. Emails must be formatted properly (email@domain.com) + + )} + {config && ( -
- - + setChecked(!checked)} - /> -
+ floatLabelLeft + > + Change Password + + )} - - - Password + handleBlur(e)} data-hj-suppress /> - + + This password will be used to secure the zip file. It will be encrypted when stored in the database. + + {invalidFields.emailRaw && ( + }> + Required. Password must not be blank + + )} +
); diff --git a/src/components/ReportingConfig/ReportingConfigForm.jsx b/src/components/ReportingConfig/ReportingConfigForm.jsx index 2bdb3ab029..fccfdf1bbf 100644 --- a/src/components/ReportingConfig/ReportingConfigForm.jsx +++ b/src/components/ReportingConfig/ReportingConfigForm.jsx @@ -2,8 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; import { - ValidationFormGroup, Input, StatefulButton, Icon, Button, + Form, StatefulButton, Icon, Button, } from '@edx/paragon'; +import { Error } from '@edx/paragon/icons'; import SFTPDeliveryMethodForm from './SFTPDeliveryMethodForm'; import EmailDeliveryMethodForm from './EmailDeliveryMethodForm'; import SUBMIT_STATES from '../../data/constants/formSubmissions'; @@ -132,6 +133,78 @@ class ReportingConfigForm extends React.Component { this.setState({ submitState: SUBMIT_STATES.COMPLETE }); } + renderSelect = data => { + const { config, reportingConfigTypes } = this.props; + const { invalidFields } = this.state; + const options = data.options.map(userType => ( + + )); + const otherProps = { + ...data.multiple && { multiple: data.multiple }, + ...data.onChange && { onChange: data.onChange }, + }; + + return ( + + {data.label} + + {options} + + {data.helpText && {data.helpText}} + {invalidFields[data.key] && data.invalidMessage && ( + }> + {data.invalidMessage} + + )} + + ); + } + + renderNumberField = data => { + const { invalidFields } = this.state; + const { config } = this.props; + const otherProps = { + ...data.max && { max: data.max }, + ...data.min && { min: data.min }, + }; + return ( + + {data.label} + this.handleBlur(e)} + {...otherProps} + /> + {data.helpText} + {invalidFields[data.key] && data.invalidMessage && ( + }> + {data.invalidMessage} + + )} + + ); + } + render() { const { config, availableCatalogs, reportingConfigTypes } = this.props; const { @@ -156,149 +229,96 @@ class ReportingConfigForm extends React.Component { }} onChange={() => this.setState({ submitState: SUBMIT_STATES.DEFAULT })} > -
- - - this.setState(prevState => ({ active: !prevState.active }))} - /> - +
+
+ + this.setState(prevState => ({ active: !prevState.active }))} + floatLabelLeft + > + Active? + + +
- - - - - - - ({ label: item[1], value: item[0] }))} - /> - + {this.renderSelect({ + key: 'dataType', + helpText: 'The type of data this report should contain. If this is an old report, you will not be able to change this field, and should create a new report', + label: 'Data Type', + options: [...dataTypesOptions, ...selectedDataTypesOption], + disabled: config && !dataTypesOptionsValues.includes(config.dataType), + })} + {this.renderSelect({ + key: 'reportType', + helpText: 'The type this report should be sent as, e.g. CSV', + label: 'Report Type', + options: reportingConfigTypes.reportType.map(item => ({ label: item[1], value: item[0] })), + })}
- - - ({ label: item[1], value: item[0] }))} - onChange={e => this.setState({ deliveryMethod: e.target.value })} - /> - - - - ({ label: item[1], value: item[0] }))} - onChange={e => this.setState({ frequency: e.target.value })} - /> - + {this.renderSelect({ + key: 'deliveryMethod', + helpText: 'The method in which the data should be sent', + label: 'Delivery Method', + options: reportingConfigTypes.deliveryMethod.map(item => ({ label: item[1], value: item[0] })), + onChange: event => this.setState({ deliveryMethod: event.target.value }), + })} + {this.renderSelect({ + key: 'frequency', + helpText: 'The frequency interval (daily, weekly, or monthly) that the report should be sent', + label: 'Frequency', + options: reportingConfigTypes.frequency.map(item => ({ label: item[1], value: item[0] })), + defaultValue: frequency, + onChange: event => this.setState({ frequency: event.target.value }), + })}
- - - this.handleBlur(e)} - /> - + {this.renderNumberField({ + key: 'dayOfMonth', + helpText: 'The day of the month to send the report. This field is required and only valid when the frequency is monthly', + isInvalid: frequency === 'monthly' && invalidFields.dayOfMonth, + label: 'Day of Month', + max: MONTHLY_MAX, + min: MONTHLY_MIN, + disabled: !(frequency === 'monthly'), + onBlur: event => this.handleBlur(event), + })}
- - - ({ label: item[1], value: item[0] }))} - defaultValue={config ? config.dayOfWeek : undefined} - /> - + {this.renderSelect({ + key: 'dayOfWeek', + helpText: 'The day of the week to send the report. This field is required and only valid when the frequency is weekly', + label: 'Day of Week', + options: reportingConfigTypes.dayOfWeek.map(item => ({ label: item[1], value: item[0] })), + disabled: !(frequency === 'weekly'), + })}
- - - this.handleBlur(e)} - /> - + {this.renderNumberField({ + key: 'hourOfDay', + helpText: 'The hour of the day to send the report, in Eastern Standard Time (EST). This is required for all frequency settings', + invalidMessage: 'Required for all frequency types', + label: 'Hour of Day', + })}
- - - + PGP Encryption Key + - + The key for encyption, if PGP encrypted file is required + {deliveryMethod === 'email' && ( )}
- - - ({ - value: item.uuid, - label: `Catalog "${item.title}" with UUID "${item.uuid}"`, - })) - } - /> - + {this.renderSelect({ + key: 'enterpriseCustomerCatalogUuids', + helpText: 'The catalogs that should be included in the report. No selection means all catalogs will be included.', + label: 'Enterprise Customer Catalogs', + options: availableCatalogs.map(item => ({ + value: item.uuid, + label: `Catalog "${item.title}" with UUID "${item.uuid}"`, + })), + multiple: true, + defaultValue: selectedCatalogs, + })}
- - + {submitState === SUBMIT_STATES.ERROR && ( + }> + There was an error submitting, please try again. + + )} + {config && (