<NME> course.js
<BEF> import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE } from "../constants";

const initialState = {
  title: '',
  RECEIVE_COURSE,
  RECEIVE_COURSE_UPDATE,
  PERSISTED_COURSE,
  UPDATE_COURSE,
  CREATED_COURSE,
  ADD_CAMPAIGN,
  DELETE_CAMPAIGN,
  DISMISS_SURVEY_NOTIFICATION,
  TOGGLE_EDITING_SYLLABUS,
  START_SYLLABUS_UPLOAD,
  SYLLABUS_UPLOAD_SUCCESS,
  LINKED_TO_SALESFORCE
} from '../constants';
import CourseUtils from '../utils/course_utils';

const initialState = {
  title: '',
  description: '',
  school: '',
  term: '',
  level: '',
  subject: '',
  expected_students: '0',
  format: '',
  start: null,
  end: null,
      };
      return newState;
    }
    default:
      return state;
  }
  updates: {},
  wikis: [],
  namespaces: [],
  article_count: 0,
  course_stats: {}
};


export default function course(state = initialState, action) {
  switch (action.type) {
    case RECEIVE_COURSE:
      return { loading: false, ...action.data.course };
    case RECEIVE_COURSE_UPDATE: {
      const courseData = action.data.course;
      const newStats = CourseUtils.newCourseStats(state, courseData);
      const newKeys = CourseUtils.courseStatsToUpdate(courseData, newStats);
      return { ...state, ...newKeys, newStats };
    }
    case PERSISTED_COURSE:
      return { ...state, ...action.data.course };
    case UPDATE_COURSE:
      return { ...state, ...action.course };
    case CREATED_COURSE:
      return { loading: false, ...action.data.course };
    case RECEIVE_INITIAL_CAMPAIGN: {
      const campaign = action.data.campaign;
      return {
        ...state,
        initial_campaign_id: campaign.id,
        initial_campaign_title: campaign.title,
        description: campaign.template_description,
        type: campaign.default_course_type,
        passcode: campaign.default_passcode
      };
    }
    case ADD_CAMPAIGN:
    case DELETE_CAMPAIGN:
      return { ...state, published: action.data.course.published };
    case RECEIVE_COURSE_CLONE:
      return { loading: false, ...action.data.course };
    case DISMISS_SURVEY_NOTIFICATION: {
      return {
        ...state,
        survey_notifications: reject(state.survey_notifications, { id: action.id })
      };
    }
    case TOGGLE_EDITING_SYLLABUS:
      return { ...state, editingSyllabus: !state.editingSyllabus };
    case START_SYLLABUS_UPLOAD:
      return { ...state, uploadingSyllabus: true };
    case SYLLABUS_UPLOAD_SUCCESS:
      return {
        ...state,
        uploadingSyllabus: false,
        editingSyllabus: false,
        syllabus: action.syllabus
      };
    case LINKED_TO_SALESFORCE:
      return { ...state, flags: action.data.flags };
    default:
      return state;
  }
}

<MSG> Fix course_string_prefix, hook up cloning flow to redux

<DFF> @@ -1,4 +1,4 @@
-import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE } from "../constants";
+import { RECEIVE_INITIAL_CAMPAIGN, RECEIVE_COURSE_CLONE, UPDATE_COURSE, CREATED_COURSE } from "../constants";
 
 const initialState = {
   title: '',
@@ -31,6 +31,8 @@ export default function course(state = initialState, action) {
       };
       return newState;
     }
+    case RECEIVE_COURSE_CLONE:
+      return { ...action.data.course };
     default:
       return state;
   }

