Files
frontend-app-authoring/src/generic/create-or-rerun-course/hooks.jsx
Jillian e5360dc1f1 fix: allow users who can create orgs to see the full list of orgs when creating a course [FC-0076] (#1689)
The fixes for https://github.com/openedx/frontend-app-authoring/issues/1577 caused issues with edx.org's Global Staff users not being able to create courses. edx.org/2U Global Staff are seeing an empty Organization drop-down list.

This issue arose due to misuse of two similarly-named flags returned by the contentstore's home API

* `can_create_organizations`: granted to Global Staff or everyone if `FEATURES[ENABLE_CREATOR_GROUP] == True`, 
* `allow_to_create_new_org`: which is actually about auto-creating organizations, so both `can_create_organizations` + `settings.ORGANIZATIONS_AUTOCREATE` must be True

In this change, we use `canCreateOrganizations` to decide whether the user can see the full list of organizations when creating courses. We preserve the use of `allowToCreateNewOrg`  when deciding whether to allow users to "typeahead" to create a new organization not in the drop-down list.
2025-02-25 21:07:43 -05:00

140 lines
4.6 KiB
JavaScript

import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useNavigate } from 'react-router-dom';
import { REGEX_RULES } from '../../constants';
import { RequestStatus, MAX_TOTAL_LENGTH, TOTAL_LENGTH_KEY } from '../../data/constants';
import { getStudioHomeData } from '../../studio-home/data/selectors';
import {
getRedirectUrlObj,
getOrganizations,
getPostErrors,
getSavingStatus,
} from '../data/selectors';
import { updateSavingStatus, updatePostErrors } from '../data/slice';
import { fetchOrganizationsQuery } from '../data/thunks';
import messages from './messages';
const useCreateOrRerunCourse = (initialValues) => {
const intl = useIntl();
const dispatch = useDispatch();
const navigate = useNavigate();
const redirectUrlObj = useSelector(getRedirectUrlObj);
const createOrRerunCourseSavingStatus = useSelector(getSavingStatus);
const allOrganizations = useSelector(getOrganizations);
const postErrors = useSelector(getPostErrors);
const {
canCreateOrganizations,
allowedOrganizations,
} = useSelector(getStudioHomeData);
const [isFormFilled, setFormFilled] = useState(false);
const [showErrorBanner, setShowErrorBanner] = useState(false);
const organizations = canCreateOrganizations ? allOrganizations : allowedOrganizations;
const { specialCharsRule, noSpaceRule } = REGEX_RULES;
const validationSchema = Yup.object().shape({
displayName: Yup.string().required(
intl.formatMessage(messages.requiredFieldError),
),
org: Yup.string()
.required(intl.formatMessage(messages.requiredFieldError))
.matches(
specialCharsRule,
intl.formatMessage(messages.disallowedCharsError),
)
.matches(noSpaceRule, intl.formatMessage(messages.noSpaceError)),
number: Yup.string()
.required(intl.formatMessage(messages.requiredFieldError))
.matches(
specialCharsRule,
intl.formatMessage(messages.disallowedCharsError),
)
.matches(noSpaceRule, intl.formatMessage(messages.noSpaceError)),
run: Yup.string()
.required(intl.formatMessage(messages.requiredFieldError))
.matches(
specialCharsRule,
intl.formatMessage(messages.disallowedCharsError),
)
.matches(noSpaceRule, intl.formatMessage(messages.noSpaceError)),
}).test(TOTAL_LENGTH_KEY, intl.formatMessage(messages.totalLengthError), function validateTotalLength() {
const { org, number, run } = this?.options.originalValue || {};
if ((org?.length || 0) + (number?.length || 0) + (run?.length || 0) > MAX_TOTAL_LENGTH) {
return this.createError({ path: TOTAL_LENGTH_KEY, message: intl.formatMessage(messages.totalLengthError) });
}
return true;
});
const {
values, errors, touched, handleChange, handleBlur, setFieldValue,
} = useFormik({
initialValues,
enableReinitialize: true,
validateOnBlur: false,
validationSchema,
});
useEffect(() => {
if (canCreateOrganizations) {
dispatch(fetchOrganizationsQuery());
}
}, []);
useEffect(() => {
setFormFilled(
Object.entries(values)
?.filter(([key]) => key !== 'undefined')
.every(([, value]) => value),
);
dispatch(updatePostErrors({}));
}, [values]);
useEffect(() => {
setShowErrorBanner(!!postErrors.errMsg);
}, [postErrors]);
useEffect(() => {
if (createOrRerunCourseSavingStatus === RequestStatus.SUCCESSFUL) {
dispatch(updateSavingStatus({ status: '' }));
const { url, destinationCourseKey } = redirectUrlObj;
// New courses' url to the outline page is provided in the url. However, for course
// re-runs the url is /course/. The actual destination for the rer-run's outline
// is in the destionationCourseKey attribute from the api.
if (url) {
if (destinationCourseKey) {
navigate(`${url}${destinationCourseKey}`);
} else {
navigate(url);
}
}
} else if (createOrRerunCourseSavingStatus === RequestStatus.FAILED) {
dispatch(updateSavingStatus({ status: '' }));
}
}, [createOrRerunCourseSavingStatus]);
const hasErrorField = (fieldName) => !!errors[fieldName] && !!touched[fieldName];
const isFormInvalid = Object.keys(errors).length;
return {
intl,
errors,
values,
postErrors,
isFormFilled,
isFormInvalid,
organizations,
showErrorBanner,
dispatch,
handleBlur,
handleChange,
hasErrorField,
setFieldValue,
setShowErrorBanner,
};
};
export { useCreateOrRerunCourse };