diff --git a/src/components/Assignments/ShadowAssignmentPointsTable/ShadowAssignmentPointsTable.js b/src/components/Assignments/ShadowAssignmentPointsTable/ShadowAssignmentPointsTable.js index 202a23762..82d70ac45 100644 --- a/src/components/Assignments/ShadowAssignmentPointsTable/ShadowAssignmentPointsTable.js +++ b/src/components/Assignments/ShadowAssignmentPointsTable/ShadowAssignmentPointsTable.js @@ -16,7 +16,7 @@ import SubmitButton from '../../forms/SubmitButton'; import DateTime from '../../widgets/DateTime'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Confirm from '../../forms/Confirm'; -import Icon, { BanIcon, EditIcon, DeleteIcon, SaveIcon, SquareIcon } from '../../icons'; +import Icon, { BanIcon, EditIcon, DeleteIcon, SquareIcon } from '../../icons'; import { createUserNameComparator } from '../../helpers/users.js'; import { arrayToObject, safeGet } from '../../../helpers/common.js'; import withLinks from '../../../helpers/withLinks.js'; @@ -128,7 +128,6 @@ class ShadowAssignmentPointsTable extends Component { hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/Groups/GroupsTree/GroupsTreeNode.js b/src/components/Groups/GroupsTree/GroupsTreeNode.js index d4bc82566..5403c17e4 100644 --- a/src/components/Groups/GroupsTree/GroupsTreeNode.js +++ b/src/components/Groups/GroupsTree/GroupsTreeNode.js @@ -11,6 +11,7 @@ import Icon, { GroupIcon, GroupExamsIcon, LoadingIcon } from '../../icons'; import withLinks from '../../../helpers/withLinks.js'; import { isRegularObject } from '../../../helpers/common.js'; import { isExam } from '../../../helpers/groups.js'; +import { OverlayTrigger, Popover } from 'react-bootstrap'; /** * Assemble the right CSS classes for the list item. @@ -30,7 +31,22 @@ const prepareClassList = lruMemoize((clickable, archived) => { const DEFAULT_ICON = ['far', 'square']; -const clickEventDisipator = ev => ev.stopPropagation(); +const clickEventDissipator = ev => ev.stopPropagation(); + +const adminsList = (primaryAdmins, autoloadAuthors, simpleClassName = '') => + primaryAdmins.map(admin => ( + + {isRegularObject(admin) ? ( + + {admin.firstName} {admin.lastName} + + ) : autoloadAuthors ? ( + + ) : ( + + )} + + )); const GroupsTreeNode = React.memo( ({ group, selectedGroupId = null, autoloadAuthors = false, isExpanded = false, buttonsCreator, links }) => { @@ -69,21 +85,32 @@ const GroupsTreeNode = React.memo( {primaryAdmins && primaryAdmins.length > 0 && ( ( - - {primaryAdmins.map(admin => ( - - {isRegularObject(admin) ? ( - - {admin.firstName} {admin.lastName} - - ) : autoloadAuthors ? ( - - ) : ( - - )} - - ))} - + {primaryAdmins.length > 2 ? ( + + + + : + + {adminsList(primaryAdmins, autoloadAuthors, 'd-block')} + + }> + + + + + ) : ( + {adminsList(primaryAdmins, autoloadAuthors)} + )} ) )} @@ -156,7 +183,7 @@ const GroupsTreeNode = React.memo( )} {buttonsCreator && ( - + {buttonsCreator(group, selectedGroupId, links)} )} diff --git a/src/components/Groups/helpers/GroupInfoTable.js b/src/components/Groups/helpers/GroupInfoTable.js index b810f45a5..fe01e2f8a 100644 --- a/src/components/Groups/helpers/GroupInfoTable.js +++ b/src/components/Groups/helpers/GroupInfoTable.js @@ -1,18 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage, FormattedNumber } from 'react-intl'; import { Table } from 'react-bootstrap'; import Box from '../../widgets/Box'; import Callout from '../../widgets/Callout'; import Markdown from '../../widgets/Markdown'; -import { SuccessOrFailureIcon } from '../../icons'; +import { InstanceIcon, SuccessOrFailureIcon } from '../../icons'; import { getLocalizedDescription } from '../../../helpers/localizedData.js'; -import { objectMap, identity } from '../../../helpers/common.js'; +import ResourceRenderer from '../../helpers/ResourceRenderer/ResourceRenderer.js'; -const knownBindingProviderLabels = { - sis: , -}; +import { getConfigVar } from '../../../helpers/config.js'; +import InsetPanel from '../../widgets/InsetPanel/InsetPanel.js'; const getDescription = (localizedTexts, locale) => { const description = getLocalizedDescription({ localizedTexts }, locale); @@ -28,117 +28,144 @@ const getDescription = (localizedTexts, locale) => { ); }; +const EXTERNAL_ATTRIBUTES = getConfigVar('EXTERNAL_ATTRIBUTES', {}); + +const getLocalizedLabel = (label, locale) => { + if (typeof label === 'object') { + if (locale in label) { + return label[locale]; + } + if ('en' in label) { + return label.en; + } + const keys = Object.keys(label); + if (keys.length > 0) { + return label[keys[0]]; + } + } + return typeof label === 'string' ? label : null; +}; + +const translateAttributeService = (service, locale) => { + const name = EXTERNAL_ATTRIBUTES[service]?.NAME; + return (name && getLocalizedLabel(name, locale)) || null; +}; + +const translateAttributeKey = (service, key, locale) => { + const name = EXTERNAL_ATTRIBUTES[service]?.KEYS[key]; + return (name && getLocalizedLabel(name, locale)) || null; +}; + const GroupInfoTable = ({ - group: { externalId, organizational, localizedTexts, public: isPublic = false, privateData }, + group: { organizational, localizedTexts, public: isPublic = false, privateData }, + externalAttributes, isAdmin, locale, }) => (
} - description={getDescription(localizedTexts, locale)} type="primary" collapsable noPadding unlimitedHeight> - - - {!organizational && privateData && ( - - - - - )} - {!organizational && ( - - - - - )} - {privateData && Boolean(privateData.threshold) && !organizational && ( - - - - - )} - {privateData && Boolean(privateData.pointsLimit) && !organizational && ( - - - - - )} - {Boolean(externalId) && ( - - - - - )} + <> + {getDescription(localizedTexts, locale)} +
- - : - - -
- - : - - -
- - : - - -
- - : - - -
- - : - - {externalId} -
+ + {!organizational && privateData && ( + + + + + )} + {!organizational && ( + + + + + )} + {privateData && Boolean(privateData.threshold) && !organizational && ( + + + + + )} + {privateData && Boolean(privateData.pointsLimit) && !organizational && ( + + + + + )} + +
+ + : + + +
+ + : + + +
+ + : + + +
+ + : + + +
- {privateData && - privateData.bindings && - Object.values( - objectMap(privateData.bindings, (codes, provider) => - codes && codes.length > 0 ? ( - - - {knownBindingProviderLabels[provider] || ( - - )} - : + {externalAttributes && ( + + {attributes => ( + + + + - - ) : null - ) - ).filter(identity)} - -
+ - {codes.map(code => ( -
- {code} -
- ))} -
+ + + {attributes.map(({ id, service, key, value }) => ( + + + + {translateAttributeService(service, locale) || {service}} + + ❭ + + {translateAttributeKey(service, key, locale) || {key}} + + ❭ + + {value} + + + ))} + + + )} +
+ )} +
{isPublic && isAdmin && ( @@ -153,7 +180,6 @@ const GroupInfoTable = ({ GroupInfoTable.propTypes = { group: PropTypes.shape({ - externalId: PropTypes.string, parentGroupId: PropTypes.string, threshold: PropTypes.number, public: PropTypes.bool.isRequired, @@ -163,9 +189,9 @@ GroupInfoTable.propTypes = { threshold: PropTypes.number, pointsLimit: PropTypes.number, publicStats: PropTypes.bool.isRequired, - bindings: PropTypes.object, }), }), + externalAttributes: ImmutablePropTypes.map, isAdmin: PropTypes.bool, locale: PropTypes.string.isRequired, }; diff --git a/src/components/Pipelines/BoxForm/BoxForm.js b/src/components/Pipelines/BoxForm/BoxForm.js index e42d7a28b..c8429d6e7 100644 --- a/src/components/Pipelines/BoxForm/BoxForm.js +++ b/src/components/Pipelines/BoxForm/BoxForm.js @@ -10,7 +10,7 @@ import { TextField, SelectField } from '../../forms/Fields'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import InsetPanel from '../../widgets/InsetPanel'; import SubmitButton from '../../forms/SubmitButton'; -import { CloseIcon, SaveIcon, RefreshIcon, InputIcon, OutputIcon } from '../../../components/icons'; +import { CloseIcon, RefreshIcon, InputIcon, OutputIcon } from '../../../components/icons'; import { encodeId, safeSet } from '../../../helpers/common.js'; import { getBoxTypeDescription } from '../comments.js'; @@ -223,9 +223,8 @@ class BoxForm extends Component { submitting={submitting} invalid={invalid} dirty={dirty} - hasSuceeded={submitSucceeded} + hasSucceeded={submitSucceeded} reset={reset} - defaultIcon={} messages={{ success: , submit: , diff --git a/src/components/Pipelines/VariableForm/VariableForm.js b/src/components/Pipelines/VariableForm/VariableForm.js index 30d2cdd0e..95b33fe96 100644 --- a/src/components/Pipelines/VariableForm/VariableForm.js +++ b/src/components/Pipelines/VariableForm/VariableForm.js @@ -10,7 +10,7 @@ import { Modal } from 'react-bootstrap'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; import SubmitButton from '../../forms/SubmitButton'; -import { CloseIcon, SaveIcon, RefreshIcon } from '../../../components/icons'; +import { CloseIcon, RefreshIcon } from '../../../components/icons'; import { KNOWN_DATA_TYPES, isArrayType } from '../../../helpers/pipelines.js'; export const newVariableInitialData = { @@ -133,9 +133,8 @@ class VariableForm extends Component { submitting={submitting} invalid={invalid} dirty={dirty} - hasSuceeded={submitSucceeded} + hasSucceeded={submitSucceeded} reset={reset} - defaultIcon={} messages={{ success: , submit: , diff --git a/src/components/SisIntegration/ArchiveTermGroups/ArchiveTermGroups.js b/src/components/SisIntegration/ArchiveTermGroups/ArchiveTermGroups.js deleted file mode 100644 index 36d45ea71..000000000 --- a/src/components/SisIntegration/ArchiveTermGroups/ArchiveTermGroups.js +++ /dev/null @@ -1,159 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Modal, Table } from 'react-bootstrap'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Field, reduxForm } from 'redux-form'; - -import { SimpleCheckboxField } from '../../forms/Fields'; -import SubmitButton from '../../forms/SubmitButton'; -import InsetPanel from '../../widgets/InsetPanel'; -import Button, { TheButtonGroup } from '../../widgets/TheButton'; -import Callout from '../../widgets/Callout'; -import { CloseIcon, SquareIcon } from '../../icons'; - -class ArchiveTermGroups extends Component { - checkAllGroups = () => { - const { groups, change } = this.props; - groups.forEach(({ id }) => { - change(`groups.${id}`, true); - }); - }; - - render() { - const { - isOpen, - onClose, - externalId, - groups, - submitting, - handleSubmit, - error, - onSubmit, - dirty = false, - submitFailed = false, - submitSucceeded = false, - invalid, - reset, - } = this.props; - - return ( - - - - {externalId} - - - - {groups.length > 0 ? ( - - - {groups.map(({ id, name }) => ( - - - - - ))} - -
- - {name}
- ) : ( - - - - )} - - {submitFailed && ( - - - - )} - - {error && {error}} -
- -
- - {groups.length > 0 && ( - <> - - - onSubmit(data).then(reset))} - submitting={submitting} - dirty={dirty} - hasSucceeded={submitSucceeded} - hasFailed={submitFailed} - disabled={invalid} - messages={{ - submit: ( - - ), - submitting: , - success: , - }} - /> - - )} - - - -
-
-
- ); - } -} - -ArchiveTermGroups.propTypes = { - handleSubmit: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - submitFailed: PropTypes.bool, - error: PropTypes.object, - dirty: PropTypes.bool, - submitSucceeded: PropTypes.bool, - submitting: PropTypes.bool, - invalid: PropTypes.bool, - reset: PropTypes.func, - change: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, - externalId: PropTypes.string, - groups: PropTypes.array.isRequired, - intl: PropTypes.object.isRequired, -}; - -const validate = ({ groups }) => { - const errors = {}; - - if (groups && groups.length > 0 && Object.values(groups).every(group => group === false)) { - errors._error = ( - - ); - } - - return errors; -}; - -export default reduxForm({ - form: 'archive-sis-term', - enableReinitialize: true, - keepDirtyOnReinitialize: false, - validate, -})(injectIntl(ArchiveTermGroups)); diff --git a/src/components/SisIntegration/ArchiveTermGroups/index.js b/src/components/SisIntegration/ArchiveTermGroups/index.js deleted file mode 100644 index b233d77f2..000000000 --- a/src/components/SisIntegration/ArchiveTermGroups/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import ArchiveTermGroups from './ArchiveTermGroups.js'; -export default ArchiveTermGroups; diff --git a/src/components/SisIntegration/CourseLabel/CourseLabel.js b/src/components/SisIntegration/CourseLabel/CourseLabel.js deleted file mode 100644 index d2296face..000000000 --- a/src/components/SisIntegration/CourseLabel/CourseLabel.js +++ /dev/null @@ -1,106 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { OverlayTrigger, Tooltip, Badge } from 'react-bootstrap'; - -import Icon, { GroupIcon } from '../../icons'; - -const days = { - cs: ['Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne'], - en: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], -}; - -const oddEven = { - cs: ['lichý', 'sudý'], - en: ['odd', 'even'], -}; - -export const getLocalizedData = (obj, locale) => { - if (obj && obj[locale]) { - return obj[locale]; - } else if (obj && Object.keys(obj).length > 0) { - return Object.keys(obj)[0]; - } else { - return null; - } -}; - -const MAIN_STYLE = { display: 'inline-block', width: '100%' }; -const SCHEDULING_STYLE = { display: 'inline-block', minWidth: '10em', wordSpacing: '0.25em' }; - -const CourseLabel = ({ - type, - captions, - code, - dayOfWeek, - time, - fortnightly, - oddWeeks, - room, - groupsCount = 0, - intl: { locale }, -}) => ( - - - ) : ( - - ) - } - /> - {dayOfWeek !== null || time !== null || room !== null ? ( - - - {getLocalizedData(days, locale)[dayOfWeek]} {time} - {fortnightly ? ({getLocalizedData(oddEven, locale)[oddWeeks ? 0 : 1]}) : ''} {room} - - - ) : ( - - - - - - )} - {getLocalizedData(captions, locale)} ({code}) - {groupsCount > 0 && ( - - - - }> - - {groupsCount > 1 && {groupsCount}x} - 1} /> - - - )} - -); - -CourseLabel.propTypes = { - type: PropTypes.string.isRequired, - captions: PropTypes.object.isRequired, - code: PropTypes.string, - dayOfWeek: PropTypes.number, - time: PropTypes.string, - fortnightly: PropTypes.bool, - oddWeeks: PropTypes.bool, - room: PropTypes.string, - groupsCount: PropTypes.number, - intl: PropTypes.object.isRequired, -}; - -export default injectIntl(CourseLabel); diff --git a/src/components/SisIntegration/CourseLabel/index.js b/src/components/SisIntegration/CourseLabel/index.js deleted file mode 100644 index b09b11c24..000000000 --- a/src/components/SisIntegration/CourseLabel/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import CourseLabel, { getLocalizedData } from './CourseLabel.js'; -export { getLocalizedData }; -export default CourseLabel; diff --git a/src/components/SisIntegration/EditTerm/EditTerm.js b/src/components/SisIntegration/EditTerm/EditTerm.js deleted file mode 100644 index 9a31992fa..000000000 --- a/src/components/SisIntegration/EditTerm/EditTerm.js +++ /dev/null @@ -1,138 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Modal } from 'react-bootstrap'; -import { FormattedMessage } from 'react-intl'; -import { Field, reduxForm } from 'redux-form'; - -import DatetimeField from '../../forms/Fields/DatetimeField.js'; -import SubmitButton from '../../forms/SubmitButton'; -import Button from '../../widgets/TheButton'; -import { CloseIcon, SaveIcon } from '../../icons'; - -const EditTerm = ({ - isOpen, - onClose, - submitting, - handleSubmit, - onSubmit, - dirty = false, - submitFailed = false, - submitSucceeded = false, - invalid, - reset, -}) => ( - - - - - - - - } - /> - } - /> - - } - /> - - - onSubmit(data).then(reset))} - submitting={submitting} - dirty={dirty} - hasSucceeded={submitSucceeded} - hasFailed={submitFailed} - invalid={invalid} - defaultIcon={} - messages={{ - submit: , - submitting: , - success: , - }} - /> - - - - -); - -EditTerm.propTypes = { - handleSubmit: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - submitFailed: PropTypes.bool, - dirty: PropTypes.bool, - submitSucceeded: PropTypes.bool, - submitting: PropTypes.bool, - invalid: PropTypes.bool, - reset: PropTypes.func, - onClose: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, -}; - -const validate = ({ beginning, end, advertiseUntil }) => { - const errors = {}; - - if (!beginning) { - errors.beginning = ( - - ); - } - - if (!end) { - errors.end = ( - - ); - } - - if (!advertiseUntil) { - errors.advertiseUntil = ( - - ); - } - - const bDate = new Date(beginning * 1000); - const eDate = new Date(end * 1000); - const aDate = new Date(advertiseUntil * 1000); - - if (aDate < bDate || aDate > eDate) { - errors.advertiseUntil = ( - - ); - } - - return errors; -}; - -export default reduxForm({ - form: 'edit-sis-term', - enableReinitialize: true, - keepDirtyOnReinitialize: false, - validate, -})(EditTerm); diff --git a/src/components/SisIntegration/EditTerm/index.js b/src/components/SisIntegration/EditTerm/index.js deleted file mode 100644 index cbb3a8981..000000000 --- a/src/components/SisIntegration/EditTerm/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import EditTerm from './EditTerm.js'; -export default EditTerm; diff --git a/src/components/SisIntegration/PlantTermGroups/PlantTermGroups.js b/src/components/SisIntegration/PlantTermGroups/PlantTermGroups.js deleted file mode 100644 index 70c1564ff..000000000 --- a/src/components/SisIntegration/PlantTermGroups/PlantTermGroups.js +++ /dev/null @@ -1,234 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Modal, Table } from 'react-bootstrap'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Field, FieldArray, reduxForm } from 'redux-form'; -import { lruMemoize } from 'reselect'; - -import DeleteGroupButtonContainer from '../../../containers/DeleteGroupButtonContainer'; -import LocalizedTextsFormField from '../../forms/LocalizedTextsFormField'; -import { TextField, SimpleCheckboxField } from '../../forms/Fields'; -import SubmitButton from '../../forms/SubmitButton'; -import Button from '../../widgets/TheButton'; -import Callout from '../../widgets/Callout'; -import Icon, { CloseIcon } from '../../icons'; -import { getLocalizedName, validateLocalizedTextsFormData } from '../../../helpers/localizedData.js'; -import { arrayToObject } from '../../../helpers/common.js'; - -const SEMESTER_LOCALIZATIONS = { - 1: [ - { - locale: 'en', - name: '1-Winter', - description: 'Winter Term', - }, - { - locale: 'cs', - name: '1-ZS', - description: 'Zimní semestr', - }, - ], - 2: [ - { - locale: 'en', - name: '2-Summer', - description: 'Summer Term', - }, - { - locale: 'cs', - name: '2-LS', - description: 'Letní semestr', - }, - ], -}; - -const fullAcademicYear = year => - String(year).match(/^[12][0-9]{3}$/) ? `${year}/${Number(String(year).substr(2)) + 1}` : year; - -export const createDefaultSemesterLocalization = (year, term) => { - year = fullAcademicYear(year); - return SEMESTER_LOCALIZATIONS[term].map(({ locale, name, description }) => ({ - locale, - name: `${year} ${name}`, - description: `${description} ${year}`, - })); -}; - -const getExistingSemestralGroups = lruMemoize((groups, rootGroups, externalId) => { - const result = arrayToObject( - rootGroups, - g => g.id, - () => [] - ); - groups - .filter(group => group.externalId === externalId) - .filter(group => result[group.parentGroupId]) - .forEach(group => result[group.parentGroupId].push(group)); - return result; -}); - -const PlantTermGroups = ({ - isOpen, - onClose, - externalId, - groups, - rootGroups, - submitting, - handleSubmit, - error, - onSubmit, - dirty = false, - submitFailed = false, - submitSucceeded = false, - invalid, - reset, - intl: { locale }, -}) => { - const existingSemestralGroups = getExistingSemestralGroups(groups, rootGroups, externalId); - - return ( - - - - - - - - - - {rootGroups.map(group => ( - - - - - - ))} - -
- {existingSemestralGroups[group.id].length === 0 ? ( - - ) : ( - - )} - - {existingSemestralGroups[group.id].length === 0 - ? getLocalizedName(group, locale) - : existingSemestralGroups[group.id].map(existGroup => ( -
- {getLocalizedName(group, locale)} / {getLocalizedName(existGroup, locale)} -
- ))} -
- {existingSemestralGroups[group.id].map(existGroup => ( - 0} - /> - ))} -
- -
- - - - } - /> - - {submitFailed && ( - - - - )} - - {error && {error}} -
- -
- onSubmit(data).then(reset))} - submitting={submitting} - dirty={dirty} - hasSucceeded={submitSucceeded} - hasFailed={submitFailed} - disabled={invalid} - messages={{ - submit: , - submitting: , - success: , - }} - /> - - -
-
-
- ); -}; - -PlantTermGroups.propTypes = { - handleSubmit: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - submitFailed: PropTypes.bool, - error: PropTypes.object, - dirty: PropTypes.bool, - submitSucceeded: PropTypes.bool, - submitting: PropTypes.bool, - invalid: PropTypes.bool, - reset: PropTypes.func, - onClose: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, - externalId: PropTypes.string, - groups: PropTypes.array.isRequired, - rootGroups: PropTypes.array.isRequired, - intl: PropTypes.object.isRequired, -}; - -const validate = ({ groups, localizedTexts }) => { - const errors = {}; - - if (groups && Object.values(groups).every(group => group === false)) { - errors._error = ( - - ); - } - - validateLocalizedTextsFormData(errors, localizedTexts, ({ name }) => { - const textErrors = {}; - if (!name.trim()) { - textErrors.name = ( - - ); - } - return textErrors; - }); - - return errors; -}; - -export default reduxForm({ - form: 'plant-sis-term', - enableReinitialize: true, - keepDirtyOnReinitialize: false, - validate, -})(injectIntl(PlantTermGroups)); diff --git a/src/components/SisIntegration/PlantTermGroups/index.js b/src/components/SisIntegration/PlantTermGroups/index.js deleted file mode 100644 index cf0dd8c53..000000000 --- a/src/components/SisIntegration/PlantTermGroups/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import PlantTermGroups, { createDefaultSemesterLocalization } from './PlantTermGroups.js'; -export { createDefaultSemesterLocalization }; -export default PlantTermGroups; diff --git a/src/components/SisIntegration/TermsList/TermsList.js b/src/components/SisIntegration/TermsList/TermsList.js deleted file mode 100644 index b3c277027..000000000 --- a/src/components/SisIntegration/TermsList/TermsList.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Table } from 'react-bootstrap'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import TermsListItem from '../TermsListItem'; - -const TermsList = ({ terms = [], createActions, intl, ...rest }) => ( - - - - - - - - - - - - {terms.map((term, i) => ( - - ))} - - {terms.length === 0 && ( - - - - )} - -
- - - - - - - - - - - -
- -
-); - -TermsList.propTypes = { - terms: PropTypes.array, - createActions: PropTypes.func, - intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired, -}; - -export default injectIntl(TermsList); diff --git a/src/components/SisIntegration/TermsList/index.js b/src/components/SisIntegration/TermsList/index.js deleted file mode 100644 index 3262d749d..000000000 --- a/src/components/SisIntegration/TermsList/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import TermsList from './TermsList.js'; -export default TermsList; diff --git a/src/components/SisIntegration/TermsListItem/TermsListItem.js b/src/components/SisIntegration/TermsListItem/TermsListItem.js deleted file mode 100644 index 81d10adf7..000000000 --- a/src/components/SisIntegration/TermsListItem/TermsListItem.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; - -import Icon from '../../icons'; -import DateTime from '../../widgets/DateTime'; - -const TermsListItem = ({ data, createActions }) => ( - - - {data.beginning * 1000 <= Date.now() && Date.now() <= data.end * 1000 && ( - - )} - - {data.year} - - {data.term === 1 && } - {data.term === 2 && } - {data.term !== 1 && data.term !== 2 && {data.term}} - - - - - - - - - - - {createActions && createActions(data.id, data)} - -); - -TermsListItem.propTypes = { - data: PropTypes.shape({ - id: PropTypes.string.isRequired, - year: PropTypes.number.isRequired, - term: PropTypes.number.isRequired, - beginning: PropTypes.number, - end: PropTypes.number, - advertiseUntil: PropTypes.number, - }), - createActions: PropTypes.func, -}; - -export default TermsListItem; diff --git a/src/components/SisIntegration/TermsListItem/index.js b/src/components/SisIntegration/TermsListItem/index.js deleted file mode 100644 index 2250dcdf0..000000000 --- a/src/components/SisIntegration/TermsListItem/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import TermsListItem from './TermsListItem.js'; -export default TermsListItem; diff --git a/src/components/Solutions/TestResultsTable/TestResultsTableRow.js b/src/components/Solutions/TestResultsTable/TestResultsTableRow.js index 897e553ef..0e92f9cd9 100644 --- a/src/components/Solutions/TestResultsTable/TestResultsTableRow.js +++ b/src/components/Solutions/TestResultsTable/TestResultsTableRow.js @@ -195,7 +195,7 @@ const TestResultsTableRow = ({ {(showJudgeLogStdout || showJudgeLogStderr) && ( - + {toggleLogOpen && ((judgeLogStdout && showJudgeLogStdout) || (judgeLogStderr && showJudgeLogStderr)) && ( } messages={{ submit: , submitting: , diff --git a/src/components/forms/AddSisTermForm/AddSisTermForm.js b/src/components/forms/AddSisTermForm/AddSisTermForm.js deleted file mode 100644 index e53fb85c1..000000000 --- a/src/components/forms/AddSisTermForm/AddSisTermForm.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; -import { reduxForm, Field } from 'redux-form'; - -import Callout from '../../widgets/Callout'; -import FormBox from '../../widgets/FormBox'; -import { SaveIcon } from '../../icons'; -import SubmitButton from '../SubmitButton'; -import { SelectField, NumericTextField } from '../Fields'; - -const messages = defineMessages({ - summerTerm: { - id: 'app.addSisTermForm.summer', - defaultMessage: 'Summer term', - }, - winterTerm: { - id: 'app.addSisTermForm.winter', - defaultMessage: 'Winter term', - }, -}); - -const AddSisTermForm = ({ - submitting, - handleSubmit, - anyTouched, - submitFailed = false, - submitSucceeded = false, - invalid, - intl: { formatMessage }, -}) => ( - } - type={submitSucceeded ? 'success' : undefined} - isOpen={true} - collapsable={false} - footer={ -
- } - messages={{ - submit: , - submitting: , - success: , - }} - /> -
- }> - {submitFailed && ( - - - - )} - - } - ignoreDirty - /> - } - options={[ - { name: formatMessage(messages.winterTerm), key: 1 }, - { name: formatMessage(messages.summerTerm), key: 2 }, - ]} - addEmptyOption - ignoreDirty - /> -
-); - -AddSisTermForm.propTypes = { - handleSubmit: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - submitFailed: PropTypes.bool, - anyTouched: PropTypes.bool, - submitSucceeded: PropTypes.bool, - submitting: PropTypes.bool, - invalid: PropTypes.bool, - intl: PropTypes.object.isRequired, -}; - -export default injectIntl( - reduxForm({ - form: 'add-sis-term', - })(AddSisTermForm) -); diff --git a/src/components/forms/AddSisTermForm/index.js b/src/components/forms/AddSisTermForm/index.js deleted file mode 100644 index 4c2720d77..000000000 --- a/src/components/forms/AddSisTermForm/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import AddSisTermForm from './AddSisTermForm.js'; -export default AddSisTermForm; diff --git a/src/components/forms/CreateExerciseForm/CreateExerciseForm.js b/src/components/forms/CreateExerciseForm/CreateExerciseForm.js index e0b2d6af5..5b74afb4a 100644 --- a/src/components/forms/CreateExerciseForm/CreateExerciseForm.js +++ b/src/components/forms/CreateExerciseForm/CreateExerciseForm.js @@ -7,7 +7,7 @@ import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; import { SelectField } from '../Fields'; import SubmitButton from '../SubmitButton'; -import { WarningIcon } from '../../../components/icons'; +import { SendIcon, WarningIcon } from '../../../components/icons'; import withLinks from '../../../helpers/withLinks.js'; @@ -52,6 +52,7 @@ class CreateExerciseForm extends Component { hasFailed={submitFailed} handleSubmit={handleSubmit} noShadow + defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/CreateUserForm/CreateUserForm.js b/src/components/forms/CreateUserForm/CreateUserForm.js index 6ea6c2477..0695c1958 100644 --- a/src/components/forms/CreateUserForm/CreateUserForm.js +++ b/src/components/forms/CreateUserForm/CreateUserForm.js @@ -9,7 +9,7 @@ import SubmitButton from '../SubmitButton'; import Callout from '../../widgets/Callout'; import Explanation from '../../widgets/Explanation'; import UsersName from '../../Users/UsersName'; -import { WarningIcon } from '../../icons'; +import { SendIcon, WarningIcon } from '../../icons'; import { validateRegistrationData } from '../../../redux/modules/users.js'; import { TextField, PasswordField, PasswordStrength, CheckboxField } from '../Fields'; @@ -149,6 +149,7 @@ const CreateUserForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} asyncValidating={asyncValidating} + defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditAssignmentForm/EditAssignmentForm.js b/src/components/forms/EditAssignmentForm/EditAssignmentForm.js index 45a2f94f6..d56aa966d 100644 --- a/src/components/forms/EditAssignmentForm/EditAssignmentForm.js +++ b/src/components/forms/EditAssignmentForm/EditAssignmentForm.js @@ -6,7 +6,7 @@ import { Container, Row, Col } from 'react-bootstrap'; import moment from 'moment'; import { lruMemoize } from 'reselect'; -import { SaveIcon, WarningIcon } from '../../icons'; +import { WarningIcon } from '../../icons'; import { DatetimeField, CheckboxField, RadioField, NumericTextField } from '../Fields'; import LocalizedTextsFormField from '../LocalizedTextsFormField'; import SubmitButton from '../SubmitButton'; @@ -917,7 +917,6 @@ class EditAssignmentForm extends Component { hasFailed={submitFailed} handleSubmit={handleSubmit(this.onSubmitWrapper)} asyncValidating={asyncValidating} - defaultIcon={} messages={submitButtonMessages} /> diff --git a/src/components/forms/EditExerciseAdvancedConfigForm/EditExerciseAdvancedConfigForm.js b/src/components/forms/EditExerciseAdvancedConfigForm/EditExerciseAdvancedConfigForm.js index f765087ed..5137e6cb2 100644 --- a/src/components/forms/EditExerciseAdvancedConfigForm/EditExerciseAdvancedConfigForm.js +++ b/src/components/forms/EditExerciseAdvancedConfigForm/EditExerciseAdvancedConfigForm.js @@ -9,7 +9,7 @@ import classnames from 'classnames'; import FormBox from '../../widgets/FormBox'; import Button from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; -import { RefreshIcon, SaveIcon } from '../../icons'; +import { RefreshIcon } from '../../icons'; import SubmitButton from '../SubmitButton'; import EditExerciseAdvancedConfigTest from './EditExerciseAdvancedConfigTest.js'; @@ -68,7 +68,6 @@ class EditExerciseAdvancedConfigForm extends Component { hasSucceeded={submitSucceeded} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} messages={SUBMIT_BUTTON_MESSAGES} />
diff --git a/src/components/forms/EditExerciseForm/EditExerciseForm.js b/src/components/forms/EditExerciseForm/EditExerciseForm.js index bbc0e0c99..c2c5301ad 100644 --- a/src/components/forms/EditExerciseForm/EditExerciseForm.js +++ b/src/components/forms/EditExerciseForm/EditExerciseForm.js @@ -14,7 +14,6 @@ import { LocalizedExerciseName } from '../../helpers/LocalizedNames'; import { validateExercise } from '../../../redux/modules/exercises.js'; import { validateLocalizedTextsFormData } from '../../../helpers/localizedData.js'; import Explanation from '../../widgets/Explanation'; -import { SaveIcon } from '../../icons'; import withLinks from '../../../helpers/withLinks.js'; const messages = defineMessages({ @@ -72,7 +71,6 @@ const EditExerciseForm = ({ hasFailed={submitFailed} handleSubmit={handleSubmit} asyncValidating={asyncValidating} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js index b3b1c6470..b99db7b7f 100644 --- a/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js +++ b/src/components/forms/EditExerciseSimpleConfigForm/EditExerciseSimpleConfigForm.js @@ -8,7 +8,7 @@ import { lruMemoize } from 'reselect'; import FormBox from '../../widgets/FormBox'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; -import { RefreshIcon, SaveIcon } from '../../icons'; +import { RefreshIcon } from '../../icons'; import SubmitButton from '../SubmitButton'; import EditExerciseSimpleConfigTest from './EditExerciseSimpleConfigTest.js'; @@ -82,7 +82,7 @@ const nonDefaultSuccessExitCodes = obj => { */ const validateFileExists = (data, errors, path, existingFiles) => { if (!existingFiles) { - return; // safeguard if the suplementary files are not loaded yet + return; // safeguard if the supplementary files are not loaded yet } let target = safeGet(data, path); @@ -248,7 +248,6 @@ class EditExerciseSimpleConfigForm extends Component { hasSucceeded={submitSucceeded} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} messages={SUBMIT_BUTTON_MESSAGES} /> diff --git a/src/components/forms/EditGroupForm/EditGroupForm.js b/src/components/forms/EditGroupForm/EditGroupForm.js index 3d7b4bc5e..c465c58ae 100644 --- a/src/components/forms/EditGroupForm/EditGroupForm.js +++ b/src/components/forms/EditGroupForm/EditGroupForm.js @@ -10,9 +10,9 @@ import FormBox from '../../widgets/FormBox'; import Explanation from '../../widgets/Explanation'; import SubmitButton from '../SubmitButton'; import LocalizedTextsFormField from '../LocalizedTextsFormField'; -import { RefreshIcon } from '../../icons'; +import { RefreshIcon, SendIcon } from '../../icons'; -import { TextField, CheckboxField, NumericTextField } from '../Fields'; +import { CheckboxField, NumericTextField } from '../Fields'; import { getLocalizedTextsInitialValues, validateLocalizedTextsFormData } from '../../../helpers/localizedData.js'; export const EDIT_GROUP_FORM_LOCALIZED_TEXTS_DEFAULT = { @@ -80,6 +80,7 @@ const EditGroupForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} + defaultIcon={createNew ? : null} messages={{ submit: createNew ? ( @@ -108,18 +109,7 @@ const EditGroupForm = ({ {isSuperAdmin && ( - - } - /> - - + } messages={{ submit: , submitting: , diff --git a/src/components/forms/EditLimitsForm/EditLimitsForm.js b/src/components/forms/EditLimitsForm/EditLimitsForm.js index 1a2f431a4..84c9b45e8 100644 --- a/src/components/forms/EditLimitsForm/EditLimitsForm.js +++ b/src/components/forms/EditLimitsForm/EditLimitsForm.js @@ -10,7 +10,7 @@ import SubmitButton from '../SubmitButton'; import FormBox from '../../widgets/FormBox'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; -import { InfoIcon, SaveIcon, RefreshIcon } from '../../icons'; +import { InfoIcon, RefreshIcon } from '../../icons'; import { encodeId, encodeNumId, identity } from '../../../helpers/common.js'; import { validateLimitsTimeTotals } from '../../../helpers/exercise/limits.js'; @@ -62,7 +62,6 @@ class EditLimitsForm extends Component { hasSucceeded={submitSucceeded} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} messages={{ submit: , submitting: ( diff --git a/src/components/forms/EditPipelineEnvironmentsForm/EditPipelineEnvironmentsForm.js b/src/components/forms/EditPipelineEnvironmentsForm/EditPipelineEnvironmentsForm.js index 66569f936..3097efa07 100644 --- a/src/components/forms/EditPipelineEnvironmentsForm/EditPipelineEnvironmentsForm.js +++ b/src/components/forms/EditPipelineEnvironmentsForm/EditPipelineEnvironmentsForm.js @@ -4,7 +4,6 @@ import { reduxForm } from 'redux-form'; import { FormattedMessage } from 'react-intl'; import EditEnvironmentList from '../EditEnvironmentSimpleForm/EditEnvironmentList.js'; -import { SaveIcon } from '../../icons'; import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; import SubmitButton from '../SubmitButton'; @@ -32,7 +31,6 @@ class EditPipelineEnvironmentsForm extends Component { hasSucceeded={submitSucceeded} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditPipelineForm/EditPipelineForm.js b/src/components/forms/EditPipelineForm/EditPipelineForm.js index b1e665bbb..a66418a63 100644 --- a/src/components/forms/EditPipelineForm/EditPipelineForm.js +++ b/src/components/forms/EditPipelineForm/EditPipelineForm.js @@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl'; import { Container, Row, Col } from 'react-bootstrap'; import { TextField, MarkdownTextAreaField, CheckboxField } from '../Fields'; -import { SaveIcon } from '../../icons'; import FormBox from '../../widgets/FormBox'; import SubmitButton from '../SubmitButton'; @@ -35,7 +34,6 @@ class EditPipelineForm extends Component { hasSucceeded={submitSucceeded} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditShadowAssignmentForm/EditShadowAssignmentForm.js b/src/components/forms/EditShadowAssignmentForm/EditShadowAssignmentForm.js index bd2bd1a93..b774da869 100644 --- a/src/components/forms/EditShadowAssignmentForm/EditShadowAssignmentForm.js +++ b/src/components/forms/EditShadowAssignmentForm/EditShadowAssignmentForm.js @@ -6,7 +6,6 @@ import { Container, Row, Col } from 'react-bootstrap'; import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; -import { SaveIcon } from '../../icons'; import { CheckboxField, NumericTextField, DatetimeField } from '../Fields'; import LocalizedTextsFormField from '../LocalizedTextsFormField'; import SubmitButton from '../SubmitButton'; @@ -58,7 +57,6 @@ const EditShadowAssignmentForm = ({ hasFailed={submitFailed} handleSubmit={handleSubmit(data => onSubmit(data).then(reset))} asyncValidating={asyncValidating} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditShadowAssignmentPointsForm/EditShadowAssignmentPointsForm.js b/src/components/forms/EditShadowAssignmentPointsForm/EditShadowAssignmentPointsForm.js index 1878d185e..fee60a5e1 100644 --- a/src/components/forms/EditShadowAssignmentPointsForm/EditShadowAssignmentPointsForm.js +++ b/src/components/forms/EditShadowAssignmentPointsForm/EditShadowAssignmentPointsForm.js @@ -10,7 +10,7 @@ import SubmitButton from '../SubmitButton'; import { TextField, DatetimeField, NumericTextField } from '../Fields'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; -import Icon, { RefreshIcon, DeleteIcon, SaveIcon } from '../../icons'; +import Icon, { RefreshIcon, DeleteIcon } from '../../icons'; export const getPointsFormInitialValues = lruMemoize((userPoints, awardeeId) => { return userPoints @@ -117,7 +117,6 @@ const EditShadowAssignmentPointsForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditSolutionNoteForm/EditSolutionNoteForm.js b/src/components/forms/EditSolutionNoteForm/EditSolutionNoteForm.js index a50c1d3d0..efb4b2a34 100644 --- a/src/components/forms/EditSolutionNoteForm/EditSolutionNoteForm.js +++ b/src/components/forms/EditSolutionNoteForm/EditSolutionNoteForm.js @@ -7,7 +7,6 @@ import { Form } from 'react-bootstrap'; import SubmitButton from '../SubmitButton'; import { TextField } from '../Fields'; import Callout from '../../widgets/Callout'; -import { SaveIcon } from '../../icons'; const EditSolutionNoteForm = ({ onSubmit, @@ -44,7 +43,6 @@ const EditSolutionNoteForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditSystemMessageForm/EditSystemMessageForm.js b/src/components/forms/EditSystemMessageForm/EditSystemMessageForm.js index b062263a6..fb67a1ac8 100644 --- a/src/components/forms/EditSystemMessageForm/EditSystemMessageForm.js +++ b/src/components/forms/EditSystemMessageForm/EditSystemMessageForm.js @@ -12,7 +12,7 @@ import Callout from '../../widgets/Callout'; import LocalizedTextsFormField from '../LocalizedTextsFormField'; import { validateLocalizedTextsFormData } from '../../../helpers/localizedData.js'; import withLinks from '../../../helpers/withLinks.js'; -import { CloseIcon, SaveIcon } from '../../icons'; +import { CloseIcon } from '../../icons'; import { roleLabelsSimpleMessages } from '../../helpers/usersRoles.js'; const typeOptions = [ @@ -114,7 +114,6 @@ const EditSystemMessageForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditTestsForm/EditTestsForm.js b/src/components/forms/EditTestsForm/EditTestsForm.js index 9be91a35c..ca26fab2b 100644 --- a/src/components/forms/EditTestsForm/EditTestsForm.js +++ b/src/components/forms/EditTestsForm/EditTestsForm.js @@ -14,7 +14,7 @@ import Box from '../../widgets/Box'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; import OptionalTooltipWrapper from '../../widgets/OptionalTooltipWrapper'; -import Icon, { CloseIcon, SaveIcon, RefreshIcon, WarningIcon } from '../../icons'; +import Icon, { CloseIcon, SendIcon, RefreshIcon, WarningIcon } from '../../icons'; import { UNIFORM_ID, WEIGHTED_ID, @@ -185,7 +185,6 @@ class EditTestsForm extends Component { dirty={dirty} hasFailed={submitFailed} handleSubmit={handleSubmit} - defaultIcon={} disabled={formValues.tests.length === 0} messages={{ submit: , @@ -284,6 +283,7 @@ class EditTestsForm extends Component { hasSucceeded={submitSucceeded} handleSubmit={handleSubmit} onSubmit={this.closeDialog} + defaultIcon={} messages={{ submit: ( } messages={{ submit: , submitting: , diff --git a/src/components/forms/EditUserRoleForm/EditUserRoleForm.js b/src/components/forms/EditUserRoleForm/EditUserRoleForm.js index c38b758b3..b92c359de 100644 --- a/src/components/forms/EditUserRoleForm/EditUserRoleForm.js +++ b/src/components/forms/EditUserRoleForm/EditUserRoleForm.js @@ -9,7 +9,6 @@ import { lruMemoize } from 'reselect'; import { knownRoles, roleLabels, roleDescriptions, UserRoleIcon } from '../../helpers/usersRoles.js'; import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; -import { SaveIcon } from '../../icons'; import SubmitButton from '../SubmitButton'; import StandaloneRadioField from '../Fields/StandaloneRadioField.js'; @@ -37,7 +36,6 @@ const EditUserRoleForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditUserSettingsForm/EditUserSettingsForm.js b/src/components/forms/EditUserSettingsForm/EditUserSettingsForm.js index daefd4482..82e1948c8 100644 --- a/src/components/forms/EditUserSettingsForm/EditUserSettingsForm.js +++ b/src/components/forms/EditUserSettingsForm/EditUserSettingsForm.js @@ -5,7 +5,6 @@ import { reduxForm, Field } from 'redux-form'; import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; -import { SaveIcon } from '../../icons'; import SubmitButton from '../SubmitButton'; import { CheckboxField, LanguageSelectField } from '../Fields'; import { isStudentRole, isSupervisorRole } from '../../helpers/usersRoles.js'; @@ -32,7 +31,6 @@ const EditUserSettingsForm = ({ hasFailed={submitFailed} invalid={invalid} dirty={dirty} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/EditUserUIDataForm/EditUserUIDataForm.js b/src/components/forms/EditUserUIDataForm/EditUserUIDataForm.js index f2a111aac..f09b434bc 100644 --- a/src/components/forms/EditUserUIDataForm/EditUserUIDataForm.js +++ b/src/components/forms/EditUserUIDataForm/EditUserUIDataForm.js @@ -6,7 +6,6 @@ import { lruMemoize } from 'reselect'; import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; -import { SaveIcon } from '../../icons'; import Explanation from '../../widgets/Explanation'; import SubmitButton from '../SubmitButton'; import { CheckboxField, SelectField, NumericTextField } from '../Fields'; @@ -64,7 +63,6 @@ const EditUserUIDataForm = ({ hasFailed={submitFailed} invalid={invalid} dirty={dirty} - defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/ExamForm/ExamForm.js b/src/components/forms/ExamForm/ExamForm.js index 178498cdd..c1e92114c 100644 --- a/src/components/forms/ExamForm/ExamForm.js +++ b/src/components/forms/ExamForm/ExamForm.js @@ -9,7 +9,7 @@ import Callout from '../../widgets/Callout'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Explanation from '../../widgets/Explanation'; import SubmitButton from '../SubmitButton'; -import { CloseIcon } from '../../icons'; +import { CloseIcon, SendIcon } from '../../icons'; import { TextField, CheckboxField, DatetimeField } from '../Fields'; @@ -194,6 +194,7 @@ const ExamForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} + defaultIcon={createNew ? : null} messages={{ submit: createNew ? ( diff --git a/src/components/forms/FilterArchiveGroupsForm/FilterArchiveGroupsForm.js b/src/components/forms/FilterArchiveGroupsForm/FilterArchiveGroupsForm.js index 2ffffa568..ddca24f38 100644 --- a/src/components/forms/FilterArchiveGroupsForm/FilterArchiveGroupsForm.js +++ b/src/components/forms/FilterArchiveGroupsForm/FilterArchiveGroupsForm.js @@ -8,6 +8,7 @@ import SubmitButton from '../SubmitButton'; import { TextField, CheckboxField } from '../Fields'; import Callout from '../../widgets/Callout'; import InsetPanel from '../../widgets/InsetPanel'; +import { SendIcon } from '../../icons'; const FilterArchiveGroupsForm = ({ onSubmit, @@ -63,6 +64,7 @@ const FilterArchiveGroupsForm = ({ hasFailed={submitFailed} invalid={invalid} dirty={dirty} + defaultIcon={} messages={{ submit: , success: , diff --git a/src/components/forms/FilterExercisesListForm/FilterExercisesListForm.js b/src/components/forms/FilterExercisesListForm/FilterExercisesListForm.js index b0f38b2b5..863bee53a 100644 --- a/src/components/forms/FilterExercisesListForm/FilterExercisesListForm.js +++ b/src/components/forms/FilterExercisesListForm/FilterExercisesListForm.js @@ -9,8 +9,8 @@ import { lruMemoize } from 'reselect'; import { getExerciseTags, getExerciseTagsLoading } from '../../../redux/selectors/exercises.js'; import { - getAllExericsesAuthors, - getAllExericsesAuthorsIsLoading, + getAllExercisesAuthors, + getAllExercisesAuthorsIsLoading, getExercisesAuthorsOfGroup, getExercisesAuthorsOfGroupIsLoading, } from '../../../redux/selectors/exercisesAuthors.js'; @@ -21,7 +21,7 @@ import ResourceRenderer from '../../helpers/ResourceRenderer'; import SubmitButton from '../SubmitButton'; import { TextField, RadioField, SelectField, TagsSelectorField } from '../Fields'; import { identity, safeGet } from '../../../helpers/common.js'; -import { ExpandCollapseIcon } from '../../icons'; +import { ExpandCollapseIcon, SendIcon } from '../../icons'; import InsetPanel from '../../widgets/InsetPanel'; import Button, { TheButtonGroup } from '../../widgets/TheButton'; import Callout from '../../widgets/Callout'; @@ -200,6 +200,7 @@ class FilterExercisesListForm extends Component { hasFailed={submitFailed} invalid={invalid} disabled={onSubmit === null} + defaultIcon={} messages={{ submit: , success: , @@ -352,10 +353,10 @@ FilterExercisesListForm.propTypes = { export default connect((state, { rootGroup = null, form }) => ({ loggedUserId: loggedInUserIdSelector(state), - authors: rootGroup ? getExercisesAuthorsOfGroup(rootGroup)(state) : getAllExericsesAuthors(state), + authors: rootGroup ? getExercisesAuthorsOfGroup(rootGroup)(state) : getAllExercisesAuthors(state), authorsLoading: rootGroup ? getExercisesAuthorsOfGroupIsLoading(rootGroup)(state) - : getAllExericsesAuthorsIsLoading(state), + : getAllExercisesAuthorsIsLoading(state), tags: getExerciseTags(state), tagsLoading: getExerciseTagsLoading(state), envValueSelector: name => formValueSelector(form)(state, name), diff --git a/src/components/forms/FilterSystemMessagesForm/FilterSystemMessagesForm.js b/src/components/forms/FilterSystemMessagesForm/FilterSystemMessagesForm.js index 0cc47ee7a..8922f753c 100644 --- a/src/components/forms/FilterSystemMessagesForm/FilterSystemMessagesForm.js +++ b/src/components/forms/FilterSystemMessagesForm/FilterSystemMessagesForm.js @@ -6,6 +6,7 @@ import { Container, Row, Col, Form } from 'react-bootstrap'; import SubmitButton from '../SubmitButton'; import { CheckboxField } from '../Fields'; +import { SendIcon } from '../../icons'; import { identity } from '../../../helpers/common.js'; const FilterSystemMessagesForm = ({ @@ -42,6 +43,7 @@ const FilterSystemMessagesForm = ({ hasFailed={submitFailed} invalid={invalid} disabled={onSubmit === null} + defaultIcon={} messages={{ submit: , success: , diff --git a/src/components/forms/FilterUsersListForm/FilterUsersListForm.js b/src/components/forms/FilterUsersListForm/FilterUsersListForm.js index fa2f54e94..6d9875eb8 100644 --- a/src/components/forms/FilterUsersListForm/FilterUsersListForm.js +++ b/src/components/forms/FilterUsersListForm/FilterUsersListForm.js @@ -10,6 +10,7 @@ import { knownRoles, roleLabelsPlural } from '../../helpers/usersRoles.js'; import { identity } from '../../../helpers/common.js'; import InsetPanel from '../../widgets/InsetPanel'; import Callout from '../../widgets/Callout'; +import { SendIcon } from '../../icons'; const FilterUsersListForm = ({ onSubmit = identity, @@ -60,6 +61,7 @@ const FilterUsersListForm = ({ hasFailed={submitFailed} invalid={invalid} disabled={onSubmit === null} + defaultIcon={} messages={{ submit: , success: , diff --git a/src/components/forms/GenerateTokenForm/GenerateTokenForm.js b/src/components/forms/GenerateTokenForm/GenerateTokenForm.js index 395143f2a..ecda9cdba 100644 --- a/src/components/forms/GenerateTokenForm/GenerateTokenForm.js +++ b/src/components/forms/GenerateTokenForm/GenerateTokenForm.js @@ -11,7 +11,7 @@ import Callout from '../../widgets/Callout'; import InsetPanel from '../../widgets/InsetPanel'; import SubmitButton from '../SubmitButton'; import { CheckboxField, SelectField } from '../Fields'; -import { CopyIcon, CopySuccessIcon } from '../../icons'; +import { CopyIcon, CopySuccessIcon, SendIcon } from '../../icons'; import { objectMap } from '../../../helpers/common.js'; import './GenerateTokenForm.css'; @@ -125,6 +125,7 @@ const GenerateTokenForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} invalid={invalid} + defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/InviteUserForm/InviteUserForm.js b/src/components/forms/InviteUserForm/InviteUserForm.js index 1a2d9adae..8bc3debef 100644 --- a/src/components/forms/InviteUserForm/InviteUserForm.js +++ b/src/components/forms/InviteUserForm/InviteUserForm.js @@ -11,7 +11,7 @@ import Callout from '../../widgets/Callout'; import Explanation from '../../widgets/Explanation'; import { validateRegistrationData } from '../../../redux/modules/users.js'; import { TextField, CheckboxField } from '../Fields'; -import { WarningIcon } from '../../icons'; +import { SendIcon, WarningIcon } from '../../icons'; import { getGroupCanonicalLocalizedName } from '../../../helpers/localizedData.js'; import { EMPTY_ARRAY } from '../../../helpers/common.js'; @@ -163,6 +163,7 @@ const InviteUserForm = ({ hasSucceeded={submitSucceeded} hasFailed={submitFailed} asyncValidating={asyncValidating} + defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/RegistrationForm/RegistrationForm.js b/src/components/forms/RegistrationForm/RegistrationForm.js index 3411841ac..9c00f9bc0 100644 --- a/src/components/forms/RegistrationForm/RegistrationForm.js +++ b/src/components/forms/RegistrationForm/RegistrationForm.js @@ -7,6 +7,7 @@ import isEmail from 'validator/lib/isEmail.js'; import { eventAggregator } from '../../../helpers/eventAggregator.js'; import Callout from '../../widgets/Callout'; import FormBox from '../../widgets/FormBox'; +import { SendIcon } from '../../icons'; import { EmailField, TextField, PasswordField, PasswordStrength, SelectField, CheckboxField } from '../Fields'; import { validateRegistrationData } from '../../../redux/modules/users.js'; import SubmitButton from '../SubmitButton'; @@ -37,6 +38,7 @@ const RegistrationForm = ({ dirty={anyTouched} asyncValidating={asyncValidating} invalid={invalid || instances.length === 0} + defaultIcon={} messages={{ submit: , submitting: , diff --git a/src/components/forms/SisBindGroupForm/SisBindGroupForm.js b/src/components/forms/SisBindGroupForm/SisBindGroupForm.js deleted file mode 100644 index ef7297dc3..000000000 --- a/src/components/forms/SisBindGroupForm/SisBindGroupForm.js +++ /dev/null @@ -1,142 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { reduxForm, Field } from 'redux-form'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Modal } from 'react-bootstrap'; - -import { SelectField } from '../Fields'; -import SubmitButton from '../SubmitButton'; -import Button, { TheButtonGroup } from '../../widgets/TheButton'; -import Callout from '../../widgets/Callout'; -import InsetPanel from '../../widgets/InsetPanel'; -import { getGroupCanonicalLocalizedName } from '../../../helpers/localizedData.js'; -import CourseLabel from '../../SisIntegration/CourseLabel'; -import { CloseIcon, InfoIcon } from '../../icons'; - -const SisBindGroupForm = ({ - isOpen, - onClose, - invalid, - dirty, - handleSubmit, - submitFailed, - submitting, - submitSucceeded, - warning, - groups, - groupsAccessor, - course, - courseGroupsCount, - intl: { locale }, -}) => ( - - - - - - - - - {course && } -
- - - - - - } - options={(groups || []) - .map(group => ({ - key: group.id, - name: getGroupCanonicalLocalizedName(group, groupsAccessor, locale), - })) - .sort((a, b) => a.name.localeCompare(b.name, locale))} - addEmptyOption - ignoreDirty - /> - - {submitFailed && ( - - - - )} - - {warning && {warning}} -
- - -
- - , - submitting: , - success: , - }} - /> - - - -
-
-
-); - -SisBindGroupForm.propTypes = { - isOpen: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - dirty: PropTypes.bool, - handleSubmit: PropTypes.func.isRequired, - invalid: PropTypes.bool, - submitting: PropTypes.bool, - submitSucceeded: PropTypes.bool, - submitFailed: PropTypes.bool, - warning: PropTypes.object, - groups: PropTypes.array, - groupsAccessor: PropTypes.func.isRequired, - course: PropTypes.object, - courseGroupsCount: PropTypes.number, - intl: PropTypes.object.isRequired, -}; - -const warn = ({ groupId }) => { - const warnings = {}; - - if (!groupId) { - warnings._warning = ( - - ); - } - - return warnings; -}; - -export default reduxForm({ - form: 'sisBindGroup', - warn, -})(injectIntl(SisBindGroupForm)); diff --git a/src/components/forms/SisBindGroupForm/index.js b/src/components/forms/SisBindGroupForm/index.js deleted file mode 100644 index f4e88dff1..000000000 --- a/src/components/forms/SisBindGroupForm/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SisBindGroupForm from './SisBindGroupForm.js'; -export default SisBindGroupForm; diff --git a/src/components/forms/SisCreateGroupForm/SisCreateGroupForm.js b/src/components/forms/SisCreateGroupForm/SisCreateGroupForm.js deleted file mode 100644 index b4d9dfddc..000000000 --- a/src/components/forms/SisCreateGroupForm/SisCreateGroupForm.js +++ /dev/null @@ -1,153 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { reduxForm, Field } from 'redux-form'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Modal } from 'react-bootstrap'; - -import { SelectField } from '../Fields'; -import SubmitButton from '../SubmitButton'; -import Button, { TheButtonGroup } from '../../widgets/TheButton'; -import Callout from '../../widgets/Callout'; -import InsetPanel from '../../widgets/InsetPanel'; -import { getGroupCanonicalLocalizedName } from '../../../helpers/localizedData.js'; -import CourseLabel from '../../SisIntegration/CourseLabel'; -import { CloseIcon, InfoIcon } from '../../icons'; - -class SisCreateGroupForm extends Component { - render() { - const { - isOpen, - onClose, - invalid, - dirty, - handleSubmit, - submitFailed, - submitting, - submitSucceeded, - warning, - groupIds, - groupsAccessor, - course, - courseGroupsCount, - intl: { locale }, - } = this.props; - - return ( - - - - - - - - - {course && } -
- - - - - - } - options={(groupIds || []) - .map(groupId => ({ - key: groupId, - name: getGroupCanonicalLocalizedName(groupId, groupsAccessor, locale), - })) - .sort((a, b) => a.name.localeCompare(b.name, locale))} - addEmptyOption - ignoreDirty - /> - - {submitFailed && ( - - - - )} - - {warning && {warning}} -
- - -
- - , - submitting: , - success: ( - - ), - }} - /> - - - -
-
-
- ); - } -} - -SisCreateGroupForm.propTypes = { - isOpen: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - dirty: PropTypes.bool, - handleSubmit: PropTypes.func.isRequired, - invalid: PropTypes.bool, - submitting: PropTypes.bool, - submitSucceeded: PropTypes.bool, - submitFailed: PropTypes.bool, - warning: PropTypes.object, - groupIds: PropTypes.array, - groupsAccessor: PropTypes.func.isRequired, - course: PropTypes.object, - courseGroupsCount: PropTypes.number, - intl: PropTypes.object.isRequired, -}; - -const warn = ({ parentGroupId }) => { - const warnings = {}; - - if (!parentGroupId) { - warnings._warning = ( - - ); - } - - return warnings; -}; - -export default reduxForm({ - form: 'sisCreateGroup', - warn, -})(injectIntl(SisCreateGroupForm)); diff --git a/src/components/forms/SisCreateGroupForm/index.js b/src/components/forms/SisCreateGroupForm/index.js deleted file mode 100644 index 336eb191f..000000000 --- a/src/components/forms/SisCreateGroupForm/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SisCreateGroupForm from './SisCreateGroupForm.js'; -export default SisCreateGroupForm; diff --git a/src/components/forms/SubmitButton/SubmitButton.js b/src/components/forms/SubmitButton/SubmitButton.js index 8cb5098a3..f86832bbc 100644 --- a/src/components/forms/SubmitButton/SubmitButton.js +++ b/src/components/forms/SubmitButton/SubmitButton.js @@ -5,12 +5,12 @@ import { Popover, OverlayTrigger } from 'react-bootstrap'; import { lruMemoize } from 'reselect'; import Button from '../../widgets/TheButton'; -import { SendIcon, LoadingIcon, SuccessIcon, WarningIcon } from '../../icons'; +import { SaveIcon, LoadingIcon, SuccessIcon, WarningIcon } from '../../icons'; import Confirm from '../Confirm'; import { getErrorMessage } from '../../../locales/apiErrorMessages.js'; const getIcons = lruMemoize(defaultIcon => ({ - submit: defaultIcon || , + submit: defaultIcon || , success: , submitting: , validating: , diff --git a/src/components/layout/Sidebar/Sidebar.js b/src/components/layout/Sidebar/Sidebar.js index a1988240a..b4a2eac11 100644 --- a/src/components/layout/Sidebar/Sidebar.js +++ b/src/components/layout/Sidebar/Sidebar.js @@ -14,7 +14,6 @@ import { LoadingIcon } from '../../icons'; import { isReady, getJsData } from '../../../redux/helpers/resourceManager'; import { isSupervisorRole, isEmpoweredSupervisorRole, isSuperadminRole } from '../../helpers/usersRoles.js'; import withLinks from '../../../helpers/withLinks.js'; -import { getExternalIdForCAS } from '../../../helpers/cas.js'; import { getConfigVar } from '../../../helpers/config.js'; import { EMPTY_ARRAY } from '../../../helpers/common.js'; import Admin from './Admin.js'; @@ -77,7 +76,6 @@ const Sidebar = ({ EXERCISES_URI, PIPELINES_URI, ARCHIVE_URI, - SIS_INTEGRATION_URI, }, intl: { locale }, }) => { @@ -193,17 +191,6 @@ const Sidebar = ({ link={ARCHIVE_URI} /> - {Boolean(getExternalIdForCAS(user)) && ( - - } - currentPath={currentUrl} - link={SIS_INTEGRATION_URI} - /> - )} - } icon={['far', 'question-circle']} diff --git a/src/containers/GroupsTreeContainer/GroupsTreeContainer.js b/src/containers/GroupsTreeContainer/GroupsTreeContainer.js index acb31050c..48a92b666 100644 --- a/src/containers/GroupsTreeContainer/GroupsTreeContainer.js +++ b/src/containers/GroupsTreeContainer/GroupsTreeContainer.js @@ -68,7 +68,7 @@ const prepareGroupObject = ( }; /** - * Prepares plain-js datastructure that could be fed to GroupsTree component. + * Prepares plain-js data structure that could be fed to GroupsTree component. * With memoization, this is basically a higher-level selector that creates hiarchial augmented group objects. */ const prepareGroupsTree = lruMemoize( diff --git a/src/containers/PipelineEditContainer/PipelineEditContainer.js b/src/containers/PipelineEditContainer/PipelineEditContainer.js index c75393706..1d658b0e7 100644 --- a/src/containers/PipelineEditContainer/PipelineEditContainer.js +++ b/src/containers/PipelineEditContainer/PipelineEditContainer.js @@ -14,15 +14,7 @@ import BoxForm, { newBoxInitialData } from '../../components/Pipelines/BoxForm'; import Button, { TheButtonGroup } from '../../components/widgets/TheButton'; import SubmitButton from '../../components/forms/SubmitButton'; import Callout from '../../components/widgets/Callout'; -import Icon, { - RefreshIcon, - SaveIcon, - DownloadIcon, - UploadIcon, - SuccessIcon, - UndoIcon, - RedoIcon, -} from '../../components/icons'; +import Icon, { RefreshIcon, DownloadIcon, UploadIcon, SuccessIcon, UndoIcon, RedoIcon } from '../../components/icons'; import { getVariablesUtilization, @@ -772,7 +764,6 @@ class PipelineEditContainer extends Component { submitting={this.state.submitting} hasFailed={this.state.submitError !== null} invalid={this.state.version < pipeline.version || (this.state.errors && this.state.errors.length > 0)} - defaultIcon={} messages={{ success: , submit: , diff --git a/src/containers/SisIntegrationContainer/SisIntegrationContainer.js b/src/containers/SisIntegrationContainer/SisIntegrationContainer.js deleted file mode 100644 index 35dfad7fa..000000000 --- a/src/containers/SisIntegrationContainer/SisIntegrationContainer.js +++ /dev/null @@ -1,236 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Link } from 'react-router-dom'; -import { Card, Table } from 'react-bootstrap'; -import { lruMemoize } from 'reselect'; - -import Box from '../../components/widgets/Box'; -import Button from '../../components/widgets/TheButton'; -import InsetPanel from '../../components/widgets/InsetPanel'; -import UsersNameContainer from '../UsersNameContainer'; -import LeaveJoinGroupButtonContainer from '../LeaveJoinGroupButtonContainer'; -import ResourceRenderer from '../../components/helpers/ResourceRenderer'; -import { AssignmentsIcon } from '../../components/icons'; -import CourseLabel, { getLocalizedData } from '../../components/SisIntegration/CourseLabel'; - -import { fetchSisStatusIfNeeded } from '../../redux/modules/sisStatus.js'; -import { fetchSisSubscribedGroups } from '../../redux/modules/sisSubscribedGroups.js'; -import { sisStateSelector } from '../../redux/selectors/sisStatus.js'; -import { sisSubscribedCoursesGroupsSelector } from '../../redux/selectors/sisSubscribedGroups.js'; -import { loggedInUserIdSelector } from '../../redux/selectors/auth.js'; -import { groupDataAccessorSelector } from '../../redux/selectors/groups.js'; - -import { getGroupCanonicalLocalizedName } from '../../helpers/localizedData.js'; -import withLinks from '../../helpers/withLinks.js'; - -const dowFix = dow => (typeof dow === 'number' ? dow : 8); - -const timeToMinutes = time => { - const [hour, minute] = String(time).split(':'); - const minutes = Number(hour) * 60 + Number(minute); - return isNaN(minutes) ? 9999 : minutes; -}; - -const preprocessCourses = lruMemoize((courses, groupsAccessor, locale) => - courses - .map(({ course, groups }) => ({ - course, - groups: groups - .map(group => { - group = groupsAccessor(group); - return group && group.toJS(); - }) - .filter(group => group), - })) - .filter(({ groups }) => groups && groups.length > 0) - .sort( - (a, b) => - dowFix(a.course.dayOfWeek) - dowFix(b.course.dayOfWeek) || - timeToMinutes(a.course.time) - timeToMinutes(b.course.time) || - Number(a.course.fortnightly) - Number(b.course.fortnightly) || - getLocalizedData(a.course.captions, locale).localeCompare(getLocalizedData(b.course.captions, locale), locale) - ) -); - -class SisIntegrationContainer extends Component { - componentDidMount() { - this.props.loadData(this.props.currentUserId); - } - - static loadData = (dispatch, loggedInUserId) => - dispatch(fetchSisStatusIfNeeded()) - .then(res => res.value) - .then( - ({ accessible, terms }) => - accessible && - terms - .filter(({ isAdvertised }) => isAdvertised) - .map(({ year, term }) => dispatch(fetchSisSubscribedGroups(loggedInUserId, year, term))) - ); - - reloadData = () => { - this.props.loadData(this.props.currentUserId); - }; - - render() { - const { - sisStatus, - currentUserId, - sisCoursesGroups, - groupsAccessor, - links: { GROUP_INFO_URI_FACTORY, GROUP_ASSIGNMENTS_URI_FACTORY }, - intl: { locale }, - } = this.props; - return ( - - } - unlimitedHeight> -
-

- -

- - {sisStatus => ( -
- {!sisStatus.accessible && ( -

- -

- )} - {sisStatus.accessible && - sisStatus.terms - .filter(({ isAdvertised }) => isAdvertised) - .map((term, i) => ( -
-
-

- {' '} - {`${term.year}-${term.term}`} -

- - {courses => ( -
- {courses && preprocessCourses(courses, groupsAccessor, locale).length > 0 ? ( - preprocessCourses(courses, groupsAccessor, locale).map( - course => - course && ( - - {course && } - - - - {course.groups && - course.groups.map((group, i) => ( - - - - - - ))} - -
- {getGroupCanonicalLocalizedName(group, groupsAccessor, locale)} - - {group.primaryAdminsIds.map(id => ( - - ))} - - - {group.privateData && - group.privateData.students.includes(currentUserId) && ( - - - - )} - - {!group.organizational && - (!group.privateData || - !group.privateData.detaining || - !group.privateData.students.includes(currentUserId)) && ( - - )} - -
-
-
- ) - ) - ) : ( - - - - )} -
- )} -
-
- ))} -
- )} -
-
-
- ); - } -} - -SisIntegrationContainer.propTypes = { - sisStatus: PropTypes.object, - currentUserId: PropTypes.string, - loadData: PropTypes.func.isRequired, - sisCoursesGroups: PropTypes.func.isRequired, - groupsAccessor: PropTypes.func.isRequired, - links: PropTypes.object, - intl: PropTypes.object.isRequired, -}; - -export default withLinks( - connect( - state => { - const currentUserId = loggedInUserIdSelector(state); - return { - sisStatus: sisStateSelector(state), - currentUserId, - sisCoursesGroups: (year, term) => sisSubscribedCoursesGroupsSelector(currentUserId, year, term)(state), - groupsAccessor: groupDataAccessorSelector(state), - }; - }, - dispatch => ({ - loadData: loggedInUserId => SisIntegrationContainer.loadData(dispatch, loggedInUserId), - }) - )(injectIntl(SisIntegrationContainer)) -); diff --git a/src/containers/SisIntegrationContainer/index.js b/src/containers/SisIntegrationContainer/index.js deleted file mode 100644 index e3b62e7d2..000000000 --- a/src/containers/SisIntegrationContainer/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SisIntegrationContainer from './SisIntegrationContainer.js'; -export default SisIntegrationContainer; diff --git a/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js b/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js deleted file mode 100644 index e47db82e9..000000000 --- a/src/containers/SisSupervisorGroupsContainer/SisSupervisorGroupsContainer.js +++ /dev/null @@ -1,635 +0,0 @@ -import React, { Component, useContext } from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Table, Accordion, Card, OverlayTrigger, Popover } from 'react-bootstrap'; -import { useAccordionButton } from 'react-bootstrap/AccordionButton'; -import AccordionContext from 'react-bootstrap/AccordionContext'; -import { Link } from 'react-router-dom'; - -import Box from '../../components/widgets/Box'; -import Button, { TheButtonGroup } from '../../components/widgets/TheButton'; -import Callout from '../../components/widgets/Callout'; -import UsersNameContainer from '../UsersNameContainer'; -import ResourceRenderer from '../../components/helpers/ResourceRenderer'; -import SisCreateGroupForm from '../../components/forms/SisCreateGroupForm'; -import SisBindGroupForm from '../../components/forms/SisBindGroupForm'; -import Confirm from '../../components/forms/Confirm'; -import CourseLabel, { getLocalizedData } from '../../components/SisIntegration/CourseLabel'; -import DeleteGroupButtonContainer from '../../containers/DeleteGroupButtonContainer'; -import Icon, { - AddIcon, - BindIcon, - UnbindIcon, - EditIcon, - GroupIcon, - AssignmentsIcon, - LoadingIcon, - StudentsIcon, -} from '../../components/icons'; - -import { fetchAllGroups, fetchGroupIfNeeded } from '../../redux/modules/groups.js'; -import { fetchSisStatusIfNeeded } from '../../redux/modules/sisStatus.js'; -import { - fetchSisSupervisedCourses, - sisCreateGroup, - sisBindGroup, - sisUnbindGroup, -} from '../../redux/modules/sisSupervisedCourses.js'; -import { fetchSisPossibleParentsIfNeeded } from '../../redux/modules/sisPossibleParents.js'; -import { sisPossibleParentsSelector } from '../../redux/selectors/sisPossibleParents.js'; -import { sisStateSelector } from '../../redux/selectors/sisStatus.js'; -import { sisSupervisedCoursesSelector } from '../../redux/selectors/sisSupervisedCourses.js'; -import { loggedInUserIdSelector } from '../../redux/selectors/auth.js'; -import { groupAccessorSelector, groupDataAccessorSelector } from '../../redux/selectors/groups.js'; - -import { getGroupCanonicalLocalizedName } from '../../helpers/localizedData.js'; -import withLinks from '../../helpers/withLinks.js'; -import { unique, arrayToObject, hasPermissions, safeGet } from '../../helpers/common.js'; - -const filterGroupsForBinding = (groups, alreadyBoundGroups) => { - const bound = arrayToObject(alreadyBoundGroups); - return groups.filter(group => !bound[group.id] && !group.organizational && !group.archived); -}; - -const MyAccordionButton = ({ children, eventKey }) => { - const { activeEventKey } = useContext(AccordionContext); - const decoratedOnClick = useAccordionButton(eventKey); - const isCurrentEventKey = activeEventKey === eventKey; - - return ( -
- - {children} -
- ); -}; - -MyAccordionButton.propTypes = { - children: PropTypes.any, - eventKey: PropTypes.string.isRequired, -}; - -class SisSupervisorGroupsContainer extends Component { - state = { createDialog: null, bindDialog: null, pendingUnbinds: {} }; - - openCreateDialog = (possibleParents, course, term) => { - this.setState({ - createDialog: { - possibleParents, - course: course.course, - groupsCount: course.groups.length, - term, - }, - }); - }; - - closeCreateDialog = () => { - this.setState({ createDialog: null }); - }; - - submitCreateDialog = data => { - const { createGroup, currentUserId } = this.props; - if (this.state.createDialog === null) { - return; - } - - const { course, term } = this.state.createDialog; - return createGroup(course.code, data, currentUserId, term.year, term.term).then(res => { - this.closeCreateDialog(); - return res; - }); - }; - - openBindDialog = (course, term) => { - this.setState({ - bindDialog: { - groups: filterGroupsForBinding(this.props.groups, course.groups), - course: course.course, - groupsCount: course.groups.length, - term, - }, - }); - }; - - closeBindDialog = () => { - this.setState({ bindDialog: null }); - }; - - submitBindDialog = data => { - const { bindGroup, currentUserId } = this.props; - if (this.state.bindDialog === null) { - return; - } - - const { course, term } = this.state.bindDialog; - return bindGroup(course.code, data, currentUserId, term.year, term.term).then(res => { - this.closeBindDialog(); - return res; - }); - }; - - isUnbindPending = (courseId, groupId) => { - const key = `${courseId}:${groupId}`; - return Boolean(this.state.pendingUnbinds[key]); - }; - - unbindGroup = (courseId, groupId, userId, year, term) => { - const key = `${courseId}:${groupId}`; - this.setState({ pendingUnbinds: { ...this.state.pendingUnbinds, [key]: true } }); - this.props.unbindGroup(courseId, groupId, userId, year, term).finally(() => { - this.setState({ pendingUnbinds: { ...this.state.pendingUnbinds, [key]: false } }); - }); - }; - - componentDidMount() { - this.props.loadData(this.props.currentUserId); - } - - static loadData = (dispatch, loggedInUserId) => { - dispatch(fetchSisStatusIfNeeded()) - .then(res => res.value) - .then( - ({ accessible, terms }) => - accessible && - terms - .filter(({ isAdvertised }) => isAdvertised) - .map(({ year, term }) => - dispatch(fetchSisSupervisedCourses(loggedInUserId, year, term)) - .then(res => res.value) - .then(({ courses }) => - courses.map(course => - dispatch(fetchSisPossibleParentsIfNeeded(course.course.code)) - .then(res => res.value) - .then(groups => - unique(groups.reduce((acc, group) => acc.concat(group.parentGroupsIds), [])).map(groupId => - dispatch(fetchGroupIfNeeded(groupId)) - ) - ) - ) - ) - ) - ); - }; - - render() { - const { - sisStatus, - sisCourses, - currentUserId, - sisPossibleParents, - groupsAccessor, - groupsResourcesAccessor, - links: { - GROUP_EDIT_URI_FACTORY, - GROUP_INFO_URI_FACTORY, - GROUP_ASSIGNMENTS_URI_FACTORY, - GROUP_STUDENTS_URI_FACTORY, - }, - intl: { locale }, - } = this.props; - - return ( - - } - unlimitedHeight> -
-

- -

- - - - - - {sisStatus => ( -
- {!sisStatus.accessible && ( -

- -

- )} - {sisStatus.accessible && - sisStatus.terms - .filter(({ isAdvertised }) => isAdvertised) - .map((term, i) => ( -
-

- {' '} - {`${term.year}-${term.term}`} -

- - {courses => ( -
- {courses && Object.keys(courses).length > 0 ? ( - - {Object.values(courses) - .sort( - (a, b) => - getLocalizedData(a.course.captions, locale).localeCompare( - getLocalizedData(b.course.captions, locale), - locale - ) || a.course.code.localeCompare(b.course.code, locale) - ) - .map(course => ( - - - - {course && ( - - - - )} - - - - - <> - - {course.groups.length > 0 ? ( - - - - - - - - - {course.groups.map(groupId => ( - - - - }> - {group => - group ? ( - - - - - - - ) : null - } - - ))} - -
- - - - - -
- -
- {group.organizational && ( - - } - /> - )} - - {getGroupCanonicalLocalizedName( - group, - groupsAccessor, - locale - )} - {( - safeGet(group, ['privateData', 'bindings', 'sis']) || - [] - ).length > 1 && ( - - - - - -
    - {group.privateData.bindings.sis - .sort() - .map(code => ( -
  • - {code} - {code === course.course.code && ( - - )} -
  • - ))} -
-
- - }> - - - -
- )} -
- {group.primaryAdminsIds.map(id => ( - - ))} - - - {hasPermissions(group, 'update') && ( - - - - )} - - - - - - {hasPermissions(group, 'viewDetail') && ( - - - - )} - - {!group.organizational && - hasPermissions(group, 'viewAssignments') && ( - - - - )} - - - this.unbindGroup( - course.course.code, - group.id, - currentUserId, - term.year, - term.term - ) - } - question={ - - } - disabled={this.isUnbindPending( - course.course.code, - group.id - )}> - - - - {hasPermissions(group, 'remove') && - group.parentGroupId !== null && - group.childGroups.length === 0 && ( - - )} - -
- ) : ( -

- -

- )} -
- - - - {possibleParents => ( -
- - - - - -
- )} -
-
- -
-
- ))} -
- ) : ( -

- -

- )} -
- )} -
-
- ))} -
- )} -
- - - - -
-
- ); - } -} - -SisSupervisorGroupsContainer.propTypes = { - sisStatus: PropTypes.object, - currentUserId: PropTypes.string, - groups: PropTypes.array, - loadData: PropTypes.func.isRequired, - sisCourses: ImmutablePropTypes.map, - createGroup: PropTypes.func.isRequired, - bindGroup: PropTypes.func.isRequired, - unbindGroup: PropTypes.func.isRequired, - links: PropTypes.object, - sisPossibleParents: ImmutablePropTypes.map, - groupsAccessor: PropTypes.func.isRequired, - groupsResourcesAccessor: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, -}; - -export default injectIntl( - withLinks( - connect( - state => { - const currentUserId = loggedInUserIdSelector(state); - return { - sisStatus: sisStateSelector(state), - currentUserId, - sisCourses: sisSupervisedCoursesSelector(state), - sisPossibleParents: sisPossibleParentsSelector(state), - groupsAccessor: groupDataAccessorSelector(state), - groupsResourcesAccessor: groupAccessorSelector(state), - }; - }, - dispatch => ({ - loadData: loggedInUserId => SisSupervisorGroupsContainer.loadData(dispatch, loggedInUserId), - createGroup: (courseId, data, userId, year, term) => - dispatch(sisCreateGroup(courseId, data, userId, year, term)).then(() => dispatch(fetchAllGroups())), - bindGroup: (courseId, data, userId, year, term) => dispatch(sisBindGroup(courseId, data, userId, year, term)), - unbindGroup: (courseId, groupId, userId, year, term) => - dispatch(sisUnbindGroup(courseId, groupId, userId, year, term)), - }) - )(SisSupervisorGroupsContainer) - ) -); diff --git a/src/containers/SisSupervisorGroupsContainer/index.js b/src/containers/SisSupervisorGroupsContainer/index.js deleted file mode 100644 index 2d6cbcead..000000000 --- a/src/containers/SisSupervisorGroupsContainer/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SisSupervisorGroupsContainer from './SisSupervisorGroupsContainer.js'; -export default SisSupervisorGroupsContainer; diff --git a/src/containers/UsersNameContainer/UsersNameContainer.js b/src/containers/UsersNameContainer/UsersNameContainer.js index d2930113c..b68cf5516 100644 --- a/src/containers/UsersNameContainer/UsersNameContainer.js +++ b/src/containers/UsersNameContainer/UsersNameContainer.js @@ -32,6 +32,7 @@ class UsersNameContainer extends Component { noAvatar = false, currentUser, isSimple = false, + simpleClassName = '', showEmail = null, showExternalIdentifiers = false, showRoleIcon = false, @@ -45,7 +46,7 @@ class UsersNameContainer extends Component { failed={isSimple ? : }> {(user, currentUser) => isSimple ? ( - + {user.name.firstName} {user.name.lastName} ) : ( @@ -76,6 +77,7 @@ UsersNameContainer.propTypes = { link: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.bool]), noAvatar: PropTypes.bool, isSimple: PropTypes.bool, + simpleClassName: PropTypes.string, showEmail: PropTypes.string, showExternalIdentifiers: PropTypes.bool, showRoleIcon: PropTypes.bool, diff --git a/src/locales/cs.json b/src/locales/cs.json index c47cc65ff..a1301f53d 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -45,15 +45,6 @@ "app.addLicense.validation.note": "Popis nemůže být prázdný.", "app.addLicense.validation.validUntilEmpty": "Konec platnosti licence musí být nastaven.", "app.addLicense.validation.validUntilInThePast": "Platnost licence musí být v budoucnosti.", - "app.addSisTermForm.failed": "Nebylo možné uložit nový SIS semestr.", - "app.addSisTermForm.processing": "Ukládání...", - "app.addSisTermForm.submit": "Uložit nový semestr", - "app.addSisTermForm.success": "Semestr byl uložen.", - "app.addSisTermForm.summer": "Letní semestr", - "app.addSisTermForm.term": "Semestr:", - "app.addSisTermForm.title": "Přidat nový semestr", - "app.addSisTermForm.winter": "Zimní semestr", - "app.addSisTermForm.year": "Rok:", "app.addStudent.cannotSearch": "Nemáte oprávnění prohledávat studenty, takže je nemůžete přidávat do skupiny přímo.", "app.addStudent.inviteButton": "Pozvat k registraci", "app.addStudent.inviteDialog.explain": "Pozvánka bude zaslána uživateli na danou mailovou adresu. Uživatel obdrží odkaz pro registraci lokálním účtem. Detaily uživatelského profilu (jméno a email) vyplňte pečlivě, uživatel nebude mít možnost je změnit. Volitelně můžete také vybrat seznam skupin, do kterých bude uživatel přidán hned po registraci.", @@ -96,13 +87,6 @@ "app.archiveGroupButton.setShort": "Archivovat", "app.archiveGroupButton.unset": "Vrátit archivaci skupiny", "app.archiveGroupButton.unsetShort": "Vrátit", - "app.archiveSisTerm.archiveGroups": "Archivovat skupiny", - "app.archiveSisTerm.archived": "Archivováno", - "app.archiveSisTerm.archiving": "Archivuji...", - "app.archiveSisTerm.failed": "Archivace selhala. Některé skupiny možná nebyly přesunuty do archivu.", - "app.archiveSisTerm.noGroups": "S tímto semestrem nejsou asociovány žádné skupiny. Je možné, že již byly všechny archivovány.", - "app.archiveSisTerm.noGroupsSelected": "Alespoň jedna skupina musí být vybrána.", - "app.archiveSisTerm.title": "Archivovat skupiny ze SIS semestru", "app.assignExerciseButton.isBroken": "Rozbitá", "app.assignExerciseButton.isLocked": "Zamčená", "app.assignExerciseButton.noRefSolutions": "Neověřená", @@ -282,7 +266,6 @@ "app.createExerciseForm.validation.noGroupSelected": "Nebyla vybrána domovská skupina.", "app.createGroup.detaining": "Zadržuje studenty", "app.createGroup.detaining.explanation": "Studenti nemohou sami opustit skupinu, odebrat je může pouze vedoucí/administrátor skupiny.", - "app.createGroup.externalId": "Externí identifikátor skupiny:", "app.createGroup.hasThreshold": "Studenti potřebují určitý počet bodů pro splnění kurzu", "app.createGroup.hasThreshold.explanation": "Potřebný počet bodů může být specifikován relativně (jako procentuální poměr z maximálního počtu bodů) nebo jako absolutní bodový limit. Pouze jedna z těchto hodnot může být vyplněna.", "app.createGroup.isExam": "Zkoušková", @@ -299,11 +282,9 @@ "app.createUserForm.validation.emailTaken": "Tato e-mailová adresa již patří jinému uživateli.", "app.createUserForm.validation.emptyPassword": "Heslo nemůže zůstat prázdné.", "app.dashboard.memberOf": "Skupiny, ve kterých jste členem", - "app.dashboard.sisGroupsStudent": "Přihlásit se do skupin asociovaných s kurzy ze SISu UK", - "app.dashboard.sisGroupsStudentExplain": "SIS kurzy, které máte zapsané v určitém semestru a které mají odpovídající skupiny v ReCodExu.", - "app.dashboard.studentNoGroups": "Zatím nejste členem žádné skupiny. Vedoucí skupiny vás může přidat do jeho/její skupiny, nebo můžete použít jiné mechanismy (jako např. dialog na stránce SIS integrace) a stát se členy skupin, které vám přísluší.", + "app.dashboard.studentNoGroups": "Zatím nejste členem žádné skupiny. Prosím, řiďte se pokyny vaší vzdělávací instituce ohledně získávání členství ve skupinách.", "app.dashboard.studentNoGroupsTitle": "Žádná členství ve skupinách", - "app.dashboard.supervisorNoGroups": "Aktuálně nemáte ve správě žádné skupiny. Skupinu vám může vytvořit administrátor, nebo můžete použít jiné mechanismy (jako např. dialog na stránce SIS integrace) pro vytvoření skupin pro vaše studenty.", + "app.dashboard.supervisorNoGroups": "Aktuálně nemáte ve správě žádné skupiny.", "app.dashboard.supervisorNoGroupsTitle": "Žádné skupiny", "app.dashboard.supervisorOf": "Skupiny pod vaší správou (jako administrátor nebo vedoucí)", "app.dashboard.title": "Celkový přehled všech skupin", @@ -619,14 +600,6 @@ "app.editShadowAssignmentPointsForm.removePoints": "Odstranit celý záznam", "app.editShadowAssignmentPointsForm.setNow": "Teď", "app.editShadowAssignmentPointsForm.validation.pointsOutOfRange": "Body jsou mimo běžný rozsah. Běžný rozsah je od 0 do {maxPoints}.", - "app.editSisTerm.advertiseUntil": "Nabízet studentům tento semestr do:", - "app.editSisTerm.beginning": "Začátek semestru:", - "app.editSisTerm.end": "Konec semestru:", - "app.editSisTerm.title": "Upravit SIS semestr", - "app.editSisTerm.validation.advertiseInLimits": "Semestr může být nabízen pouze v době jeho trvání.", - "app.editSisTerm.validation.noAdvertiseUntil": "Datum konce nabízení semestru je povinný.", - "app.editSisTerm.validation.noBeginning": "Začátek semestru je povinný.", - "app.editSisTerm.validation.noEnd": "Konec semestru je povinný.", "app.editSolutionNoteForm.failed": "Nebylo možné uložit poznámku k řešení.", "app.editSolutionNoteForm.note": "Poznámka:", "app.editSystemMessageForm.role": "Uživatelé s touto a vyšší rolí uvidí tuto zprávu.", @@ -1068,15 +1041,11 @@ "app.group.organizationalExplain": "Tato skupina je organizační, takže nemůže mít žádné studenty ani zadané úlohy. Nicméně úlohy spojené s touto skupinou je možné zadat v podskupinách.", "app.group.setRoot": "Vybrat", "app.group.students": "Studenti", - "app.group.unbind": "Odvázat", - "app.group.unbind.confirmQuestion": "Opravdu si přejete zrušit tuto vazbu na skupinu? Skupina zůstane na svém místě, ale bez odpovídající vazby na SIS ji nemusí nalézt studenti.", "app.group.unsetRoot": "Zrušit výběr", "app.groupAssignments.groupExercises": "Úlohy k zadání ve skupině", "app.groupDetail.assignments": "Zadané úlohy", - "app.groupDetail.bindings.genericProvider": "Externí vazby na \"{provider}\"", - "app.groupDetail.bindings.sis": "Kódy rozvrhových lístků ze SIS UK", "app.groupDetail.description": "Popis skupiny", - "app.groupDetail.externalId": "Externí identifikace skupiny", + "app.groupDetail.externalAttributes": "Externí atributy:", "app.groupDetail.hasPublicStats": "Studenti smí vidět body ostatních studentů", "app.groupDetail.isPublic": "Tuto skupinu vidí každý (a může se do ní přihlásit)", "app.groupDetail.loading": "Načítání dat skupiny...", @@ -1159,6 +1128,8 @@ "app.groupStudents.privateStats": "Administrátor skupiny omezil přístup k výsledkům studentů. Z toho důvodu můžete vidět pouze vlastní výsledky, nikoli výsledky ostatních studentů.", "app.groupStudents.studentsResultsTable": "Studenti a jejich výsledky", "app.groupStudents.title": "Studenti ve skupině", + "app.groupTree.treeViewLeaf.adminPopover.title": "Administrátoři skupiny", + "app.groupTree.treeViewLeaf.adminsCount": "{count} {count, plural, one {administrátor} =2 {administrátoři} =3 {administrátoři} =4 {administrátoři} other {administrátorů}}", "app.groupTree.treeViewLeaf.archivedTooltip": "Tato skupina byla odsunuta do archivu", "app.groupTree.treeViewLeaf.examTooltip": "Zkoušková skupina", "app.groupTree.treeViewLeaf.organizationalTooltip": "Skupina je organizační (nemá žádné studenty ani zadané úlohy)", @@ -1502,11 +1473,6 @@ "app.pipelinesList.judgeOnlyIconTooltip": "Pipeline pouze se sudím", "app.pipelinesList.stdoutIconTooltip": "Testované řešení by mělo vypsat svoje výsledky na standardní výstup", "app.pipelinesList.universalPipelineIconTooltip": "Univerzální pipeline, která se používá v konfiguracích běžných úloh.", - "app.plantSisTerm.noGroupsSelected": "Musí být vybrána alespoň jedna rodičovská skupina.", - "app.plantSisTerm.plantGroups": "Osadit skupiny", - "app.plantSisTerm.planted": "Osazeno", - "app.plantSisTerm.planting": "Sázím...", - "app.plantSisTerm.title": "Osadit skupiny pro semestr dle SISu", "app.pointsForm.bonusPoints": "Bonusové body:", "app.pointsForm.failed": "Ukládání bonusových bodů se nezdařilo.", "app.pointsForm.maxPointsEver": "(max. bodů bez ohledu na termíny je {maxPointsEver})", @@ -1767,7 +1733,6 @@ "app.sidebar.menu.admin.instances": "Instance", "app.sidebar.menu.admin.messages": "Systémové zprávy", "app.sidebar.menu.admin.server": "Správa serveru", - "app.sidebar.menu.admin.sis": "SIS integrace [stará]", "app.sidebar.menu.admin.title": "Administrátor", "app.sidebar.menu.admin.users": "Uživatelé", "app.sidebar.menu.archive": "Archiv", @@ -1779,47 +1744,6 @@ "app.sidebar.menu.pipelines": "Pipeline", "app.sidebar.menu.signIn": "Přihlásit se", "app.sidebar.menu.title": "Menu", - "app.sisBindGroupForm.emptyGroup": "Nejprve vyberte skupinu.", - "app.sisBindGroupForm.failed": "Svazování skupiny selhalo. Zkuste to prosím později.", - "app.sisBindGroupForm.group": "Skupina ke svázání:", - "app.sisBindGroupForm.info": "Vybraný kurz (uvedený výše) bude svázán s vybranou existující skupinou. Pokud nemáte vhodnou skupinu vytvořenou, použijte raději tlačítko 'Vytvořit novou'.", - "app.sisBindGroupForm.submit": "Svázat", - "app.sisBindGroupForm.submitting": "Svazuji...", - "app.sisBindGroupForm.success": "Skupina byla svázána.", - "app.sisBindGroupForm.title": "Svázat existující skupinu z ReCodExu s rozvrhovým lístkem SISu", - "app.sisCreateGroupForm.emptyParentGroup": "Nejprve vyberte rodičovskou skupinu.", - "app.sisCreateGroupForm.failed": "Vytváření skupiny selhalo. Zkuste to prosím později.", - "app.sisCreateGroupForm.info": "Nově vytvořená skupina bude umístěna přímo pod zvolenou rodičovskou skupinu a bude automaticky svázána s vybraným kurzem (uvedeným výše). Název nové skupiny bude odvozen z názvu kurzu a jeho rozvržení (můžete jej změnit později).", - "app.sisCreateGroupForm.parentGroup": "Rodičovská skupina:", - "app.sisCreateGroupForm.submit": "Vytvořit", - "app.sisCreateGroupForm.submitting": "Vytvářím...", - "app.sisCreateGroupForm.success": "Skupina byla vytvořena.", - "app.sisCreateGroupForm.title": "Vytvořit novou skupinu sváznou s rozvrhovým lístkem SISu", - "app.sisIntegration.deleteConfirm": "Opravdu chcete smazat SIS semestr?", - "app.sisIntegration.identityInfo": "Váš účet v ReCodExu je asociován se SIS identifikátorem \"{externalId}\".", - "app.sisIntegration.list": "SIS Semestry", - "app.sisIntegration.noAccessible": "Váš účet nepodporuje integraci se SISem. Přihlašte se prosím pomocí CAS-UK.", - "app.sisIntegration.noCasIdentifier": "Váš účet v ReCodExu není asociován s žádným SIS identifikátorem.", - "app.sisIntegration.noCoursesGroupsAvailable": "V tuto chvíli nejsou v ReCodExu žádné skupiny odpovídající předmětům, které máte zapsány tento semestr.", - "app.sisIntegration.noSisGroups": "V tuto chvíli nemáte v SISu žádné rozvrhové lístky pro tento semestr.", - "app.sisIntegration.plantButton": "Osadit", - "app.sisIntegration.title": "UK SIS Integrace", - "app.sisIntegration.yearTerm": "Rok a semestr:", - "app.sisSupervisor.bindGroupButton": "Svázat s existující skupinou", - "app.sisSupervisor.createGroupButton": "Vytvořit novou skupinu", - "app.sisSupervisor.groupAdmins": "Administrátoři skupiny", - "app.sisSupervisor.groupsAlreadyExist": "S daným kurzem je již asociována skupina.", - "app.sisSupervisor.lab": "Cvičení", - "app.sisSupervisor.lecture": "Přednáška", - "app.sisSupervisor.multiGroupPopover.title": "Skupina má vícero vazeb:", - "app.sisSupervisor.noAccessible": "Váš účet nepodporuje integraci se SISem. Přihlašte se prosím pomocí CAS-UK.", - "app.sisSupervisor.noSisGroups": "V současnosti nejsou v ReCodExu žádné skupiny, které by odpovídaly tomuto lístku ze SISu.", - "app.sisSupervisor.noUsersInNewGroupsWarning": "Upozornění: při založení nebo svázání skupiny s lístkem ze SISu nejsou do skupiny přidáni žádní studenti. Svázání pouze zajistí, že je skupina viditelná pro příslušné studenty, a že se do ní mohou sami přihlásit.", - "app.sisSupervisor.notScheduled": "nerozvrženo", - "app.sisSupervisor.organizationalGroupWarning": "Studenti se nemohou přidat do organizační skupiny.", - "app.sisSupervisor.sisGroupsCreate": "Zakládání skupin asociovaných s kurzy ze SISu UK", - "app.sisSupervisor.sisGroupsCreateExplain": "SIS kurzy, které vyučujete v určitém semestru a které mají mapování do ReCodExu. Můžete vytvořit novou skupinu s vazbou na SIS, nebo svázat existující skupinu na tyto kurzy.", - "app.sisSupervisor.yearTerm": "Rok a semestr:", "app.solution.actions.accept": "Akceptovat", "app.solution.actions.acceptLong": "Akceptovat jako finální", "app.solution.actions.points.clearOverride": "Zrušit přenastavení bodů", @@ -2069,14 +1993,6 @@ "app.systemMessagesList.visibleTo": "Viditelné do", "app.tabbedArrayField.empty": "Nyní zde nejsou žádné záložky.", "app.tabbedArrayField.reallyRemoveQuestion": "Opravdu chcete tuto záložku odstranit?", - "app.termsList.advertiseUntil": "Nabízet do", - "app.termsList.end": "Konec", - "app.termsList.noTerms": "V tomto seznamu nejsou žádné semestry ze SISu.", - "app.termsList.start": "Počátek", - "app.termsList.summer": "Léto", - "app.termsList.term": "Semestr", - "app.termsList.winter": "Zima", - "app.termsList.year": "Rok", "app.uploadFiles.addFileButton": "Vybrat soubor(y) pro nahrání", "app.uploadFiles.cancelIconTooltip": "Nahrávání se ruší...", "app.uploadFiles.completingIconTooltip": "Server konsoliduje nahraná data...", @@ -2212,4 +2128,4 @@ "recodex-judge-shuffle-all": "Sudí neuspořádaných tokenů a řádků", "recodex-judge-shuffle-newline": "Sudí neuspořádaných tokenů (ignorující konce řádků)", "recodex-judge-shuffle-rows": "Sudí neuspořádaných řádků" -} +} \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index 8efdff02a..aeb6bade9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -45,15 +45,6 @@ "app.addLicense.validation.note": "Note cannot be empty.", "app.addLicense.validation.validUntilEmpty": "The expiration date of the valid period of the license must be set.", "app.addLicense.validation.validUntilInThePast": "The expiration date of the valid period of the license must be in the future.", - "app.addSisTermForm.failed": "Cannot save the new SIS term.", - "app.addSisTermForm.processing": "Saving...", - "app.addSisTermForm.submit": "Save new term", - "app.addSisTermForm.success": "The term is saved.", - "app.addSisTermForm.summer": "Summer term", - "app.addSisTermForm.term": "Term:", - "app.addSisTermForm.title": "Add new term", - "app.addSisTermForm.winter": "Winter term", - "app.addSisTermForm.year": "Year:", "app.addStudent.cannotSearch": "You do not have permissions to search students, so you cannot add them explicitly.", "app.addStudent.inviteButton": "Invite to Register", "app.addStudent.inviteDialog.explain": "An invitation will be sent to the user at given email address. The user will receive a link for registration as a local user. User profile details (name and email) must be filled in correctly, since the user will not be able to modify them. Optionally, you may select a list of groups to which the user will be assigned immediately after registration.", @@ -96,13 +87,6 @@ "app.archiveGroupButton.setShort": "Archive", "app.archiveGroupButton.unset": "Excavate from Archive", "app.archiveGroupButton.unsetShort": "Excavate", - "app.archiveSisTerm.archiveGroups": "Archive Groups", - "app.archiveSisTerm.archived": "Archived", - "app.archiveSisTerm.archiving": "Archiving...", - "app.archiveSisTerm.failed": "Archiving failed. Some of the groups may not have been archived.", - "app.archiveSisTerm.noGroups": "There are no groups associated with this semester. Perhaps they have all been archived already.", - "app.archiveSisTerm.noGroupsSelected": "At least one group needs to be selected.", - "app.archiveSisTerm.title": "Archive Groups of SIS Term", "app.assignExerciseButton.isBroken": "Broken", "app.assignExerciseButton.isLocked": "Locked", "app.assignExerciseButton.noRefSolutions": "Unproven", @@ -282,7 +266,6 @@ "app.createExerciseForm.validation.noGroupSelected": "No group of residence has been selected.", "app.createGroup.detaining": "Detaining", "app.createGroup.detaining.explanation": "Students cannot leave the group on their own. Only a group supervisor/admin can remove them.", - "app.createGroup.externalId": "External ID of the group:", "app.createGroup.hasThreshold": "Students require certain number of points to complete the course", "app.createGroup.hasThreshold.explanation": "The required amount of points can be specified relatively (percentage of total sum of points) or as an absolute point limit. Only one of these values should be specified.", "app.createGroup.isExam": "Exam", @@ -299,11 +282,9 @@ "app.createUserForm.validation.emailTaken": "This email address is already taken by someone else.", "app.createUserForm.validation.emptyPassword": "The password cannot be empty.", "app.dashboard.memberOf": "Groups you are a member of", - "app.dashboard.sisGroupsStudent": "Join Groups Associated with UK SIS Courses", - "app.dashboard.sisGroupsStudentExplain": "SIS courses you are enrolled to in particular semesters and which have corresponding groups in ReCodEx.", - "app.dashboard.studentNoGroups": "You are not a member of any group yet. A group supervisor may add you into his/her group, or you can use other mechanisms (like the dialog on the SIS integration page) to join some groups that apply to you.", + "app.dashboard.studentNoGroups": "You are not a member of any group yet. Please, follow the guidelines provided by your educational institution about how to join a group.", "app.dashboard.studentNoGroupsTitle": "No Group Memberships", - "app.dashboard.supervisorNoGroups": "You are currently not supervising any groups. An administrator may create a group for you or you can use other mechanisms (like the dialog on the SIS integration page) to create groups for your students.", + "app.dashboard.supervisorNoGroups": "You are currently not supervising any groups.", "app.dashboard.supervisorNoGroupsTitle": "No Groups", "app.dashboard.supervisorOf": "Groups managed by you (as admin or supervisor)", "app.dashboard.title": "Dashboard — a Complete Overview", @@ -619,14 +600,6 @@ "app.editShadowAssignmentPointsForm.removePoints": "Remove Points Record", "app.editShadowAssignmentPointsForm.setNow": "Now", "app.editShadowAssignmentPointsForm.validation.pointsOutOfRange": "Points are out of regular range. Regular score for this assignment is between 0 and {maxPoints}.", - "app.editSisTerm.advertiseUntil": "Advertise this term to students until:", - "app.editSisTerm.beginning": "Beginning of the term:", - "app.editSisTerm.end": "End of the term:", - "app.editSisTerm.title": "Edit SIS Term", - "app.editSisTerm.validation.advertiseInLimits": "The term can be advertised only in its period.", - "app.editSisTerm.validation.noAdvertiseUntil": "End date of advertising the term is required.", - "app.editSisTerm.validation.noBeginning": "Start of the term is required.", - "app.editSisTerm.validation.noEnd": "End of the term is required.", "app.editSolutionNoteForm.failed": "Cannot save the solution note.", "app.editSolutionNoteForm.note": "Note:", "app.editSystemMessageForm.role": "Users with this role and its children can see notification.", @@ -1068,15 +1041,11 @@ "app.group.organizationalExplain": "This group is organizational, so it cannot have any students nor assignments. However, it may have attached exercises which can be assigned in sub-groups.", "app.group.setRoot": "Select", "app.group.students": "Students", - "app.group.unbind": "Unbind", - "app.group.unbind.confirmQuestion": "Do you really wish to unbind the group? The group will linger on, but it will be detached from the SIS so the students will not see it.", "app.group.unsetRoot": "Unset", "app.groupAssignments.groupExercises": "Exercises for Assignment in the Group", "app.groupDetail.assignments": "Assignments", - "app.groupDetail.bindings.genericProvider": "External binding to \"{provider}\"", - "app.groupDetail.bindings.sis": "SIS UK scheduling event codes", "app.groupDetail.description": "Group Description", - "app.groupDetail.externalId": "External identification of the group", + "app.groupDetail.externalAttributes": "External Attributes:", "app.groupDetail.hasPublicStats": "Students can see progress of other students", "app.groupDetail.isPublic": "Everyone can see and join this group", "app.groupDetail.loading": "Loading group data...", @@ -1159,6 +1128,8 @@ "app.groupStudents.privateStats": "The admin of the group has restricted the access to the results of students. Therefore, you can see only your own results, not the results of other students.", "app.groupStudents.studentsResultsTable": "Students and Their Results", "app.groupStudents.title": "Group Students", + "app.groupTree.treeViewLeaf.adminPopover.title": "Group administrators", + "app.groupTree.treeViewLeaf.adminsCount": "{count} {count, plural, one {admin} other {admins}}", "app.groupTree.treeViewLeaf.archivedTooltip": "The group is archived", "app.groupTree.treeViewLeaf.examTooltip": "Exam group", "app.groupTree.treeViewLeaf.organizationalTooltip": "The group is organizational (it does not have any students or assignments)", @@ -1502,11 +1473,6 @@ "app.pipelinesList.judgeOnlyIconTooltip": "Judge-only pipeline", "app.pipelinesList.stdoutIconTooltip": "Tested solution is expected to yield results to standard output", "app.pipelinesList.universalPipelineIconTooltip": "Universal pipeline which is used in common (simple) exercise configurations.", - "app.plantSisTerm.noGroupsSelected": "At least one parent group needs to be selected.", - "app.plantSisTerm.plantGroups": "Plant Groups", - "app.plantSisTerm.planted": "Planted", - "app.plantSisTerm.planting": "Planting...", - "app.plantSisTerm.title": "Plant Groups for SIS Term", "app.pointsForm.bonusPoints": "Bonus points:", "app.pointsForm.failed": "Cannot save the bonus points.", "app.pointsForm.maxPointsEver": "(max. points limit regardless deadlines is {maxPointsEver})", @@ -1767,7 +1733,6 @@ "app.sidebar.menu.admin.instances": "Instances", "app.sidebar.menu.admin.messages": "System Messages", "app.sidebar.menu.admin.server": "Server Management", - "app.sidebar.menu.admin.sis": "SIS Integration [old]", "app.sidebar.menu.admin.title": "Administration", "app.sidebar.menu.admin.users": "Users", "app.sidebar.menu.archive": "Archive", @@ -1779,47 +1744,6 @@ "app.sidebar.menu.pipelines": "Pipelines", "app.sidebar.menu.signIn": "Sign in", "app.sidebar.menu.title": "Menu", - "app.sisBindGroupForm.emptyGroup": "You need to select the group first.", - "app.sisBindGroupForm.failed": "Binding group failed. Please try again later.", - "app.sisBindGroupForm.group": "Group to bind:", - "app.sisBindGroupForm.info": "The selected course (mentioned above) will be bound to selected existing group. If you do not have an appropriate group yet, use 'Create group' button instead.", - "app.sisBindGroupForm.submit": "Bind", - "app.sisBindGroupForm.submitting": "Binding...", - "app.sisBindGroupForm.success": "The group was bound.", - "app.sisBindGroupForm.title": "Bind existing ReCodEx group with SIS scheduling event", - "app.sisCreateGroupForm.emptyParentGroup": "You need to select the parent group first.", - "app.sisCreateGroupForm.failed": "Creating group failed. Please try again later.", - "app.sisCreateGroupForm.info": "The newly created group will be placed right under selected parent group and it will be automatically bind to selected course (mentioned above). The name of the new group will be derived from the name of the course and its scheduling (you may change it later).", - "app.sisCreateGroupForm.parentGroup": "Parent group:", - "app.sisCreateGroupForm.submit": "Create", - "app.sisCreateGroupForm.submitting": "Creating...", - "app.sisCreateGroupForm.success": "The group was created.", - "app.sisCreateGroupForm.title": "Create a new group associated with SIS scheduling event", - "app.sisIntegration.deleteConfirm": "Are you sure you want to delete the SIS term?", - "app.sisIntegration.identityInfo": "Your ReCodEx account is associated with SIS identity identifier \"{externalId}\".", - "app.sisIntegration.list": "SIS Terms", - "app.sisIntegration.noAccessible": "Your account does not support SIS integration. Please, log in using CAS-UK.", - "app.sisIntegration.noCasIdentifier": "Your ReCodEx account is not associated with any SIS identity identifier.", - "app.sisIntegration.noCoursesGroupsAvailable": "There are currently no groups in ReCodEx bound to courses you are enrolled to in this semester.", - "app.sisIntegration.noSisGroups": "Currently you have no courses in SIS for this semester.", - "app.sisIntegration.plantButton": "Plant", - "app.sisIntegration.title": "UK SIS Integration", - "app.sisIntegration.yearTerm": "Year and term:", - "app.sisSupervisor.bindGroupButton": "Bind Existing Group", - "app.sisSupervisor.createGroupButton": "Create New Group", - "app.sisSupervisor.groupAdmins": "Group Administrators", - "app.sisSupervisor.groupsAlreadyExist": "Group(s) have been already associated with this course.", - "app.sisSupervisor.lab": "Lab (seminar)", - "app.sisSupervisor.lecture": "Lecture", - "app.sisSupervisor.multiGroupPopover.title": "The group has multiple bindings:", - "app.sisSupervisor.noAccessible": "Your account does not support SIS integration. Please, log in using CAS-UK.", - "app.sisSupervisor.noSisGroups": "Currently there are no ReCodEx groups matching this SIS course.", - "app.sisSupervisor.noUsersInNewGroupsWarning": "Please note that when a group is created from or bound to a SIS course, no students are added to this group. The binding process only ensures that the group is visible to the students and they are allowed to join it.", - "app.sisSupervisor.notScheduled": "not scheduled", - "app.sisSupervisor.organizationalGroupWarning": "Students cannot join organizational groups.", - "app.sisSupervisor.sisGroupsCreate": "Create Groups Associated with UK SIS Courses", - "app.sisSupervisor.sisGroupsCreateExplain": "SIS courses you teach in particular semesters and which have mapping to ReCodEx. You may create new groups with binding or bind existing groups to these courses.", - "app.sisSupervisor.yearTerm": "Year and term:", "app.solution.actions.accept": "Accept", "app.solution.actions.acceptLong": "Accept as Final", "app.solution.actions.points.clearOverride": "Clear Points Override", @@ -2069,14 +1993,6 @@ "app.systemMessagesList.visibleTo": "Visible To", "app.tabbedArrayField.empty": "There are currently no tabs.", "app.tabbedArrayField.reallyRemoveQuestion": "Do you really wish to delete this tab?", - "app.termsList.advertiseUntil": "Advertise Until", - "app.termsList.end": "End", - "app.termsList.noTerms": "There are no SIS terms in this list.", - "app.termsList.start": "Start", - "app.termsList.summer": "Summer", - "app.termsList.term": "Term", - "app.termsList.winter": "Winter", - "app.termsList.year": "Year", "app.uploadFiles.addFileButton": "Select File(s) for Upload", "app.uploadFiles.cancelIconTooltip": "Canceling the upload...", "app.uploadFiles.completingIconTooltip": "The server is consolidating uploaded data...", diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index 88e87eaa4..1acca06d4 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -162,7 +162,7 @@ class Dashboard extends Component {

@@ -233,7 +233,7 @@ class Dashboard extends Component {

diff --git a/src/pages/EditAssignment/EditAssignment.js b/src/pages/EditAssignment/EditAssignment.js index a46bc6f15..d433f0def 100644 --- a/src/pages/EditAssignment/EditAssignment.js +++ b/src/pages/EditAssignment/EditAssignment.js @@ -241,7 +241,6 @@ EditAssignment.propTypes = { visibility: PropTypes.string, visibleFrom: PropTypes.object, canViewLimitRatios: PropTypes.bool, - allowVisibleFrom: PropTypes.bool, exerciseSync: PropTypes.func.isRequired, validateAssignment: PropTypes.func.isRequired, links: PropTypes.object, @@ -262,7 +261,6 @@ export default withLinks( hasNotificationAsyncJob: hasPendingNotificationAsyncJob(state, assignmentId), deadlines: editAssignmentFormSelector(state, 'deadlines'), visibility: editAssignmentFormSelector(state, 'visibility'), - allowVisibleFrom: editAssignmentFormSelector(state, 'allowVisibleFrom'), visibleFrom: editAssignmentFormSelector(state, 'visibleFrom'), canViewLimitRatios: editAssignmentFormSelector(state, 'canViewLimitRatios'), }; diff --git a/src/pages/EditGroup/EditGroup.js b/src/pages/EditGroup/EditGroup.js index 4d5a6cc94..c7796081a 100644 --- a/src/pages/EditGroup/EditGroup.js +++ b/src/pages/EditGroup/EditGroup.js @@ -53,14 +53,8 @@ class EditGroup extends Component { } getInitialValues = lruMemoize( - ({ - localizedTexts, - externalId, - public: isPublic, - privateData: { publicStats, threshold, pointsLimit, detaining }, - }) => ({ + ({ localizedTexts, public: isPublic, privateData: { publicStats, threshold, pointsLimit, detaining } }) => ({ localizedTexts: getLocalizedTextsInitialValues(localizedTexts, EDIT_GROUP_FORM_LOCALIZED_TEXTS_DEFAULT), - externalId, isPublic, publicStats, detaining, @@ -330,20 +324,11 @@ export default withLinks( reset: () => dispatch(reset('editGroup')), loadAsync: () => dispatch(fetchGroupIfNeeded(groupId)), reload: () => dispatch(fetchGroup(groupId)), - editGroup: ({ - localizedTexts, - externalId, - isPublic, - publicStats, - detaining, - hasThreshold, - threshold, - pointsLimit, - }) => + editGroup: ({ localizedTexts, isPublic, publicStats, detaining, hasThreshold, threshold, pointsLimit }) => dispatch( editGroup(groupId, { localizedTexts: transformLocalizedTextsFormData(localizedTexts), - externalId, + externalId: '', isPublic, publicStats, detaining, diff --git a/src/pages/ExerciseAssignments/ExerciseAssignments.js b/src/pages/ExerciseAssignments/ExerciseAssignments.js index 612060a9f..7e546f94c 100644 --- a/src/pages/ExerciseAssignments/ExerciseAssignments.js +++ b/src/pages/ExerciseAssignments/ExerciseAssignments.js @@ -12,7 +12,7 @@ import { ExerciseNavigation } from '../../components/layout/Navigation'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import Box from '../../components/widgets/Box'; import Callout from '../../components/widgets/Callout'; -import { AssignmentsIcon, LockIcon, CheckRequiredIcon, SaveIcon } from '../../components/icons'; +import { AssignmentsIcon, LockIcon, CheckRequiredIcon } from '../../components/icons'; import ExerciseCallouts, { exerciseCalloutsAreVisible } from '../../components/Exercises/ExerciseCallouts'; import ExerciseButtons from '../../components/Exercises/ExerciseButtons'; import AssignmentsTable from '../../components/Assignments/Assignment/AssignmentsTable'; @@ -123,6 +123,7 @@ class ExerciseAssignments extends Component { groupsAccessor, deadlines, visibility, + canViewLimitRatios, syncAssignment, editAssignment, deleteAssignment, @@ -230,8 +231,8 @@ class ExerciseAssignments extends Component { visibility={visibility} showSendNotification submitButtonMessages={SUBMIT_BUTTON_MESSAGES} - defaultIcon={} mergeJudgeLogs={exercise.mergeJudgeLogs} + canViewLimitRatios={canViewLimitRatios} /> )} @@ -262,6 +263,7 @@ ExerciseAssignments.propTypes = { groupsAccessor: PropTypes.func.isRequired, deadlines: PropTypes.string, visibility: PropTypes.string, + canViewLimitRatios: PropTypes.bool, intl: PropTypes.object.isRequired, loadAsync: PropTypes.func.isRequired, assignExercise: PropTypes.func.isRequired, @@ -285,6 +287,7 @@ export default connect( groupsAccessor: groupDataAccessorSelector(state), deadlines: multiAssignFormSelector(state, 'deadlines'), visibility: multiAssignFormSelector(state, 'visibility'), + canViewLimitRatios: multiAssignFormSelector(state, 'canViewLimitRatios'), }; }, (dispatch, { params: { exerciseId } }) => ({ diff --git a/src/pages/GroupInfo/GroupInfo.js b/src/pages/GroupInfo/GroupInfo.js index f0f3d80e6..2d6294427 100644 --- a/src/pages/GroupInfo/GroupInfo.js +++ b/src/pages/GroupInfo/GroupInfo.js @@ -14,11 +14,17 @@ import { addSupervisor, addObserver, removeMember, + fetchGroupAttributes, } from '../../redux/modules/groups.js'; import { fetchByIds, fetchUser } from '../../redux/modules/users.js'; import { loggedInUserIdSelector } from '../../redux/selectors/auth.js'; import { isLoggedAsSuperAdmin, loggedInUserSelector } from '../../redux/selectors/users.js'; -import { groupSelector, groupDataAccessorSelector, groupsSelector } from '../../redux/selectors/groups.js'; +import { + groupSelector, + groupDataAccessorSelector, + groupsSelector, + groupAttributesSelector, +} from '../../redux/selectors/groups.js'; import { primaryAdminsOfGroupSelector, supervisorsOfGroupSelector, @@ -55,6 +61,7 @@ class GroupInfo extends Component { .then(res => res.value) .then(group => Promise.all([ + hasPermissions(group, 'viewPublicDetail') ? dispatch(fetchGroupAttributes(groupId)) : Promise.resolve(), dispatch(fetchByIds(safeGet(group, ['primaryAdminsIds']) || [])), dispatch(fetchByIds(safeGet(group, ['privateData', 'supervisors']) || [])), dispatch(fetchByIds(safeGet(group, ['privateData', 'observers']) || [])), @@ -109,6 +116,7 @@ class GroupInfo extends Component { isOrganizational, isExam, pendingMemberships, + externalAttributes, addAdmin, addSupervisor, addObserver, @@ -175,6 +183,7 @@ class GroupInfo extends Component { supervisors={supervisors} isAdmin={isAdminOrSuperadmin} groups={groups} + externalAttributes={externalAttributes} locale={locale} /> )} @@ -294,6 +303,13 @@ GroupInfo.propTypes = { isSupervisor: PropTypes.bool, isSuperAdmin: PropTypes.bool, isStudent: PropTypes.bool, + hasThreshold: PropTypes.bool, + threshold: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + pointsLimit: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + isOrganizational: PropTypes.bool, + isExam: PropTypes.bool, + pendingMemberships: ImmutablePropTypes.list, + externalAttributes: ImmutablePropTypes.map, addSubgroup: PropTypes.func, loadAsync: PropTypes.func, refetchUsers: PropTypes.func.isRequired, @@ -301,12 +317,6 @@ GroupInfo.propTypes = { addSupervisor: PropTypes.func.isRequired, addObserver: PropTypes.func.isRequired, removeMember: PropTypes.func.isRequired, - hasThreshold: PropTypes.bool, - threshold: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - pointsLimit: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - isOrganizational: PropTypes.bool, - isExam: PropTypes.bool, - pendingMemberships: ImmutablePropTypes.list, links: PropTypes.object, intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired, }; @@ -336,6 +346,7 @@ const mapStateToProps = (state, { params: { groupId } }) => { isOrganizational: addSubgroupFormSelector(state, 'isOrganizational'), isExam: addSubgroupFormSelector(state, 'isExam'), pendingMemberships: pendingMembershipsSelector(state, groupId), + externalAttributes: groupAttributesSelector(state, groupId), }; }; diff --git a/src/pages/SisIntegration/SisIntegration.js b/src/pages/SisIntegration/SisIntegration.js deleted file mode 100644 index 83f096ae3..000000000 --- a/src/pages/SisIntegration/SisIntegration.js +++ /dev/null @@ -1,410 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { Row, Col } from 'react-bootstrap'; -import moment from 'moment'; - -import Page from '../../components/layout/Page'; -import FetchManyResourceRenderer from '../../components/helpers/FetchManyResourceRenderer'; -import SisIntegrationContainer from '../../containers/SisIntegrationContainer'; -import SisSupervisorGroupsContainer from '../../containers/SisSupervisorGroupsContainer'; -import AddSisTermForm from '../../components/forms/AddSisTermForm/AddSisTermForm.js'; -import TermsList from '../../components/SisIntegration/TermsList/TermsList.js'; -import Confirm from '../../components/forms/Confirm'; -import Icon, { ArchiveIcon, EditIcon, DeleteIcon, UserIcon } from '../../components/icons'; -import PlantTermGroups, { createDefaultSemesterLocalization } from '../../components/SisIntegration/PlantTermGroups'; -import ArchiveTermGroups from '../../components/SisIntegration/ArchiveTermGroups'; -import EditTerm from '../../components/SisIntegration/EditTerm'; -import Box from '../../components/widgets/Box/Box.js'; -import ResourceRenderer from '../../components/helpers/ResourceRenderer'; -import Button, { TheButtonGroup } from '../../components/widgets/TheButton'; -import Callout from '../../components/widgets/Callout'; -import NotVerifiedEmailCallout from '../../components/Users/NotVerifiedEmailCallout'; - -import { fetchAllTerms, create, deleteTerm, editTerm } from '../../redux/modules/sisTerms.js'; -import { createGroup, fetchAllGroups, setArchived } from '../../redux/modules/groups.js'; -import { fetchUser } from '../../redux/modules/users.js'; -import { loggedInUserSelector, getLoggedInUserEffectiveRole } from '../../redux/selectors/users.js'; -import { fetchManyStatus, readySisTermsSelector } from '../../redux/selectors/sisTerms.js'; -import { notArchivedGroupsSelector } from '../../redux/selectors/groups.js'; -import { loggedUserAdminOfGroupsSelector } from '../../redux/selectors/usersGroups.js'; - -import { - getLocalizedTextsInitialValues, - getLocalizedName, - transformLocalizedTextsFormData, -} from '../../helpers/localizedData.js'; -import { isStudentRole, isSupervisorRole, isSuperadminRole } from '../../components/helpers/usersRoles.js'; -import { getExternalIdForCAS } from '../../helpers/cas.js'; -import { arrayToObject } from '../../helpers/common.js'; - -const ADD_SIS_TERM_INITIAL_VALUES = { - year: new Date(new Date().getTime() - 86400000 * 180).getFullYear(), // actual year (shifted by 180 days back) - term: '', -}; - -class SisIntegration extends Component { - state = { - openPlant: null, - plantInitialValues: null, - plantRootGroups: [], - openArchive: null, - archiveInitialValues: null, - archiveGroups: [], - openEdit: null, - editInitialValues: null, - }; - - // planting new groups - openPlantDialog = ({ year, term }, groups) => { - const id = `${year}-${term}`; - const { - intl: { locale }, - } = this.props; - - const mainRootGroup = groups.find(group => group.parentGroupId === null); - const plantRootGroups = mainRootGroup - ? groups.filter(group => group.parentGroupId === mainRootGroup.id).filter(group => group.externalId) - : []; - plantRootGroups.sort((a, b) => getLocalizedName(a, locale).localeCompare(getLocalizedName(b, locale), locale)); - - const localizations = createDefaultSemesterLocalization(year, term); - const plantInitialValues = { - groups: arrayToObject( - plantRootGroups, - g => g.id, - () => false - ), - localizedTexts: getLocalizedTextsInitialValues( - localizations, - localizations.find(l => l.locale === 'en') - ), - externalId: id, - }; - - this.setState({ openPlant: id, plantInitialValues, plantRootGroups }); - }; - - closePlantDialog = () => { - this.setState({ openPlant: null }); - }; - - submitPlantDialog = ({ groups, ...data }) => { - const { addSubgroup, refreshGroups } = this.props; - const groupTemplate = { - publicStats: false, - detaining: false, - isPublic: false, - isOrganizational: true, - hasThreshold: false, - noAdmin: true, - }; - - return Promise.all( - Object.entries(groups) - .filter(([_, selected]) => selected) - .map(([id, _]) => this.state.plantRootGroups.find(group => group.id === id)) - .filter(group => group) - .map(group => addSubgroup(group, { ...groupTemplate, ...data })) - ).then(() => { - this.closePlantDialog(); - return refreshGroups(); - }); - }; - - // archiving - openArchiveDialog = ({ year, term }, groups) => { - const externalId = `${year}-${term}`; - const { - intl: { locale }, - } = this.props; - - const mainRootGroup = groups.find(group => group.parentGroupId === null); - const rootGroups = mainRootGroup ? groups.filter(group => group.parentGroupId === mainRootGroup.id) : []; - const rootGroupsIndex = arrayToObject(rootGroups); - const archiveGroups = groups - .filter(group => rootGroupsIndex[group.parentGroupId] && group.externalId === externalId) - .map(group => ({ - id: group.id, - name: `${getLocalizedName(rootGroupsIndex[group.parentGroupId], locale)} / ${getLocalizedName(group, locale)}`, - })); - - archiveGroups.sort((a, b) => a.name.localeCompare(b.name, locale)); - - const archiveInitialValues = { - groups: arrayToObject( - archiveGroups, - g => g.id, - () => false - ), - }; - - this.setState({ openArchive: externalId, archiveInitialValues, archiveGroups }); - }; - - closeArchiveDialog = () => { - this.setState({ openArchive: null }); - }; - - submitArchiveDialog = ({ groups }) => { - const { setArchived, refreshGroups } = this.props; - return Promise.all( - Object.entries(groups) - .filter(([_, selected]) => selected) - .map(([id, _]) => setArchived(id)) - ).then(() => { - this.closeArchiveDialog(); - return refreshGroups(); - }); - }; - - // editing semester parameters - openEditDialog = (openEdit, data) => { - const editInitialValues = { - beginning: data.beginning * 1000, - end: data.end * 1000, - advertiseUntil: data.advertiseUntil * 1000, - }; - this.setState({ openEdit, editInitialValues }); - }; - - closeEditDialog = () => { - this.setState({ openEdit: null, editInitialValues: null }); - }; - - submitEditDialog = data => this.props.editTerm(this.state.openEdit, data).then(this.closeEditDialog); - - static loadAsync = (params, dispatch) => dispatch(fetchAllTerms()); - - componentDidMount() { - const { loadAsync, effectiveRole } = this.props; - isSuperadminRole(effectiveRole) && loadAsync(); - } - - componentDidUpdate(prevProps) { - if (this.props.effectiveRole !== prevProps.effectiveRole && isSuperadminRole(this.props.effectiveRole)) { - this.props.loadAsync(); - } - } - - render() { - const { - loggedInUser, - effectiveRole, - fetchStatus, - adminOfGroups, - allGroups, - createNewTerm, - deleteTerm, - sisTerms, - refreshUser, - } = this.props; - - return ( - }> - {user => { - const externalId = getExternalIdForCAS(user); - - return externalId ? ( - <> - {user && !user.isVerified && ( - refreshUser(user.id)} /> - )} - - }> - - - - - {groups => ( - <> - {isStudentRole(effectiveRole) && ( - - - - - - )} - - {isSupervisorRole(effectiveRole) && ( - - - - - - )} - - {isSuperadminRole(effectiveRole) && ( - - - - {() => ( - } - noPadding - unlimitedHeight> - ( - - {Date.now() <= data.advertiseUntil * 1000 && ( - - )} - - - deleteTerm(id)} - question={ - - }> - - - - )} - /> - - )} - - - - - - - )} - - - - - - - - )} - - - ) : ( - - - - ); - }} - - ); - } -} - -SisIntegration.propTypes = { - loggedInUser: ImmutablePropTypes.map, - effectiveRole: PropTypes.string, - fetchStatus: PropTypes.string, - sisTerms: PropTypes.array.isRequired, - adminOfGroups: ImmutablePropTypes.map, - allGroups: ImmutablePropTypes.map, - loadAsync: PropTypes.func.isRequired, - createNewTerm: PropTypes.func, - deleteTerm: PropTypes.func, - editTerm: PropTypes.func, - addSubgroup: PropTypes.func, - setArchived: PropTypes.func, - refreshGroups: PropTypes.func, - refreshUser: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, -}; - -const mapStateToProps = state => { - return { - loggedInUser: loggedInUserSelector(state), - effectiveRole: getLoggedInUserEffectiveRole(state), - fetchStatus: fetchManyStatus(state), - sisTerms: readySisTermsSelector(state), - adminOfGroups: loggedUserAdminOfGroupsSelector(state), - allGroups: notArchivedGroupsSelector(state), - }; -}; - -const mapDispatchToProps = (dispatch, { params }) => ({ - loadAsync: () => SisIntegration.loadAsync(params, dispatch), - createNewTerm: data => dispatch(create(data)), - deleteTerm: id => dispatch(deleteTerm(id)), - editTerm: (id, data) => { - // convert deadline times to timestamps - const processedData = Object.assign({}, data, { - beginning: moment(data.beginning).unix(), - end: moment(data.end).unix(), - advertiseUntil: moment(data.advertiseUntil).unix(), - }); - return dispatch(editTerm(id, processedData)); - }, - addSubgroup: (parentGroup, { localizedTexts, ...data }) => - dispatch( - createGroup({ - ...data, - localizedTexts: transformLocalizedTextsFormData(localizedTexts), - instanceId: parentGroup.privateData.instanceId, - parentGroupId: parentGroup.id, - }) - ).then(), - setArchived: groupId => dispatch(setArchived(groupId, true)), - refreshGroups: () => dispatch(fetchAllGroups()), - refreshUser: userId => dispatch(fetchUser(userId)), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SisIntegration)); diff --git a/src/pages/SisIntegration/index.js b/src/pages/SisIntegration/index.js deleted file mode 100644 index eeb735160..000000000 --- a/src/pages/SisIntegration/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import SisIntegration from './SisIntegration.js'; -export default SisIntegration; diff --git a/src/pages/routes.js b/src/pages/routes.js index c188c0feb..f573ced9b 100644 --- a/src/pages/routes.js +++ b/src/pages/routes.js @@ -44,7 +44,6 @@ import Registration from './Registration'; import ResetPassword from './ResetPassword'; import ServerManagement from './ServerManagement'; import ShadowAssignment from './ShadowAssignment'; -import SisIntegration from './SisIntegration'; import Solution from './Solution'; import SolutionPlagiarisms from './SolutionPlagiarisms'; import SolutionSourceCodes from './SolutionSourceCodes'; @@ -181,7 +180,6 @@ const routesDescriptors = [ r('app/user/:userId/edit', EditUser, 'EDIT_USER_URI_FACTORY', true), r('app/submission-failures', SubmissionFailures, 'FAILURES_URI', true), r('app/system-messages', SystemMessages, 'MESSAGES_URI', true), - r('app/sis-integration', SisIntegration, 'SIS_INTEGRATION_URI', true), r('app/archive', Archive, 'ARCHIVE_URI', true), r('app/server', ServerManagement, 'SERVER_MANAGEMENT_URI', true), r('admin/instances', Instances, 'ADMIN_INSTANCES_URI', true), diff --git a/src/redux/modules/groups.js b/src/redux/modules/groups.js index bbfe50a6d..2abc134e5 100644 --- a/src/redux/modules/groups.js +++ b/src/redux/modules/groups.js @@ -13,9 +13,6 @@ import { additionalActionTypes as additionalInvitationsActionTypes, } from './groupInvitations.js'; import { actionTypes as shadowAssignmentsActionTypes } from './shadowAssignments.js'; -import { actionTypes as sisSupervisedCoursesActionTypes } from './sisSupervisedCoursesTypes.js'; -import { actionTypes as sisSubscribedCoursesActionTypes } from './sisSubscribedGroups.js'; -import { actionTypes as sisPossibleParentsActionTypes } from './sisPossibleParents.js'; import { selectedInstanceId } from '../selectors/auth.js'; import { objectMap, arrayToObject } from '../../helpers/common.js'; @@ -42,6 +39,7 @@ export const additionalActionTypes = { ...createActionsWithPostfixes('LOCK_STUDENT_EXAM', 'recodex/groups'), ...createActionsWithPostfixes('UNLOCK_STUDENT_EXAM', 'recodex/groups'), ...createActionsWithPostfixes('RELOCATE', 'recodex/groups'), + ...createActionsWithPostfixes('GET_ATTRIBUTES', 'recodex/groups'), }; export const loadGroup = actions.pushResource; @@ -167,6 +165,14 @@ export const relocateGroup = (groupId, newParentId) => endpoint: `/groups/${groupId}/relocate/${newParentId}`, }); +export const fetchGroupAttributes = groupId => + createApiAction({ + type: additionalActionTypes.GET_ATTRIBUTES, + method: 'GET', + endpoint: `/group-attributes/${groupId}`, + meta: { groupId }, + }); + /* * Exam-related stuff */ @@ -222,6 +228,11 @@ export const unlockStudentFromExam = (groupId, userId) => * Reducer */ +const sortAttributes = attributes => + attributes.sort( + (a, b) => a.service.localeCompare(b.service) || a.key.localeCompare(b.key) || a.value.localeCompare(b.value) + ); + const reducer = handleActions( Object.assign({}, reduceActions, { [actionTypes.ADD_FULFILLED]: (state, action) => { @@ -326,6 +337,18 @@ const reducer = handleActions( state ), + [additionalActionTypes.GET_ATTRIBUTES_PENDING]: (state, { meta: { groupId } }) => + state.setIn(['attributes', groupId], createRecord()), + + [additionalActionTypes.GET_ATTRIBUTES_FULFILLED]: (state, { meta: { groupId }, payload }) => + state.setIn( + ['attributes', groupId], + createRecord({ state: resourceStatus.FULFILLED, data: sortAttributes(payload) }) + ), + + [additionalActionTypes.GET_ATTRIBUTES_REJECTED]: (state, { meta: { groupId }, payload: error }) => + state.setIn(['attributes', groupId], createRecord({ state: resourceStatus.FAILED, error })), + [additionalActionTypes.SET_EXAM_FLAG_PENDING]: (state, { meta: { groupId } }) => state.setIn(['resources', groupId, 'pending-group-type'], true), @@ -427,50 +450,6 @@ const reducer = handleActions( ) ), - [sisSupervisedCoursesActionTypes.CREATE_FULFILLED]: (state, { payload: data }) => - state.setIn(['resources', data.id], createRecord({ state: resourceStatus.FULFILLED, data })), - - [sisSupervisedCoursesActionTypes.BIND_FULFILLED]: (state, { payload: data }) => - state.setIn(['resources', data.id], createRecord({ state: resourceStatus.FULFILLED, data })), - - [sisSupervisedCoursesActionTypes.UNBIND_FULFILLED]: (state, { meta: { courseId, groupId } }) => - state.updateIn(['resources', groupId, 'data', 'privateData', 'bindings', 'sis'], bindings => - bindings.filter(binding => binding !== courseId) - ), - - [sisSupervisedCoursesActionTypes.FETCH_FULFILLED]: (state, { payload: { groups } }) => - state.update('resources', oldGroups => - oldGroups.merge( - arrayToObject( - groups, - o => o.id, - data => createRecord({ state: resourceStatus.FULFILLED, data }) - ) - ) - ), - - [sisSubscribedCoursesActionTypes.FETCH_FULFILLED]: (state, { payload: { groups } }) => - state.update('resources', oldGroups => - oldGroups.merge( - arrayToObject( - groups, - o => o.id, - data => createRecord({ state: resourceStatus.FULFILLED, data }) - ) - ) - ), - - [sisPossibleParentsActionTypes.FETCH_FULFILLED]: (state, { payload: groups }) => - state.update('resources', oldGroups => - oldGroups.merge( - arrayToObject( - groups, - o => o.id, - data => createRecord({ state: resourceStatus.FULFILLED, data }) - ) - ) - ), - [invitationsActionTypes.FETCH_FULFILLED]: (state, { payload: { groups } }) => state.update('resources', oldGroups => oldGroups.merge( diff --git a/src/redux/modules/sisPossibleParents.js b/src/redux/modules/sisPossibleParents.js deleted file mode 100644 index 1a12e1072..000000000 --- a/src/redux/modules/sisPossibleParents.js +++ /dev/null @@ -1,29 +0,0 @@ -import { handleActions } from 'redux-actions'; -import factory, { initialState, createRecord, resourceStatus } from '../helpers/resourceManager'; - -const resourceName = 'sisPossibleParents'; -const { actionTypes, actions, reduceActions } = factory({ - resourceName, - apiEndpointFactory: courseId => `/extensions/sis/remote-courses/${courseId}/possible-parents`, -}); - -export { actionTypes }; - -/** - * Actions & reducer - */ - -export const fetchSisPossibleParentsIfNeeded = actions.fetchOneIfNeeded; - -const reducer = handleActions( - Object.assign({}, reduceActions, { - [actionTypes.FETCH_FULFILLED]: (state, { payload, meta: { id } }) => - state.setIn( - ['resources', id], - createRecord({ state: resourceStatus.FULFILLED, data: payload.map(group => group.id) }) - ), - }), - initialState -); - -export default reducer; diff --git a/src/redux/modules/sisStatus.js b/src/redux/modules/sisStatus.js deleted file mode 100644 index 4f65c24a8..000000000 --- a/src/redux/modules/sisStatus.js +++ /dev/null @@ -1,18 +0,0 @@ -import { handleActions } from 'redux-actions'; -import factory, { initialState } from '../helpers/resourceManager'; - -/** - * Create actions & reducer - */ - -const resourceName = 'sisStatus'; -const { actions, reduceActions } = factory({ - resourceName, - apiEndpointFactory: () => '/extensions/sis/status', -}); - -export const fetchSisStatusIfNeeded = () => actions.fetchOneIfNeeded('status'); - -const reducer = handleActions(Object.assign({}, reduceActions, {}), initialState); - -export default reducer; diff --git a/src/redux/modules/sisSubscribedGroups.js b/src/redux/modules/sisSubscribedGroups.js deleted file mode 100644 index 3f55006b8..000000000 --- a/src/redux/modules/sisSubscribedGroups.js +++ /dev/null @@ -1,47 +0,0 @@ -import { handleActions } from 'redux-actions'; -import factory, { initialState, createRecord, resourceStatus } from '../helpers/resourceManager'; -import { createApiAction } from '../middleware/apiMiddleware.js'; -import { fromJS } from 'immutable'; - -/** - * Create actions & reducer - */ - -const resourceName = 'sisSubscribedGroups'; -const { reduceActions } = factory({ - resourceName, -}); - -export const actionTypes = { - FETCH: 'recodex/sisSubscribedGroups/FETCH', - FETCH_PENDING: 'recodex/sisSubscribedGroups/FETCH_PENDING', - FETCH_REJECTED: 'recodex/sisSubscribedGroups/FETCH_REJECTED', - FETCH_FULFILLED: 'recodex/sisSubscribedGroups/FETCH_FULFILLED', -}; - -export const fetchSisSubscribedGroups = (userId, year, term) => - createApiAction({ - type: actionTypes.FETCH, - method: 'GET', - endpoint: `/extensions/sis/users/${userId}/subscribed-groups/${year}/${term}/as-student`, - meta: { userId, year, term }, - }); - -const reducer = handleActions( - Object.assign({}, reduceActions, { - [actionTypes.FETCH_PENDING]: (state, { meta: { userId, year, term } }) => - state.setIn(['resources', userId, `${year}-${term}`], createRecord()), - - [actionTypes.FETCH_REJECTED]: (state, { meta: { userId, year, term } }) => - state.setIn(['resources', userId, `${year}-${term}`], createRecord({ state: resourceStatus.FAILED })), - - [actionTypes.FETCH_FULFILLED]: (state, { payload: { courses }, meta: { userId, year, term } }) => - state.setIn( - ['resources', userId, `${year}-${term}`], - createRecord({ state: resourceStatus.FULFILLED, data: fromJS(courses) }) - ), - }), - initialState -); - -export default reducer; diff --git a/src/redux/modules/sisSupervisedCourses.js b/src/redux/modules/sisSupervisedCourses.js deleted file mode 100644 index 682312d19..000000000 --- a/src/redux/modules/sisSupervisedCourses.js +++ /dev/null @@ -1,103 +0,0 @@ -import { handleActions } from 'redux-actions'; -import factory, { initialState, createRecord, resourceStatus } from '../helpers/resourceManager'; -import { createApiAction } from '../middleware/apiMiddleware.js'; -import { fromJS } from 'immutable'; -import { actionTypes } from './sisSupervisedCoursesTypes.js'; -import { actionTypes as groupsActionTypes } from './groups.js'; -/** - * Create actions & reducer - */ - -const resourceName = 'sisSupervisedCourses'; -const { reduceActions } = factory({ - resourceName, -}); - -export const fetchSisSupervisedCourses = (userId, year, term) => - createApiAction({ - type: actionTypes.FETCH, - method: 'GET', - endpoint: `/extensions/sis/users/${userId}/supervised-courses/${year}/${term}`, - meta: { userId, year, term }, - }); - -export const sisCreateGroup = (courseId, data, userId, year, term) => - createApiAction({ - type: actionTypes.CREATE, - method: 'POST', - endpoint: `/extensions/sis/remote-courses/${courseId}/create`, - meta: { userId, courseId, year, term }, - body: { ...data }, - }); - -export const sisBindGroup = (courseId, data, userId, year, term) => - createApiAction({ - type: actionTypes.BIND, - method: 'POST', - endpoint: `/extensions/sis/remote-courses/${courseId}/bind`, - meta: { courseId, userId, year, term }, - body: { ...data }, - }); - -export const sisUnbindGroup = (courseId, groupId, userId, year, term) => - createApiAction({ - type: actionTypes.UNBIND, - method: 'DELETE', - endpoint: `/extensions/sis/remote-courses/${courseId}/bindings/${groupId}`, - meta: { courseId, groupId, userId, year, term }, - }); - -const reducer = handleActions( - Object.assign({}, reduceActions, { - [actionTypes.CREATE_FULFILLED]: (state, { meta: { userId, courseId, year, term }, payload: { id: groupId } }) => - state.updateIn(['resources', userId, `${year}-${term}`, 'data', courseId, 'groups'], groups => - groups.push(groupId) - ), - - [actionTypes.BIND_FULFILLED]: (state, { meta: { courseId, userId, year, term }, payload: { id: groupId } }) => - state.updateIn(['resources', userId, `${year}-${term}`, 'data', courseId, 'groups'], groups => - groups.push(groupId) - ), - - [actionTypes.UNBIND_FULFILLED]: (state, { meta: { courseId, groupId, userId } }) => - state.updateIn(['resources', userId], terms => - terms.map(term => term.updateIn(['data', courseId, 'groups'], groups => groups.filter(g => g !== groupId))) - ), - - [actionTypes.FETCH_PENDING]: (state, { meta: { userId, year, term } }) => - state.setIn(['resources', userId, `${year}-${term}`], createRecord()), - - [actionTypes.FETCH_REJECTED]: (state, { meta: { userId, year, term } }) => - state.setIn(['resources', userId, `${year}-${term}`], createRecord({ state: resourceStatus.FAILED })), - - [actionTypes.FETCH_FULFILLED]: (state, { payload: { courses }, meta: { userId, year, term } }) => - state.setIn( - ['resources', userId, `${year}-${term}`], - createRecord({ - state: resourceStatus.FULFILLED, - data: fromJS( - courses.reduce((map, p) => { - map[p.course.code] = p; - return map; - }, {}) - ), - }) - ), - - [groupsActionTypes.REMOVE_FULFILLED]: (state, { meta: { id: groupId } }) => - state.update('resources', users => - users.map(userTerms => - userTerms.map(term => - term.update( - 'data', - courses => - courses && courses.map(course => course.update('groups', groups => groups.filter(g => g !== groupId))) - ) - ) - ) - ), - }), - initialState -); - -export default reducer; diff --git a/src/redux/modules/sisSupervisedCoursesTypes.js b/src/redux/modules/sisSupervisedCoursesTypes.js deleted file mode 100644 index 61e797c40..000000000 --- a/src/redux/modules/sisSupervisedCoursesTypes.js +++ /dev/null @@ -1,13 +0,0 @@ -// action types declaration was moved outside the auth module to break cyclic import dependencies -export const actionTypes = { - FETCH: 'recodex/sisSupervisedCourses/FETCH', - FETCH_PENDING: 'recodex/sisSupervisedCourses/FETCH_PENDING', - FETCH_REJECTED: 'recodex/sisSupervisedCourses/FETCH_REJECTED', - FETCH_FULFILLED: 'recodex/sisSupervisedCourses/FETCH_FULFILLED', - CREATE: 'recodex/sisSupervisedCourses/CREATE', - CREATE_FULFILLED: 'recodex/sisSupervisedCourses/CREATE_FULFILLED', - BIND: 'recodex/sisSupervisedCourses/BIND', - BIND_FULFILLED: 'recodex/sisSupervisedCourses/BIND_FULFILLED', - UNBIND: 'recodex/sisSupervisedCourses/UNBIND', - UNBIND_FULFILLED: 'recodex/sisSupervisedCourses/UNBIND_FULFILLED', -}; diff --git a/src/redux/modules/sisTerms.js b/src/redux/modules/sisTerms.js deleted file mode 100644 index a0ed01bc1..000000000 --- a/src/redux/modules/sisTerms.js +++ /dev/null @@ -1,28 +0,0 @@ -import { handleActions } from 'redux-actions'; -import factory, { initialState } from '../helpers/resourceManager'; - -/** - * Create actions & reducer - */ - -const resourceName = 'sisTerms'; -const { actions, reduceActions } = factory({ - resourceName, - apiEndpointFactory: id => `/extensions/sis/terms/${id}`, -}); - -export const fetchManyEndpoint = '/extensions/sis/terms'; - -export const fetchAllTerms = () => - actions.fetchMany({ - endpoint: fetchManyEndpoint, - }); -export const fetchTermsIfNeeded = actions.fetchIfNeeded; -export const fetchTermIfNeeded = actions.fetchOneIfNeeded; -export const create = actions.addResource; -export const editTerm = actions.updateResource; -export const deleteTerm = actions.removeResource; - -const reducer = handleActions(Object.assign({}, reduceActions, {}), initialState); - -export default reducer; diff --git a/src/redux/reducer.js b/src/redux/reducer.js index fb5bdfb10..b092695ef 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -39,11 +39,6 @@ import referenceSolutionEvaluations from './modules/referenceSolutionEvaluations import registration from './modules/registration.js'; import runtimeEnvironments from './modules/runtimeEnvironments.js'; import shadowAssignments from './modules/shadowAssignments.js'; -import sisPossibleParents from './modules/sisPossibleParents.js'; -import sisStatus from './modules/sisStatus.js'; -import sisSubscribedGroups from './modules/sisSubscribedGroups.js'; -import sisSupervisedCourses from './modules/sisSupervisedCourses.js'; -import sisTerms from './modules/sisTerms.js'; import solutions from './modules/solutions.js'; import solutionFiles from './modules/solutionFiles.js'; import solutionReviews from './modules/solutionReviews.js'; @@ -99,11 +94,6 @@ const createRecodexReducers = (token, instanceId, lang) => ({ registration, runtimeEnvironments, shadowAssignments, - sisPossibleParents, - sisSubscribedGroups, - sisSupervisedCourses, - sisStatus, - sisTerms, solutions, solutionFiles, solutionReviews, diff --git a/src/redux/selectors/exercisesAuthors.js b/src/redux/selectors/exercisesAuthors.js index cc71db2b0..db74bca99 100644 --- a/src/redux/selectors/exercisesAuthors.js +++ b/src/redux/selectors/exercisesAuthors.js @@ -6,13 +6,13 @@ import { usersSelector } from './users.js'; const exericsesAuthorsAllSelector = state => state.exercisesAuthors.get('all'); const exericsesAuthorsOfGroupSelector = groupId => state => state.exercisesAuthors.getIn(['groups', groupId]); -export const getAllExericsesAuthors = createSelector( +export const getAllExercisesAuthors = createSelector( [exericsesAuthorsAllSelector, usersSelector], (authors, users) => (authors && isReady(authors) && users && authors.get('data').map(id => users.get(id))) || EMPTY_LIST ); -export const getAllExericsesAuthorsIsLoading = createSelector([exericsesAuthorsAllSelector], authors => +export const getAllExercisesAuthorsIsLoading = createSelector([exericsesAuthorsAllSelector], authors => Boolean(authors && isLoading(authors)) ); diff --git a/src/redux/selectors/groups.js b/src/redux/selectors/groups.js index c79071a2e..1e5f8e626 100644 --- a/src/redux/selectors/groups.js +++ b/src/redux/selectors/groups.js @@ -170,3 +170,5 @@ export const getGroupsAdmins = groups => { groups.forEach(group => group && group.primaryAdminsIds && group.primaryAdminsIds.forEach(id => ids.add(id))); return Array.from(ids); }; + +export const groupAttributesSelector = (state, groupId) => state.groups.getIn(['attributes', groupId], null); diff --git a/src/redux/selectors/sisPossibleParents.js b/src/redux/selectors/sisPossibleParents.js deleted file mode 100644 index 2afe118ba..000000000 --- a/src/redux/selectors/sisPossibleParents.js +++ /dev/null @@ -1 +0,0 @@ -export const sisPossibleParentsSelector = state => state.sisPossibleParents.get('resources'); diff --git a/src/redux/selectors/sisStatus.js b/src/redux/selectors/sisStatus.js deleted file mode 100644 index 6f39d2f42..000000000 --- a/src/redux/selectors/sisStatus.js +++ /dev/null @@ -1,8 +0,0 @@ -import { createSelector } from 'reselect'; - -const getResources = state => state.sisStatus.get('resources'); - -export const sisStateSelector = createSelector( - getResources, - resources => resources.get('status') -); diff --git a/src/redux/selectors/sisSubscribedGroups.js b/src/redux/selectors/sisSubscribedGroups.js deleted file mode 100644 index f4b9681e4..000000000 --- a/src/redux/selectors/sisSubscribedGroups.js +++ /dev/null @@ -1,11 +0,0 @@ -import { createSelector } from 'reselect'; -import { Map } from 'immutable'; - -const getResources = state => state.sisSubscribedGroups.get('resources'); - -export const sisSubscribedCoursesGroupsSelector = (userId, year, term) => - createSelector(getResources, resources => - resources && resources.get(userId) && resources.getIn([userId, `${year}-${term}`]) - ? resources.getIn([userId, `${year}-${term}`]) - : Map() - ); diff --git a/src/redux/selectors/sisSupervisedCourses.js b/src/redux/selectors/sisSupervisedCourses.js deleted file mode 100644 index 8f8d4fddb..000000000 --- a/src/redux/selectors/sisSupervisedCourses.js +++ /dev/null @@ -1,22 +0,0 @@ -import { createSelector } from 'reselect'; -import { Map } from 'immutable'; -import { EMPTY_MAP } from '../../helpers/common.js'; -import { loggedInUserIdSelector } from './auth.js'; - -const getResources = state => state.sisSupervisedCourses.get('resources'); - -const getSisStateTerms = state => state.sisStatus.getIn(['resources', 'status', 'data', 'terms']); - -export const sisSupervisedCoursesSelector = createSelector( - [getResources, getSisStateTerms, loggedInUserIdSelector], - (resources, terms, userId) => { - return resources && terms && terms.size > 0 && resources.get(userId) - ? Map( - terms.map(term => { - const termKey = term.get('year') + '-' + term.get('term'); - return [termKey, resources.getIn([userId, termKey], EMPTY_MAP)]; - }) - ) - : EMPTY_MAP; - } -); diff --git a/src/redux/selectors/sisTerms.js b/src/redux/selectors/sisTerms.js deleted file mode 100644 index 73b1d084a..000000000 --- a/src/redux/selectors/sisTerms.js +++ /dev/null @@ -1,20 +0,0 @@ -import { createSelector } from 'reselect'; -import { fetchManyEndpoint } from '../modules/sisTerms.js'; -import { isReady, getJsData } from '../helpers/resourceManager'; - -const getTerms = state => state.sisTerms; -const getResources = exercises => exercises.get('resources'); - -export const termsSelector = createSelector(getTerms, getResources); -export const termSelector = termId => createSelector(termsSelector, terms => terms.get(termId)); - -export const fetchManyStatus = createSelector(getTerms, state => state.getIn(['fetchManyStatus', fetchManyEndpoint])); - -export const readySisTermsSelector = createSelector(termsSelector, terms => - terms - .toList() - .filter(isReady) - .map(getJsData) - .sort((a, b) => a.year * 10 + a.term < b.year * 10 + b.term) - .toArray() -);