{'commit': 'diff --git a/app/assets/javascripts/actions/course_creation_actions.js b/app/assets/javascripts/actions/course_creation_actions.js\nindex fd043649b..d767a898d 100644\n--- a/app/assets/javascripts/actions/course_creation_actions.js\n+++ b/app/assets/javascripts/actions/course_creation_actions.js\n@@ -1,6 +1,6 @@\n \n import API from \'../utils/api.js\';\n-import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE, API_FAIL } from \'../constants\';\n+import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE, RECEIVE_COURSE_CLONE, API_FAIL } from \'../constants\';\n \n const fetchCampaignPromise = slug => {\n   return new Promise((res, rej) =>\n@@ -25,9 +25,15 @@ export const updateCourse = course => ({ type: UPDATE_COURSE, course });\n \n export const submitCourse = (course, failureCallback) => dispatch => {\n   return API.saveCourse(course, null)\n-    .then(resp => (dispatch({ type: CREATED_COURSE, data: resp })))\n+    .then(resp => dispatch({ type: CREATED_COURSE, data: resp }))\n     .catch(resp => {\n       failureCallback();\n       dispatch({ type: API_FAIL, data: resp });\n     });\n };\n+\n+export const cloneCourse = courseId => dispatch => {\n+  return API.cloneCourse(courseId)\n+    .then(resp => dispatch({ type: RECEIVE_COURSE_CLONE, data: resp }))\n+    .catch(resp => dispatch({ type: API_FAIL, data: resp }));\n+};\ndiff --git a/app/assets/javascripts/actions/server_actions.js b/app/assets/javascripts/actions/server_actions.js\nindex 154418443..3fb7b051c 100644\n--- a/app/assets/javascripts/actions/server_actions.js\n+++ b/app/assets/javascripts/actions/server_actions.js\n@@ -27,13 +27,6 @@ const ServerActions = Flux.createActions({\n       .catch(resp => ({ actionType: \'API_FAIL\', data: resp }));\n   },\n \n-  // Specific\n-  cloneCourse(courseId) {\n-    return API.cloneCourse(courseId)\n-      .then(resp => ({ actionType: \'RECEIVE_COURSE_CLONE\', data: resp }))\n-      .catch(resp => ({ actionType: \'API_FAIL\', data: resp }));\n-  },\n-\n   fetchLookups(key) {\n     return API.fetchLookups(key)\n       .then(resp => ({\ndiff --git a/app/assets/javascripts/components/course_creator/course_creator.jsx b/app/assets/javascripts/components/course_creator/course_creator.jsx\nindex c3d957a4d..3a834ea98 100644\n--- a/app/assets/javascripts/components/course_creator/course_creator.jsx\n+++ b/app/assets/javascripts/components/course_creator/course_creator.jsx\n@@ -6,7 +6,7 @@ import { Link } from \'react-router\';\n \n import ValidationStore from \'../../stores/validation_store.js\';\n import ValidationActions from \'../../actions/validation_actions.js\';\n-import { fetchCampaign, updateCourse, submitCourse } from \'../../actions/course_creation_actions.js\';\n+import { fetchCampaign, updateCourse, submitCourse, cloneCourse } from \'../../actions/course_creation_actions.js\';\n import ServerActions from \'../../actions/server_actions.js\';\n import { fetchCoursesForUser } from "../../actions/user_courses_actions.js";\n \n@@ -38,7 +38,8 @@ const CourseCreator = createReactClass({\n     courseCreator: PropTypes.object.isRequired,\n     updateCourse: PropTypes.func.isRequired,\n     submitCourse: PropTypes.func.isRequired,\n-    fetchCampaign: PropTypes.func.isRequired\n+    fetchCampaign: PropTypes.func.isRequired,\n+    cloneCourse: PropTypes.func.isRequired\n   },\n \n   mixins: [ValidationStore.mixin],\n@@ -94,7 +95,7 @@ const CourseCreator = createReactClass({\n       this.setState({ isSubmitting: true });\n       ValidationActions.setInvalid(\n         \'exists\',\n-        CourseUtils.i18n(\'creator.checking_for_uniqueness\', this.props.course_string_prefix),\n+        CourseUtils.i18n(\'creator.checking_for_uniqueness\', this.state.course_string_prefix),\n         true\n       );\n       return ServerActions.checkCourse(\'exists\', CourseUtils.generateTempId(this.props.course));\n@@ -104,6 +105,7 @@ const CourseCreator = createReactClass({\n   handleCourse(course) {\n     if (this.state.shouldRedirect === true) {\n       window.location = `/courses/${course.slug}`;\n+      return this.setState({ shouldRedirect: false });\n     }\n     if (!this.state.isSubmitting && !this.state.justSubmitted) { return; }\n     if (ValidationStore.isValid()) {\n@@ -181,7 +183,7 @@ const CourseCreator = createReactClass({\n   useThisClass() {\n     const select = this.courseSelect;\n     const courseId = select.options[select.selectedIndex].getAttribute(\'data-id-key\');\n-    ServerActions.cloneCourse(courseId);\n+    this.props.cloneCourse(courseId);\n     return this.setState({ isSubmitting: true, shouldRedirect: true });\n   },\n \n@@ -206,11 +208,11 @@ const CourseCreator = createReactClass({\n \n     let instructions;\n     if (showNewOrClone) {\n-      instructions = CourseUtils.i18n(\'creator.new_or_clone\', this.props.course_string_prefix);\n+      instructions = CourseUtils.i18n(\'creator.new_or_clone\', this.state.course_string_prefix);\n     } else if (showCloneChooser) {\n-      instructions = CourseUtils.i18n(\'creator.choose_clone\', this.props.course_string_prefix);\n+      instructions = CourseUtils.i18n(\'creator.choose_clone\', this.state.course_string_prefix);\n     } else if (showCourseForm) {\n-      instructions = CourseUtils.i18n(\'creator.intro\', this.props.course_string_prefix);\n+      instructions = CourseUtils.i18n(\'creator.intro\', this.state.course_string_prefix);\n     }\n \n     let formStyle;\n@@ -246,8 +248,8 @@ const CourseCreator = createReactClass({\n           required\n           validation={CourseUtils.courseSlugRegex()}\n           editable\n-          label={CourseUtils.i18n(\'creator.course_term\', this.props.course_string_prefix)}\n-          placeholder={CourseUtils.i18n(\'creator.course_term_placeholder\', this.props.course_string_prefix)}\n+          label={CourseUtils.i18n(\'creator.course_term\', this.state.course_string_prefix)}\n+          placeholder={CourseUtils.i18n(\'creator.course_term_placeholder\', this.state.course_string_prefix)}\n         />\n       );\n       courseLevel = (\n@@ -263,7 +265,7 @@ const CourseCreator = createReactClass({\n           value={this.props.course.subject}\n           value_key="subject"\n           editable\n-          label={CourseUtils.i18n(\'creator.course_subject\', this.props.course_string_prefix)}\n+          label={CourseUtils.i18n(\'creator.course_subject\', this.state.course_string_prefix)}\n           placeholder={I18n.t(\'courses.creator.subject\')}\n         />\n       );\n@@ -277,8 +279,8 @@ const CourseCreator = createReactClass({\n           required\n           type="number"\n           max="999"\n-          label={CourseUtils.i18n(\'creator.expected_number\', this.props.course_string_prefix)}\n-          placeholder={CourseUtils.i18n(\'creator.expected_number\', this.props.course_string_prefix)}\n+          label={CourseUtils.i18n(\'creator.expected_number\', this.state.course_string_prefix)}\n+          placeholder={CourseUtils.i18n(\'creator.expected_number\', this.state.course_string_prefix)}\n         />\n       );\n       roleDescription = (\n@@ -362,16 +364,16 @@ const CourseCreator = createReactClass({\n           <Notifications />\n           <div className="container">\n             <div className="wizard__panel active" style={formStyle}>\n-              <h3>{CourseUtils.i18n(\'creator.create_new\', this.props.course_string_prefix)}</h3>\n+              <h3>{CourseUtils.i18n(\'creator.create_new\', this.state.course_string_prefix)}</h3>\n               <p>{instructions}</p>\n               <div className={cloneOptions}>\n-                <button className="button dark" onClick={this.showCourseForm}>{CourseUtils.i18n(\'creator.create_label\', this.props.course_string_prefix)}</button>\n-                <button className="button dark" onClick={this.showCloneChooser}>{CourseUtils.i18n(\'creator.clone_previous\', this.props.course_string_prefix)}</button>\n+                <button className="button dark" onClick={this.showCourseForm}>{CourseUtils.i18n(\'creator.create_label\', this.state.course_string_prefix)}</button>\n+                <button className="button dark" onClick={this.showCloneChooser}>{CourseUtils.i18n(\'creator.clone_previous\', this.state.course_string_prefix)}</button>\n               </div>\n               <div className={selectClassName}>\n                 <select id="reuse-existing-course-select" ref={(dropdown) => { this.courseSelect = dropdown; }}>{options}</select>\n-                <button className="button dark" onClick={this.useThisClass}>{CourseUtils.i18n(\'creator.clone_this\', this.props.course_string_prefix)}</button>\n-                <button className="button dark right" onClick={this.cancelClone}>{CourseUtils.i18n(\'cancel\', this.props.course_string_prefix)}</button>\n+                <button className="button dark" onClick={this.useThisClass}>{CourseUtils.i18n(\'creator.clone_this\', this.state.course_string_prefix)}</button>\n+                <button className="button dark right" onClick={this.cancelClone}>{CourseUtils.i18n(\'cancel\', this.state.course_string_prefix)}</button>\n               </div>\n               <div className={courseFormClass}>\n                 <div className="column">\n@@ -385,8 +387,8 @@ const CourseCreator = createReactClass({\n                     required\n                     validation={CourseUtils.courseSlugRegex()}\n                     editable\n-                    label={CourseUtils.i18n(\'creator.course_title\', this.props.course_string_prefix)}\n-                    placeholder={CourseUtils.i18n(\'creator.course_title\', this.props.course_string_prefix)}\n+                    label={CourseUtils.i18n(\'creator.course_title\', this.state.course_string_prefix)}\n+                    placeholder={CourseUtils.i18n(\'creator.course_title\', this.state.course_string_prefix)}\n                   />\n                   <TextInput\n                     id="course_school"\n@@ -396,8 +398,8 @@ const CourseCreator = createReactClass({\n                     required\n                     validation={CourseUtils.courseSlugRegex()}\n                     editable\n-                    label={CourseUtils.i18n(\'creator.course_school\', this.props.course_string_prefix)}\n-                    placeholder={CourseUtils.i18n(\'creator.course_school\', this.props.course_string_prefix)}\n+                    label={CourseUtils.i18n(\'creator.course_school\', this.state.course_string_prefix)}\n+                    placeholder={CourseUtils.i18n(\'creator.course_school\', this.state.course_string_prefix)}\n                   />\n                   {term}\n                   {courseLevel}\n@@ -415,7 +417,7 @@ const CourseCreator = createReactClass({\n                     value_key="start"\n                     required\n                     editable\n-                    label={CourseUtils.i18n(\'creator.start_date\', this.props.course_string_prefix)}\n+                    label={CourseUtils.i18n(\'creator.start_date\', this.state.course_string_prefix)}\n                     placeholder={I18n.t(\'courses.creator.start_date_placeholder\')}\n                     blank\n                     isClearable={false}\n@@ -428,7 +430,7 @@ const CourseCreator = createReactClass({\n                     value_key="end"\n                     required\n                     editable\n-                    label={CourseUtils.i18n(\'creator.end_date\', this.props.course_string_prefix)}\n+                    label={CourseUtils.i18n(\'creator.end_date\', this.state.course_string_prefix)}\n                     placeholder={I18n.t(\'courses.creator.end_date_placeholder\')}\n                     blank\n                     date_props={dateProps.end}\n@@ -437,7 +439,7 @@ const CourseCreator = createReactClass({\n                     showTime={this.state.use_start_and_end_times}\n                   />\n                   {this.state.use_start_and_end_times ? timeZoneMessage : null}\n-                  <span className="text-input-component__label"><strong>{CourseUtils.i18n(\'creator.course_description\', this.props.course_string_prefix)}:</strong></span>\n+                  <span className="text-input-component__label"><strong>{CourseUtils.i18n(\'creator.course_description\', this.state.course_string_prefix)}:</strong></span>\n                   <TextAreaInput\n                     id="course_description"\n                     onChange={this.updateCourse}\n@@ -445,7 +447,7 @@ const CourseCreator = createReactClass({\n                     value_key="description"\n                     required={descriptionRequired}\n                     editable\n-                    placeholder={CourseUtils.i18n(\'creator.course_description_placeholder\', this.props.course_string_prefix)}\n+                    placeholder={CourseUtils.i18n(\'creator.course_description_placeholder\', this.state.course_string_prefix)}\n                   />\n                   {roleDescription}\n                 </div>\n@@ -455,7 +457,7 @@ const CourseCreator = createReactClass({\n                 <div className="right">\n                   <div><p className="red">{this.state.error_message}</p></div>\n                   <Link className="button" to="/" id="course_cancel">{I18n.t(\'application.cancel\')}</Link>\n-                  <button onClick={this.saveCourse} className="dark button button__submit">{CourseUtils.i18n(\'creator.create_button\', this.props.course_string_prefix)}</button>\n+                  <button onClick={this.saveCourse} className="dark button button__submit">{CourseUtils.i18n(\'creator.create_button\', this.state.course_string_prefix)}</button>\n                 </div>\n               </div>\n             </div>\n@@ -476,7 +478,8 @@ const mapDispatchToProps = ({\n   fetchCampaign,\n   fetchCoursesForUser,\n   updateCourse,\n-  submitCourse\n+  submitCourse,\n+  cloneCourse\n });\n \n // exporting two difference ways as a testing hack.\ndiff --git a/app/assets/javascripts/constants/course.js b/app/assets/javascripts/constants/course.js\nindex 7e4f4f7d6..c44e7c463 100644\n--- a/app/assets/javascripts/constants/course.js\n+++ b/app/assets/javascripts/constants/course.js\n@@ -1,3 +1,4 @@\n export const RECEIVE_INITIAL_CAMPAIGN = \'RECEIVE_INITIAL_CAMPAIGN\';\n+export const RECEIVE_COURSE_CLONE = \'RECEIVE_COURSE_CLONE\';\n export const UPDATE_COURSE = \'UPDATE_COURSE\';\n export const CREATED_COURSE = \'CREATED_COURSE\';\ndiff --git a/app/assets/javascripts/reducers/course.js b/app/assets/javascripts/reducers/course.js\nindex 2b790c7b3..22efe9d11 100644\n--- a/app/assets/javascripts/reducers/course.js\n+++ b/app/assets/javascripts/reducers/course.js\n@@ -1,4 +1,4 @@\n-import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE } from "../constants";\n+import { RECEIVE_INITIAL_CAMPAIGN, RECEIVE_COURSE_CLONE, UPDATE_COURSE, CREATED_COURSE } from "../constants";\n \n const initialState = {\n   title: \'\',\n@@ -31,6 +31,8 @@ export default function course(state = initialState, action) {\n       };\n       return newState;\n     }\n+    case RECEIVE_COURSE_CLONE:\n+      return { ...action.data.course };\n     default:\n       return state;\n   }\n', 'commit_subject': 'Fix course_string_prefix, hook up cloning flow to redux', 'repo_name': 'WikiEducationFoundation/WikiEduDashboard', 'committer': {'name': 'Sage Ross', 'email': '0bc9f67b2b08dbd979104ac630343507d3de0edf@gmail.com', 'time_sec': '1513801111', 'tz_offset': '-480', 'date': {'seconds': '1513801111'}}, 'commit_tree': '7fde6765b6a43c99fa69f6c8e0f841b545f0d250', 'commit_message': 'Fix course_string_prefix, hook up cloning flow to redux\n', 'language_name': 'Python', 'license': 'mit', 'hunks': '@@ -1,4 +1,4 @@\n-import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE } from "../constants";\n+import { RECEIVE_INITIAL_CAMPAIGN, RECEIVE_COURSE_CLONE, UPDATE_COURSE, CREATED_COURSE } from "../constants";\n \n const initialState = {\n   title: \'\',\n@@ -31,6 +31,8 @@ export default function course(state = initialState, action) {\n       };\n       return newState;\n     }\n+    case RECEIVE_COURSE_CLONE:\n+      return { ...action.data.course };\n     default:\n       return state;\n   }\n', 'addition_count': 3, 'deletion_count': 1, 'src_file': 'a/app/assets/javascripts/reducers/course.js', 'tgt_file': 'b/app/assets/javascripts/reducers/course.js', 'file_extension': '.js', 'before_file': 'import { RECEIVE_INITIAL_CAMPAIGN, UPDATE_COURSE, CREATED_COURSE } from "../constants";\n\nconst initialState = {\n  title: \'\',\n  RECEIVE_COURSE,\n  RECEIVE_COURSE_UPDATE,\n  PERSISTED_COURSE,\n  UPDATE_COURSE,\n  CREATED_COURSE,\n  ADD_CAMPAIGN,\n  DELETE_CAMPAIGN,\n  DISMISS_SURVEY_NOTIFICATION,\n  TOGGLE_EDITING_SYLLABUS,\n  START_SYLLABUS_UPLOAD,\n  SYLLABUS_UPLOAD_SUCCESS,\n  LINKED_TO_SALESFORCE\n} from \'../constants\';\nimport CourseUtils from \'../utils/course_utils\';\n\nconst initialState = {\n  title: \'\',\n  description: \'\',\n  school: \'\',\n  term: \'\',\n  level: \'\',\n  subject: \'\',\n  expected_students: \'0\',\n  format: \'\',\n  start: null,\n  end: null,\n      };\n      return newState;\n    }\n    default:\n      return state;\n  }\n  updates: {},\n  wikis: [],\n  namespaces: [],\n  article_count: 0,\n  course_stats: {}\n};\n\n\nexport default function course(state = initialState, action) {\n  switch (action.type) {\n    case RECEIVE_COURSE:\n      return { loading: false, ...action.data.course };\n    case RECEIVE_COURSE_UPDATE: {\n      const courseData = action.data.course;\n      const newStats = CourseUtils.newCourseStats(state, courseData);\n      const newKeys = CourseUtils.courseStatsToUpdate(courseData, newStats);\n      return { ...state, ...newKeys, newStats };\n    }\n    case PERSISTED_COURSE:\n      return { ...state, ...action.data.course };\n    case UPDATE_COURSE:\n      return { ...state, ...action.course };\n    case CREATED_COURSE:\n      return { loading: false, ...action.data.course };\n    case RECEIVE_INITIAL_CAMPAIGN: {\n      const campaign = action.data.campaign;\n      return {\n        ...state,\n        initial_campaign_id: campaign.id,\n        initial_campaign_title: campaign.title,\n        description: campaign.template_description,\n        type: campaign.default_course_type,\n        passcode: campaign.default_passcode\n      };\n    }\n    case ADD_CAMPAIGN:\n    case DELETE_CAMPAIGN:\n      return { ...state, published: action.data.course.published };\n    case RECEIVE_COURSE_CLONE:\n      return { loading: false, ...action.data.course };\n    case DISMISS_SURVEY_NOTIFICATION: {\n      return {\n        ...state,\n        survey_notifications: reject(state.survey_notifications, { id: action.id })\n      };\n    }\n    case TOGGLE_EDITING_SYLLABUS:\n      return { ...state, editingSyllabus: !state.editingSyllabus };\n    case START_SYLLABUS_UPLOAD:\n      return { ...state, uploadingSyllabus: true };\n    case SYLLABUS_UPLOAD_SUCCESS:\n      return {\n        ...state,\n        uploadingSyllabus: false,\n        editingSyllabus: false,\n        syllabus: action.syllabus\n      };\n    case LINKED_TO_SALESFORCE:\n      return { ...state, flags: action.data.flags };\n    default:\n      return state;\n  }\n}\n', 'message': 'Fix course_string_prefix, hook up cloning flow to redux\n', 'author_name': 'Sage Ross', 'lang': 'js'}