diff --git a/apps/i18n/common/en_us.json b/apps/i18n/common/en_us.json index cca779b626741..6c40e41be0d68 100644 --- a/apps/i18n/common/en_us.json +++ b/apps/i18n/common/en_us.json @@ -1,6 +1,7 @@ { "activity": "Activity", "actions": "Actions", + "add": "Add", "addRemoveCleverClassrooms": "Visit https://clever.com/ and confirm your classroom is connected to Code.org.", "addRemoveGoogleClassrooms": "Visit https://classroom.google.com/ to add and remove classrooms.", "addSectionName": "Enter a name for your section that will help you remember which classroom it is for. Your students will also be able to see this name.", @@ -52,6 +53,7 @@ "assignUnit": "Assign Unit", "authorizeGoogleClassrooms": "Click here to authorize Google Classroom.", "authorizeGoogleClassroomsText": "Before you can sync Google Classroom, you must give Code.org permission to access your Google Classroom account.", + "autoGenerated": "Auto-generated", "autolock": "Note: Stage auto-locks after 24 hours.", "backToActivity": "Back to activity", "backToPreviousLevel": "Back to previous level", @@ -796,11 +798,15 @@ "removeFromClassGallery": "Remove from Class Gallery", "removeFromPublicGallery": "Remove from Public Gallery", "removeStudent": "Remove student", - "removeStudentConfirm": "Are you sure you want to remove this student?", + "removeStudentConfirm1": "If your student currently logs in through a secret picture or secret pair of words, the student may no longer be able to log into their account if you remove them from your section. If this is the case, please give your student a chance to keep using their Code.org account by letting them create a personal login.", + "removeStudentConfirm2": "Send home these instructions on how to create a personal login.", + "removeStudentConfirm3": "Give them at least a few days to follow these instructions before you remove them.", + "removeStudentHeader": "Are you sure you want to remove this student?", "rename": "Rename", "repeat": "repeat", "replayButton": "Replay", "reportAbuse": "Report Abuse", + "required": "required", "reset": "Reset", "resetProgram": "Reset", "resetPassword": "Reset password", diff --git a/apps/src/code-studio/initApp/project.js b/apps/src/code-studio/initApp/project.js index 4dbda6a11dcf0..f568e978e2a4f 100644 --- a/apps/src/code-studio/initApp/project.js +++ b/apps/src/code-studio/initApp/project.js @@ -20,7 +20,7 @@ var channels = require('./clientApi').create('/v3/channels'); var showProjectAdmin = require('../showProjectAdmin'); var header = require('../header'); -import {queryParams, hasQueryParam} from '../utils'; +import {queryParams, hasQueryParam, updateQueryParam} from '../utils'; // Name of the packed source file var SOURCE_FILE = 'main.json'; @@ -867,6 +867,7 @@ var projects = module.exports = { 'analysis-events', { study: 'project-data-integrity', + study_group: 'v2', event: errorType, data_int: errorCount, project_id: current.id + '', @@ -882,7 +883,8 @@ var projects = module.exports = { shareUrl: this.getShareUrl(), currentSourceVersionId: currentSourceVersionId, }), - } + }, + {includeUserId: true} ); }, updateCurrentData_(err, data, options = {}) { @@ -1294,15 +1296,17 @@ function fetchAbuseScoreAndPrivacyViolations(callback) { } /** - * Temporarily allow for setting Maker APIs enabled / disabled via URL parameters. + * Allow setting Maker APIs enabled / disabled via URL parameters. */ function setMakerAPIsStatusFromQueryParams() { if (hasQueryParam('enableMaker')) { currentSources.makerAPIsEnabled = true; + updateQueryParam('enableMaker', undefined, true); } if (hasQueryParam('disableMaker')) { currentSources.makerAPIsEnabled = false; + updateQueryParam('disableMaker', undefined, true); } } diff --git a/apps/src/gamelab/ErrorDialogStack.jsx b/apps/src/gamelab/ErrorDialogStack.jsx index 1ccecb1754100..270e01535e43b 100644 --- a/apps/src/gamelab/ErrorDialogStack.jsx +++ b/apps/src/gamelab/ErrorDialogStack.jsx @@ -10,6 +10,7 @@ import Button from '@cdo/apps/templates/Button'; import DialogFooter from '@cdo/apps/templates/teacherDashboard/DialogFooter'; import * as animationActions from './animationListModule'; import firehoseClient from '@cdo/apps/lib/util/firehose'; +import {getCurrentId} from '../code-studio/initApp/project'; /** * Renders error dialogs in sequence, given a stack of errors. @@ -29,11 +30,13 @@ class ErrorDialogStack extends React.Component { 'analysis-events', { study: 'animation_no_load', - study_group: 'animation_no_load_v2', + study_group: 'animation_no_load_v3', event: 'delete_selected', + project_id: getCurrentId(), data_json: JSON.stringify({'version': this.props.animationList.propsByKey[key].version, 'animationName': this.props.animationList.propsByKey[key].name}) - } + }, + {includeUserId: true} ); this.props.deleteAnimation(key); this.props.dismissError(); @@ -45,11 +48,13 @@ class ErrorDialogStack extends React.Component { 'analysis-events', { study: 'animation_no_load', - study_group: 'animation_no_load_v2', + study_group: 'animation_no_load_v3', event: 'reload_selected', + project_id: getCurrentId(), data_json: JSON.stringify({'version': this.props.animationList.propsByKey[key].version, 'animationName': this.props.animationList.propsByKey[key].name}) - } + }, + {includeUserId: true} ); location.reload(); } diff --git a/apps/src/gamelab/GameLabJr.interpreted.js b/apps/src/gamelab/GameLabJr.interpreted.js index 3d6c24ae455e3..f2379339fc3f4 100644 --- a/apps/src/gamelab/GameLabJr.interpreted.js +++ b/apps/src/gamelab/GameLabJr.interpreted.js @@ -216,6 +216,7 @@ function makeNewGroup() { addBehavior(group[i], behavior, name); } }; + group.destroy = group.destroyEach; return group; } diff --git a/apps/src/gamelab/animationListModule.js b/apps/src/gamelab/animationListModule.js index e077989cd19bb..bb9c01d3112c6 100644 --- a/apps/src/gamelab/animationListModule.js +++ b/apps/src/gamelab/animationListModule.js @@ -14,7 +14,7 @@ import * as assetPrefix from '../assetManagement/assetPrefix'; import {selectAnimation} from './AnimationTab/animationTabModule'; import {reportError} from './errorDialogStackModule'; import {throwIfSerializedAnimationListIsInvalid} from './shapes'; -import {projectChanged, isOwner} from '../code-studio/initApp/project'; +import {projectChanged, isOwner, getCurrentId} from '../code-studio/initApp/project'; import firehoseClient from '@cdo/apps/lib/util/firehose'; // TODO: Overwrite version ID within session @@ -568,13 +568,15 @@ function loadAnimationFromSource(key, callback) { // Log data about when this scenario occurs firehoseClient.putRecord( 'analysis-events', - { - study: 'animation_no_load', - study_group: 'animation_no_load_v2', - event: isOwner() ? 'animation_not_loaded_owner' : 'animation_not_loaded_viewer', - data_json: JSON.stringify({'sourceUrl': sourceUrl, 'version': state.propsByKey[key].version, - 'animationName': state.propsByKey[key].name, 'error': err.message}) - } + { + study: 'animation_no_load', + study_group: 'animation_no_load_v3', + event: isOwner() ? 'animation_not_loaded_owner' : 'animation_not_loaded_viewer', + project_id: getCurrentId(), + data_json: JSON.stringify({'sourceUrl': sourceUrl, 'version': state.propsByKey[key].version, + 'animationName': state.propsByKey[key].name, 'error': err.message}) + }, + {includeUserId: true} ); if (isOwner()) { diff --git a/apps/src/gamelab/blocks.js b/apps/src/gamelab/blocks.js index 8009e41ce28ef..11f8511c6396c 100644 --- a/apps/src/gamelab/blocks.js +++ b/apps/src/gamelab/blocks.js @@ -6,6 +6,8 @@ import { const SPRITE_CATEGORY = 'sprites'; const EVENT_CATEGORY = 'events'; const EVENT_LOOP_CATEGORY = 'event_loop'; +const VARIABLES_CATEGORY = 'variables'; +const WORLD_CATEGORY = 'world'; const CATEGORIES = { [SPRITE_CATEGORY]: { color: [184, 1.00, 0.74], @@ -16,6 +18,12 @@ const CATEGORIES = { [EVENT_LOOP_CATEGORY]: { color: [322, 0.90, 0.95], }, + [VARIABLES_CATEGORY]: { + color: [312, 0.32, 0.62], + }, + [WORLD_CATEGORY]: { + color: [240, 0.45, 0.65], + }, }; const SPRITES = [ @@ -29,13 +37,19 @@ export default { ORDER_COMMA, ORDER_FUNCTION_CALL, ORDER_MEMBER, + ORDER_NONE, } = Blockly.JavaScript; - const SPRITE_TYPE = blockly.BlockValueType.NUMBER; + + // TODO(ram): Create Blockly.BlockValueType.SPRITE + const SPRITE_TYPE = blockly.BlockValueType.NONE; const generator = blockly.Generator.get('JavaScript'); const createJsWrapperBlock = ({ category, func, + expression, + orderPrecedence, + name, blockText, args, returnType, @@ -43,7 +57,9 @@ export default { eventBlock, eventLoopBlock, }) => { - const blockName = `gamelab_${func}`; + args = args || []; + const blockName = `gamelab_${name || func}`; + blockly.Blocks[blockName] = { helpUrl: '', init: function () { @@ -59,8 +75,7 @@ export default { this.setInputsInline(true); if (returnType) { - // TODO(ram): Create Blockly.BlockValueType.SPRITE - this.setOutput(true, Blockly.BlockValueType.NUMBER); + this.setOutput(true, returnType); } else { if (eventLoopBlock) { this.appendStatementInput('DO'); @@ -105,6 +120,14 @@ export default { values.push(`function () {\n${handlerCode}}`); } + if (expression) { + if (returnType !== undefined) { + return [`${prefix}${expression}`, orderPrecedence || ORDER_NONE]; + } else { + return `${prefix}${expression}`; + } + } + if (returnType !== undefined) { return [`${prefix}${func}(${values.join(', ')})`, ORDER_FUNCTION_CALL]; } else { @@ -116,15 +139,33 @@ export default { createJsWrapperBlock({ category: SPRITE_CATEGORY, func: 'makeNewSprite', - blockText: 'Make a new {ANIMATION} sprite at {X} {Y}', + blockText: 'make a new {ANIMATION} sprite at {X} {Y}', args: [ - { name: 'ANIMATION', options: SPRITES}, - { name: 'X', type: blockly.BlockValueType.NUMBER}, - { name: 'Y', type: blockly.BlockValueType.NUMBER}, + { name: 'ANIMATION', options: SPRITES }, + { name: 'X', type: blockly.BlockValueType.NUMBER }, + { name: 'Y', type: blockly.BlockValueType.NUMBER }, ], returnType: SPRITE_TYPE, }); + createJsWrapperBlock({ + category: SPRITE_CATEGORY, + func: 'makeNewGroup', + blockText: 'make a new group', + args: [], + returnType: SPRITE_TYPE, + }); + + createJsWrapperBlock({ + category: SPRITE_CATEGORY, + func: 'add', + blockText: 'add {SPRITE} to group {THIS}', + args: [ + { name: 'SPRITE', type: SPRITE_TYPE }, + ], + methodCall: true, + }); + createJsWrapperBlock({ category: SPRITE_CATEGORY, func: 'moveUp', @@ -220,5 +261,71 @@ export default { args: [], eventLoopBlock: true, }); + + createJsWrapperBlock({ + category: EVENT_CATEGORY, + func: 'whenTouching', + blockText: 'when {SPRITE1} is touching {SPRITE2}', + args: [ + { name: 'SPRITE1', type: SPRITE_TYPE }, + { name: 'SPRITE2', type: SPRITE_TYPE }, + ], + eventBlock: true, + }); + + createJsWrapperBlock({ + category: SPRITE_CATEGORY, + func: 'displace', + blockText: '{THIS} blocks {SPRITE} from moving', + args: [ + {name: 'SPRITE', type: SPRITE_TYPE }, + ], + methodCall: true, + }); + + createJsWrapperBlock({ + category: SPRITE_CATEGORY, + func: 'destroy', + blockText: 'remove {THIS}', + args: [], + methodCall: true, + }); + + createJsWrapperBlock({ + category: VARIABLES_CATEGORY, + expression: 'arguments[0]', + orderPrecedence: ORDER_MEMBER, + name: 'firstTouched', + blockText: 'first touched sprite', + returnType: SPRITE_TYPE, + }); + + createJsWrapperBlock({ + category: VARIABLES_CATEGORY, + expression: 'arguments[1]', + orderPrecedence: ORDER_MEMBER, + name: 'secondTouched', + blockText: 'second touched sprite', + returnType: SPRITE_TYPE, + }); + + createJsWrapperBlock({ + category: SPRITE_CATEGORY, + expression: 'length', + orderPrecedence: ORDER_MEMBER, + name: 'groupLength', + blockText: 'number of sprites in {THIS}', + methodCall: true, + returnType: blockly.BlockValueType.NUMBER, + }); + + createJsWrapperBlock({ + category: WORLD_CATEGORY, + func: 'setBackground', + blockText: 'set background color {COLOR}', + args: [ + { name: 'COLOR', type: blockly.BlockValueType.COLOUR }, + ], + }); }, }; diff --git a/apps/src/lib/kits/maker/CircuitPlaygroundBoard.js b/apps/src/lib/kits/maker/CircuitPlaygroundBoard.js index fdaa63b4b0b5c..200498bf516ee 100644 --- a/apps/src/lib/kits/maker/CircuitPlaygroundBoard.js +++ b/apps/src/lib/kits/maker/CircuitPlaygroundBoard.js @@ -161,7 +161,7 @@ export default class CircuitPlaygroundBoard extends EventEmitter { // on the next run. // Note: This doesn't seem to be necessary when using browser-serialport // and the Chrome App connector, but it is required for native - // node serialport in the Maker Toolkit Browser. + // node serialport in the Code.org Maker App. if (this.serialPort_ && typeof this.serialPort_.close === 'function') { this.serialPort_.close(); } diff --git a/apps/src/templates/MakerLanding.jsx b/apps/src/templates/MakerLanding.jsx index f2dd362e060b0..f89577439d2b6 100644 --- a/apps/src/templates/MakerLanding.jsx +++ b/apps/src/templates/MakerLanding.jsx @@ -2,7 +2,6 @@ import TopCourse from './studioHomepages/TopCourse'; import VerticalImageResourceCardRow from './VerticalImageResourceCardRow'; import shapes from './studioHomepages/shapes'; -import {pegasus} from '@cdo/apps/lib/util/urlHelpers'; import i18n from '@cdo/locale'; const styles = { @@ -26,7 +25,7 @@ export default class MakerLanding extends Component { { title: i18n.makerNewProjectTitle(), description: i18n.makerNewProjectDesc(), - link: pegasus('/applab'), + link: '/projects/applab/new?enableMaker=true', image: "applab-project", buttonText: i18n.makerNewProjectButton() }, diff --git a/apps/src/templates/manageStudents/ManageStudentsActionsCell.jsx b/apps/src/templates/manageStudents/ManageStudentsActionsCell.jsx index cd33c84df3d91..af99419b445fc 100644 --- a/apps/src/templates/manageStudents/ManageStudentsActionsCell.jsx +++ b/apps/src/templates/manageStudents/ManageStudentsActionsCell.jsx @@ -4,7 +4,7 @@ import PopUpMenu, {MenuBreak} from "@cdo/apps/lib/ui/PopUpMenu"; import color from "../../util/color"; import FontAwesome from '../FontAwesome'; import Button from '../Button'; -import {startEditingStudent, cancelEditingStudent, removeStudent, saveStudent} from './manageStudentsRedux'; +import {startEditingStudent, cancelEditingStudent, removeStudent, saveStudent, addStudent} from './manageStudentsRedux'; import {connect} from 'react-redux'; import BaseDialog from '../BaseDialog'; import DialogFooter from "../teacherDashboard/DialogFooter"; @@ -19,15 +19,17 @@ const styles = { class ManageStudentActionsCell extends Component { static propTypes = { id: PropTypes.number.isRequired, - sectionId: PropTypes.number.isRequired, + sectionId: PropTypes.number, isEditing: PropTypes.bool, isSaving: PropTypes.bool, disableSaving: PropTypes.bool, + isAddRow: PropTypes.bool, // Provided by redux startEditingStudent: PropTypes.func, cancelEditingStudent: PropTypes.func, removeStudent: PropTypes.func, saveStudent: PropTypes.func, + addStudent: PropTypes.func, }; state = { @@ -71,6 +73,10 @@ class ManageStudentActionsCell extends Component { this.props.saveStudent(this.props.id); }; + onAdd = () => { + this.props.addStudent(this.props.id); + }; + render() { return (
@@ -91,7 +97,7 @@ class ManageStudentActionsCell extends Component { } - {this.props.isEditing && + {(this.props.isEditing && !this.props.isAddRow) &&
} + {this.props.isAddRow && +
+
+ } -

{i18n.removeStudent()}

-
{i18n.removeStudentConfirm()}
+

{i18n.removeStudentHeader()}

+
+ {i18n.removeStudentConfirm1() + ' '} + + {i18n.removeStudentConfirm2()} + + {' ' + i18n.removeStudentConfirm3()} +
diff --git a/apps/src/templates/manageStudents/ManageStudentsTable.jsx b/apps/src/templates/manageStudents/ManageStudentsTable.jsx index e06f78e4cb220..958fed5a83c54 100644 --- a/apps/src/templates/manageStudents/ManageStudentsTable.jsx +++ b/apps/src/templates/manageStudents/ManageStudentsTable.jsx @@ -24,6 +24,7 @@ export const studentSectionDataPropType = PropTypes.shape({ secretPicturePath: PropTypes.string, sectionId: PropTypes.number, loginType: PropTypes.string, + isAddRow: PropTypes.bool, }); /** @enum {number} */ @@ -61,13 +62,30 @@ const passwordFormatter = (loginType, {rowData}) => { } {rowData.isEditing &&
- Auto-generated + {i18n.autoGenerated()}
} ); }; +// The "add row" should always be pinned to the top when sorting. +// This function takes into account having multiple "add rows" +const sortRows = (data, columnIndexList, orderList) => { + let addRows = []; + let studentRows = []; + for (let i = 0; i ); }; @@ -279,7 +298,7 @@ class ManageStudentsTable extends Component { const sortedRows = sort.sorter({ columns, sortingColumns, - sort: orderBy, + sort: sortRows, })(this.props.studentData); return ( diff --git a/apps/src/templates/manageStudents/manageStudentsRedux.js b/apps/src/templates/manageStudents/manageStudentsRedux.js index 1f3480ba9ac8c..4c8ad998ef4cd 100644 --- a/apps/src/templates/manageStudents/manageStudentsRedux.js +++ b/apps/src/templates/manageStudents/manageStudentsRedux.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +import {SectionLoginType} from '@cdo/apps/util/sharedConstants'; const SET_LOGIN_TYPE = 'manageStudents/SET_LOGIN_TYPE'; const SET_STUDENTS = 'manageStudents/SET_STUDENTS'; @@ -11,6 +12,7 @@ const SET_SECRET_WORDS = 'manageStudents/SET_SECRET_WORDS'; const EDIT_STUDENT = 'manageStudents/EDIT_STUDENT'; const START_SAVING_STUDENT = 'manageStudents/START_SAVING_STUDENT'; const SAVE_STUDENT_SUCCESS = 'manageStudents/SAVE_STUDENT_SUCCESS'; +const ADD_STUDENT_SUCCESS = 'manageStudents/ADD_STUDENT_SUCCESS'; export const setLoginType = loginType => ({ type: SET_LOGIN_TYPE, loginType }); export const setSectionId = sectionId => ({ type: SET_SECTION_ID, sectionId}); @@ -23,6 +25,7 @@ export const setSecretWords = (studentId, words) => ({ type: SET_SECRET_WORDS, s export const editStudent = (studentId, studentData) => ({ type: EDIT_STUDENT, studentId, studentData }); export const startSavingStudent = (studentId) => ({ type: START_SAVING_STUDENT, studentId }); export const saveStudentSuccess = (studentId) => ({ type: SAVE_STUDENT_SUCCESS, studentId }); +export const addStudentSuccess = (studentData) => ({ type: ADD_STUDENT_SUCCESS, studentData }); export const saveStudent = (studentId) => { return (dispatch, getState) => { @@ -37,6 +40,34 @@ export const saveStudent = (studentId) => { }; }; +export const addStudent = (studentId) => { + return (dispatch, getState) => { + const state = getState().manageStudents; + dispatch(startSavingStudent(studentId)); + addStudentOnServer(state.editingData[studentId], state.sectionId, (error, data) => { + if (error) { + console.error(error); + } + dispatch(addStudentSuccess(convertAddedStudent(data))); + }); + }; +}; + +// This doesn't get used to make a server call, but does +// need to be unique from the rest of the ids. +const addRowId = 0; + +const blankAddRow = { + id: addRowId, + name: '', + age: '', + gender: '', + username: '', + loginType: '', + isEditing: true, + isAddRow: true, +}; + const initialState = { loginType: '', studentData: {}, @@ -46,9 +77,28 @@ const initialState = { export default function manageStudents(state=initialState, action) { if (action.type === SET_LOGIN_TYPE) { + let addRowInitialization = {}; + if (action.loginType === SectionLoginType.word || action.loginType === SectionLoginType.picture) { + addRowInitialization = { + studentData: { + [addRowId]: { + ...blankAddRow, + loginType: action.loginType, + } + }, + editingData: { + [addRowId]: { + ...blankAddRow, + loginType: action.loginType, + + } + } + }; + } return { ...state, loginType: action.loginType, + ...addRowInitialization, }; } if (action.type === SET_SECTION_ID) { @@ -60,7 +110,10 @@ export default function manageStudents(state=initialState, action) { if (action.type === SET_STUDENTS) { return { ...state, - studentData: action.studentData, + studentData: { + ...state.studentData, + ...action.studentData + }, }; } if (action.type === START_EDITING_STUDENT) { @@ -122,6 +175,29 @@ export default function manageStudents(state=initialState, action) { editingData: _.omit(state.editingData, action.studentId), }; } + if (action.type === ADD_STUDENT_SUCCESS) { + return { + ...state, + studentData: { + [action.studentData.id]: { + ...action.studentData, + loginType: state.loginType + }, + ...state.studentData, + [addRowId]: { + ...blankAddRow, + loginType: state.loginType + }, + }, + editingData: { + ...state.editingData, + [addRowId]: { + ...blankAddRow, + loginType: state.loginType + } + } + }; + } if (action.type === EDIT_STUDENT) { return { ...state, @@ -196,6 +272,25 @@ export const convertStudentServerData = (studentData, loginType, sectionId) => { return studentLookup; }; +// Converts added student from /v2/sections/sectionid/students to a key/value +// object for the redux store +export const convertAddedStudent = (studentData, loginType, sectionId) => { + let student = studentData[0]; + const studentObject = { + id: student.id, + name: student.name, + username: student.username, + age: student.age, + gender: student.gender, + secretWords: student.secret_words, + secretPicturePath: student.secret_picture_path, + loginType: loginType, + sectionId: sectionId, + isEditing: false, + }; + return studentObject; +}; + // Converts key/value id/student pairs to an array of student objects for the // component to display // TODO(caleybrock): memoize this - sections could be a few thousand students @@ -222,3 +317,23 @@ const updateStudentOnServer = (updatedStudentInfo, onComplete) => { onComplete(status, null); }); }; + +// Make a post request to add a student. +const addStudentOnServer = (updatedStudentInfo, sectionId, onComplete) => { + const studentToAdd = [{ + editing: true, + name: updatedStudentInfo.name, + age: updatedStudentInfo.age, + gender: updatedStudentInfo.gender, + }]; + $.ajax({ + url: `/v2/sections/${sectionId}/students`, + method: 'POST', + contentType: 'application/json;charset=UTF-8', + data: JSON.stringify(studentToAdd), + }).done((data) => { + onComplete(null, data); + }).fail((jqXhr, status) => { + onComplete(status, null); + }); +}; diff --git a/apps/test/unit/templates/manageStudents/manageStudentsReduxTest.js b/apps/test/unit/templates/manageStudents/manageStudentsReduxTest.js index 9972bf3a338ae..d774d9630902d 100644 --- a/apps/test/unit/templates/manageStudents/manageStudentsReduxTest.js +++ b/apps/test/unit/templates/manageStudents/manageStudentsReduxTest.js @@ -12,6 +12,7 @@ import manageStudents, { editStudent, startSavingStudent, saveStudentSuccess, + addStudentSuccess, } from '@cdo/apps/templates/manageStudents/manageStudentsRedux'; const studentEmailData = { @@ -272,4 +273,78 @@ describe('manageStudentsRedux', () => { assert.equal(afterSaveState.studentData[1].name, "New name"); }); }); + + describe('add student', () => { + const expectedBlankRow = { + id: 0, + name: '', + age: '', + gender: '', + username: '', + loginType: '', + isEditing: true, + isAddRow: true, + }; + it('setLoginType creates an add row for word login types', () => { + const action = setLoginType('word'); + const nextState = manageStudents(initialState, action); + assert.deepEqual(nextState.studentData[0], {...expectedBlankRow, loginType: 'word'}); + assert.deepEqual(nextState.editingData[0], {...expectedBlankRow, loginType: 'word'}); + }); + + it('setLoginType creates an add row for picture login types', () => { + const action = setLoginType('picture'); + const nextState = manageStudents(initialState, action); + assert.deepEqual(nextState.studentData[0], {...expectedBlankRow, loginType: 'picture'}); + assert.deepEqual(nextState.editingData[0], {...expectedBlankRow, loginType: 'picture'}); + }); + + it('addStudentSuccess updates studentData,removes editingData, and adds new blank row', () => { + // Initial state with blank row + const initialState = { + loginType: 'picture', + studentData: { + 0: { + ...expectedBlankRow, + loginType: 'picture', + } + }, + editingData: { + 0: { + ...expectedBlankRow, + loginType: 'picture', + } + }, + sectionId: 10, + }; + + const studentDataToAdd = { + id: 10, + name: 'new student', + age: 17, + gender: 'f', + secretPicturePath: '/wizard.jpg', + loginType: 'picture', + sectionId: 10, + isEditing: false, + }; + + // Add student + const addStudentSuccessAction = addStudentSuccess(studentDataToAdd); + const addedStudentState = manageStudents(initialState, addStudentSuccessAction); + + assert.deepEqual(addedStudentState.editingData[0], { + ...expectedBlankRow, + loginType: 'picture', + }); + assert.deepEqual(addedStudentState.studentData[0], { + ...expectedBlankRow, + loginType: 'picture', + }); + assert.deepEqual(addedStudentState.studentData[10], { + ...studentDataToAdd + }); + }); + + }); }); diff --git a/bin/cron/send_workshop_reminder_emails b/bin/cron/send_workshop_reminder_emails deleted file mode 100755 index aaf08f5127020..0000000000000 --- a/bin/cron/send_workshop_reminder_emails +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env ruby - -# This script sends reminder via poste for professional development workshop -# signups. - -require File.expand_path('../../../pegasus/src/env', __FILE__) -require 'active_support' -require 'active_support/core_ext/object/blank' -require src_dir 'database' -require 'cdo/only_one' -require 'cdo/poste' - -# @param date_as_string [String] a date string in the form MM/DD/YY or MM/DD/YYYY. -# @returns [Date | nil] a Date for date_as_string or nil if it does not parse -# to a date. -def parse_workshop_date(date_as_string) - return nil if date_as_string.blank? - year_part = date_as_string.split('/')[2] - format_string = year_part.length == 2 ? '%m/%d/%y' : '%m/%d/%Y' - Date.strptime(date_as_string, format_string) -rescue ArgumentError - nil -end - -# Sends a workshop reminder email to the recipient specified by form. -# @param form_id [Integer] the ID for the ProfessionalDevelopmentWorkshopSignup -# form -# @param email [String] the email for the ProfessionalDevelopmentWorkshopSignup -# form -def send_reminder_email(form_id, email) - recipient = Poste2.ensure_recipient(email, ip_address: '127.0.0.1') - Poste2.send_message('workshop_signup_reminder', recipient, form_id: form_id) -end - -def main - DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshop').each do |workshop| - # Parse the JSON blob in data to determine the workshop date. - parsed_data = JSON.parse(workshop[:data]) - workshop_date = parse_workshop_date(parsed_data['dates'].first['date_s']) - # Only send workshop reminders if twelve days away, so skip otherwise. - next unless workshop_date == Date.today + 12 - # Determine all the signups for this workshop, sending an email unless the - # signup status is cancelled. - DB[:forms]. - where(kind: 'ProfessionalDevelopmentWorkshopSignup'). - where(parent_id: workshop[:id]). - each do |signup| - parsed_signup_data = JSON.parse(signup[:data]) - next if parsed_signup_data['status_s'] == 'cancelled' - send_reminder_email(signup[:id], signup[:email]) - end - end -end - -main if only_one_running?(__FILE__) diff --git a/bin/cron/with_pg/Gemfile b/bin/cron/with_pg/Gemfile new file mode 100644 index 0000000000000..4e805c45b7430 --- /dev/null +++ b/bin/cron/with_pg/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +eval_gemfile File.join('../../../', 'Gemfile') + +gem 'pg' diff --git a/bin/generate_professional_development_workshop_teachers_report_csv b/bin/generate_professional_development_workshop_teachers_report_csv deleted file mode 100755 index b3b2f119d1db0..0000000000000 --- a/bin/generate_professional_development_workshop_teachers_report_csv +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -require File.expand_path('../../pegasus/src/env', __FILE__) -require src_dir 'database' -require pegasus_dir 'helpers/professional_development_workshop_helpers' -require 'cdo/csv' -require 'cdo/activity_constants' - -puts CSV.generate_from_dataset(generate_professional_development_workshop_teachers_report) diff --git a/cookbooks/Berksfile.lock b/cookbooks/Berksfile.lock index cd17c5fb293e3..7418714641aeb 100644 --- a/cookbooks/Berksfile.lock +++ b/cookbooks/Berksfile.lock @@ -63,7 +63,7 @@ GRAPH seven_zip (>= 0.0.0) windows (>= 0.0.0) build-essential (2.1.3) - cdo-apps (0.2.280) + cdo-apps (0.2.281) apt (>= 0.0.0) build-essential (>= 0.0.0) cdo-cloudwatch-extra-metrics (>= 0.0.0) diff --git a/cookbooks/cdo-apps/metadata.rb b/cookbooks/cdo-apps/metadata.rb index 9b6916ead1193..d412fe40d5540 100644 --- a/cookbooks/cdo-apps/metadata.rb +++ b/cookbooks/cdo-apps/metadata.rb @@ -4,7 +4,7 @@ license 'All rights reserved' description 'Installs/Configures cdo-apps' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version '0.2.280' +version '0.2.281' depends 'apt' depends 'build-essential' diff --git a/cookbooks/cdo-apps/templates/default/crontab.erb b/cookbooks/cdo-apps/templates/default/crontab.erb index 7fa6d91d0ffcf..eb9db2831bca0 100644 --- a/cookbooks/cdo-apps/templates/default/crontab.erb +++ b/cookbooks/cdo-apps/templates/default/crontab.erb @@ -82,7 +82,6 @@ if node.chef_environment == 'production' # production daemon cronjob at:'20 */2 * * *', do:deploy_dir('bin', 'cron', 'activity-monitor') - cronjob at:'5 6 * * *', do:deploy_dir('bin', 'cron', 'send_workshop_reminder_emails') cronjob at:'15 16 * * *', do:dashboard_dir('bin','scheduled_ops_emails') cronjob at:'30 14 * * *', do:dashboard_dir('bin','scheduled_pd_workshop_emails') cronjob at:'* 14 * * 1', do:dashboard_dir('bin','scheduled_pd_application_emails') diff --git a/dashboard/app/assets/stylesheets/extra_links.scss b/dashboard/app/assets/stylesheets/extra_links.scss index 1e68fd16af673..c60acf807611a 100644 --- a/dashboard/app/assets/stylesheets/extra_links.scss +++ b/dashboard/app/assets/stylesheets/extra_links.scss @@ -24,5 +24,5 @@ /* only display scrollbars when needed */ overflow: auto; /* admin box appears in front of the rest of the level */ - z-index: 400; + z-index: 1000; } diff --git a/dashboard/app/controllers/api/v1/regional_partners_controller.rb b/dashboard/app/controllers/api/v1/regional_partners_controller.rb index 9209b3ac83fcf..45478d3520fbf 100644 --- a/dashboard/app/controllers/api/v1/regional_partners_controller.rb +++ b/dashboard/app/controllers/api/v1/regional_partners_controller.rb @@ -11,4 +11,11 @@ def index render json: regional_partner_mapping, include: :regional_partner end end + + # GET /api/v1/regional-partner/for_user + def for_user + regional_partners = current_user.permission?(UserPermission::WORKSHOP_ADMIN) ? RegionalPartner.all : current_user.regional_partners + + render json: regional_partners.order(:name).map {|partner| {id: partner.id, name: partner.name}} + end end diff --git a/dashboard/app/controllers/discourse_sso_controller.rb b/dashboard/app/controllers/discourse_sso_controller.rb index bff0e33e7c783..77f0743c2a835 100644 --- a/dashboard/app/controllers/discourse_sso_controller.rb +++ b/dashboard/app/controllers/discourse_sso_controller.rb @@ -12,7 +12,6 @@ def sso sso.external_id = current_user.id # from devise sso.sso_secret = secret sso.sso_url = CDO.discourse_sso_url - sso.add_groups = 'Verified-Teachers' if current_user.verified_teacher? redirect_to sso.to_url(CDO.discourse_sso_url) end diff --git a/dashboard/app/controllers/maker_controller.rb b/dashboard/app/controllers/maker_controller.rb index e99088b823563..e0c1aab5a19df 100644 --- a/dashboard/app/controllers/maker_controller.rb +++ b/dashboard/app/controllers/maker_controller.rb @@ -1,11 +1,14 @@ require 'cdo/script_constants' class MakerController < ApplicationController - authorize_resource class: :maker_discount, except: :setup + authorize_resource class: :maker_discount, except: [:home, :setup] # Maker Toolkit is currently used in CSD unit 6. # Retrieves the current CSD unit 6 level that the user is working on. def home + # Redirect to login if not signed in + authenticate_user! + csd_unit_6_script = Script.find_by_name(Script::CSD6_NAME) current_level = current_user.next_unpassed_progression_level(csd_unit_6_script) @csd_unit_6 = { diff --git a/dashboard/app/controllers/projects_controller.rb b/dashboard/app/controllers/projects_controller.rb index d8ce3660adbf4..03200e551040c 100644 --- a/dashboard/app/controllers/projects_controller.rb +++ b/dashboard/app/controllers/projects_controller.rb @@ -237,12 +237,17 @@ def create_new return end return if redirect_under_13_without_tos_teacher(@level) - redirect_to action: 'edit', channel_id: ChannelToken.create_channel( + channel = ChannelToken.create_channel( request.ip, StorageApps.new(storage_id('user')), data: initial_data, type: params[:key] ) + redirect_to( + action: 'edit', + channel_id: channel, + enableMaker: params['enableMaker'] ? true : nil + ) end private def initial_data diff --git a/dashboard/app/models/levels/gamelab_jr.rb b/dashboard/app/models/levels/gamelab_jr.rb index 2c79da35f14ab..72c4c9a92bcfa 100644 --- a/dashboard/app/models/levels/gamelab_jr.rb +++ b/dashboard/app/models/levels/gamelab_jr.rb @@ -52,12 +52,28 @@ def common_blocks(type) + + + + + + + + + + + + + + + + @@ -68,6 +84,7 @@ def common_blocks(type) + @@ -91,6 +108,41 @@ def common_blocks(type) + + + + + + + + + + + + + 10 + + + + + + + + 1 + + + + + 10 + + + + + 1 + + + + XML end diff --git a/dashboard/app/models/pd/teachercon1819_registration.rb b/dashboard/app/models/pd/teachercon1819_registration.rb index 6d53490e11f8e..e685ce1a2ee7a 100644 --- a/dashboard/app/models/pd/teachercon1819_registration.rb +++ b/dashboard/app/models/pd/teachercon1819_registration.rb @@ -11,8 +11,8 @@ # # Indexes # -# index_pd_teachercon1819_registrations_on_pd_application_id (pd_application_id) -# index_pd_teachercon1819_registrations_on_regional_partner_id (regional_partner_id) +# index_pd_summer_event1819_registrations_on_pd_application_id (pd_application_id) +# index_pd_summer_event1819_registrations_on_regional_partner_id (regional_partner_id) # require 'cdo/shared_constants/pd/teachercon1819_registration_constants' diff --git a/dashboard/app/views/maker/home.html.haml b/dashboard/app/views/maker/home.html.haml index 7ac79f6405399..afb877e85d49c 100644 --- a/dashboard/app/views/maker/home.html.haml +++ b/dashboard/app/views/maker/home.html.haml @@ -1,3 +1,4 @@ +- @page_title = 'Code.org Maker App' - maker_home_data = {course: @csd_unit_6} %script{src: minifiable_asset_path('js/maker/home.js'), data: {makerHome: maker_home_data.to_json}} diff --git a/dashboard/app/views/pd/workshop_mailer/_teacher_enrollment_details.html.haml b/dashboard/app/views/pd/workshop_mailer/_teacher_enrollment_details.html.haml index a8b5b2f319791..316af7b0f8cc0 100644 --- a/dashboard/app/views/pd/workshop_mailer/_teacher_enrollment_details.html.haml +++ b/dashboard/app/views/pd/workshop_mailer/_teacher_enrollment_details.html.haml @@ -33,7 +33,8 @@ %ul %li It helps us understand who the population of teachers involved in our - Computer Science Principles (CSP) PD program are. + = @workshop.course + PD program are. %li Your students will be asked to take a similar survey as part of the course. This gives you a bit of a preview of the kinds of questions we diff --git a/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_receipt.html.haml b/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_receipt.html.haml index 1883291320556..fc184d69fef9b 100644 --- a/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_receipt.html.haml +++ b/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_receipt.html.haml @@ -10,7 +10,10 @@ - if @workshop.local_summer? %p Thanks for enrolling in - = "#{@workshop.organizer.name}’#{@workshop.organizer.name.ends_with?('s') ? '' : 's'} 5-day Summer workshop on the Code.org CS Principles curriculum." + = "#{@workshop.organizer.name}’#{@workshop.organizer.name.ends_with?('s') ? '' : 's'} " + 5-day Summer workshop on the Code.org + = @workshop.course + curriculum. - else %p Thanks for enrolling in Code.org’s diff --git a/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_reminder.html.haml b/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_reminder.html.haml index 4d7d96cc3d0a7..b95fe23b78ab3 100644 --- a/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_reminder.html.haml +++ b/dashboard/app/views/pd/workshop_mailer/teacher_enrollment_reminder.html.haml @@ -10,7 +10,8 @@ - if @workshop.local_summer? %p This is a reminder about your upcoming 5-day Summer workshop on the Code.org - CS Principles curriculum. + = @workshop.course + curriculum. - else %p This is a reminder about your upcoming Code.org diff --git a/dashboard/app/views/scripts/show.html.haml b/dashboard/app/views/scripts/show.html.haml index 9d01d3b75d104..b5835314cf520 100644 --- a/dashboard/app/views/scripts/show.html.haml +++ b/dashboard/app/views/scripts/show.html.haml @@ -70,11 +70,22 @@ - @script.courses.each do |course| %li= link_to course_path(course) - levels = Script.get_from_cache(@script.name).stages.map{ |stage| stage.script_levels.map{ |sl| sl.level }}.flatten + .row + .span1 + = "Index" + .span3 + = "Level name" + .span3 + = "Template level" + .span3 + = "Contained levels" - levels.each_with_index do |level, index| .row .span1 = index + 1 .span3 = level.name - .span8 - = level.instructions + .span3 + = level.properties['project_template_level_name'] + .span3 + = level.properties['contained_level_names'].join(", ") diff --git a/dashboard/config/routes.rb b/dashboard/config/routes.rb index 38f2ec8cf6294..dcc69969edb29 100644 --- a/dashboard/config/routes.rb +++ b/dashboard/config/routes.rb @@ -553,6 +553,7 @@ module OPS get 'schools/:school_district_id/:school_type', to: 'schools#index', defaults: {format: 'json'} get 'schools/:id', to: 'schools#show', defaults: {format: 'json'} get 'regional-partners/:school_district_id/:course', to: 'regional_partners#index', defaults: {format: 'json'} + get 'regional-partners/for_user', 'regional_partners' get 'projects/gallery/public/:project_type/:limit(/:published_before)', to: 'projects/public_gallery#index', defaults: {format: 'json'} diff --git a/dashboard/config/scripts/levels/New Game Lab Jr Project.level b/dashboard/config/scripts/levels/New Game Lab Jr Project.level index 044abaf19dd57..0fb7728cb2b53 100644 --- a/dashboard/config/scripts/levels/New Game Lab Jr Project.level +++ b/dashboard/config/scripts/levels/New Game Lab Jr Project.level @@ -231,6 +231,15 @@ + + + + + #ff0000 + + + + sprite @@ -278,6 +287,46 @@ + + + + sprite + + + + + other + + + + + + + sprite + + + + + + + group + + + + + + + + sprite + + + + + group + + + + @@ -288,8 +337,79 @@ + + + + sprite + + + + + other + + + + + + + + + + 10 + + + + + WHILE + + + i + + + 1 + + + + + 10 + + + + + 1 + + + + + + + + + 1 + + + + + 100 + + + + + + + + EQ + + + AND + + + + TRUE + + diff --git a/dashboard/test/controllers/api/v1/regional_partners_controller_test.rb b/dashboard/test/controllers/api/v1/regional_partners_controller_test.rb index 975951ca6ddf9..1f3f079592459 100644 --- a/dashboard/test/controllers/api/v1/regional_partners_controller_test.rb +++ b/dashboard/test/controllers/api/v1/regional_partners_controller_test.rb @@ -43,4 +43,35 @@ class Api::V1::RegionalPartnersControllerTest < ActionController::TestCase assert_equal workshop_days, response['workshop_days'] end end + + test 'for_user gets regional partners for user' do + program_manager = create :teacher + + regional_partner_for_user = create :regional_partner, name: 'Regional Partner' + regional_partner_for_user.program_manager = program_manager.id + + another_regional_partner_for_user = create :regional_partner, name: 'Another Regional Partner' + another_regional_partner_for_user.program_manager = program_manager.id + + create :regional_partner, name: 'Other regional partner' + + sign_in program_manager + + get :for_user + response = JSON.parse(@response.body) + assert_equal [ + {'name' => 'Another Regional Partner', 'id' => another_regional_partner_for_user.id}, + {'name' => 'Regional Partner', 'id' => regional_partner_for_user.id} + ], response + end + + test 'for_user gets all regional partners for workshop admin' do + regional_partner = create :regional_partner, name: 'New regional partner' + sign_in (create :workshop_admin) + + get :for_user + response = JSON.parse(@response.body) + assert_equal RegionalPartner.count, response.length + assert response.include?({'id' => regional_partner.id, 'name' => regional_partner.name}) + end end diff --git a/dashboard/test/controllers/api_controller_test.rb b/dashboard/test/controllers/api_controller_test.rb index dff9d16b7ea50..8f63da1d0ff24 100644 --- a/dashboard/test/controllers/api_controller_test.rb +++ b/dashboard/test/controllers/api_controller_test.rb @@ -402,21 +402,14 @@ class ApiControllerTest < ActionController::TestCase } ] - response_body = JSON.parse(@response.body) - # Since the order of the levelgroup_results with the response isn't defined, we manually - # compare the actual and expected responses. - # TODO(asher): Generalize this to somewhere where it can be reused. - assert_equal 1, response_body.length - assert_equal ['stage', 'levelgroup_results'], response_body[0].keys - assert_equal expected_response[0]['stage'], response_body[0]['stage'] - assert_equal expected_response[0]['levelgroup_results'].count, - response_body[0]['levelgroup_results'].count - expected_response[0]['levelgroup_results'].each do |result| - assert response_body[0]['levelgroup_results'].include? result - end - response_body[0]['levelgroup_results'].each do |result| - assert expected_response[0]['levelgroup_results'].include? result - end + actual_response = JSON.parse(@response.body) + assert_equal 1, actual_response.length + assert_equal ['stage', 'levelgroup_results'], actual_response[0].keys + assert_equal expected_response[0]['stage'], actual_response[0]['stage'] + assert_levelgroup_results_match( + expected_response[0]['levelgroup_results'], + actual_response[0]['levelgroup_results'] + ) end test "should get surveys for section with script with single page anonymous level_group assessment" do @@ -541,7 +534,14 @@ class ApiControllerTest < ActionController::TestCase } ] - assert_equal expected_response, JSON.parse(@response.body) + actual_response = JSON.parse(@response.body) + assert_equal 1, actual_response.length + assert_equal ['stage', 'levelgroup_results'], actual_response[0].keys + assert_equal expected_response[0]['stage'], actual_response[0]['stage'] + assert_levelgroup_results_match( + expected_response[0]['levelgroup_results'], + actual_response[0]['levelgroup_results'] + ) end test "no anonymous survey data via assessment call" do @@ -1600,4 +1600,68 @@ class ApiControllerTest < ActionController::TestCase {method: "get", path: "/api/student_progress/2/15"} ) end + + # + # Given two arrays, checks that they represent equivalent bags (or multisets) + # of elements. + # + # Equivalent: [1, 1, 2], [1, 2, 1] + # Not equivalent: [1, 1, 2], [1, 2, 2] + # + # Optionally takes a comparator block. If omitted, == comparison is used. + # + # equivalent_bags?([2, 3, 4], [12, 13, 14]) {|a,b| a%10 == b%10} + # + # @param [Array] bag_a + # @param [Array] bag_b + # @param [Block] (optional) comparator + # @return [Boolean] true if sets are equivalent, false if not + # + def equivalent_bags?(bag_a, bag_b) + bag_b_remaining = bag_b.clone + bag_a.each do |a| + match_index = bag_b_remaining.find_index do |b| + if block_given? + yield a, b + else + a == b + end + end + if match_index.nil? + return false + else + bag_b_remaining.delete_at match_index + end + end + bag_b_remaining.empty? + end + + test 'equivalent_bags? helper' do + assert equivalent_bags? [], [] + assert equivalent_bags? [1, 1, 1, 2, 2], [2, 1, 2, 1, 1] + refute equivalent_bags? [1, 1, 1, 2, 2], [1, 1, 2, 2, 2] + assert equivalent_bags? [2, 3, 4], [12, 13, 14] {|a, b| a % 10 == b % 10} + refute equivalent_bags? [2, 3, 4], [11, 12, 13] {|a, b| a % 10 == b % 10} + end + + def assert_levelgroup_results_match(expected_results, actual_results) + match = equivalent_bags?(expected_results, actual_results) do |expected, actual| + expected['type'] == actual['type'] && + expected['question'] == actual['question'] && + expected['answer_texts'] == actual['answer_texts'] && + equivalent_bags?(expected['results'], actual['results']) + end + assert match, </edit' do + get :create_new, params: {key: 'applab'} + assert_response :redirect + assert @response.headers['Location'].ends_with? '/edit' + end + + test '/applab/new with enableMaker param preserves param in redirect' do + get :create_new, params: {key: 'applab', enableMaker: 'true'} + assert_response :redirect + assert @response.headers['Location'].ends_with? '/edit?enableMaker=true' + end end diff --git a/dashboard/test/mailers/previews/pd_workshop_mailer_preview.rb b/dashboard/test/mailers/previews/pd_workshop_mailer_preview.rb index 8e0b03bdc695e..da6509ee159dc 100644 --- a/dashboard/test/mailers/previews/pd_workshop_mailer_preview.rb +++ b/dashboard/test/mailers/previews/pd_workshop_mailer_preview.rb @@ -18,6 +18,10 @@ def teacher_enrollment_receipt__csf mail :teacher_enrollment_receipt, Pd::Workshop::COURSE_CSF end + def teacher_enrollment_receipt__csd_summer_workshop + mail :teacher_enrollment_receipt, Pd::Workshop::COURSE_CSD, Pd::Workshop::SUBJECT_CSD_SUMMER_WORKSHOP + end + def teacher_enrollment_receipt__csp_summer_workshop mail :teacher_enrollment_receipt, Pd::Workshop::COURSE_CSP, Pd::Workshop::SUBJECT_CSP_SUMMER_WORKSHOP end @@ -38,6 +42,10 @@ def teacher_enrollment_reminder__csf mail :teacher_enrollment_reminder, Pd::Workshop::COURSE_CSF end + def teacher_enrollment_reminder__csd_summer_workshop + mail :teacher_enrollment_reminder, Pd::Workshop::COURSE_CSD, Pd::Workshop::SUBJECT_CSD_SUMMER_WORKSHOP + end + def teacher_enrollment_reminder__csp_summer_workshop mail :teacher_enrollment_reminder, Pd::Workshop::COURSE_CSP, Pd::Workshop::SUBJECT_CSP_SUMMER_WORKSHOP end diff --git a/lib/cdo/redshift.rb b/lib/cdo/redshift.rb new file mode 100644 index 0000000000000..fc222f516969b --- /dev/null +++ b/lib/cdo/redshift.rb @@ -0,0 +1,31 @@ +# WARNING: This is explicitly not included in our root Gemfile for the reasons discussed in the PR +# https://github.com/code-dot-org/code-dot-org/pull/14056. A separate gemfile should be created for +# any scripts needing usage of this client and loaded via RakeUtils.with_bundle_dir. +# @example: +# RakeUtils.with_bundle_dir(File.dirname(__FILE__)) do +# require 'cdo/redshift' +# end + +RakeUtils.with_bundle_dir(File.expand_path('../../../bin/cron/with_pg', __FILE__)) do + require 'pg' +end +require 'singleton' + +# A thin wrapper around PG, providing a mechanism to execute SQL commands on our AWS Redshift +# instance. +class RedshiftClient + include Singleton + + def initialize + port = 5439 + options = '' + tty = '' + dbname = 'dashboard' + login = 'dev' + @conn = PGconn.new(CDO.redshift_host, port, options, tty, dbname, login, CDO.redshift_password) + end + + def exec(sql_query) + @conn.exec(sql) + end +end diff --git a/pegasus/cache/i18n/en-US.yml b/pegasus/cache/i18n/en-US.yml index 1ee4d30fe37b9..e60fb6593129c 100644 --- a/pegasus/cache/i18n/en-US.yml +++ b/pegasus/cache/i18n/en-US.yml @@ -1076,12 +1076,12 @@ course2_gradelevel: "Ages 6-18" course3_gradelevel: "Ages 8-18" course4_gradelevel: "Ages 10-18" - coursea_gradelevel: "Grade 5+" - courseb_gradelevel: "Grade 6+" - coursec_gradelevel: "Grade 7+" - coursed_gradelevel: "Grade 8+" - coursee_gradelevel: "Grade 9+" - coursef_gradelevel: "Grade 10+" + coursea_gradelevel: "Ages 4-7" + courseb_gradelevel: "Ages 5-8" + coursec_gradelevel: "Ages 6-10" + coursed_gradelevel: "Ages 7-11" + coursee_gradelevel: "Ages 8-12" + coursef_gradelevel: "Ages 9-13" accelerated_gradelevel: "Ages 10-18" codeorg_platformtext: "Modern browsers, smartphones, tablets" codeintl_platformtext: "Modern browsers, smartphones, tablets" @@ -1184,12 +1184,12 @@ course2_shortdescription_congrats: "For students with basic reading skills, this course builds on Course 1. Students will create programs to solve problems and develop interactive games or stories they can share. Recommended for grades 2-5." course3_shortdescription_congrats: "Ready for the next level? Students will delve deeper into programming topics introduced in previous courses to find flexible solutions to more complex problems. By the end of this course, students create interactive stories and games they can share with anyone. Recommended for grades 4-5." course4_shortdescription_congrats: "Ready to go further? Students will delve deeper into programming topics introduced in previous courses to find flexible solutions to more complex problems. By the end of this course, students create interactive stories and games they can share with anyone. Recommended for grades 4-8." - coursea_shortdescription_congrats: "" - courseb_shortdescription_congrats: "" - coursec_shortdescription_congrats: "" - coursed_shortdescription_congrats: "" - coursee_shortdescription_congrats: "" - coursef_shortdescription_congrats: "" + coursea_shortdescription_congrats: "An introduction to computer science for pre-readers." + courseb_shortdescription_congrats: "An introduction to computer science for pre-readers. (Similar to Course A, but with more variety for older students.)" + coursec_shortdescription_congrats: "Learn the basics of computer science and create your own art, stories, and games." + coursed_shortdescription_congrats: "Quickly cover concepts from Course C, then go further with algorithms, nested loops, conditionals, and more." + coursee_shortdescription_congrats: "Quickly cover concepts in Course C & D and then go further with functions." + coursef_shortdescription_congrats: "Learn all the concepts in Computer Science Fundamentals and create your own art, story or game." codeorg_longdescription: "Learn the basic concepts of Computer Science with drag and drop programming. This is a game-like, self-directed tutorial starring video lectures by Bill Gates, Mark Zuckerberg, Angry Birds and Plants vs. Zombies. Learn repeat-loops, conditionals, and basic algorithms. Available in 34 languages." codeintl_longdescription: "Learn the basic concepts of Computer Science with drag and drop programming. This is a game-like, self-directed tutorial starring video lectures by Bill Gates, Mark Zuckerberg, Angry Birds and Plants vs. Zombies. Learn repeat-loops, conditionals, and basic algorithms. Available in 34 languages." thinkersmithspanish_longdescription: "Mediante el uso de un \"Vocabulario Robot\" predefinido, los estudiantes descubrirán como guiarse de modo tal de llevar a cabo tareas específicas sin ser estas discutidas previamente. Este segmento enseña a los estudiantes la conexión entre símbolos y acciones así como la valiosa habilidad de depuración." diff --git a/pegasus/data/cdo-partners.csv b/pegasus/data/cdo-partners.csv index ed647c14ab7e4..fe504c8e0f121 100644 --- a/pegasus/data/cdo-partners.csv +++ b/pegasus/data/cdo-partners.csv @@ -197,15 +197,18 @@ Codeforsu,http://www.codeforsu.org,international,TRUE,FALSE,TRUE, CoderDojo,http://coderdojo.com/,international,TRUE,FALSE,TRUE, CodingMarch,http://www.codingmarch.com/,international,FALSE,FALSE,TRUE, CPIT,http://www.cpit.ac.nz/news-and-events/news/cpit-supports-hour-of-code?utm_source=Homepage&utm_medium=Banner&utm_campaign=ICT%20-%20Hour%20of%20Code#,international,TRUE,FALSE,TRUE, +Czechitas,https://www.czechitas.cz/en/,international,TRUE,FALSE,TRUE, Edvira,http://edvira.com,international,TRUE,FALSE,TRUE, Excited Digital Learning,https://vimeo.com/excited,international,TRUE,FALSE,TRUE, Fundacao Lemann,http://www.fundacaolemann.org.br/,international,TRUE,FALSE,TRUE, Fundacion Sadosky,http://www.fundacionsadosky.org.ar/,international,TRUE,FALSE,TRUE, Gabinete de Modernizacao das Tecnologias Educativas,http://www02.madeira-edu.pt/dre/main.aspx,international,TRUE,FALSE,TRUE, +IAMAI Academy,https://iamai.hk/,international,FALSE,FALSE,TRUE, Inspiring Fifty,http://www.inspiringfifty.com/,international,TRUE,FALSE,TRUE, Jawwal,http://www.jawwal.ps/,international,TRUE,FALSE,TRUE, Kids Code Jeunesse,http://kidscodejeunesse.org/,international,TRUE,FALSE,TRUE, Kids and Code,http://www.kidsandcode.org,international,TRUE,FALSE,TRUE, +Korea Information Science Education Federation,http://kcode.kr/,international,TRUE,FALSE,TRUE, Love To Code,http://www.lovetocode.kz/,international,TRUE,FALSE,TRUE, Malaysia Digital Economy Corporation,https://mdec.com.my/,international,TRUE,FALSE,TRUE, Miur,http://www.istruzione.it/,international,TRUE,FALSE,TRUE, @@ -218,6 +221,7 @@ Programar,http://program.ar/,international,TRUE,FALSE,TRUE, Programma Il Futuro,https://www.programmailfuturo.it/,international,TRUE,FALSE,TRUE, Rishs International School,http://www.rishsinternationalschool.com/,international,FALSE,FALSE,TRUE, RobinCode,http://www.robincode.org/,international,TRUE,FALSE,TRUE, +Sault Ste. Marie Innovation Centre,https://ssmic.com/,international,FALSE,FALSE,TRUE, Student Edge,https://studentedge.com.au/,international,TRUE,FALSE,TRUE, Suriname Online Education Center,http://www.soec.sr,international,TRUE,FALSE,TRUE, Transforma Espana,http://ftransformaespana.es/,international,TRUE,FALSE,TRUE, diff --git a/pegasus/data/cdo-state-promote.csv b/pegasus/data/cdo-state-promote.csv index ad7a9befbb774..fecd460f791c1 100644 --- a/pegasus/data/cdo-state-promote.csv +++ b/pegasus/data/cdo-state-promote.csv @@ -24,7 +24,7 @@ ME,Maine,1384,3.4,112,24,-1272,No,No,No,No,No,No,No,No,No,,,,,76323,105631032,44 MI,Michigan,14358,3.6,1793,78,-12565,No,Yes,Yes,No,No,Yes,No,No,No,,,,,80478,1155503124,47350,5,16%,0.93%,1685,24%,56,30,0,1,87,85,13%,14,1025,23%,32,19,0,0,12%,660,25%,24,11,0,1,6%,www.code.org/promote/MI,Utica Community Schools,Mason-Lake Oceana Mathematics and Science Center at West Shore ESD,none,8,464,14,85,29,4,2316000,11163,6698,4806,5708,5193,2430,962,438946,7144,,,,,,,,,,,, MN,Minnesota,12498,2.6,895,39,-11603,No,Yes,No,No,No,No,No,No,No,,,,Add your support,90134,1126494732,51330,0,15%,0.98%,1201,20%,56,27,3,1,87,59,18%,20,745,17%,24,11,2,0,15%,456,25%,32,16,1,1,8%,www.code.org/promote/MN,no school districts in the state,Twin Cities Public Television,none,5,1294,1,5,0,2,1563000,9034,4128,3164,4704,2038,999,694,318748,6610,,,,,,,,,,,, MO,Missouri,10146,3.7,1138,47,-9008,No,No,No,No,No,No,No,No,No,,,,,82050,832479300,44620,1,17%,0.95%,631,20%,29,34,1,0,64,69,21%,22,466,16%,21,19,1,0,18%,165,33%,8,15,0,0,10%,www.code.org/promote/MO,no school districts in the state,Union Station Science City and Washington University at St. Louis Institute for School Partnership,none,4,789,0,3,0,0,1357000,3606,2269,2547,1716,1567,942,399,301079,6289,,,,,,,,,,,, -MS,Mississippi,1289,4.4,155,14,-1134,No,No,Yes,No,No,No,No,No,Yes,,,,,69507,89594523,38300,0,12%,0.11%,105,23%,11,12,0,0,23,13,7%,10,19,5%,3,3,0,0,3%,86,27%,8,9,0,0,6%,www.code.org/promote/MS,no school districts in the state,Mississippi State University,none,1,255,2,1,0,0,150000,1076,933,493,387,813,165,16,90737,2526,,,,,,,,,,,, +MS,Mississippi,1289,4.4,155,14,-1134,No,No,Yes,Other,No,No,No,No,Yes,,,,,69507,89594523,38300,0,12%,0.11%,105,23%,11,12,0,0,23,13,7%,10,19,5%,3,3,0,0,3%,86,27%,8,9,0,0,6%,www.code.org/promote/MS,no school districts in the state,Mississippi State University,none,1,255,2,1,0,0,150000,1076,933,493,387,813,165,16,90737,2526,,Mississippi is in the process of developing K-12 computer science standards.,,,,,,,,,,K-12 CS standards in progress MT,Montana,888,2.7,75,10,-813,No,No,Yes,No,No,Yes,No,No,No,,,,,64375,57165000,41440,0,11%,0.18%,13,15%,2,0,0,0,2,2,2%,2,13,15%,2,0,0,0,2%,0,0%,0,0,0,0,0%,www.code.org/promote/MT,no school districts in the state,America Campaign - Big Sky Code Academy and Technology and Innovation in Education,none,0,0,12,20,0,0,185000,485,374,120,341,116,4,9,40783,1186,,,,,,,,,,,, NC,North Carolina,18069,4.5,1284,71,-16785,No,Yes,Yes,No,Yes,No,No,No,No,,,,,88971,1607616999,45280,13,20%,0.98%,2411,24%,163,178,6,2,349,95,15%,0,1435,24%,89,106,5,1,9%,976,25%,74,72,1,1,10%,www.code.org/promote/NC,"Alamance-Burlington School System, Chatham County Schools, Durham Public Schools, Franklin County School, Granville County Schools, Johnston County Schools, Orange County Schools, Rockingham County Schools, Warren County Schools, and Wilson County Schools",The Friday Institute,none,9,2248,54,28,0,38,2456000,11770,6626,4421,10183,3806,12482,1425,458574,11010,,,,,,,,,,,, ND,North Dakota,935,3.3,117,5,-818,No,Yes,Yes,No,No,No,No,No,No,,,,,70311,65740785,47130,0,7%,1.27%,96,11%,4,1,1,0,6,10,19%,7,40,3%,1,0,0,0,13%,56,18%,3,1,1,0,6%,www.code.org/promote/ND,no school districts in the state,America Campaign - Big Sky Code Academy and Technology and Innovation in Education,none,1,10,0,0,0,0,104000,354,151,170,1,95,6,35,33064,1117,,,,,,,,,,,, diff --git a/pegasus/helpers/professional_development_workshop_helpers.rb b/pegasus/helpers/professional_development_workshop_helpers.rb deleted file mode 100644 index 44a38905a64c2..0000000000000 --- a/pegasus/helpers/professional_development_workshop_helpers.rb +++ /dev/null @@ -1,151 +0,0 @@ -require 'tzinfo' - -def parse_date(date_string) - return nil if date_string.nil? - - time = Chronic.parse(date_string) - return nil if time.nil? - - date = time.to_date - - # midnight pacific time - tz = TZInfo::Timezone.get('US/Pacific') - tz.local_to_utc(Time.utc(date.year, date.month, date.day)) -end - -def generate_professional_development_workshop_payment_report(from=nil, to=nil) - # generate a report to be used for paying affiliates - - if from && to - from = parse_date(from) - to = parse_date(to) - end - - DB[:forms].where(kind: "ProfessionalDevelopmentWorkshop").map do |row| - data = JSON.parse(row[:data]) rescue {} - processed_data = JSON.parse(row[:processed_data]) rescue {} - - stopped_at = Chronic.parse(data['stopped_dt']) - - # TODO: database can't do the below date filtering because stopped_at is in the serialized JSON - next unless stopped_at - next if from && to && (stopped_at < from || stopped_at > to) - - { - name: row[:name], - user_id: row[:user_id], - email: row[:email], - type: data["type_s"], - section_url: "https://code.org/teacher-dashboard#/sections/#{data['section_id_s']}", - stopped_at: stopped_at.to_s, - total_attendee_count: processed_data['total_attendee_count_i'], - qualifying_attendee_count: processed_data['qualifying_attendee_count_i'] - } - end.compact -end - -def generate_professional_development_workshop_teachers_report - # Grab the script IDs for the CSF scripts. - csf_script_ids = DASHBOARD_DB[:scripts]. - where('name IN ("20-hour", "course1", "course2", "course3", "course4")'). - select(:id). - map(&:values). - flatten - - # generate a report about the teachers trained by affiliates and their students' progress - PEGASUS_DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshop').map do |affiliate| - data = JSON.parse(affiliate[:data]) rescue {} - - section_id = data['section_id_s'] - next unless section_id - - # a row for each teacher trained by an affiliate - DASHBOARD_DB[:followers]. - where(section_id: section_id). - join(:users, id: :student_user_id). - select(:users__id___id, :users__name___name, :users__email___email).map do |teacher| - # get data on students of the teacher - teacher_user_id = teacher[:id] - next unless teacher_user_id - - students_count = DASHBOARD_DB[:followers]. - where(user_id: teacher_user_id). - join(:users, id: :student_user_id). - count - - students_with_progress_count = DASHBOARD_DB[:followers]. - where(followers__user_id: teacher_user_id). - join(:users, id: :student_user_id). - join(:user_scripts, user_id: :id). - where(script_id: csf_script_ids). - count - - { - teacher_id: teacher_user_id, - students_count: students_count, - students_with_progress_count: students_with_progress_count - } - end.compact - end.compact.flatten -end - -def generate_professional_development_workshop_signup_report(secret) - workshop = PEGASUS_DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshop', secret: secret).first - - PEGASUS_DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshopSignup', parent_id: workshop[:id]).map do |row| - data = JSON.parse(row[:data]) rescue {} - if data['status_s'] == 'cancelled' - nil - else - { - name: data['name_s'], - email: data['email_s'], - role: (data['teacher_role_ss'] - ['Other']).concat(data['teacher_role_other_ss'] || []).uniq.to_csv, - experience: data['teacher_tech_experience_level_s'].gsub(/ \(.*$/, ''), - school_name: data['school_name_s'], - school_location: data['school_location_s'], - school_type: (data['school_type_ss'] - ['Other']).concat(data['school_type_other_ss'] || []).uniq.to_csv, - school_district: data['school_district_s'], - school_levels: (data['school_levels_ss'] - ['Other']).concat(data['school_levels_other_ss'] || []).uniq.to_csv, - students: data['number_students_s'], - } - end - end.compact -end - -def generate_professional_development_workshops_report(from=nil, to=nil) - from = Chronic.parse(from.to_s) - to = Chronic.parse(to.to_s) - - PEGASUS_DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshop').map do |workshop| - data = JSON.parse(workshop[:data]) rescue {} - - if first_date = data['dates'].first - first_date = first_date['date_s'] - first_date = Chronic.parse(first_date.to_s) - end - - next unless first_date - next if from && to && (first_date < from || first_date > to) - - signup_count = 0 - - PEGASUS_DB[:forms]. - where(kind: 'ProfessionalDevelopmentWorkshopSignup'). - and(parent_id: workshop[:id]). - map do |signup| - signup_data = JSON.parse(signup[:data]) rescue {} - signup_count += 1 unless signup_data['status_s'] == 'cancelled' - end - - { - Name: data['name_s'], - User_ID: workshop[:user_id], - Email: data['email_s'], - Date: data['dates'].map {|i| i['date_s']}.join('
'), - Location: data['location_name_s'] + ' (' + data['location_address_s'] + ')', - Type: data['type_s'], - Signups: signup_count.to_s + '/' + data['capacity_s'], - } - end.compact.flatten -end diff --git a/pegasus/routes/v2_forms_routes.rb b/pegasus/routes/v2_forms_routes.rb index bebe571e373a3..46e16ba678910 100644 --- a/pegasus/routes/v2_forms_routes.rb +++ b/pegasus/routes/v2_forms_routes.rb @@ -89,29 +89,6 @@ call(env.merge('REQUEST_METHOD' => 'REVIEW', 'PATH_INFO' => "/v2/forms/#{kind}/#{secret}")) end -get '/v2/forms/ProfessionalDevelopmentWorkshopSignup/:secret/status/cancelled' do |secret| - def send_receipts(form) - templates = ['workshop_signup_cancel_receipt', 'workshop_signup_cancel_notice'] - recipient = Poste2.create_recipient(form[:email], name: form[:name], ip_address: form[:updated_ip]) - templates.each do |template| - Poste2.send_message(template, recipient, form_id: form[:id]) - end - templates.count - end - - dont_cache - form = DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshopSignup', secret: secret).first - forbidden! if form.empty? - data = JSON.parse(form[:data]) - data['status_s'] = 'cancelled' - DB[:forms].where(kind: 'ProfessionalDevelopmentWorkshopSignup', secret: secret).update(data: data.to_json, indexed_at: nil) - - send_receipts(form) - - content_type :json - data.to_json -end - get '/v2/forms/:parent_kind/:parent_secret/children/:kind' do |parent_kind, parent_secret, kind| dont_cache results = [] diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AK.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AK.pdf.fetch index 94ff0b5c1af31..f6e39a04b9b84 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AK.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AK.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/22bae7914bd4f357160b205e077e38a6-AK.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/88f5d0c1b643d7d988b610907b7d0e01-AK.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AL.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AL.pdf.fetch index 87266213004de..f743a395e3841 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AL.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AL.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/0a270d7c3a548f66e27f3d69aace44b6-AL.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/433c972f6688be2baf56c3edcfb9e92e-AL.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AR.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AR.pdf.fetch index 429152e177036..d0c6cc60898bf 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AR.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AR.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/c4172f461b7a895c41037cca801b542e-AR.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/0919d18471ef19742a932ded451ba40f-AR.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AZ.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AZ.pdf.fetch index cffd2aa2bcaee..a335ebc281f17 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/AZ.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/AZ.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/1eeb5052be35e7b9d3d43a79cde42651-AZ.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/8d54dc0087a643336af57465595e4909-AZ.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/CA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/CA.pdf.fetch index bce1b23a6a81d..3cd02806ce9bc 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/CA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/CA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/e20f6a0ee36593fa77037c16748ac83e-CA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/e50e410d673ba8db19a84f453ee117fe-CA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/CO.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/CO.pdf.fetch index a1beed16ed37d..eb5f38e7b8cae 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/CO.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/CO.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/763f85e900b525298b95d204724d6117-CO.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/178b4c9052ec6ab0f9a7437ad41e6c9d-CO.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/CT.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/CT.pdf.fetch index 7523f1410d31a..7e20a7364ce2b 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/CT.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/CT.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/b19cef528acf1349ada0a9d3f3eac161-CT.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/6edea68b21a65af22777e617f9ae06f5-CT.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/DC.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/DC.pdf.fetch index 6f4de6f06ed97..39373b0095162 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/DC.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/DC.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/f51801a97034c994c27955f396339e43-DC.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/82164f686b92fc202b82574372dc4d4b-DC.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/DE.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/DE.pdf.fetch index 678dc5af77016..6caec3de20e34 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/DE.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/DE.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/a109c6a71b651251fdd43bab4de838e6-DE.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/391fb6b4b7a0f7f4bb29f80fc32b899b-DE.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/FL.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/FL.pdf.fetch index bf191a4898c8f..0489f8398dfb8 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/FL.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/FL.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/90263a9b7ed9c9ec15df7383534463dc-FL.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/b7635690a4329a79bca0ecdab7d68e4c-FL.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/GA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/GA.pdf.fetch index cf63231162052..8b024887240c6 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/GA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/GA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/b70ac7c750bd2ca9b19eba369e01622a-GA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/9ada406ee6959642c5db2f14a5eead23-GA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/HI.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/HI.pdf.fetch index 6ceadeb61e205..62a3d934145a7 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/HI.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/HI.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/556cd7f6312eba4122f6acc0db4e6838-HI.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/258f014f9272e5e7bf96bda0c5ef9d1e-HI.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/IA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/IA.pdf.fetch index 558b2e61db18a..8298e67109ae1 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/IA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/IA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/fbaac4b3cd4efad53ad92b67521529bc-IA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/dfd269ea87a72fe2b0e2fac83bcbbfd3-IA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/ID.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/ID.pdf.fetch index 70d5aa6c6259f..e0ba865ac73b4 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/ID.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/ID.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/3a5df5fbabbfa29df169d80ca0f9864a-ID.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/70583b6b6da74982d58e2292e6a7b861-ID.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/IL.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/IL.pdf.fetch index e72cd8f27a1e9..a0ceac2094136 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/IL.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/IL.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/e2e0950aecd2f69e2ca4eeb9decc45f5-IL.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/2d82071b2d95e1a942e0e8864db3421a-IL.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/IN.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/IN.pdf.fetch index 7fb2adcecada1..688d47f8a6089 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/IN.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/IN.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/2ac67f0d3a857667c6833a2073ad352a-IN.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/1b1295ecd9af6f7b463598a31453fea3-IN.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/KS.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/KS.pdf.fetch index fa44f8bddff3e..002c33515efe0 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/KS.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/KS.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/f7354510903bc473fec8eac2b01e27cf-KS.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/e98fa8d859a8585aaa3bdeb927a676e7-KS.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/KY.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/KY.pdf.fetch index 98c43ba9e1376..cc83ec45f1b8e 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/KY.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/KY.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/63f2be270c453c07dd27c9dffe03a29a-KY.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/d17db8ce154bcd17d423a9f457452978-KY.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/LA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/LA.pdf.fetch index 11ec2b83100f0..d8cf80d420fc2 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/LA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/LA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/a593e7d1223ba8bd6ade47968f384da3-LA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/cdba14e45084464b7bbbb9043367a461-LA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MA.pdf.fetch index a97edf9ff36ad..caca3888f3965 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/f9fd844d2f582e9472e92109b29d780a-MA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/7efab1641ce3f088b2d57e97543cd909-MA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MD.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MD.pdf.fetch index af23d7fd4c346..1a1b80d95de5a 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MD.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MD.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/7562a8b0cf78211a0557e587487fc876-MD.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/ae60d85378c66f37cfe9aa120c6e9748-MD.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/ME.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/ME.pdf.fetch index b81fe5b5169d5..09c5f22731948 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/ME.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/ME.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/82697e536c5ff2fda1e3541d458c4a3b-ME.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/044c189b8b717bb52dbb0ba768707dc7-ME.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MI.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MI.pdf.fetch index bf1b9711242c2..be4a5b1372e26 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MI.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MI.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/471132d81bb8376e45395576c35edaed-MI.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/2820ff7f78490b62bf05ff36e1128e16-MI.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MN.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MN.pdf.fetch index 9ed3122d915d6..59fb8e85d70c5 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MN.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MN.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/d9e695e5ef00bddd09ee98a0f2155e59-MN.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/ad3634d9ee9a144c51965086c2465d4b-MN.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MO.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MO.pdf.fetch index 08ea575797a6d..c7d2dabc5ecef 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MO.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MO.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/4a8c7f8551e757138cf6051bb68577ce-MO.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/80a1d213896ac6ee39aa526e10ffbd34-MO.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MS.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MS.pdf.fetch index dcbb7de1025c5..a69624cdbf46b 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MS.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MS.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/ebf060c084c52535dabeb08417c1831c-MS.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/2314ee7c23b57beaf5ffb055fc7b9694-MS.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MT.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MT.pdf.fetch index e07f01836bc34..6359b0474f536 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/MT.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/MT.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/428d433b60fb2f987c1ec263d3602e08-MT.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/20104e7cb71fcdeebcd1b9786fa582a3-MT.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NC.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NC.pdf.fetch index 1fb709bfb8784..f0e04107d620c 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NC.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NC.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/23de3ec84e3ed2e1072f73c3dc0d4cc0-NC.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/244c13f998488f910c5043a5ed5d4bf4-NC.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/ND.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/ND.pdf.fetch index b15535ea90e29..61d9e8ec1c56c 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/ND.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/ND.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/c3db3d5a8acf5ccd0c69f5640415dd5a-ND.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/b4df4dc0f7d7da55cb93278e714a51a1-ND.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NE.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NE.pdf.fetch index 9b986570f064f..672ab73c64260 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NE.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NE.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/30f67b735da4e0320b01205b18344d50-NE.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/bf2a5eba124ee5b6fd832087a2bbe999-NE.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NH.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NH.pdf.fetch index 29549350afbd6..547542ec9c423 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NH.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NH.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/6e4379473a0ef42653e3e48959c14de6-NH.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/9a998f94d9f250f2c56aa313789900f1-NH.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NJ.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NJ.pdf.fetch index 80851c8a10fc2..e3913e62d0af0 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NJ.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NJ.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/142de33601b5d9586fb1d84e0e2ad496-NJ.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/9795d18aada3868c571b2f1e66c8c36c-NJ.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NM.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NM.pdf.fetch index f21faf6becd46..f1e9026620bfc 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NM.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NM.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/4d3c07ced2cd7b82c4b18257c7405d7e-NM.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/19749d6261a03232978ba7d49bdd1b02-NM.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NV.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NV.pdf.fetch index 4ebb6d1e9ee37..623eb3e255ec2 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NV.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NV.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/e8b902539aba6f77dc23f91956c0681f-NV.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/4ffdc9f89956b6b890265f424dbe53e8-NV.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NY.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NY.pdf.fetch index e071a5f25a564..4647ac1e96dd4 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/NY.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/NY.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/68d7d758fe1be9f5c3977a0a9a11ab40-NY.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/99e2e0c36b36ea36520a4e647bc08eac-NY.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/OH.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/OH.pdf.fetch index dabaf97fdbc1f..981429bf52c5a 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/OH.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/OH.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/29e09d00cecdbef4263751a23b2d4fb7-OH.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/3a71a96041af6dd15e14d020d1b90e16-OH.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/OK.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/OK.pdf.fetch index a10d65dd13e67..dc528b566181f 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/OK.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/OK.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/33bb5c2552f4a1e8be50f0d607145990-OK.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/9fd5ee5696e7364385905fbff670d129-OK.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/OR.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/OR.pdf.fetch index 67dc139dc9416..4ca8596feaf04 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/OR.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/OR.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/e0d2e85769a89429808d52b42c6576a4-OR.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/0b4f31ed9c1b4f722bf4c164675270a5-OR.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/PA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/PA.pdf.fetch index a31d05ad4f561..eda73b6cea17f 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/PA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/PA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/aacede228b7c23554ec0767278b15efb-PA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/c1eb3da2f7cb044a29686fb50d4228ea-PA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/RI.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/RI.pdf.fetch index 339187fd31867..006f95d6ad18b 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/RI.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/RI.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/ea9835f2821cf25370d237ad36722450-RI.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/d7328c20d3b5bf7ae193e847bf6100b5-RI.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/SC.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/SC.pdf.fetch index 90482a83ed39c..434b75af501ed 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/SC.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/SC.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/2b63ccefb27f3cd4ae084e0038b7ccbd-SC.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/d074476232a4979fd39299bcd2b92734-SC.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/SD.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/SD.pdf.fetch index becee7ca29205..4db5d5cddecb6 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/SD.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/SD.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/d19b405845e1aa11ee3198f3b977a6a5-SD.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/b863096b5306154158c5d5990a66f5b7-SD.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/TN.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/TN.pdf.fetch index 202608f03d00f..a1cd34c4c85fb 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/TN.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/TN.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/ad822dafd48e9bb999ee36b8fc253863-TN.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/245e022c1d8fec537564f1f1bfe10c09-TN.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/TX.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/TX.pdf.fetch index 91739b1050ab7..3f4da8a0b6d51 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/TX.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/TX.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/60b2f82a42b570ac24af0d1ab1a83492-TX.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/11b79777efd282603dff9561e7c79d42-TX.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/UT.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/UT.pdf.fetch index ce90e8f88207f..67bf84e583abc 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/UT.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/UT.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/3ba03247c86d32fbd63a02ccca8b6016-UT.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/4974e6411959e4e9681813e8ef00dfec-UT.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/VA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/VA.pdf.fetch index 442a928cb11d3..bda226def5e5c 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/VA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/VA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/0b7502cd5c770d2ed902595c4fd539ed-VA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/d2f11b7b73bc3caebb72fbc2929bd8cf-VA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/VT.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/VT.pdf.fetch index 27cc00d164e51..d4e362808e61c 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/VT.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/VT.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/42a2c4347f0147ffe25421539a0e4274-VT.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/d9e90927879dbe166692785327fa58fd-VT.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WA.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WA.pdf.fetch index 383eafd79abb6..41de73d45dd6a 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WA.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WA.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/944e09d04e1b20a51e389a70d26b1042-WA.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/4260d9b6f12db8a4b3ad60931c756164-WA.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WI.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WI.pdf.fetch index 67be1fa1c3a94..663329f71cb24 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WI.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WI.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/a5be685ede30291fbed0ca69e0b5a424-WI.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/1f93fc0a2f7635ad89225d8b42713bd5-WI.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WV.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WV.pdf.fetch index a2288e3fd487b..af44637cf7780 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WV.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WV.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/94c14dc0d435933a15e595d52157af9a-WV.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/1ea8e5509b13ece74f61fcffe693c268-WV.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WY.pdf.fetch b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WY.pdf.fetch index bb4a7ce963305..a2987b325eb32 100644 --- a/pegasus/sites.v3/code.org/public/advocacy/state-facts/WY.pdf.fetch +++ b/pegasus/sites.v3/code.org/public/advocacy/state-facts/WY.pdf.fetch @@ -1 +1 @@ -https://cdo-fetch.s3.amazonaws.com/82c8812d4cbc3e6f609b9e194e651f1b-WY.pdf \ No newline at end of file +https://cdo-fetch.s3.amazonaws.com/119b1b6409715bd60e8e6d9793f98fed-WY.pdf \ No newline at end of file diff --git a/pegasus/sites.v3/code.org/public/educate/professional-learning/cs-discoveries-faqs.pdf b/pegasus/sites.v3/code.org/public/educate/professional-learning/cs-discoveries-faqs.pdf deleted file mode 100644 index f5f80700f0f2e..0000000000000 Binary files a/pegasus/sites.v3/code.org/public/educate/professional-learning/cs-discoveries-faqs.pdf and /dev/null differ diff --git a/pegasus/sites.v3/code.org/public/educate/professional-learning/cs-principles-faqs.pdf b/pegasus/sites.v3/code.org/public/educate/professional-learning/cs-principles-faqs.pdf deleted file mode 100644 index df388604ae0b2..0000000000000 Binary files a/pegasus/sites.v3/code.org/public/educate/professional-learning/cs-principles-faqs.pdf and /dev/null differ diff --git a/pegasus/sites.v3/code.org/public/educate/regional-partner/faq.md b/pegasus/sites.v3/code.org/public/educate/regional-partner/faq.md index 5d193d110e7a4..303788c5c53a3 100644 --- a/pegasus/sites.v3/code.org/public/educate/regional-partner/faq.md +++ b/pegasus/sites.v3/code.org/public/educate/regional-partner/faq.md @@ -60,7 +60,6 @@ We are particularly looking for new Regional Partners in: - Louisiana - North California (Humboldt or Shasta County preferred) - Oregon -- Southern Illinois - Vermont diff --git a/pegasus/sites.v3/code.org/public/educate/regional-partner/playbook/funding.md b/pegasus/sites.v3/code.org/public/educate/regional-partner/playbook/funding.md index 7aa22abdad496..5f3d31af113f5 100644 --- a/pegasus/sites.v3/code.org/public/educate/regional-partner/playbook/funding.md +++ b/pegasus/sites.v3/code.org/public/educate/regional-partner/playbook/funding.md @@ -9,6 +9,8 @@ nav: regional_partner_playbook_nav # Topics +- [Announcements](#announce) + **Organization Sustainability:**
- [Goals](#goal) @@ -29,6 +31,22 @@ nav: regional_partner_playbook_nav - [Supports for Pursuing Federal and State Grants](#grants) +# Announcements + +### Federal Funding - Consortia Development Survey +In March, the US Department of Education will release details about the federal grant opportunities for CS we first heard about last fall. Code.org will not be applying for these federal funds and will not lead or directly sign onto any application. We do, however, want to explore bringing together some regional partners to apply together as a consortium. There are two potential funding targets at the US Dept. of Ed -- SEED (Supporting Effective Educator Development) and EIR (Education, Innovation and Research) grant programs run by the Department of Ed. These grants typically span 3-5 years with funding of $1.5M-$5M. The grants can fund CS professional development, as well as some program management and evaluation expenses. These grants also come with compliance requirements for recipients. + +Code.org’s role would be to help facilitate the formation of one or more consortium (3-6 partners each) and support the development of a grant proposal with a lead partner. Each consortium member’s role would be to execute on the Code.org programs and partner with the lead for the consortium on funding and compliance requests. + +We’ve developed a short survey to gauge your interest and readiness for joining a consortium and to gather some initial information. + +The timeline is pretty tight on this because of the budget issues Washington has faced. When Department of Ed releases the grant notices in early March, there will only be 60 days to build a consortium and write the application. Once it is filed we’d expect to know the results by late summer, with funds available sometime in the fall, if successful. + +**Please complete this survey by February 27th**. If you are interested, we’ll get in touch with you prior to the Summit to invite you to a 30-minute meeting for prospective partners on Wednesday, March 7 (at the March Summit). + +[**Back to the top**](#top) +
+________________ # How does your organization become sustainable? This year most of your funding will come from Code.org. diff --git a/pegasus/sites.v3/code.org/public/educate/resources/recruit.md b/pegasus/sites.v3/code.org/public/educate/resources/recruit.md index 67cdb603ca9ef..307578238010b 100644 --- a/pegasus/sites.v3/code.org/public/educate/resources/recruit.md +++ b/pegasus/sites.v3/code.org/public/educate/resources/recruit.md @@ -39,7 +39,7 @@ Hear from current students as they talk about the topics covered in each course ### **Sign up for CS Discoveries** -<%=view :display_video_thumbnail, id: "signup", video_code: "g4qfsH8bc8s", play_button: 'center' %> +<%=view :display_video_thumbnail, id: "intro_csd", video_code: "g4qfsH8bc8s", play_button: 'center' %> [**YouTube**](http://www.youtube.com/watch?v=g4qfsH8bc8s) | [**Download**](https://videos.code.org/cs-discoveries/what-is-cs-discoveries.mp4) diff --git a/pegasus/sites.v3/code.org/public/images/avatars/czechitas.png b/pegasus/sites.v3/code.org/public/images/avatars/czechitas.png new file mode 100644 index 0000000000000..069ca6fcb961a Binary files /dev/null and b/pegasus/sites.v3/code.org/public/images/avatars/czechitas.png differ diff --git a/pegasus/sites.v3/code.org/public/images/avatars/korea_information_science_education_federation.png b/pegasus/sites.v3/code.org/public/images/avatars/korea_information_science_education_federation.png new file mode 100644 index 0000000000000..9a8161e908749 Binary files /dev/null and b/pegasus/sites.v3/code.org/public/images/avatars/korea_information_science_education_federation.png differ diff --git a/pegasus/sites.v3/code.org/public/private/professional-development-workshop-report.csv.erb b/pegasus/sites.v3/code.org/public/private/professional-development-workshop-report.csv.erb deleted file mode 100644 index ec55f1fef35d6..0000000000000 --- a/pegasus/sites.v3/code.org/public/private/professional-development-workshop-report.csv.erb +++ /dev/null @@ -1,11 +0,0 @@ -<%= - -dont_cache -content_type :csv -response.headers['Content-Disposition'] = "attachment;filename=#{File.basename(request.path_info)}" - -require 'cdo/csv' - -CSV.generate_from_dataset(generate_professional_development_workshop_payment_report(params[:from], params[:to])) - -%> diff --git a/pegasus/sites.v3/code.org/public/private/professional-development-workshop-report.md b/pegasus/sites.v3/code.org/public/private/professional-development-workshop-report.md deleted file mode 100644 index d23b9bca2fb3a..0000000000000 --- a/pegasus/sites.v3/code.org/public/private/professional-development-workshop-report.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Professional Development Workshops ---- - -# Professional Development Workshops - -<% - rows = generate_professional_development_workshop_payment_report(params[:from], params[:to]) -%> - -## Filter by date: - -
- From: - To: - -
- -<% if rows.empty? %> -## No results -<% else %> - -## [ Download CSV](/private/professional-development-workshop-report.csv?from=<%= params[:from] ? URI.escape(params[:from]) : ''%>&to=<%= params[:to] ? URI.escape(params[:to]) : '' %>) - -## Preview - - - - <% rows[0].keys.each do |key| %> - - <% end %> - - <% rows.each do |row| %> - - <% row.values.each do |value| %> - - <% end %> - - <% end %> -
<%= key %>
<%= value %>
- -<% end %> diff --git a/pegasus/sites.v3/code.org/public/professional-development-workshops/cancel/splat.haml b/pegasus/sites.v3/code.org/public/professional-development-workshops/cancel/splat.haml deleted file mode 100644 index 345b5bfb0b9c9..0000000000000 --- a/pegasus/sites.v3/code.org/public/professional-development-workshops/cancel/splat.haml +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Cancel workshop registration -nav: blank -rightbar: blank ---- -- dont_cache -- workshop_signup_secret = request.splat_path_info[1..-1] -- pass unless workshop_signup = Form2.from_row(DB[:forms].where(kind:'ProfessionalDevelopmentWorkshopSignup', secret:workshop_signup_secret).first) -- workshop_signup_data = workshop_signup.data -- pass unless workshop = Form2.from_row(DB[:forms].where(kind:'ProfessionalDevelopmentWorkshop', id:workshop_signup.parent_id).first) -- workshop_data = workshop.data - -%h1 Cancel workshop registration - -- if workshop_signup_data['status_s'] == 'cancelled' - %p Your registration for this workshop has already been cancelled. -- else - %form#cancel-workshop-registration-form{role: "form", onsubmit: "event.preventDefault(); cancelWorkshopRegistrationFormSubmit();", style: 'margin-top: 30px;'} - #error-message{style: 'display: none'} - %p Are you sure you want to cancel registration for the following workshop? - .row - .col-sm-4{style: "text-align: right;"} Date: - .col-sm-8 - -workshop_data['dates'].each do |date| - =date['date_s'] + ', ' + date['start_time_s'] + '-' + date['end_time_s'] - %br/ - .row - .col-sm-4{style: "text-align: right;"} Location: - .col-sm-8 - = workshop_data['location_name_s'] - %br/ - = workshop_data['location_address_s'] - .row{style: "margin-top: 30px;"} - .col-sm-8.col-sm-offset-4 - .form-group - %button#btn-submit.btn.btn-default{type: "submit"} Cancel registration - - #thanks{:style=>"display: none;"} - :markdown - Your registration has been cancelled. You can sign up for a different workshop [here](/professional-development-workshops). - -:javascript - - function processResponse(data) - { - $('#cancel-workshop-registration-form').hide(); - $('#thanks').show(); - } - - function processError(data) - { - $('#error-message').text('An error occurred. Please try again or contact us if you continue to receive this error.').show(); - $('body').scrollTop(0); - $("#btn-submit").removeAttr('disabled'); - $("#btn-submit").removeClass("button_disabled").addClass("button_enabled"); - } - - function cancelWorkshopRegistrationFormSubmit() - { - $("#btn-submit").attr('disabled','disabled'); - $("#btn-submit").removeClass("button_enabled").addClass("button_disabled"); - - $.ajax({ - url: "/v2/forms/ProfessionalDevelopmentWorkshopSignup/#{workshop_signup_secret}/status/cancelled", - type: "get", - }).done(processResponse).fail(processError); - - return false; - } diff --git a/pegasus/sites.v3/code.org/public/professional-development-workshops/splat.haml b/pegasus/sites.v3/code.org/public/professional-development-workshops/splat.haml deleted file mode 100644 index 8f5286c83b4c6..0000000000000 --- a/pegasus/sites.v3/code.org/public/professional-development-workshops/splat.haml +++ /dev/null @@ -1,328 +0,0 @@ ---- -title: Register for a workshop ---- -- dont_cache -- workshop_id = request.splat_path_info[1..-1] -- pass unless form = Form2.from_row(DB[:forms].where(kind:'ProfessionalDevelopmentWorkshop', id:workshop_id).first) -- data = form.data - --signup_count = 0 --signup_rows = DB[:forms].where(kind:'ProfessionalDevelopmentWorkshopSignup').and(parent_id:workshop_id) --signup_rows.each do |signup_row| - -signup = JSON.parse(signup_row[:data]) - -signup_count += 1 unless signup['status_s'] == 'cancelled' - --affiliate = Form2.from_row(DASHBOARD_DB[:users].where(id: form.user_id).first) - --if first_date = data['dates'].first - -first_date = Chronic.parse(first_date['date_s'] + ' ' + first_date['start_time_s']) - -%script{type: "text/javascript", src: "/js/sifter.min.js"} -%script{type: "text/javascript", src: "/js/microplugin.min.js"} -%script{type: "text/javascript", src: "/js/selectize.min.js"} -%script{type: "text/javascript", src: "/js/selectize-fast-click.js"} -%link{rel: "stylesheet", type: "text/css", href: "/css/selectize.bootstrap3.css"}/ -%script{type: "text/javascript", src: "https://maps.googleapis.com/maps/api/js?sensor=true&libraries=places,geometry&v=3.7"} - -/[if lt IE 9] -%script{src: "/js/es5-shim.min.js"} - -.row - .col-sm-8 - %h1 - Register for a Code Studio workshop (for teachers, grades K-5) - -#affiliate-workshop-signup-form-wrapper.row - .col-sm-8 - -if (signup_count >= data['capacity_s'].to_i) || (first_date < Time.now) - .alert.alert-danger{role: "alert"} - This workshop is no longer accepting signups. Please choose another one or contact the facilitator (email below) to be notified when additional workshops are announced. - %p Taught by Code.org Affiliates who are experienced computer science educators, our free workshops will prepare you to teach the Code Studio courses for grades K-5. - - .col-sm-6 - %h2 Workshop details - .row - .col-sm-4{style: "text-align: right;"} Date: - .col-sm-8 - -data['dates'].each do |date| - =date['date_s'] + ', ' + date['start_time_s'] + '-' + date['end_time_s'] - %br/ - .row - .col-sm-4{style: "text-align: right;"} Location: - .col-sm-8 - = data['location_name_s'] - - if data['location_address_s'] != data['location_name_s'] - %br/ - = data['location_address_s'] - - -unless data['notes_s'].nil_or_empty? - %p{style: "margin-top: 1em;"}= data['notes_s'] - - -if (signup_count < data['capacity_s'].to_i) && (first_date >= Time.now) - %form#affiliate-workshop-signup-form{role: "form", onsubmit: "event.preventDefault(); affiliateWorkshopSignupFormSubmit();"} - - %p.form-intro-text{style: "margin-top: 2em;"} Fields marked with a * are required. Your email address will be shared with the workshop facilitator. - #error-message{style: 'display: none'} - - .main-section - %h2 Your information - .form-group - %label.control-label{for: "workshop-signup-name"} Your Name - %span.form-required-field * - %div - %input#workshop-signup-name.form-control{name: "name_s", placeholder: "Your name", type: "text", required: true}/ - .form-group - %label.control-label{for: "workshop-signup-email"} Email address - %span.form-required-field * - %div - %input#workshop-signup-email.form-control{name: "email_s", placeholder: "Email address", type: "email", required: true}/ - .form-group - %label.control-label{for: "workshop-signup-email-confirm"} Confirm email address - %span.form-required-field * - %div - %input#workshop-signup-email-confirm.form-control{name: "email_confirm_s", placeholder: "Email address", type: "email", required: true}/ - - .form-group - %label.control-label{for: "workshop-signup-teacher-role"} Role (select all that apply) - %span.form-required-field * - %div - %select#workshop-signup-teacher-role.form-control{name: "teacher_role_ss[]", type: "select", required: true, multiple: true} - %option{selected: true, value: ""} - -ProfessionalDevelopmentWorkshopSignup.teacher_roles.each do |label| - %option{value: label}= label - %option#workshop-signup-teacher-role-other-option{value: 'Other'} Other (enter below) - %div{id: 'workshop-signup-teacher-role-other-wrapper', style: 'display: none;'} - %label.control-label{for: "workshop-signup-teacher-role-other"}< - %em{style: 'font-weight: normal;'} Other role(s) (comma-separated) - %span.form-required-field * - %div - %input#workshop-signup-teacher-role-other.form-control{name: "teacher_role_other_ss", placeholder: "Other role(s)", type: "text"}/ - - .form-group - %label.control-label{for: "workshop-signup-teacher-tech-experience-level"} Level of computer science experience (all levels are welcome!) - %span.form-required-field * - %div - %select#workshop-signup-teacher-tech-experience-level.form-control{name: "teacher_tech_experience_level_s", type: "select", required: true} - %option{selected: true, value: ""} - -ProfessionalDevelopmentWorkshopSignup.teacher_tech_experience_levels.each do |level| - %option{value: level}= level - - .main-section - %h2 School information - .form-group - %label.control-label{for: "workshop-signup-school-name"} School name - %span.form-required-field * - %div - %input#workshop-signup-school-name.form-control{name: "school_name_s", placeholder: "School name", type: "text", required: true}/ - .form-group - %label.control-label{for: "workshop-signup-school-location"} School location - %span.form-required-field * - %div - %input#workshop-signup-school-location.form-control{name: "school_location_s", placeholder: "Location", type: "text", required: true}/ - .form-group - %label.control-label{for: "workshop-signup-school-type"} School type (select all that apply) - %span.form-required-field * - %div - %select#workshop-signup-school-type.form-control{name: "school_type_ss[]", type: "select", required: true, multiple: true} - %option{selected: true, value: ""} - -ProfessionalDevelopmentWorkshopSignup.school_types.each do |label| - %option{value: label}= label - %option#workshop-signup-school-type-other-option{value: 'Other'} Other (enter below) - %div{id: 'workshop-signup-school-type-other-wrapper', style: 'display: none;'} - %label.control-label{for: "workshop-signup-school-type-other"}< - %em{style: 'font-weight: normal;'} Other school type(s) (comma-separated) - %span.form-required-field * - %div - %input#workshop-signup-school-type-other.form-control{name: "school_type_other_ss", placeholder: "Other school type(s)", type: "text"}/ - .form-group - %label.control-label{for: "workshop-signup-school-district"} School district - %div - %input#workshop-signup-school-district.form-control{name: "school_district_s", placeholder: "School district", type: "text"}/ - .form-group - %label.control-label{for: "workshop-signup-school-levels"} Grade you teach (select all that apply) - %span.form-required-field * - %div - %select#workshop-signup-school-levels.form-control{name: "school_levels_ss[]", type: "select", required: true, multiple: true} - %option{selected: true, value: ""} - -ProfessionalDevelopmentWorkshopSignup.school_levels.each do |label| - %option{value: label}= label - %option#workshop-signup-school-levels-other-option{value: 'Other'} Other (enter below) - %div{id: 'workshop-signup-school-levels-other-wrapper', style: 'display: none;'} - %label.control-label{for: "workshop-signup-school-levels-other"}< - %em{style: 'font-weight: normal;'} Other school level(s) (comma-separated) - %span.form-required-field * - %div - %input#workshop-signup-school-levels-other.form-control{name: "school_levels_other_ss", placeholder: "Other school level(s)", type: "text"}/ - .form-group - %label.control-label{for: "workshop-signup-number-students"} Number of students you teach - %span.form-required-field * - %div - %input#workshop-signup-number-students.form-control{name: "number_students_s", placeholder: "Number of students", type: "text", required: true}/ - - .form-group{style: 'margin-top: 45px;'} - %button#btn-submit.btn.btn-default{type: "submit"} Submit - - -if File.file?(sites_v3_dir("code.org/views/workshop_affiliates/#{affiliate[:id]}_bio.md")) - .col-sm-5.col-sm-offset-1 - %h2{style: "font-size: 18px;"} Facilitated by: - -if File.file?(pegasus_dir("sites.v3/code.org/public/images/affiliate-images/#{affiliate[:id]}.jpg")) - %img{src: "/images/affiliate-images/fit-180/#{affiliate[:id]}.jpg", style: "margin-bottom: .5em;"} - %div - =view "workshop_affiliates/#{affiliate[:id]}_bio" - --if (signup_count < data['capacity_s'].to_i) && (first_date >= Time.now) - #thanks{:style=>"display: none;"} - .row - .col-sm-8 - :markdown - Thank you for signing up to attend a Code.org K-5 workshop. - - **To prepare for your workshop:** - - Sign up for a [teacher account](http://learn.code.org/users/sign_up?user%5Buser_type%5D=teacher) at Code.org if you don’t already have one. Review the following [introductory course materials](http://code.org/educate/k5/introPD). These will give you a head start into the course materials to be covered and maximizing learning time during the in-person workshop. - - **IMPORTANT: Bring your own laptop:** - - This workshop requires you bring your own laptop. To teach the Code Studio courses in your school you will want to make sure your school’s tablets, laptop carts or computer lab support modern browsers. Review [this page](https://support.code.org/hc/en-us/articles/202591743-What-kind-of-operating-system-and-browser-do-I-need-to-use-Code-org-s-online-learning-system-) for more information regarding compatible operating systems and browsers. - - %h2 Workshop details - .row - .col-sm-4{style: "text-align: right;"} Date: - .col-sm-8 - -data['dates'].each do |date| - =date['date_s'] + ', ' + date['start_time_s'] + '-' + date['end_time_s'] - %br/ - .row - .col-sm-4{style: "text-align: right;"} Location: - .col-sm-8 - = data['location_name_s'] - - if data['location_address_s'] != data['location_name_s'] - %br/ - = data['location_address_s'] - - .row - .col-sm-4{style: "text-align: right;"} Workshop facilitator: - .col-sm-8 - = affiliate.name - %br/ - = affiliate.email - - -unless data['notes_s'].nil_or_empty? - %p{style: "margin-top: 1em;"}= data['notes_s'] - --if (signup_count < data['capacity_s'].to_i) && (first_date >= Time.now) - - :javascript - - $(document).ready(function() { - $('#affiliate-workshop-signup-form select').selectize({ - plugins: ['fast_click'] - }); - - var location_input = document.getElementById('workshop-signup-school-location'); - var location_autocomplete = new google.maps.places.SearchBox(location_input); // Google Maps autocomplete. - - $('#workshop-signup-school-type').change(function() { - if ($.inArray('Other', $(this).val()) > -1) { - $('#workshop-signup-school-type-other-wrapper').show(); - } else { - $('#workshop-signup-school-type-other').val(''); - $('#workshop-signup-school-type-other-wrapper').hide(); - } - }).triggerHandler('change'); - - $('#workshop-signup-school-levels').change(function() { - if ($.inArray('Other', $(this).val()) > -1) { - $('#workshop-signup-school-levels-other-wrapper').show(); - } else { - $('#workshop-signup-school-levels-other').val(''); - $('#workshop-signup-school-levels-other-wrapper').hide(); - } - }).triggerHandler('change'); - - $('#workshop-signup-teacher-role').change(function() { - if ($.inArray('Other', $(this).val()) > -1) { - $('#workshop-signup-teacher-role-other-wrapper').show(); - } else { - $('#workshop-signup-teacher-role-other').val(''); - $('#workshop-signup-teacher-role-other-wrapper').hide(); - } - }).triggerHandler('change'); - }); - - function processResponse(data) - { - // Facebook Conversion Tracking Pixel script START - (function() { - var _fbq = window._fbq || (window._fbq = []); - if (!_fbq.loaded) { - var fbds = document.createElement('script'); - fbds.async = true; - fbds.src = '//connect.facebook.net/en_US/fbds.js'; - var s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(fbds, s); - _fbq.loaded = true; - } - })(); - window._fbq = window._fbq || []; - window._fbq.push(['track', '6023436586161', {'value':'0.01','currency':'USD'}]); - // Facebook Conversion Tracking Pixel script END - - $('#affiliate-workshop-signup-form-wrapper').hide(); - $('#thanks').show(); - } - - function processError(data) - { - $('.has-error').removeClass('has-error'); - - var errors = data.responseJSON; - var error_keys = Object.keys(errors); - var errors_count = error_keys.length; - var required_error = false; - var error_messages = []; - - for (var i = 0; i < errors_count; i++) { - var error_list = errors[error_keys[i]]; - - if (error_list.indexOf('required') != -1) { - required_error = true; - } - - if (error_list.indexOf('mismatch') != -1) { - error_messages.push('The email address fields do not match.'); - $('#workshop-signup-email-confirm').parents('.form-group').addClass('has-error'); - } - - error_id = '#workshop-signup-' + error_keys[i].replace(/_/g, '-'); - error_id = error_id.replace(/-[sb]s?$/, ''); - $(error_id).parents('.form-group').addClass('has-error'); - } - - if (required_error) { - error_messages.push('At least one required field was left empty.'); - } - - if (error_messages.length > 0) { - $('#error-message').html('

' + error_messages.join('

') + '

').show(); - } - - $('body').scrollTop(0); - $("#btn-submit").removeAttr('disabled'); - $("#btn-submit").removeClass("button_disabled").addClass("button_enabled"); - } - - function affiliateWorkshopSignupFormSubmit() - { - $("#btn-submit").attr('disabled','disabled'); - $("#btn-submit").removeClass("button_enabled").addClass("button_disabled"); - - $.ajax({ - url: "/forms/ProfessionalDevelopmentWorkshop/#{workshop_id}/children/ProfessionalDevelopmentWorkshopSignup", - type: "post", - dataType: "json", - data: $('#affiliate-workshop-signup-form').serialize() - }).done(processResponse).fail(processError); - - return false; - } diff --git a/pegasus/sites.v3/hourofcode.com/public/images/czechitas.png b/pegasus/sites.v3/hourofcode.com/public/images/czechitas.png new file mode 100644 index 0000000000000..069ca6fcb961a Binary files /dev/null and b/pegasus/sites.v3/hourofcode.com/public/images/czechitas.png differ diff --git a/pegasus/sites.v3/hourofcode.com/public/images/iamai_academy.jpg b/pegasus/sites.v3/hourofcode.com/public/images/iamai_academy.jpg new file mode 100644 index 0000000000000..bb04ab23ccc82 Binary files /dev/null and b/pegasus/sites.v3/hourofcode.com/public/images/iamai_academy.jpg differ diff --git a/pegasus/sites.v3/hourofcode.com/public/images/korea_information_science_education_federation.png b/pegasus/sites.v3/hourofcode.com/public/images/korea_information_science_education_federation.png new file mode 100644 index 0000000000000..9a8161e908749 Binary files /dev/null and b/pegasus/sites.v3/hourofcode.com/public/images/korea_information_science_education_federation.png differ diff --git a/pegasus/sites.v3/hourofcode.com/public/images/sault_ste_marie_innovation_centre.jpg b/pegasus/sites.v3/hourofcode.com/public/images/sault_ste_marie_innovation_centre.jpg new file mode 100644 index 0000000000000..f7d916992cd07 Binary files /dev/null and b/pegasus/sites.v3/hourofcode.com/public/images/sault_ste_marie_innovation_centre.jpg differ diff --git a/pegasus/sites.v3/hourofcode.com/public/international-partners.md b/pegasus/sites.v3/hourofcode.com/public/international-partners.md index 1611d11b0c866..11ebec29ab69d 100644 --- a/pegasus/sites.v3/hourofcode.com/public/international-partners.md +++ b/pegasus/sites.v3/hourofcode.com/public/international-partners.md @@ -81,6 +81,7 @@ You, too, can play a leading role to get more people in your country involved! I | Portugal | Gabinete de Modernização das Tecnologias Educativas | Rodolfo Pinto | http://www02.madeira-edu.pt/dre/main.aspx | | Romania | ADFABER | Alin Chiriac | alin@adfaber.org | | Singapore | Infocomm Media Development Authority of Singapore | | https://www.imda.gov.sg/ | +| South Korea | Korea Information Science Education Federation | | http://kcode.kr/
hwankim92@gmail.com | | Spain | Acludi | Juana Martínez | www.acludi.blogspot.com
acludi@yahoo.com | | Spain | CSTA Spain | Santiago Fernández-Cabaleiro | www.aapri.es
santiagocabaleiro@gmail.com | | Spain | Informatics Faculty - University of the Basque Country (UPV/EHU) | Edurne Larraza Mendiluze | https://www.ehu.eus/en/web/informatika-fakultatea/home
edurne.larraza@ehu.eus | diff --git a/shared/middleware/files_api.rb b/shared/middleware/files_api.rb index 6a8c5f8e7aadb..9dff70b138914 100644 --- a/shared/middleware/files_api.rb +++ b/shared/middleware/files_api.rb @@ -267,7 +267,7 @@ def put_file(endpoint, encrypted_channel_id, filename, body) version_to_replace = params['version'] timestamp = params['firstSaveTimestamp'] tab_id = params['tabId'] - buckets.check_current_version(encrypted_channel_id, filename, version_to_replace, timestamp, tab_id) + buckets.check_current_version(encrypted_channel_id, filename, version_to_replace, timestamp, tab_id, current_user_id) response = buckets.create_or_replace(encrypted_channel_id, filename, body, version_to_replace) @@ -472,7 +472,7 @@ def copy_file(endpoint, encrypted_channel_id, filename, source_filename) not_authorized unless owns_channel?(encrypted_channel_id) - get_bucket_impl(endpoint).new.restore_previous_version(encrypted_channel_id, filename, request.GET['version']).to_json + get_bucket_impl(endpoint).new.restore_previous_version(encrypted_channel_id, filename, request.GET['version'], current_user_id).to_json end # diff --git a/shared/middleware/helpers/bucket_helper.rb b/shared/middleware/helpers/bucket_helper.rb index a8f6474206bdf..316a96603f20b 100644 --- a/shared/middleware/helpers/bucket_helper.rb +++ b/shared/middleware/helpers/bucket_helper.rb @@ -174,7 +174,7 @@ def create_or_replace(encrypted_channel_id, filename, body, version = nil, abuse response end - def check_current_version(encrypted_channel_id, filename, version_to_replace, timestamp, tab_id) + def check_current_version(encrypted_channel_id, filename, version_to_replace, timestamp, tab_id, user_id) return unless filename == 'main.json' && @bucket == CDO.sources_s3_bucket && version_to_replace owner_id, channel_id = storage_decrypt_channel_id(encrypted_channel_id) @@ -188,9 +188,11 @@ def check_current_version(encrypted_channel_id, filename, version_to_replace, ti FirehoseClient.instance.put_record( 'analysis-events', study: 'project-data-integrity', + study_group: 'v2', event: 'replace-non-current-main-json', project_id: encrypted_channel_id, + user_id: user_id, data_json: { replacedVersionId: version_to_replace, @@ -260,7 +262,7 @@ def list_versions(encrypted_channel_id, filename) # Copies the given version of the file to make it the current revision. # (All intermediate versions are preserved.) - def restore_previous_version(encrypted_channel_id, filename, version_id) + def restore_previous_version(encrypted_channel_id, filename, version_id, user_id) owner_id, channel_id = storage_decrypt_channel_id(encrypted_channel_id) key = s3_path owner_id, channel_id, filename @@ -270,12 +272,14 @@ def restore_previous_version(encrypted_channel_id, filename, version_id) FirehoseClient.instance.put_record( 'analysis-events', study: 'project-data-integrity', + study_group: 'v2', event: 'version-restored', # Make it easy to limit our search to restores in the sources bucket for a certain project. project_id: encrypted_channel_id, data_string: @bucket, + user_id: user_id, data_json: { restoredVersionId: version_id, newVersionId: response.version_id,