diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb6d7fc725ff7..760e42d58640c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ Please test your changes before submitting them to us! ### Manual testing -We support recent versions of Firefox, Chrome, Internet Explorer, Edge, iOS Safari and the Android browsers ([full list of supported browsers and versions](https://support.code.org/hc/en-us/articles/202591743)). Be sure to try your feature out in [IE9](docs/testing-ie9.md), iOS and Android if it's a risk. [BrowserStack live](http://www.browserstack.com) or [Sauce Labs manual](https://saucelabs.com/manual) let you run manual tests in these browsers remotely. +We support recent versions of Firefox, Chrome, Internet Explorer, Edge, iOS Safari and the Android browsers ([full list of supported browsers and versions](https://support.code.org/hc/en-us/articles/202591743)). Be sure to try your feature out in [IE9](docs/testing-ie9.md), iOS and Android if it's a risk. [Sauce Labs](https://saucelabs.com/manual) or [BrowserStack live](http://www.browserstack.com) let you run manual tests in these browsers remotely. ### Unit tests @@ -38,7 +38,7 @@ For dashboard changes, be sure to test your changes using `rake test`. For [apps ### UI tests -Our continuous integration server regularly runs a suite of [UI tests](./dashboard/test/ui) using Selenium / Cucumber which run against many browsers via [BrowserStack Automate](https://www.browserstack.com/automate), and can also be run locally using `chromedriver`. See the [README](./dashboard/test/ui) in that folder for instructions. +Our continuous integration server regularly runs a suite of [UI tests](./dashboard/test/ui) using Selenium / Cucumber which run against many browsers via [Sauce Labs](https://saucelabs.com/), and can also be run locally using `chromedriver`. See the [README](./dashboard/test/ui) in that folder for instructions. If your changes might affect level paths, blockly UI, or critical path site logic, be sure to test your changes with a local UI test. diff --git a/apps/Gruntfile.js b/apps/Gruntfile.js index b602812739076..3316a6419170e 100644 --- a/apps/Gruntfile.js +++ b/apps/Gruntfile.js @@ -488,7 +488,7 @@ module.exports = function (grunt) { 'newer:messages', 'exec:convertScssVars', 'newer:copy:static', - 'newer:concat', + 'concat', 'exec:mochaTest' ]); diff --git a/apps/i18n/common/ar_sa.json b/apps/i18n/common/ar_sa.json index 6c623cb6149c3..1531cbdb2ffb1 100644 --- a/apps/i18n/common/ar_sa.json +++ b/apps/i18n/common/ar_sa.json @@ -231,5 +231,7 @@ "when": "عندما", "whenRun": "عند التشغيل", "workspaceHeaderShort": "مساحة العمل: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/az_az.json b/apps/i18n/common/az_az.json index 335eb3ca2b6b6..e3a0af0669b28 100644 --- a/apps/i18n/common/az_az.json +++ b/apps/i18n/common/az_az.json @@ -231,5 +231,7 @@ "when": "nə zaman", "whenRun": "icra etdikdə", "workspaceHeaderShort": "iş sahəsi: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/bg_bg.json b/apps/i18n/common/bg_bg.json index 3a6247a1ec746..77afb19e276e6 100644 --- a/apps/i18n/common/bg_bg.json +++ b/apps/i18n/common/bg_bg.json @@ -231,5 +231,7 @@ "when": "когато", "whenRun": "при стартиране", "workspaceHeaderShort": "Работна област: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/bn_bd.json b/apps/i18n/common/bn_bd.json index adc62c880f03e..f4f1add7fa07d 100644 --- a/apps/i18n/common/bn_bd.json +++ b/apps/i18n/common/bn_bd.json @@ -231,5 +231,7 @@ "when": "যখন", "whenRun": "চালানোর সময়", "workspaceHeaderShort": "কর্মপরিসর:", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/bs_ba.json b/apps/i18n/common/bs_ba.json index 8133c86fa4a32..7633a8739a1e4 100755 --- a/apps/i18n/common/bs_ba.json +++ b/apps/i18n/common/bs_ba.json @@ -231,5 +231,7 @@ "when": "kada", "whenRun": "pri pokretanju", "workspaceHeaderShort": "Radni prostor: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ca_es.json b/apps/i18n/common/ca_es.json index 592b134e7823b..d09a5c9fd4d5c 100644 --- a/apps/i18n/common/ca_es.json +++ b/apps/i18n/common/ca_es.json @@ -231,5 +231,7 @@ "when": "quan", "whenRun": "quan s'executa", "workspaceHeaderShort": "Zona de treball: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/cs_cz.json b/apps/i18n/common/cs_cz.json index 4c8e1ea9e0a91..488b796a8cd07 100644 --- a/apps/i18n/common/cs_cz.json +++ b/apps/i18n/common/cs_cz.json @@ -231,5 +231,7 @@ "when": "když", "whenRun": "po spuštění", "workspaceHeaderShort": "Pracovní prostor: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/da_dk.json b/apps/i18n/common/da_dk.json index 01c5ab1916445..a9f033929ca66 100644 --- a/apps/i18n/common/da_dk.json +++ b/apps/i18n/common/da_dk.json @@ -231,5 +231,7 @@ "when": "når", "whenRun": "når programmet kører", "workspaceHeaderShort": "Arbejdsområde: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/de_de.json b/apps/i18n/common/de_de.json index 519b59ff46d87..0b91270be05fd 100644 --- a/apps/i18n/common/de_de.json +++ b/apps/i18n/common/de_de.json @@ -231,5 +231,7 @@ "when": "wenn", "whenRun": "beim Ausführen", "workspaceHeaderShort": "Arbeitsbereich: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/el_gr.json b/apps/i18n/common/el_gr.json index f38fb0d890d14..c39df1beaad97 100644 --- a/apps/i18n/common/el_gr.json +++ b/apps/i18n/common/el_gr.json @@ -231,5 +231,7 @@ "when": "όταν", "whenRun": "όταν εκτελείται", "workspaceHeaderShort": "Χώρος εργασίας: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/en_gb.json b/apps/i18n/common/en_gb.json index 3cb486a599631..b7c7f11a33e0a 100644 --- a/apps/i18n/common/en_gb.json +++ b/apps/i18n/common/en_gb.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/en_us.json b/apps/i18n/common/en_us.json index e34d67089233d..badf860a44d7c 100644 --- a/apps/i18n/common/en_us.json +++ b/apps/i18n/common/en_us.json @@ -24,6 +24,7 @@ "completedWithoutRecommendedBlock": "Congratulations! You completed Puzzle {puzzleNumber}. (But you could use a different block for stronger code.)", "continue": "Continue", "copy": "Copy", + "currentVersion": "Current Version", "debugConsoleHeader": "Debug Console", "debugCommandsHeaderWhenOpen": "Debug Commands", "debugCommandsHeaderWhenClosed": "Show Debug Commands", @@ -184,6 +185,7 @@ "recommendedBlockContextualHintTitle": "Try using a block like this to solve the puzzle.", "repeat": "repeat", "resetProgram": "Reset", + "restoreThisVersion": "Restore this Version", "rotateText": "Rotate your device.", "runProgram": "Run", "runTooltip": "Run the program defined by the blocks in the workspace.", diff --git a/apps/i18n/common/es_es.json b/apps/i18n/common/es_es.json index a8e67f6790314..ed03ead99934c 100644 --- a/apps/i18n/common/es_es.json +++ b/apps/i18n/common/es_es.json @@ -231,5 +231,7 @@ "when": "cuando", "whenRun": "cuando se ejecuta", "workspaceHeaderShort": "Espacio de trabajo: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/es_mx.json b/apps/i18n/common/es_mx.json index 4fe02bb8aa3d4..dbbaa4c669abc 100644 --- a/apps/i18n/common/es_mx.json +++ b/apps/i18n/common/es_mx.json @@ -231,5 +231,7 @@ "when": "cuando", "whenRun": "cuando se ejecuta", "workspaceHeaderShort": "Espacio de trabajo: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/et_ee.json b/apps/i18n/common/et_ee.json index cdef103f5f344..1ece35b9dd8d3 100644 --- a/apps/i18n/common/et_ee.json +++ b/apps/i18n/common/et_ee.json @@ -231,5 +231,7 @@ "when": "kui", "whenRun": "pärast käivitamist", "workspaceHeaderShort": "Tööruum: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/eu_es.json b/apps/i18n/common/eu_es.json index 50807fcbeee86..b446d6d273a9c 100644 --- a/apps/i18n/common/eu_es.json +++ b/apps/i18n/common/eu_es.json @@ -231,5 +231,7 @@ "when": "-enean", "whenRun": "martxan dagoenean", "workspaceHeaderShort": "Lan eremua: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/fa_af.json b/apps/i18n/common/fa_af.json index 2ccb6020b0158..7d975517c57a3 100644 --- a/apps/i18n/common/fa_af.json +++ b/apps/i18n/common/fa_af.json @@ -231,5 +231,7 @@ "when": "وقتی", "whenRun": "زمان اجرا", "workspaceHeaderShort": "محیط کار: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/fa_ir.json b/apps/i18n/common/fa_ir.json index f320bcd62091d..e10eace8c140c 100644 --- a/apps/i18n/common/fa_ir.json +++ b/apps/i18n/common/fa_ir.json @@ -231,5 +231,7 @@ "when": "وقتی", "whenRun": "زمان اجرا", "workspaceHeaderShort": "محیط کار: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/fi_fi.json b/apps/i18n/common/fi_fi.json index 8e63c3a67ec88..da744bfaec28b 100644 --- a/apps/i18n/common/fi_fi.json +++ b/apps/i18n/common/fi_fi.json @@ -231,5 +231,7 @@ "when": "kun", "whenRun": "suoritettaessa", "workspaceHeaderShort": "Työtila: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/fil_ph.json b/apps/i18n/common/fil_ph.json index e51798934ee2d..31bd6d82eaf22 100644 --- a/apps/i18n/common/fil_ph.json +++ b/apps/i18n/common/fil_ph.json @@ -231,5 +231,7 @@ "when": "kelan", "whenRun": "kapag tumakbo", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/fr_fr.json b/apps/i18n/common/fr_fr.json index 5449b77511a71..bc3f53cf2edcb 100644 --- a/apps/i18n/common/fr_fr.json +++ b/apps/i18n/common/fr_fr.json @@ -231,5 +231,7 @@ "when": "quand", "whenRun": "quand l'exécution commence", "workspaceHeaderShort": "Espace de travail :", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ga_ie.json b/apps/i18n/common/ga_ie.json index 82212f3fba4c7..0a8b2e1899c71 100644 --- a/apps/i18n/common/ga_ie.json +++ b/apps/i18n/common/ga_ie.json @@ -231,5 +231,7 @@ "when": "nuair", "whenRun": "nuair a ritear é", "workspaceHeaderShort": "Spás oibre: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/gl_es.json b/apps/i18n/common/gl_es.json index 89dbeb047c97a..06b9ba359d1a8 100644 --- a/apps/i18n/common/gl_es.json +++ b/apps/i18n/common/gl_es.json @@ -231,5 +231,7 @@ "when": "cando", "whenRun": "cando se executa", "workspaceHeaderShort": "Espazo de traballo: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/he_il.json b/apps/i18n/common/he_il.json index 206c94dd3f7b4..5b9c11f5f1069 100644 --- a/apps/i18n/common/he_il.json +++ b/apps/i18n/common/he_il.json @@ -231,5 +231,7 @@ "when": "מתי", "whenRun": "התחל ריצה", "workspaceHeaderShort": "סביבת העבודה: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/hi_in.json b/apps/i18n/common/hi_in.json index 2f4fc5b9454b9..aba89ff70e711 100644 --- a/apps/i18n/common/hi_in.json +++ b/apps/i18n/common/hi_in.json @@ -231,5 +231,7 @@ "when": "कब", "whenRun": "जब चलाएँ", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/hr_hr.json b/apps/i18n/common/hr_hr.json index 54dbc6515db11..b4745f33982fd 100644 --- a/apps/i18n/common/hr_hr.json +++ b/apps/i18n/common/hr_hr.json @@ -231,5 +231,7 @@ "when": "kada", "whenRun": "pri pokretanju", "workspaceHeaderShort": "Radna površina: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/hu_hu.json b/apps/i18n/common/hu_hu.json index 19f79ac41b2a2..b6a3e98e9dbf9 100644 --- a/apps/i18n/common/hu_hu.json +++ b/apps/i18n/common/hu_hu.json @@ -231,5 +231,7 @@ "when": "amikor", "whenRun": "futtatáskor", "workspaceHeaderShort": "Munkaterület: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/hy_am.json b/apps/i18n/common/hy_am.json index ffdc692ebd52e..6428b5883bccf 100755 --- a/apps/i18n/common/hy_am.json +++ b/apps/i18n/common/hy_am.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/id_id.json b/apps/i18n/common/id_id.json index 35a4bff8edf75..199e7cc82116e 100644 --- a/apps/i18n/common/id_id.json +++ b/apps/i18n/common/id_id.json @@ -231,5 +231,7 @@ "when": "ketika", "whenRun": "ketika dijalankan", "workspaceHeaderShort": "Area kerja: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/is_is.json b/apps/i18n/common/is_is.json index 11dc9a1c33420..29cc612dcaaef 100644 --- a/apps/i18n/common/is_is.json +++ b/apps/i18n/common/is_is.json @@ -231,5 +231,7 @@ "when": "þegar", "whenRun": "þegar keyrt", "workspaceHeaderShort": "Vinnusvæði: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/it_it.json b/apps/i18n/common/it_it.json index 711b86c032075..38ae555735b57 100644 --- a/apps/i18n/common/it_it.json +++ b/apps/i18n/common/it_it.json @@ -231,5 +231,7 @@ "when": "quando", "whenRun": "quando si clicca su \"Esegui\"", "workspaceHeaderShort": "Area di lavoro: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ja_jp.json b/apps/i18n/common/ja_jp.json index 8ad49e6a28e90..59cc55e90dc27 100644 --- a/apps/i18n/common/ja_jp.json +++ b/apps/i18n/common/ja_jp.json @@ -231,5 +231,7 @@ "when": "とき", "whenRun": "実行した時", "workspaceHeaderShort": "ワークスペース:", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ka_ge.json b/apps/i18n/common/ka_ge.json index 5ef17429f4c68..656ade7cddcbd 100644 --- a/apps/i18n/common/ka_ge.json +++ b/apps/i18n/common/ka_ge.json @@ -231,5 +231,7 @@ "when": "როდის", "whenRun": "გაშვებისას", "workspaceHeaderShort": "სამუშაო სივრცე: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/kk_kz.json b/apps/i18n/common/kk_kz.json index 0ebb34ae16cd6..362b837a520cf 100644 --- a/apps/i18n/common/kk_kz.json +++ b/apps/i18n/common/kk_kz.json @@ -231,5 +231,7 @@ "when": "кезде", "whenRun": "қосу кезінде", "workspaceHeaderShort": "Жұмыс аумағы: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/km_kh.json b/apps/i18n/common/km_kh.json index 43f4d158d72ad..e423ccb258815 100644 --- a/apps/i18n/common/km_kh.json +++ b/apps/i18n/common/km_kh.json @@ -231,5 +231,7 @@ "when": "នៅ​ពេល", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ko_kr.json b/apps/i18n/common/ko_kr.json index bba4435a44212..0d6500015308f 100644 --- a/apps/i18n/common/ko_kr.json +++ b/apps/i18n/common/ko_kr.json @@ -231,5 +231,7 @@ "when": "~할 때", "whenRun": "실행하면", "workspaceHeaderShort": "작업 영역: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ku_iq.json b/apps/i18n/common/ku_iq.json index 8ed237d87e3c0..7afa7f1ce8106 100644 --- a/apps/i18n/common/ku_iq.json +++ b/apps/i18n/common/ku_iq.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/lt_lt.json b/apps/i18n/common/lt_lt.json index 502ab719591f3..67c12954a56a5 100644 --- a/apps/i18n/common/lt_lt.json +++ b/apps/i18n/common/lt_lt.json @@ -231,5 +231,7 @@ "when": "kada", "whenRun": "paleidus", "workspaceHeaderShort": "Darbo laukas: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/lv_lv.json b/apps/i18n/common/lv_lv.json index 4ac029b87fbbf..02215ff979f8d 100644 --- a/apps/i18n/common/lv_lv.json +++ b/apps/i18n/common/lv_lv.json @@ -231,5 +231,7 @@ "when": "kad", "whenRun": "kad izpilda", "workspaceHeaderShort": "Darba virsma: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/mi_nz.json b/apps/i18n/common/mi_nz.json index 20173a26e9269..e5c0fe91f206a 100644 --- a/apps/i18n/common/mi_nz.json +++ b/apps/i18n/common/mi_nz.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/mk_mk.json b/apps/i18n/common/mk_mk.json index 8d7f174489e01..05e8774f63719 100644 --- a/apps/i18n/common/mk_mk.json +++ b/apps/i18n/common/mk_mk.json @@ -231,5 +231,7 @@ "when": "Кога", "whenRun": "Кога трча", "workspaceHeaderShort": "Работна површина: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/mr_in.json b/apps/i18n/common/mr_in.json index 3cb486a599631..b7c7f11a33e0a 100644 --- a/apps/i18n/common/mr_in.json +++ b/apps/i18n/common/mr_in.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ms_my.json b/apps/i18n/common/ms_my.json index cc2acf291db0a..b64d02d1cf902 100644 --- a/apps/i18n/common/ms_my.json +++ b/apps/i18n/common/ms_my.json @@ -231,5 +231,7 @@ "when": "apabila", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/mt_mt.json b/apps/i18n/common/mt_mt.json index e91cc7fd88a85..fb4f081745c05 100755 --- a/apps/i18n/common/mt_mt.json +++ b/apps/i18n/common/mt_mt.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ne_np.json b/apps/i18n/common/ne_np.json index 687941ad8c4fd..e036be730f968 100644 --- a/apps/i18n/common/ne_np.json +++ b/apps/i18n/common/ne_np.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/nl_nl.json b/apps/i18n/common/nl_nl.json index 03105b0acd866..d243005ec754f 100644 --- a/apps/i18n/common/nl_nl.json +++ b/apps/i18n/common/nl_nl.json @@ -231,5 +231,7 @@ "when": "wanneer", "whenRun": "als gestart", "workspaceHeaderShort": "Werkplaats: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/nn_no.json b/apps/i18n/common/nn_no.json index 7740276daa5ae..296fb91558881 100755 --- a/apps/i18n/common/nn_no.json +++ b/apps/i18n/common/nn_no.json @@ -231,5 +231,7 @@ "when": "når", "whenRun": "når den kjører", "workspaceHeaderShort": "Arbeidsområde: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/no_no.json b/apps/i18n/common/no_no.json index 84ce35c377a59..f73162f2bf284 100644 --- a/apps/i18n/common/no_no.json +++ b/apps/i18n/common/no_no.json @@ -231,5 +231,7 @@ "when": "når", "whenRun": "når den køyrer", "workspaceHeaderShort": "Arbeidsområde: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/pl_pl.json b/apps/i18n/common/pl_pl.json index 27d2799e5c2f3..9375145df567b 100644 --- a/apps/i18n/common/pl_pl.json +++ b/apps/i18n/common/pl_pl.json @@ -231,5 +231,7 @@ "when": "kiedy", "whenRun": "po uruchomieniu", "workspaceHeaderShort": "Obszar roboczy: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ps_af.json b/apps/i18n/common/ps_af.json index 9d57641d92d3e..78e12c6b217c2 100755 --- a/apps/i18n/common/ps_af.json +++ b/apps/i18n/common/ps_af.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/pt_br.json b/apps/i18n/common/pt_br.json index 0a428b830010d..7e508dde28af0 100644 --- a/apps/i18n/common/pt_br.json +++ b/apps/i18n/common/pt_br.json @@ -231,5 +231,7 @@ "when": "quando", "whenRun": "quando executar", "workspaceHeaderShort": "Área de trabalho: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/pt_pt.json b/apps/i18n/common/pt_pt.json index 354ac00a736b7..383078b005978 100644 --- a/apps/i18n/common/pt_pt.json +++ b/apps/i18n/common/pt_pt.json @@ -231,5 +231,7 @@ "when": "quando", "whenRun": "quando executar", "workspaceHeaderShort": "Espaço de trabalho: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ro_ro.json b/apps/i18n/common/ro_ro.json index 4e9f16b7bafbc..71bd8cd62aa5c 100644 --- a/apps/i18n/common/ro_ro.json +++ b/apps/i18n/common/ro_ro.json @@ -231,5 +231,7 @@ "when": "când", "whenRun": "când rulezi", "workspaceHeaderShort": "Spaţiu de lucru: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ru_ru.json b/apps/i18n/common/ru_ru.json index 4800c70183e6a..1d40347335764 100644 --- a/apps/i18n/common/ru_ru.json +++ b/apps/i18n/common/ru_ru.json @@ -231,5 +231,7 @@ "when": "когда", "whenRun": "При запуске", "workspaceHeaderShort": "Место сбора блоков: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/se_fi.json b/apps/i18n/common/se_fi.json index 84effd24308cd..8c7135a917f24 100644 --- a/apps/i18n/common/se_fi.json +++ b/apps/i18n/common/se_fi.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/si_lk.json b/apps/i18n/common/si_lk.json index dae8df24c0552..3f62b96d973a5 100644 --- a/apps/i18n/common/si_lk.json +++ b/apps/i18n/common/si_lk.json @@ -231,5 +231,7 @@ "when": "විට", "whenRun": "දුවන විට", "workspaceHeaderShort": "වැඩ අවකාශය: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/sk_sk.json b/apps/i18n/common/sk_sk.json index 6c7ecadf5ce53..bdec28a583940 100644 --- a/apps/i18n/common/sk_sk.json +++ b/apps/i18n/common/sk_sk.json @@ -231,5 +231,7 @@ "when": "keď", "whenRun": "pri spustení", "workspaceHeaderShort": "Pracovná plocha: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/sl_si.json b/apps/i18n/common/sl_si.json index 95dd84ffba4c8..10e0f7ddca8da 100644 --- a/apps/i18n/common/sl_si.json +++ b/apps/i18n/common/sl_si.json @@ -231,5 +231,7 @@ "when": "ko", "whenRun": "ob zagonu", "workspaceHeaderShort": "Delovni prostor: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/sq_al.json b/apps/i18n/common/sq_al.json index 5ec255e877dbb..3e958e6b836e5 100644 --- a/apps/i18n/common/sq_al.json +++ b/apps/i18n/common/sq_al.json @@ -231,5 +231,7 @@ "when": "kur", "whenRun": "kur vrapon", "workspaceHeaderShort": "Vendi i punës: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/sr_sp.json b/apps/i18n/common/sr_sp.json index 6a3fa2f63c062..dc20ca04c6b59 100644 --- a/apps/i18n/common/sr_sp.json +++ b/apps/i18n/common/sr_sp.json @@ -231,5 +231,7 @@ "when": "када", "whenRun": "Када кренеш", "workspaceHeaderShort": "Радни простор: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/sv_se.json b/apps/i18n/common/sv_se.json index 7ba92bc707708..a52db4279fd44 100644 --- a/apps/i18n/common/sv_se.json +++ b/apps/i18n/common/sv_se.json @@ -231,5 +231,7 @@ "when": "när", "whenRun": "vid start", "workspaceHeaderShort": "Arbetsyta: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ta_in.json b/apps/i18n/common/ta_in.json index 8562ad7b07d19..c72130e215af0 100644 --- a/apps/i18n/common/ta_in.json +++ b/apps/i18n/common/ta_in.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/th_th.json b/apps/i18n/common/th_th.json index 889d2a9961d1f..1c3ea60184442 100644 --- a/apps/i18n/common/th_th.json +++ b/apps/i18n/common/th_th.json @@ -231,5 +231,7 @@ "when": "เมื่อ", "whenRun": "เมื่อเรียกให้ทำงาน", "workspaceHeaderShort": "พื้นที่ทำงาน: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/tr_tr.json b/apps/i18n/common/tr_tr.json index 6a074b250360e..053f91df3ef1b 100644 --- a/apps/i18n/common/tr_tr.json +++ b/apps/i18n/common/tr_tr.json @@ -231,5 +231,7 @@ "when": "Ne zaman", "whenRun": "Çalıştığı zaman", "workspaceHeaderShort": "Çalışma alanı: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/uk_ua.json b/apps/i18n/common/uk_ua.json index 8642f995e1083..9a055567967a2 100644 --- a/apps/i18n/common/uk_ua.json +++ b/apps/i18n/common/uk_ua.json @@ -231,5 +231,7 @@ "when": "коли", "whenRun": "коли гра починається", "workspaceHeaderShort": "Робоча область: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/ur_pk.json b/apps/i18n/common/ur_pk.json index b70fe28314a75..203c32da08247 100644 --- a/apps/i18n/common/ur_pk.json +++ b/apps/i18n/common/ur_pk.json @@ -231,5 +231,7 @@ "when": "کب", "whenRun": "جب رن ہو تو", "workspaceHeaderShort": "ورک اسپیس ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/uz_uz.json b/apps/i18n/common/uz_uz.json index 2ef2c201697d6..10227b8e95be3 100644 --- a/apps/i18n/common/uz_uz.json +++ b/apps/i18n/common/uz_uz.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/vi_vn.json b/apps/i18n/common/vi_vn.json index 8de621e5210f4..1103021a5125b 100644 --- a/apps/i18n/common/vi_vn.json +++ b/apps/i18n/common/vi_vn.json @@ -231,5 +231,7 @@ "when": "Khi nào", "whenRun": "Khi chạy", "workspaceHeaderShort": "Không gian làm việc:", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/zh_cn.json b/apps/i18n/common/zh_cn.json index 8c9c6572dc231..147e064ef633d 100644 --- a/apps/i18n/common/zh_cn.json +++ b/apps/i18n/common/zh_cn.json @@ -231,5 +231,7 @@ "when": "当", "whenRun": "当运行时", "workspaceHeaderShort": "工作区域", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/zh_tw.json b/apps/i18n/common/zh_tw.json index d9270e4cd9c62..b7baa76509a6a 100644 --- a/apps/i18n/common/zh_tw.json +++ b/apps/i18n/common/zh_tw.json @@ -231,5 +231,7 @@ "when": "當", "whenRun": "當按下\"執行\"時", "workspaceHeaderShort": "工作區:", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/i18n/common/zu_za.json b/apps/i18n/common/zu_za.json index 90c0e38efc2c9..f01c69a944470 100644 --- a/apps/i18n/common/zu_za.json +++ b/apps/i18n/common/zu_za.json @@ -231,5 +231,7 @@ "when": "when", "whenRun": "when run", "workspaceHeaderShort": "Workspace: ", - "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more." + "currentVersion": "Current Version", + "errorGenericLintError": "Your program contains an editor warning that needs to be corrected. Hover over the icons near the line numbers in the editor to learn more.", + "restoreThisVersion": "Restore this Version" } \ No newline at end of file diff --git a/apps/src/applab/AppLabView.jsx b/apps/src/applab/AppLabView.jsx index 2d328776f7624..a42b9743039e9 100644 --- a/apps/src/applab/AppLabView.jsx +++ b/apps/src/applab/AppLabView.jsx @@ -119,7 +119,7 @@ var AppLabView = React.createClass({ }, componentDidMount: function () { - if (this.props.instructionsInTopPane) { + if (this.props.showInstructions) { this.adjustTopPaneHeight(); window.addEventListener('resize', this.onResize); @@ -129,7 +129,7 @@ var AppLabView = React.createClass({ }, componentWillUnmount: function () { - if (this.props.instructionsInTopPane) { + if (this.props.showInstructions) { window.removeEventListener("resize", this.onResize); } }, @@ -148,7 +148,7 @@ var AppLabView = React.createClass({ } // Or we may not display the instructions pane at all - if (!this.props.instructionsInTopPane) { + if (!this.props.showInstructions) { height = 0; } @@ -179,7 +179,7 @@ var AppLabView = React.createClass({ - {this.props.instructionsInTopPane && ; diff --git a/apps/src/templates/VersionRow.jsx b/apps/src/templates/VersionRow.jsx index e649f07da09a0..02656c5c3470b 100644 --- a/apps/src/templates/VersionRow.jsx +++ b/apps/src/templates/VersionRow.jsx @@ -1,11 +1,13 @@ /* globals $ */ +var msg = require('../locale'); /** * A single row in the VersionHistory dialog, describing one version of a project. */ var VersionRow = React.createClass({ propTypes: { + versionId: React.PropTypes.string.isRequired, lastModified: React.PropTypes.instanceOf(Date), isLatest: React.PropTypes.bool, onChoose: React.PropTypes.func @@ -22,11 +24,15 @@ var VersionRow = React.createClass({ render: function () { var button; if (this.props.isLatest) { - button = ; + button = ; } else { - button = ; + button = [ + + {button} diff --git a/apps/src/utils.js b/apps/src/utils.js index c24f7256ea3a1..6d0f28e226e15 100644 --- a/apps/src/utils.js +++ b/apps/src/utils.js @@ -360,7 +360,7 @@ exports.unescapeText = function (text) { // First, convert every
tag that isn't at the very beginning of the string // to a newline. This avoids generating an incorrect blank line at the start // if the first line is wrapped in a
. - cleanedText = cleanedText.replace(/(?!^)
/gi, '\n'); + cleanedText = cleanedText.replace(/(?!^)]*>/gi, '\n'); cleanedText = cleanedText.replace(/<[^>]+>/gi, ''); // Strip all other tags cleanedText = cleanedText.replace(/ /gi, ' '); // Unescape nonbreaking spaces diff --git a/apps/test/ChartApiTest.js b/apps/test/applab/ChartApiTest.js similarity index 99% rename from apps/test/ChartApiTest.js rename to apps/test/applab/ChartApiTest.js index 47e70eb50600c..ec9f9c0b7498d 100644 --- a/apps/test/ChartApiTest.js +++ b/apps/test/applab/ChartApiTest.js @@ -1,7 +1,7 @@ 'use strict'; /* global describe, beforeEach, it, Promise */ -var assert = require('./util/testUtils').assert; +var assert = require('../util/testUtils').assert; var ChartApi = require('@cdo/apps/applab/ChartApi'); var GoogleChart = require('@cdo/apps/applab/GoogleChart'); diff --git a/apps/test/CrosshairOverlayTest.js b/apps/test/applab/CrosshairOverlayTest.js similarity index 91% rename from apps/test/CrosshairOverlayTest.js rename to apps/test/applab/CrosshairOverlayTest.js index cc27bf7cd3424..37832302860ec 100644 --- a/apps/test/CrosshairOverlayTest.js +++ b/apps/test/applab/CrosshairOverlayTest.js @@ -1,4 +1,4 @@ -var testUtils = require('./util/testUtils'); +var testUtils = require('../util/testUtils'); var assert = testUtils.assert; var CrosshairOverlay = require('@cdo/apps/applab/CrosshairOverlay'); diff --git a/apps/test/EventSandboxerTest.js b/apps/test/applab/EventSandboxerTest.js similarity index 99% rename from apps/test/EventSandboxerTest.js rename to apps/test/applab/EventSandboxerTest.js index 79ca5d393905d..b0253658edb09 100644 --- a/apps/test/EventSandboxerTest.js +++ b/apps/test/applab/EventSandboxerTest.js @@ -1,7 +1,7 @@ /** @file Tests of App Lab event sanitization. */ 'use strict'; -var testUtils = require('./util/testUtils'); +var testUtils = require('../util/testUtils'); var assert = testUtils.assert; var EventSandboxer = require('@cdo/apps/applab/EventSandboxer'); diff --git a/apps/test/applabTest.js b/apps/test/applab/applabTest.js similarity index 98% rename from apps/test/applabTest.js rename to apps/test/applab/applabTest.js index 7984222a42d66..8637c07c0ed50 100644 --- a/apps/test/applabTest.js +++ b/apps/test/applab/applabTest.js @@ -1,4 +1,4 @@ -var testUtils = require('./util/testUtils'); +var testUtils = require('../util/testUtils'); var assert = testUtils.assert; testUtils.setupLocales('applab'); testUtils.setExternalGlobals(); @@ -155,6 +155,11 @@ describe('getText/setText commands', function () { assert.equal(getInnerText(element), 'text\nwith\nnewlines'); }); + it('converts divs with attributes to newlines', function () { + element.innerHTML = 'Line 1
Line 2
'; + assert.equal(getInnerText(element), 'Line 1\nLine 2'); + }); + it('converts

to blank lines', function () { element.innerHTML = 'text

with


empty newlines
'; assert.equal(getInnerText(element), 'text\n\nwith\n\n\nempty newlines'); diff --git a/apps/test/dataCallbacksTests.js b/apps/test/applab/dataCallbacksTests.js similarity index 97% rename from apps/test/dataCallbacksTests.js rename to apps/test/applab/dataCallbacksTests.js index e146d8c7d9fbd..31a11c9b0ae2b 100644 --- a/apps/test/dataCallbacksTests.js +++ b/apps/test/applab/dataCallbacksTests.js @@ -1,5 +1,5 @@ var sinon = require('sinon'); -var testUtils = require('./util/testUtils'); +var testUtils = require('../util/testUtils'); var assert = testUtils.assert; testUtils.setupLocales('applab'); testUtils.setExternalGlobals(); diff --git a/apps/test/setPropertyDropdownTest.js b/apps/test/applab/setPropertyDropdownTest.js similarity index 96% rename from apps/test/setPropertyDropdownTest.js rename to apps/test/applab/setPropertyDropdownTest.js index ef9e00b5d8f27..5014140bc8679 100644 --- a/apps/test/setPropertyDropdownTest.js +++ b/apps/test/applab/setPropertyDropdownTest.js @@ -1,4 +1,4 @@ -var testUtils = require('./util/testUtils'); +var testUtils = require('../util/testUtils'); var assert = testUtils.assert; testUtils.setupLocales(); diff --git a/apps/test/util/runTests.js b/apps/test/util/runTests.js index 15e317af99e52..06c6646f619e3 100644 --- a/apps/test/util/runTests.js +++ b/apps/test/util/runTests.js @@ -50,7 +50,10 @@ exec(command, function (err, stdout, stderr) { var globs = [ './test/*.js', + './test/applab/*.js', './test/calc/*.js', + './test/craft/*.js', + './test/gamelab/*.js', './test/netsim/*.js' ]; diff --git a/apps/test/utilityTests.js b/apps/test/utilityTests.js index 02d7f83d5ea10..2a57484eeadd2 100644 --- a/apps/test/utilityTests.js +++ b/apps/test/utilityTests.js @@ -600,6 +600,28 @@ describe('utils.unescapeText', function () { assert.equal(expected, unescapeText(input)); }); + it('Adds line break for divs with attributes', function () { + var input, expected; + + input = 'Line 1
Line 2
'; + expected = [ + 'Line 1', + 'Line 2' + ].join('\n'); + assert.equal(expected, unescapeText(input), 'div with attribute'); + }); + + it('Does not add leading newline for span-wrapped leading line', function () { + var input, expected; + + input = 'Line1
Line2
'; + expected = [ + 'Line1', + 'Line2' + ].join('\n'); + assert.equal(expected, unescapeText(input), 'first line span'); + }); + it('If input starts with
treats that as the first line', function () { var input = '
Line 1
Line 2
'; var expected = [ diff --git a/aws/build b/aws/build deleted file mode 100755 index 830cf36950cf7..0000000000000 --- a/aws/build +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env ruby -# -# BUILD is core command in our polling-based continuous deployment system. It checks for upstream -# commits on the current branch. -# -# If no changes are detected it exits silently. -# -# If changes are detected, BUILD fetches them, gets the commit messages for them (written out as -# the temp file 'rebuild') and displays them in HipChat as a purple "build started" message. Then -# BUILD invokes BUILD.RAKE to run the build using dependency-based build rules. -# -# NOTE: When the build starts a BUILD-STARTED file is created, and this file is deleted when the -# build script finishes. This file is NOT deleted if the build system itself is broken and the -# presence of this file will cause the system to attempt to pull and rebuild constantly over and -# over until it succeeds. This used to happen frequently but as the build system has matured it -# now almost never happens. But, this is probably a safeguard to keep around because in the rare -# cases where the build scripts are damaged, this ensures the machines can be restored simply by -# checking in a fix (vs. needing to SSH to the machine and fix manually). -# -# NOTE: Creating the BUILD-STARTED (e.g. `ssh daemon.code.org "touch ~/production/build-started"`) is -# an easy way to force a rebuild without needing to make a commit. -# -require_relative '../deployment' -require 'cdo/rake_utils' -require 'cdo/hip_chat' -require 'cdo/only_one' - -def main() - Dir.chdir(deploy_dir) do - return 0 unless RakeUtils.git_updates_available? || File.file?('build-started') || !CDO.daemon - FileUtils.touch 'build-started' - - RakeUtils.git_fetch - count = RakeUtils.git_update_count - RakeUtils.git_pull if count > 0 - count = [1, count].max - - log = `git log --pretty=format:"%h %s (%an)" -n #{count}` - IO.write(deploy_dir('rebuild'), log) - - HipChat.log "https://github.com/code-dot-org/code-dot-org/commit/#{`git rev-parse HEAD`}", message_format: 'text', color: 'purple' - - # Ensure updated Gemfile.lock dependencies are installed. - RakeUtils.bundle_install - - # Run `sudo chef-client` to ensure that the deploy respect updates to the Chef config - # (e.g. the cdo-servers attribute describing which servers to deploy). - Dir.chdir(aws_dir) do - # Ensure the chef cookbooks are up to date. - RakeUtils.rake '--rakefile', 'build.rake', 'chef_update' - RakeUtils.system 'sudo', 'chef-client' - end - - status = 1 - Dir.chdir(aws_dir) do - status = RakeUtils.rake '--rakefile', 'build.rake', *ARGV - end - - FileUtils.rm 'build-started' if File.file?('build-started') - - exit status - end -end - -main() if only_one_running?(__FILE__) diff --git a/aws/build b/aws/build new file mode 120000 index 0000000000000..076b3c8c2197f --- /dev/null +++ b/aws/build @@ -0,0 +1 @@ +./ci_build \ No newline at end of file diff --git a/aws/build.rake b/aws/build.rake deleted file mode 100644 index f2b8545be18fe..0000000000000 --- a/aws/build.rake +++ /dev/null @@ -1,377 +0,0 @@ -# -*- coding: utf-8 -*- -# BUILD.RAKE used to contain everything that is now in the top-level Rakefile, i.e. it used to be -# the entire build system and developers needed to remember seperate steps to build each project -# or wait for CI. Since then, the building of projects has been moved out to the top-level Rakefile -# (which this now calls) and this Rakefile is responsible for the "integration" portions of continuous -# integration. -# -# This Rakefile CAN be confusing to read because it is the kind of Rakefile that globs the filesystem -# and then calls functions that generate the rules that are eventually invoked. -# - -require_relative '../deployment' -require 'cdo/rake_utils' -require 'cdo/hip_chat' -require 'cdo/only_one' -require 'shellwords' -require 'cdo/aws/cloudfront' -require 'cdo/aws/s3_packaging' - -# -# build_task - BUILDS a TASK that uses a hidden (.dotfile) to keep build steps idempotent. The file -# ".-built" dependes on the files listed in dependencies. If any of those are newer, build_task -# yields to the block provided and then updates ".-built"'s timestamp so that it is up-to-date -# with dependencies. In short, it let's create blocks of Ruby code that are only invoked when one of -# the dependent files changes. -# - -def format_duration(total_seconds) - total_seconds = total_seconds.to_i - minutes = (total_seconds / 60).to_i - seconds = total_seconds - (minutes * 60) - "%.1d:%.2d minutes" % [minutes, seconds] -end - -def with_hipchat_logging(name) - start_time = Time.now - HipChat.log "Running #{name}..." - yield if block_given? - HipChat.log "#{name} succeeded in #{format_duration(Time.now - start_time)}" - -rescue => e - # notify developers room and our own room - "#{name} failed in #{format_duration(Time.now - start_time)}".tap do |message| - HipChat.log message, color: 'red', notify: 1 - HipChat.developers message, color: 'red', notify: 1 - end - # log detailed error information in our own room - HipChat.log "/quote #{e}\n#{CDO.backtrace e}", message_format: 'text' - raise -end - -def build_task(name, dependencies=[], params={}) - path = aws_dir(".#{name}-built") - - file path => dependencies do - with_hipchat_logging(name) do - yield if block_given? - touch path - end - end - - path -end - -# -# threaded_each provide a simple way to process an array of elements using multiple threads. -# create_threads is a helper for threaded_each. -# -def create_threads(count) - [].tap do |threads| - count.times do - threads << Thread.new do - yield - end - end - end -end - -def threaded_each(array, thread_count=2) - # NOTE: Queue is used here because it is threadsafe - it is the ONLY threadsafe datatype in base ruby! - # Without Queue, the array would need to be protected using a Mutex. - queue = Queue.new.tap do |queue| - array.each do |i| - queue << i - end - end - - threads = create_threads(thread_count) do - until queue.empty? - next unless item = queue.pop(true) rescue nil - yield item if block_given? - end - end - - threads.each(&:join) -end - -# -# Define the BLOCKLY[-CORE] BUILD task -# -BLOCKLY_CORE_DEPENDENCIES = []#[aws_dir('build.rake')] -BLOCKLY_CORE_PRODUCT_FILES = Dir.glob(blockly_core_dir('build-output', '**/*')) -BLOCKLY_CORE_SOURCE_FILES = Dir.glob(blockly_core_dir('**/*')) - BLOCKLY_CORE_PRODUCT_FILES -BLOCKLY_CORE_TASK = build_task('blockly-core', BLOCKLY_CORE_DEPENDENCIES + BLOCKLY_CORE_SOURCE_FILES) do - # only let staging build/commit blockly-core - if rack_env?(:staging) - apps_sentinel = apps_dir('/lib/blockly/sentinel') - RakeUtils.rake '--rakefile', deploy_dir('Rakefile'), 'build:blockly_core' - HipChat.log 'Committing updated blockly core files...', color: 'purple' - message = "Automatically built.\n\n#{IO.read(deploy_dir('rebuild-apps'))}" - RakeUtils.system 'git', 'add', *BLOCKLY_CORE_PRODUCT_FILES - RakeUtils.system 'echo', "\"#{Time.new}\" >| #{apps_sentinel}" - RakeUtils.system 'git', 'add', apps_sentinel - RakeUtils.system 'git', 'commit', '-m', Shellwords.escape(message) - RakeUtils.git_push - end -end - -task :apps_task do - packager = S3Packaging.new('apps', apps_dir, dashboard_dir('public/apps-package')) - - updated_package = packager.update_from_s3 - if updated_package - HipChat.log "Downloaded apps package from S3: #{packager.commit_hash}" - next # no need to do anything if we already got a package from s3 - end - - # Test and staging are the only environments that should be uploading new packages - raise 'No valid apps package found' unless rack_env?(:staging) || rack_env?(:test) - - raise 'Wont build apps with staged changes' if RakeUtils.git_staged_changes?(apps_dir) - - HipChat.log 'Building apps...' - RakeUtils.system 'cp', deploy_dir('rebuild'), deploy_dir('rebuild-apps') - RakeUtils.rake '--rakefile', deploy_dir('Rakefile'), 'build:apps' - HipChat.log 'apps built' - - HipChat.log 'testing apps..' - Dir.chdir(apps_dir) { RakeUtils.system 'grunt test' } - HipChat.log 'apps test finished' - - # upload to s3 - package = packager.upload_package_to_s3('/build/package') - HipChat.log "Uploaded apps package to S3: #{packager.commit_hash}" - packager.decompress_package(package) -end - -# -# Define the CODE STUDIO BUILD task. -# -task :code_studio_task do - packager = S3Packaging.new('code-studio', code_studio_dir, dashboard_dir('public/code-studio-package')) - - updated_package = packager.update_from_s3 - if updated_package - HipChat.log "Downloaded code-studio package from S3: #{packager.commit_hash}" - next # no need to do anything if we already got a package from s3 - end - - # Test and staging are the only environments that should be uploading new packages - raise 'No valid code-studio package found' unless rack_env?(:staging) || rack_env?(:test) - - raise 'Wont build code-studio with staged changes' if RakeUtils.git_staged_changes?(code_studio_dir) - - HipChat.log 'Building code-studio...' - RakeUtils.system 'cp', deploy_dir('rebuild'), deploy_dir('rebuild-code-studio') - RakeUtils.rake '--rakefile', deploy_dir('Rakefile'), 'build:code_studio' - HipChat.log 'code-studio built' - - # upload to s3 - package = packager.upload_package_to_s3('/build') - HipChat.log "Uploaded code-studio package to S3: #{packager.commit_hash}" - packager.decompress_package(package) -end - -file deploy_dir('rebuild') do - touch deploy_dir('rebuild') -end - -def stop_frontend(name, host, log_path) - # NOTE: The order of these prefers deploy-speed over user-experience. Stopping varnish first - # immediately terminates connections to the backends so they stop immediately. Changing the - # order to stop Dashboard and Pegasus first would drain the user connections before stopping - # which can take a minutes. The Load Balancer has health-checks that will pull the instances - # out of rotation and those checks can be directed at either varnish itself, the services through - # varnish, or the services directly. So, changing the order here means evalulating whether or not - # the ELB health-checks make sense to begin diverting traffic at the right time (vs. returning 503s) - command = [ - 'sudo service varnish stop', - 'sudo service dashboard stop', - 'sudo service pegasus stop', - ].join(' ; ') - - RakeUtils.system 'ssh', '-i', '~/.ssh/deploy-id_rsa', host, "'#{command} 2>&1'", '>>', log_path -end - -# -# upgrade_frontend - this is called by daemon to update the user-facing front-end instances. for -# this to work, daemon has an SSH key (deploy-id_rsa) that gives it access to connect to the front-ends. -# -def upgrade_frontend(name, host) - commands = [ - "cd #{rack_env}", - 'git pull --ff-only', - 'sudo bundle install', - 'rake build', - ] - command = commands.join(' && ') - - HipChat.log "Upgrading #{name} (#{host})..." - - log_path = aws_dir "deploy-#{name}.log" - - # Stop the frontend before running the commands so that the git pull doesn't modify files - # out from under a running instance. The rake build command will restart the instance. - stop_frontend name, host, log_path - - success = false - begin - RakeUtils.system 'ssh', '-i', '~/.ssh/deploy-id_rsa', host, "'#{command} 2>&1'", '>', log_path - #HipChat.log "Upgraded #{name} (#{host})." - success = true - rescue - HipChat.log "#{name} (#{host}) failed to upgrade, removing from rotation.", color: 'red' - # The frontend is in indeterminate state, so make sure it is stopped. - stop_frontend name, host, log_path - success = false - end - - puts IO.read log_path - success -end - -# Synchronize the Chef cookbooks to the Chef repo for this environment using Berkshelf. -task :chef_update do - if CDO.daemon && CDO.chef_managed - RakeUtils.with_bundle_dir(cookbooks_dir) do - RakeUtils.bundle_exec 'berks', 'install' - RakeUtils.bundle_exec 'berks', 'upload', (rack_env?(:production) ? '' : '--no-freeze') - RakeUtils.bundle_exec 'berks', 'apply', rack_env - end - end -end - -# Deploy updates to CloudFront in parallel with the local build to optimize total CI build time. -multitask build_with_cloudfront: [:build, :cloudfront] - -# Update CloudFront distribution with any changes to the http cache configuration. -# If there are changes to be applied, the update can take 15 minutes to complete. -task :cloudfront do - if CDO.daemon && CDO.chef_managed - with_hipchat_logging('Update CloudFront') do - AWS::CloudFront.create_or_update - end - end -end - -# Perform a normal local build by calling the top-level Rakefile. -# Additionally run the lint task if specified for the environment. -task build: [:chef_update] do - Dir.chdir(deploy_dir) do - with_hipchat_logging("rake lint") do - RakeUtils.rake 'lint' if CDO.lint - end - with_hipchat_logging("rake build") do - RakeUtils.rake 'build' - end - end -end - -# Update the front-end instances, in parallel, updating up to 20% of the -# instances at any one time. -MAX_FRONTEND_UPGRADE_FAILURES = 5 -task :deploy do - with_hipchat_logging("deploy frontends") do - if CDO.daemon && CDO.app_servers.any? - Dir.chdir(deploy_dir) do - num_failures = 0 - thread_count = (CDO.app_servers.keys.length * 0.20).ceil - threaded_each CDO.app_servers.keys, thread_count do |name| - succeeded = upgrade_frontend name, CDO.app_servers[name] - if !succeeded - num_failures += 1 - raise 'too many frontend upgrade failures, aborting deploy' if num_failures > MAX_FRONTEND_UPGRADE_FAILURES - end - end - end - end - end -end - -$websites = build_task('websites', [deploy_dir('rebuild'), BLOCKLY_CORE_TASK, :apps_task, :code_studio_task, :build_with_cloudfront, :deploy]) -task 'websites' => [$websites] {} - -task :pegasus_unit_tests do - Dir.chdir(pegasus_dir) do - with_hipchat_logging("pegasus ruby unit tests") do - RakeUtils.rake 'test' - end - end -end - -task :shared_unit_tests do - Dir.chdir(shared_dir) do - with_hipchat_logging("shared ruby unit tests") do - RakeUtils.rake 'test' - end - end -end - -task :dashboard_unit_tests do - Dir.chdir(dashboard_dir) do - name = "dashboard ruby unit tests" - with_hipchat_logging(name) do - # Unit tests mess with the database so stop the service before running them - RakeUtils.stop_service CDO.dashboard_unicorn_name - RakeUtils.rake 'db:schema:load' - RakeUtils.rake 'test' - RakeUtils.rake "seed:all" - RakeUtils.start_service CDO.dashboard_unicorn_name - end - end -end - -task :ui_test_flakiness do - Dir.chdir(deploy_dir) do - flakiness_output = `./bin/test_flakiness 5` - HipChat.log "Flakiest tests:
#{flakiness_output}
" - end -end - -UI_TEST_SYMLINK = dashboard_dir 'public/ui_test' -file UI_TEST_SYMLINK do - Dir.chdir(dashboard_dir('public')) do - RakeUtils.system_ 'ln', '-s', '../test/ui', 'ui_test' - end -end - -task :regular_ui_tests => [UI_TEST_SYMLINK] do - Dir.chdir(dashboard_dir('test/ui')) do - HipChat.log 'Running dashboard UI tests...' - failed_browser_count = RakeUtils.system_with_hipchat_logging 'bundle', 'exec', './runner.rb', '-d', 'test-studio.code.org', '--parallel', '70', '--magic_retry', '--html', '--fail_fast' - if failed_browser_count == 0 - message = '┬──┬ ノ( ゜-゜ノ) UI tests for dashboard succeeded.' - HipChat.log message - HipChat.developers message, color: 'green' - else - message = "(╯°□°)╯︵ ┻━┻ UI tests for dashboard failed on #{failed_browser_count} browser(s)." - HipChat.log message, color: 'red' - HipChat.developers message, color: 'red', notify: 1 - end - end -end - -task :eyes_ui_tests => [UI_TEST_SYMLINK] do - Dir.chdir(dashboard_dir('test/ui')) do - HipChat.log 'Running dashboard UI visual tests...' - eyes_features = `grep -lr '@eyes' features`.split("\n") - failed_browser_count = RakeUtils.system_with_hipchat_logging 'bundle', 'exec', './runner.rb', '-c', 'ChromeLatestWin7,iPhone', '-d', 'test-studio.code.org', '--eyes', '--html', '-f', eyes_features.join(","), '--parallel', (eyes_features.count * 2).to_s - if failed_browser_count == 0 - message = '⊙‿⊙ Eyes tests for dashboard succeeded, no changes detected.' - HipChat.log message - HipChat.developers message, color: 'green' - else - message = 'ಠ_ಠ Eyes tests for dashboard failed. See
the console for results or to modify baselines.' - HipChat.log message, color: 'red' - HipChat.developers message, color: 'red', notify: 1 - end - end -end - -# do the eyes and browserstack ui tests in parallel -multitask ui_tests: [:eyes_ui_tests, :regular_ui_tests] - -$websites_test = build_task('websites-test', [deploy_dir('rebuild'), BLOCKLY_CORE_TASK, :apps_task, :code_studio_task, :build_with_cloudfront, :deploy, :pegasus_unit_tests, :shared_unit_tests, :dashboard_unit_tests, :ui_test_flakiness, :ui_tests]) - -task 'test-websites' => [$websites_test] diff --git a/aws/build.rake b/aws/build.rake new file mode 120000 index 0000000000000..31ff7721e4cc7 --- /dev/null +++ b/aws/build.rake @@ -0,0 +1 @@ +./ci_build.rake \ No newline at end of file diff --git a/aws/build_and_mail_log b/aws/build_and_mail_log deleted file mode 100755 index 23b310f6e8b2e..0000000000000 --- a/aws/build_and_mail_log +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env ruby -# -# BUILD_AND_MAIL_LOG is a terrible name. The MAIL_LOG part was split out into CRONJOB. -# -# This script actually defers to BUILD for the actual building and then delivers a -# higher fidelity set of HipChat notifications than CRONJOB provides, e.g. red-colored -# notification messages when the build fails, gray colored blocks with Git commit -# messages, etc. -# -# FUTURE DIRECTION: This should probably be collapsed into BUILD itself. -# - -require_relative '../deployment' -require 'cdo/rake_utils' -require 'cdo/hip_chat' -require 'cdo/only_one' -require 'mail' - -def format_duration(total_seconds) - total_seconds = total_seconds.to_i - minutes = (total_seconds / 60).to_i - seconds = total_seconds - (minutes * 60) - "%.1d:%.2d minutes" % [minutes, seconds] -end - -def main() - start_time = Time.now.to_i - # Run the build script and exit if it didn't do anything. - script = File.expand_path('../build', __FILE__) - log = `\"#{script}\" #{ARGV.join(' ')} 2>&1`.strip - status = $?.exitstatus - return status if status == 0 && log.empty? - - Dir.chdir(deploy_dir) do - commit_url = "https://github.com/code-dot-org/code-dot-org/commit/#{RakeUtils.git_revision}" - # Notify the HipChat channels about what happened. - projects = ARGV.join(' ') - time_message = " 🕐 #{format_duration(Time.now.to_i - start_time)}" - if status == 0 - message = "#{projects} built." + time_message - - HipChat.log message, color: 'green' - - HipChat.developers message, color: 'green' - HipChat.developers commit_url, color: 'gray', message_format: 'text' - else - message = "#{projects} failed to build!" + time_message - - HipChat.log message, color: 'red' - HipChat.log "/quote #{log}", color: 'gray', message_format: 'text' - - HipChat.developers message, color: 'red', notify: 1 - HipChat.developers commit_url, color: 'gray', message_format: 'text' - end - end - - # Return the same output and status code that BUILD returned. - puts log - status -end - -main() if only_one_running?(__FILE__) diff --git a/aws/build_and_mail_log b/aws/build_and_mail_log new file mode 120000 index 0000000000000..076b3c8c2197f --- /dev/null +++ b/aws/build_and_mail_log @@ -0,0 +1 @@ +./ci_build \ No newline at end of file diff --git a/aws/ci_build b/aws/ci_build new file mode 100755 index 0000000000000..b926c2926b11d --- /dev/null +++ b/aws/ci_build @@ -0,0 +1,124 @@ +#!/usr/bin/env ruby +# +# CI_BUILD is the core command in our polling-based continuous deployment system. It checks for upstream +# commits on the current branch. +# +# If no changes are detected it exits silently. +# +# If changes are detected, CI_BUILD fetches them, gets the commit messages for them (written out as +# the temp file 'rebuild') and displays them in chat as a purple "build started" message. Then +# CI_BUILD invokes CI_BUILD.RAKE to run the build using dependency-based build rules. +# +# NOTE: When the build starts a BUILD-STARTED file is created, and this file is deleted when the +# build script finishes. This file is NOT deleted if the build system itself is broken and the +# presence of this file will cause the system to attempt to pull and rebuild constantly over and +# over until it succeeds. This may happen often, but it is probably a safeguard to keep around because +# in the rare cases where the build scripts are damaged, this ensures the machines can be restored simply +# by checking in a fix (vs. needing to SSH to the machine and fix manually). +# +# NOTE: running `touch build-started` (or running the `bin/start-build` script) +# is an easy way to force a rebuild without needing to make a commit. +# +require_relative '../deployment' +require 'cdo/rake_utils' +require 'cdo/hip_chat' +require 'cdo/only_one' +require 'mail' + +STARTED = 'build-started' + +def format_duration(total_seconds) + total_seconds = total_seconds.to_i + minutes = (total_seconds / 60).to_i + seconds = total_seconds - (minutes * 60) + "%.1d:%.2d minutes" % [minutes, seconds] +end + +def capture_stdout + old_stdout = $stdout + $stdout = StringIO.new('', 'w') + yield + $stdout.string +ensure + $stdout = old_stdout +end + +def build() + Dir.chdir(deploy_dir) do + return 0 unless RakeUtils.git_updates_available? || File.file?(STARTED) || !CDO.daemon + FileUtils.touch STARTED + + RakeUtils.git_fetch + count = RakeUtils.git_update_count + RakeUtils.git_pull if count > 0 + count = [1, count].max + + log = `git log --pretty=format:"%h %s (%an)" -n #{count}` + IO.write(deploy_dir('rebuild'), log) + + HipChat.log "https://github.com/code-dot-org/code-dot-org/commit/#{`git rev-parse HEAD`}", message_format: 'text', color: 'purple' + + # Ensure updated Gemfile.lock dependencies are installed. + RakeUtils.bundle_install + + # Run `sudo chef-client` to ensure that the deploy respect updates to the Chef config + # (e.g. the cdo-servers attribute describing which servers to deploy). + Dir.chdir(aws_dir) do + # Ensure the chef cookbooks are up to date. + RakeUtils.rake '--rakefile', 'ci_build.rake', 'chef_update' + RakeUtils.system 'sudo', 'chef-client' + end + + status = 0 + Dir.chdir(aws_dir) do + status = begin + RakeUtils.rake '--rakefile', 'ci_build.rake', *ARGV + rescue => e + CDO.backtrace e + end + end + + FileUtils.rm STARTED if File.file?(STARTED) + + status + end +end + +def main() + start_time = Time.now.to_i + # Run the build command and exit if it didn't do anything. + status = 0 + log = capture_stdout { status = build } + return status if status == 0 && log.empty? + + Dir.chdir(deploy_dir) do + commit_url = "https://github.com/code-dot-org/code-dot-org/commit/#{RakeUtils.git_revision}" + # Notify the HipChat channels about what happened. + projects = ARGV.join(' ') + projects = 'websites' if projects.empty? + time_message = " 🕐 #{format_duration(Time.now.to_i - start_time)}" + if status == 0 + message = "#{projects} built." + time_message + + HipChat.log message, color: 'green' + + HipChat.developers message, color: 'green' + HipChat.developers commit_url, color: 'gray', message_format: 'text' + else + message = "#{projects} failed to build!" + time_message + + HipChat.log message, color: 'red' + HipChat.log "/quote #{log}", color: 'gray', message_format: 'text' + HipChat.log "/quote #{status}", color: 'gray', message_format: 'text' + + HipChat.developers message, color: 'red', notify: 1 + HipChat.developers commit_url, color: 'gray', message_format: 'text' + end + end + + # Return the same output and status code that BUILD returned. + puts log + status +end + +main() if only_one_running?(__FILE__) diff --git a/aws/ci_build.rake b/aws/ci_build.rake new file mode 100644 index 0000000000000..4c78e7f016d64 --- /dev/null +++ b/aws/ci_build.rake @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- +# CI_BUILD.RAKE used to contain everything that is now in the top-level Rakefile, i.e. it used to be +# the entire build system and developers needed to remember separate steps to build each project +# or wait for CI. Since then, the building of projects has been moved out to the top-level Rakefile +# (which this now calls) and this Rakefile is responsible for the "integration" portions of continuous +# integration. +# +# This Rakefile CAN be confusing to read because it is the kind of Rakefile that globs the filesystem +# and then calls functions that generate the rules that are eventually invoked. +# + +require_relative '../deployment' +require 'cdo/rake_utils' +require 'cdo/hip_chat' +require 'cdo/only_one' +require 'shellwords' +require 'cdo/aws/cloudfront' +require 'cdo/aws/s3_packaging' + +def format_duration(total_seconds) + total_seconds = total_seconds.to_i + minutes = (total_seconds / 60).to_i + seconds = total_seconds - (minutes * 60) + "%.1d:%.2d minutes" % [minutes, seconds] +end + +def with_hipchat_logging(name) + start_time = Time.now + HipChat.log "Running #{name}..." + yield if block_given? + HipChat.log "#{name} succeeded in #{format_duration(Time.now - start_time)}" + +rescue => e + # notify developers room and our own room + "#{name} failed in #{format_duration(Time.now - start_time)}".tap do |message| + HipChat.log message, color: 'red', notify: 1 + HipChat.developers message, color: 'red', notify: 1 + end + # log detailed error information in our own room + HipChat.log "/quote #{e}\n#{CDO.backtrace e}", message_format: 'text' + raise +end + +# +# build_task - BUILDS a TASK that uses a hidden (.dotfile) to keep build steps idempotent. The file +# ".-built" depends on the files listed in dependencies. If any of those are newer, build_task +# yields to the block provided and then updates ".-built"'s timestamp so that it is up-to-date +# with dependencies. In short, it let's create blocks of Ruby code that are only invoked when one of +# the dependent files changes. +# +def build_task(name, dependencies=[], params={}) + path = aws_dir(".#{name}-built") + + file path => dependencies do + with_hipchat_logging(name) do + yield if block_given? + touch path + end + end + + path +end + +# +# threaded_each provide a simple way to process an array of elements using multiple threads. +# create_threads is a helper for threaded_each. +# +def create_threads(count) + [].tap do |threads| + count.times do + threads << Thread.new do + yield + end + end + end +end + +def threaded_each(array, thread_count=2) + # NOTE: Queue is used here because it is threadsafe - it is the ONLY threadsafe datatype in base ruby! + # Without Queue, the array would need to be protected using a Mutex. + queue = Queue.new.tap do |queue| + array.each do |i| + queue << i + end + end + + threads = create_threads(thread_count) do + until queue.empty? + next unless item = queue.pop(true) rescue nil + yield item if block_given? + end + end + + threads.each(&:join) +end + +# +# Define the BLOCKLY[-CORE] BUILD task +# +BLOCKLY_CORE_DEPENDENCIES = []#[aws_dir('build.rake')] +BLOCKLY_CORE_PRODUCT_FILES = Dir.glob(blockly_core_dir('build-output', '**/*')) +BLOCKLY_CORE_SOURCE_FILES = Dir.glob(blockly_core_dir('**/*')) - BLOCKLY_CORE_PRODUCT_FILES +BLOCKLY_CORE_TASK = build_task('blockly-core', BLOCKLY_CORE_DEPENDENCIES + BLOCKLY_CORE_SOURCE_FILES) do + # only let staging build/commit blockly-core + if rack_env?(:staging) + apps_sentinel = apps_dir('/lib/blockly/sentinel') + RakeUtils.rake '--rakefile', deploy_dir('Rakefile'), 'build:blockly_core' + HipChat.log 'Committing updated blockly core files...', color: 'purple' + message = "Automatically built.\n\n#{IO.read(deploy_dir('rebuild-apps'))}" + RakeUtils.system 'git', 'add', *BLOCKLY_CORE_PRODUCT_FILES + RakeUtils.system 'echo', "\"#{Time.new}\" >| #{apps_sentinel}" + RakeUtils.system 'git', 'add', apps_sentinel + RakeUtils.system 'git', 'commit', '-m', Shellwords.escape(message) + RakeUtils.git_push + end +end + +task :apps_task do + packager = S3Packaging.new('apps', apps_dir, dashboard_dir('public/apps-package')) + + updated_package = packager.update_from_s3 + if updated_package + HipChat.log "Downloaded apps package from S3: #{packager.commit_hash}" + next # no need to do anything if we already got a package from s3 + end + + # Test and staging are the only environments that should be uploading new packages + raise 'No valid apps package found' unless rack_env?(:staging) || rack_env?(:test) + + raise 'Wont build apps with staged changes' if RakeUtils.git_staged_changes?(apps_dir) + + HipChat.log 'Building apps...' + RakeUtils.system 'cp', deploy_dir('rebuild'), deploy_dir('rebuild-apps') + RakeUtils.rake '--rakefile', deploy_dir('Rakefile'), 'build:apps' + HipChat.log 'apps built' + + HipChat.log 'testing apps..' + Dir.chdir(apps_dir) { RakeUtils.system 'grunt test' } + HipChat.log 'apps test finished' + + # upload to s3 + package = packager.upload_package_to_s3('/build/package') + HipChat.log "Uploaded apps package to S3: #{packager.commit_hash}" + packager.decompress_package(package) +end + +# +# Define the CODE STUDIO BUILD task. +# +task :code_studio_task do + packager = S3Packaging.new('code-studio', code_studio_dir, dashboard_dir('public/code-studio-package')) + + updated_package = packager.update_from_s3 + if updated_package + HipChat.log "Downloaded code-studio package from S3: #{packager.commit_hash}" + next # no need to do anything if we already got a package from s3 + end + + # Test and staging are the only environments that should be uploading new packages + raise 'No valid code-studio package found' unless rack_env?(:staging) || rack_env?(:test) + + raise 'Wont build code-studio with staged changes' if RakeUtils.git_staged_changes?(code_studio_dir) + + HipChat.log 'Building code-studio...' + RakeUtils.system 'cp', deploy_dir('rebuild'), deploy_dir('rebuild-code-studio') + RakeUtils.rake '--rakefile', deploy_dir('Rakefile'), 'build:code_studio' + HipChat.log 'code-studio built' + + # upload to s3 + package = packager.upload_package_to_s3('/build') + HipChat.log "Uploaded code-studio package to S3: #{packager.commit_hash}" + packager.decompress_package(package) +end + +file deploy_dir('rebuild') do + touch deploy_dir('rebuild') +end + +def stop_frontend(name, host, log_path) + # NOTE: The order of these prefers deploy-speed over user-experience. Stopping varnish first + # immediately terminates connections to the backends so they stop immediately. Changing the + # order to stop Dashboard and Pegasus first would drain the user connections before stopping + # which can take a minutes. The Load Balancer has health-checks that will pull the instances + # out of rotation and those checks can be directed at either varnish itself, the services through + # varnish, or the services directly. So, changing the order here means evalulating whether or not + # the ELB health-checks make sense to begin diverting traffic at the right time (vs. returning 503s) + command = [ + 'sudo service varnish stop', + 'sudo service dashboard stop', + 'sudo service pegasus stop', + ].join(' ; ') + + RakeUtils.system 'ssh', '-i', '~/.ssh/deploy-id_rsa', host, "'#{command} 2>&1'", '>>', log_path +end + +# +# upgrade_frontend - this is called by daemon to update the user-facing front-end instances. for +# this to work, daemon has an SSH key (deploy-id_rsa) that gives it access to connect to the front-ends. +# +def upgrade_frontend(name, host) + commands = [ + "cd #{rack_env}", + 'git pull --ff-only', + 'sudo bundle install', + 'rake build', + ] + command = commands.join(' && ') + + HipChat.log "Upgrading #{name} (#{host})..." + + log_path = aws_dir "deploy-#{name}.log" + + # Stop the frontend before running the commands so that the git pull doesn't modify files + # out from under a running instance. The rake build command will restart the instance. + stop_frontend name, host, log_path + + success = false + begin + RakeUtils.system 'ssh', '-i', '~/.ssh/deploy-id_rsa', host, "'#{command} 2>&1'", '>', log_path + #HipChat.log "Upgraded #{name} (#{host})." + success = true + rescue + HipChat.log "#{name} (#{host}) failed to upgrade, removing from rotation.", color: 'red' + # The frontend is in indeterminate state, so make sure it is stopped. + stop_frontend name, host, log_path + success = false + end + + puts IO.read log_path + success +end + +# Synchronize the Chef cookbooks to the Chef repo for this environment using Berkshelf. +task :chef_update do + if CDO.daemon && CDO.chef_managed + RakeUtils.with_bundle_dir(cookbooks_dir) do + RakeUtils.bundle_exec 'berks', 'install' + RakeUtils.bundle_exec 'berks', 'upload', (rack_env?(:production) ? '' : '--no-freeze') + RakeUtils.bundle_exec 'berks', 'apply', rack_env + end + end +end + +# Deploy updates to CloudFront in parallel with the local build to optimize total CI build time. +multitask build_with_cloudfront: [:build, :cloudfront] + +# Update CloudFront distribution with any changes to the http cache configuration. +# If there are changes to be applied, the update can take 15 minutes to complete. +task :cloudfront do + if CDO.daemon && CDO.chef_managed + with_hipchat_logging('Update CloudFront') do + AWS::CloudFront.create_or_update + end + end +end + +# Perform a normal local build by calling the top-level Rakefile. +# Additionally run the lint task if specified for the environment. +task build: [:chef_update] do + Dir.chdir(deploy_dir) do + with_hipchat_logging("rake lint") do + RakeUtils.rake 'lint' if CDO.lint + end + with_hipchat_logging("rake build") do + RakeUtils.rake 'build' + end + end +end + +# Update the front-end instances, in parallel, updating up to 20% of the +# instances at any one time. +MAX_FRONTEND_UPGRADE_FAILURES = 5 +task :deploy do + with_hipchat_logging("deploy frontends") do + if CDO.daemon && CDO.app_servers.any? + Dir.chdir(deploy_dir) do + num_failures = 0 + thread_count = (CDO.app_servers.keys.length * 0.20).ceil + threaded_each CDO.app_servers.keys, thread_count do |name| + succeeded = upgrade_frontend name, CDO.app_servers[name] + if !succeeded + num_failures += 1 + raise 'too many frontend upgrade failures, aborting deploy' if num_failures > MAX_FRONTEND_UPGRADE_FAILURES + end + end + end + end + end +end + +$websites = build_task('websites', [ + deploy_dir('rebuild'), + BLOCKLY_CORE_TASK, + :apps_task, + :code_studio_task, + :build_with_cloudfront, + :deploy +]) + +task 'websites' => [$websites] {} + +task :pegasus_unit_tests do + Dir.chdir(pegasus_dir) do + with_hipchat_logging("pegasus ruby unit tests") do + RakeUtils.rake 'test' + end + end +end + +task :shared_unit_tests do + Dir.chdir(shared_dir) do + with_hipchat_logging("shared ruby unit tests") do + RakeUtils.rake 'test' + end + end +end + +task :dashboard_unit_tests do + Dir.chdir(dashboard_dir) do + name = "dashboard ruby unit tests" + with_hipchat_logging(name) do + # Unit tests mess with the database so stop the service before running them + RakeUtils.stop_service CDO.dashboard_unicorn_name + RakeUtils.rake 'db:schema:load' + RakeUtils.rake 'test' + RakeUtils.rake "seed:all" + RakeUtils.start_service CDO.dashboard_unicorn_name + end + end +end + +task :ui_test_flakiness do + Dir.chdir(deploy_dir) do + flakiness_output = `./bin/test_flakiness 5` + HipChat.log "Flakiest tests:
#{flakiness_output}
" + end +end + +UI_TEST_SYMLINK = dashboard_dir 'public/ui_test' +file UI_TEST_SYMLINK do + Dir.chdir(dashboard_dir('public')) do + RakeUtils.system_ 'ln', '-s', '../test/ui', 'ui_test' + end +end + +task :regular_ui_tests => [UI_TEST_SYMLINK] do + Dir.chdir(dashboard_dir('test/ui')) do + HipChat.log 'Running dashboard UI tests...' + failed_browser_count = RakeUtils.system_with_hipchat_logging 'bundle', 'exec', './runner.rb', '-d', 'test-studio.code.org', '--parallel', '70', '--magic_retry', '--html', '--fail_fast' + if failed_browser_count == 0 + message = '┬──┬ ノ( ゜-゜ノ) UI tests for dashboard succeeded.' + HipChat.log message + HipChat.developers message, color: 'green' + else + message = "(╯°□°)╯︵ ┻━┻ UI tests for dashboard failed on #{failed_browser_count} browser(s)." + HipChat.log message, color: 'red' + HipChat.developers message, color: 'red', notify: 1 + end + end +end + +task :eyes_ui_tests => [UI_TEST_SYMLINK] do + Dir.chdir(dashboard_dir('test/ui')) do + HipChat.log 'Running dashboard UI visual tests...' + eyes_features = `grep -lr '@eyes' features`.split("\n") + failed_browser_count = RakeUtils.system_with_hipchat_logging 'bundle', 'exec', './runner.rb', '-c', 'ChromeLatestWin7,iPhone', '-d', 'test-studio.code.org', '--eyes', '--html', '-f', eyes_features.join(","), '--parallel', (eyes_features.count * 2).to_s + if failed_browser_count == 0 + message = '⊙‿⊙ Eyes tests for dashboard succeeded, no changes detected.' + HipChat.log message + HipChat.developers message, color: 'green' + else + message = 'ಠ_ಠ Eyes tests for dashboard failed. See the console for results or to modify baselines.' + HipChat.log message, color: 'red' + HipChat.developers message, color: 'red', notify: 1 + end + end +end + +# do the eyes and sauce labs ui tests in parallel +multitask ui_tests: [:eyes_ui_tests, :regular_ui_tests] + +$websites_test = build_task('websites-test', [ + 'websites', + :pegasus_unit_tests, + :shared_unit_tests, + :dashboard_unit_tests, + :ui_test_flakiness, + :ui_tests +]) + +task 'test-websites' => [$websites_test] +task 'default' => rack_env?(:test) ? 'test-websites' : 'websites' diff --git a/bin/cronjob b/bin/cronjob index a19c1a602c606..acdb86d0c742b 100755 --- a/bin/cronjob +++ b/bin/cronjob @@ -7,16 +7,13 @@ # SUCCESS (no notifications): The process exited with a 0 exit code with no output (after # stripping whitespace). # -# WARNING (email notification, HipChat yellow message): The process exited with a non-0 exit code +# WARNING (email notification): The process exited with a non-0 exit code # or returned output. # # ERROR (not currently differentiated): A non-0 exit code could be elevated to mean error and # result in a red HipChat message and/or channel notification. # -# FUTURE THOUGHTS: It may make sense to deliver WARNING and ERROR cases to HoneyBadger.io in -# addition to (or instead of) HipChat. -# -# CRONJOBs are defined the crontab: `chef/cookbooks/cdo-apps/templates/default/crontab.erb`. +# CRONJOBs are defined the cdo-apps::crontab Chef recipe: `chef/cookbooks/cdo-apps/recipes/crontab.rb`. # require_relative '../deployment.rb' diff --git a/bin/start-build b/bin/start-build index a642dd9ca163c..c0c9ec76ffb2d 100755 --- a/bin/start-build +++ b/bin/start-build @@ -2,8 +2,8 @@ require_relative '../deployment.rb' require 'cdo/hip_chat' -# restarts the build on chef-managed servers by touching build-started, -# which a cron job looks for every minute as a signal to restart the build. +# restarts the build on daemon servers by touching build-started, +# where a cron job looks for every minute as a signal to restart the build. raise "#{$0} does not work in development mode" if rack_env?(:development) diff --git a/code-studio/src/js/clientState.js b/code-studio/src/js/clientState.js index bf5ba0af3f431..173d34bf25369 100644 --- a/code-studio/src/js/clientState.js +++ b/code-studio/src/js/clientState.js @@ -9,6 +9,8 @@ var sessionStorage = window.sessionStorage; var clientState = module.exports = {}; +clientState.queryParams = require('./utils').queryParams; + /** * Number of days before client state cookie expires. * @type {number} @@ -32,27 +34,6 @@ clientState.reset = function () { } catch (e) {} }; -/** - * Gets the URL querystring params. - * @param name {string=} Optionally pull a specific param. - * @return {object|string} Hash of params, or param string if `name` is specified. - */ -clientState.queryParams = function (name) { - var pairs = location.search.substr(1).split('&'); - var params = {}; - pairs.forEach(function (pair) { - var split = pair.split('='); - if (split.length === 2) { - params[split[0]] = split[1]; - } - }); - - if (name) { - return params[name]; - } - return params; -}; - /** * Returns the client-cached copy of the level source for the given script * level, if it's newer than the given timestamp. diff --git a/code-studio/src/js/initApp/initApp.js b/code-studio/src/js/initApp/initApp.js index 19a010ab8c7e9..1225e355e8bac 100644 --- a/code-studio/src/js/initApp/initApp.js +++ b/code-studio/src/js/initApp/initApp.js @@ -45,7 +45,7 @@ window.apps = { if (appOptions.level.projectTemplateLevelName || appOptions.app === 'applab' || appOptions.app === 'gamelab') { $('#clear-puzzle-header').hide(); // Only show Version History button if the user owns this project - if (project.isOwner()) { + if (project.isEditable()) { $('#versions-header').show(); } } diff --git a/code-studio/src/js/initApp/project.js b/code-studio/src/js/initApp/project.js index 423d0a29c3d92..48710c3afc860 100644 --- a/code-studio/src/js/initApp/project.js +++ b/code-studio/src/js/initApp/project.js @@ -15,6 +15,7 @@ var channels = require('./clientApi').create('/v3/channels'); var showProjectAdmin = require('../showProjectAdmin'); var header = require('../header'); +var queryParams = require('../utils').queryParams; // Name of the packed source file var SOURCE_FILE = 'main.json'; @@ -145,6 +146,11 @@ var projects = module.exports = { }); }, + /** + * Is the current project (if any) editable by the logged in user (if any)? + */ + isEditable: isEditable, + /** * @returns {boolean} true if we're frozen */ @@ -594,7 +600,7 @@ var projects = module.exports = { fetchAbuseScore(function () { deferred.resolve(); }); - }); + }, queryParams('version')); } }); } else { @@ -612,7 +618,7 @@ var projects = module.exports = { fetchAbuseScore(function () { deferred.resolve(); }); - }); + }, queryParams('version')); } }); } else { @@ -641,8 +647,9 @@ var projects = module.exports = { * sources api * @param {object} channelData Data we fetched from channels api * @param {function} callback + * @param {string?} version Optional version to load */ -function fetchSource(channelData, callback) { +function fetchSource(channelData, callback, version) { // Explicitly remove levelSource/levelHtml from channels delete channelData.levelSource; delete channelData.levelHtml; @@ -654,12 +661,16 @@ function fetchSource(channelData, callback) { projects.setTitle(current.name); if (channelData.migratedToS3) { - sources.fetch(current.id + '/' + SOURCE_FILE, function (err, data) { + var url = current.id + '/' + SOURCE_FILE; + if (version) { + url += '?version=' + version; + } + sources.fetch(url, function (err, data) { if (err) { console.warn('unable to fetch project source file', err); data = { source: '', - html: '', + html: '' }; } unpackSources(data); @@ -698,7 +709,7 @@ function executeCallback(callback, data) { * is the current project (if any) editable by the logged in user (if any)? */ function isEditable() { - return (current && current.isOwner && !current.frozen); + return current && current.isOwner && !current.frozen && !queryParams('version'); } /** @@ -747,7 +758,6 @@ function redirectFromHashUrl() { return false; } - var pathInfo = parsePath(); location.href = newUrl; return true; } @@ -765,18 +775,20 @@ function parsePath() { pathname += location.hash.replace('#', '/'); } - if (pathname.split('/')[PathPart.PROJECTS] !== 'p' && - pathname.split('/')[PathPart.PROJECTS] !== 'projects') { + var tokens = pathname.split('/'); + + if (tokens[PathPart.PROJECTS] !== 'p' && + tokens[PathPart.PROJECTS] !== 'projects') { return { appName: null, channelId: null, - action: null, + action: null }; } return { - appName: pathname.split('/')[PathPart.APP], - channelId: pathname.split('/')[PathPart.CHANNEL_ID], - action: pathname.split('/')[PathPart.ACTION] + appName: tokens[PathPart.APP], + channelId: tokens[PathPart.CHANNEL_ID], + action: tokens[PathPart.ACTION] }; } diff --git a/code-studio/src/js/utils.js b/code-studio/src/js/utils.js new file mode 100644 index 0000000000000..5e10917ecfe11 --- /dev/null +++ b/code-studio/src/js/utils.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = { + /** + * Gets the URL querystring params. + * @param name {string=} Optionally pull a specific param. + * @return {object|string} Hash of params, or param string if `name` is specified. + */ + queryParams: function (name) { + var pairs = location.search.substr(1).split('&'); + var params = {}; + pairs.forEach(function (pair) { + var split = pair.split('='); + if (split.length === 2) { + params[split[0]] = split[1]; + } + }); + + if (name) { + return params[name]; + } + return params; + } +}; diff --git a/cookbooks/Berksfile.lock b/cookbooks/Berksfile.lock index b9070790dcb9d..8941132efd14b 100644 --- a/cookbooks/Berksfile.lock +++ b/cookbooks/Berksfile.lock @@ -51,7 +51,7 @@ GRAPH seven_zip (>= 0.0.0) windows (>= 0.0.0) build-essential (2.1.3) - cdo-apps (0.2.17) + cdo-apps (0.2.19) apt (>= 0.0.0) build-essential (>= 0.0.0) cdo-java-7 (>= 0.0.0) diff --git a/cookbooks/cdo-apps/.kitchen.yml b/cookbooks/cdo-apps/.kitchen.yml index 0ae9bd6179a01..d708d0505ca13 100644 --- a/cookbooks/cdo-apps/.kitchen.yml +++ b/cookbooks/cdo-apps/.kitchen.yml @@ -22,7 +22,7 @@ platforms: - recipe[cdo-apps] suites: - name: default - - name: nginx + - name: daemon attributes: cdo-apps: - nginx_enabled: true + daemon: true diff --git a/cookbooks/cdo-apps/Berksfile.lock b/cookbooks/cdo-apps/Berksfile.lock index 76541f58ce985..bdd7b85a16bec 100644 --- a/cookbooks/cdo-apps/Berksfile.lock +++ b/cookbooks/cdo-apps/Berksfile.lock @@ -4,10 +4,14 @@ DEPENDENCIES metadata: true cdo-github-access path: ../cdo-github-access + cdo-java-7 + path: ../cdo-java-7 cdo-mysql path: ../cdo-mysql cdo-nginx path: ../cdo-nginx + cdo-nodejs + path: ../cdo-nodejs cdo-postfix path: ../cdo-postfix cdo-repository @@ -29,15 +33,19 @@ GRAPH 7-zip (1.0.2) windows (>= 1.2.2) apt (2.9.2) - brightbox-ruby (1.2.1) - apt (>= 0.0.0) + ark (1.0.1) + build-essential (>= 0.0.0) + seven_zip (>= 0.0.0) + windows (>= 0.0.0) build-essential (2.3.1) 7-zip (>= 0.0.0) - cdo-apps (0.2.6) + cdo-apps (0.2.19) apt (>= 0.0.0) build-essential (>= 0.0.0) + cdo-java-7 (>= 0.0.0) cdo-mysql (>= 0.0.0) cdo-nginx (>= 0.0.0) + cdo-nodejs (>= 0.0.0) cdo-postfix (>= 0.0.0) cdo-repository (>= 0.0.0) cdo-ruby (>= 0.0.0) @@ -46,27 +54,42 @@ GRAPH omnibus_updater (>= 0.0.0) sudo-user (>= 0.0.0) cdo-github-access (0.1.1) + cdo-java-7 (0.1.0) + build-essential (>= 0.0.0) cdo-mysql (0.1.1) - cdo-nginx (0.0.2) + cdo-nginx (0.0.8) apt (>= 0.0.0) ssl_certificate (>= 0.0.0) + cdo-nodejs (0.2.1) + nodejs (>= 0.0.0) cdo-postfix (0.2.0) apt (>= 0.0.0) postfix (>= 0.0.0) cdo-repository (0.2.1) cdo-github-access (>= 0.0.0) - cdo-ruby (0.1.1) - brightbox-ruby (>= 0.0.0) - build-essential (>= 0.0.0) - cdo-secrets (0.1.0) - cdo-varnish (0.3.6) + cdo-ruby (0.2.0) + cdo-secrets (0.1.1) + cdo-varnish (0.3.10) apt (>= 0.0.0) chef_handler (1.3.0) + homebrew (2.1.0) + build-essential (>= 2.1.2) + nodejs (2.4.4) + apt (>= 0.0.0) + ark (>= 0.0.0) + build-essential (>= 0.0.0) + homebrew (>= 0.0.0) + yum-epel (>= 0.0.0) ohai (2.1.0) omnibus_updater (2.0.0) postfix (3.7.0) + seven_zip (2.0.0) + windows (>= 1.2.2) ssl_certificate (1.11.0) sudo-user (0.1.0) ohai (>= 0.0.0) windows (1.39.1) chef_handler (>= 0.0.0) + yum (3.10.0) + yum-epel (0.6.6) + yum (~> 3.10.0) diff --git a/cookbooks/cdo-apps/attributes/default.rb b/cookbooks/cdo-apps/attributes/default.rb index bd818044a3525..8fb230849bc96 100644 --- a/cookbooks/cdo-apps/attributes/default.rb +++ b/cookbooks/cdo-apps/attributes/default.rb @@ -14,3 +14,4 @@ }, 'nginx_enabled' => true } +default['omnibus_updater']['version'] = '12.7.2' diff --git a/cookbooks/cdo-apps/metadata.rb b/cookbooks/cdo-apps/metadata.rb index 9c6403b448acd..876b6bb3bac09 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.17' +version '0.2.19' depends 'apt' depends 'build-essential' diff --git a/cookbooks/cdo-apps/templates/default/crontab.erb b/cookbooks/cdo-apps/templates/default/crontab.erb index 110400341b44c..ce2e4c76f164f 100644 --- a/cookbooks/cdo-apps/templates/default/crontab.erb +++ b/cookbooks/cdo-apps/templates/default/crontab.erb @@ -1,96 +1,97 @@ <% -require 'shellwords' + require 'shellwords' -$crontab = [] + $crontab = [] -def home_dir(*paths) - File.join '/home', node[:current_user], *paths -end - -def deploy_dir(*paths) - home_dir node.chef_environment, *paths -end - -def bin_dir(*paths) - deploy_dir 'bin', *paths -end + def home_dir(*paths) + File.join '/home', node[:current_user], *paths + end -def dashboard_dir(*paths) - deploy_dir 'dashboard', *paths -end + def deploy_dir(*paths) + home_dir node.chef_environment, *paths + end -def pegasus_dir(*paths) - deploy_dir 'pegasus', *paths -end + def bin_dir(*paths) + deploy_dir 'bin', *paths + end -def shared_dir(*paths) - deploy_dir 'shared', *paths -end + def dashboard_dir(*paths) + deploy_dir 'dashboard', *paths + end -def cronjob(params) - time = params[:at].to_s - action = params[:do].to_s + def pegasus_dir(*paths) + deploy_dir 'pegasus', *paths + end - notify = params[:notify].to_s - action = "BUNDLE_GEMFILE=#{deploy_dir('Gemfile')} bundle exec #{bin_dir('cronjob')} #{action.shellescape} #{notify}".strip + def shared_dir(*paths) + deploy_dir 'shared', *paths + end - $crontab << "#{time} #{action}" -end + def cronjob(params) + time = params[:at].to_s + action = params[:do].to_s -def crontab() - $crontab.join("\n") -end + notify = params[:notify].to_s + action = "BUNDLE_GEMFILE=#{deploy_dir('Gemfile')} bundle exec #{bin_dir('cronjob')} #{action.shellescape} #{notify}".strip -# for multi-instance envs (ie production) there should be one daemon, -# so cronjobs that run once per environment go here (standalone env -# instances are all their own daemon) -if node['cdo-apps']['daemon'] - unless node.chef_environment == 'production' # non-production daemons - cronjob at:'@reboot', do:"#{deploy_dir('bin','solr-server')} > #{pegasus_dir('log','solr.log')} 2>&1" + $crontab << "#{time} #{action}" end - if node.chef_environment == 'staging' && node.name == 'staging' # 'real' staging only - cronjob at:'@reboot', do:home_dir('.dropbox-dist', 'dropboxd') - cronjob at:'*/5 * * * *', do:deploy_dir('bin', 'import_google_sheets') - cronjob at:'*/2 * * * *', do:deploy_dir('bin','run_server_generate_pdfs'), notify:'dev+crontab@code.org' - cronjob at:'*/2 * * * *', do:pegasus_dir('sites','virtual','run_server_generate_curriculum_pdfs'), notify:'dev+crontab@code.org' - cronjob at:'*/2 * * * *', do:pegasus_dir('sites','virtual','collate_pdfs'), notify:'dev+crontab@code.org' - cronjob at:'*/2 * * * *', do:dashboard_dir('bin','build_scripts'), notify:'dev+crontab@code.org' - cronjob at:'*/5 * * * *', do:deploy_dir('bin','fetch-external-resources'), notify:'dev+crontab@code.org' - cronjob at:'30 16 * * *', do:deploy_dir('bin', 'cron', 'update_dotd') + def crontab() + $crontab.join("\n") end - 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', 'send_workshop_reminder_emails') - cronjob at:'15 16 * * *', do:dashboard_dir('bin','scheduled_ops_emails') - cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'index-users-in-solr') - cronjob at:'25 7 * * *', do:deploy_dir('bin', 'update-hoc-map') + # for multi-instance envs (ie production) there should be one daemon, + # so cronjobs that run once per environment go here (standalone env + # instances are all their own daemon) + if node['cdo-apps']['daemon'] + unless node.chef_environment == 'production' # non-production daemons + cronjob at:'@reboot', do:"#{deploy_dir('bin','solr-server')} > #{pegasus_dir('log','solr.log')} 2>&1" + end + + if node.chef_environment == 'staging' && node.name == 'staging' # 'real' staging only + cronjob at:'@reboot', do:home_dir('.dropbox-dist', 'dropboxd') + cronjob at:'*/5 * * * *', do:deploy_dir('bin', 'import_google_sheets') + cronjob at:'*/2 * * * *', do:deploy_dir('bin','run_server_generate_pdfs'), notify:'dev+crontab@code.org' + cronjob at:'*/2 * * * *', do:pegasus_dir('sites','virtual','run_server_generate_curriculum_pdfs'), notify:'dev+crontab@code.org' + cronjob at:'*/2 * * * *', do:pegasus_dir('sites','virtual','collate_pdfs'), notify:'dev+crontab@code.org' + cronjob at:'*/2 * * * *', do:dashboard_dir('bin','build_scripts'), notify:'dev+crontab@code.org' + cronjob at:'*/5 * * * *', do:deploy_dir('bin','fetch-external-resources'), notify:'dev+crontab@code.org' + cronjob at:'30 16 * * *', do:deploy_dir('bin', 'cron', 'update_dotd') + end + + 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', 'send_workshop_reminder_emails') + cronjob at:'15 16 * * *', do:dashboard_dir('bin','scheduled_ops_emails') + cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'index-users-in-solr') + cronjob at:'25 7 * * *', do:deploy_dir('bin', 'update-hoc-map') + end + + # 'daemons' in all environments + cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'cron', 'process_forms') + cronjob at:'35 * * * *', do:deploy_dir('bin', 'cron', 'analyze_hoc_activity') + cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'deliver_poste_messages') + cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'cron', 'geocode_hoc_activity') + cronjob at:'55 8 * * *', do:deploy_dir('bin', 'cron', 'ops_data_pull') + cronjob at:'45 5 * * *', do:deploy_dir('bin', 'cron', 'admin_stats') + cronjob at:'40 4 * * *', do:deploy_dir('bin', 'cron', 'funometer') + cronjob at:'30 6 * * *', do:deploy_dir('bin', 'cron', 'admin_progress') + cronjob at:'10 7 * * *', do:deploy_dir('bin', 'cron', 'retention_stats') + cronjob at:'*/1 * * * *', do:deploy_dir('aws', 'ci_build'), notify: 'dev+build@code.org' end - # 'daemons' in all environments - cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'cron', 'process_forms') - cronjob at:'35 * * * *', do:deploy_dir('bin', 'cron', 'analyze_hoc_activity') - cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'deliver_poste_messages') - cronjob at:'*/1 * * * *', do:deploy_dir('bin', 'cron', 'geocode_hoc_activity') - cronjob at:'55 8 * * *', do:deploy_dir('bin', 'cron', 'ops_data_pull') - cronjob at:'45 5 * * *', do:deploy_dir('bin', 'cron', 'admin_stats') - cronjob at:'40 4 * * *', do:deploy_dir('bin', 'cron', 'funometer') - cronjob at:'30 6 * * *', do:deploy_dir('bin', 'cron', 'admin_progress') - cronjob at:'10 7 * * *', do:deploy_dir('bin', 'cron', 'retention_stats') -end - -# cronjobs that run on all instances in all environments go here: - -# Restart Pegasus and dashboard every 8 hours at 0, 8, 16 UTC (12AM, 8AM, 4PM). -# The restart interval from from every 4 hours on 2016/4/26. After recent memory -# leak fixes this is probably no longer necessary, but we are keeping it at this -# level until we have chance to confirm non-leakage over longer intervals. -cronjob at:"#{rand(20)} */8 * * *", do:'service dashboard upgrade && service pegasus upgrade' - -cronjob at:"#{rand(60)} * * * *", do:"#{deploy_dir('bin','upload-logs-to-s3')} dashboard pegasus" - -cronjob at:'@reboot', do:'chef-client' + # cronjobs that run on all instances in all environments go here: + + # Restart Pegasus and dashboard every 8 hours at 0, 8, 16 UTC (12AM, 8AM, 4PM). + # The restart interval from from every 4 hours on 2016/4/26. After recent memory + # leak fixes this is probably no longer necessary, but we are keeping it at this + # level until we have chance to confirm non-leakage over longer intervals. + cronjob at:"#{rand(20)} */8 * * *", do:'service dashboard upgrade && service pegasus upgrade' + + cronjob at:"#{rand(60)} * * * *", do:"#{deploy_dir('bin','upload-logs-to-s3')} dashboard pegasus" + + cronjob at:'@reboot', do:'chef-client' %> # # node: <%= node.name %> diff --git a/cookbooks/cdo-apps/test/integration/nginx/serverspec/ruby_spec.rb b/cookbooks/cdo-apps/test/integration/daemon/serverspec/ruby_spec.rb similarity index 100% rename from cookbooks/cdo-apps/test/integration/nginx/serverspec/ruby_spec.rb rename to cookbooks/cdo-apps/test/integration/daemon/serverspec/ruby_spec.rb diff --git a/dashboard/app/controllers/levels_controller.rb b/dashboard/app/controllers/levels_controller.rb index 196e89d87b754..61cb72fefd474 100644 --- a/dashboard/app/controllers/levels_controller.rb +++ b/dashboard/app/controllers/levels_controller.rb @@ -53,10 +53,12 @@ def edit_blocks toolbox_blocks = @level.complete_toolbox(type) # Levels which support solution blocks use those blocks as the - # toolbox for required and recommended block editors + # toolbox for required and recommended block editors, plus the + # special "pick one" block if @level.respond_to?("get_solution_blocks") && (type == 'required_blocks' || type == 'recommended_blocks') - toolbox_blocks = "#{@level.get_solution_blocks.join('')}" + blocks = @level.get_solution_blocks + [""] + toolbox_blocks = "#{blocks.join('')}" end level_view_options( diff --git a/dashboard/app/models/ability.rb b/dashboard/app/models/ability.rb index a0865d2704f72..7924e94a447d5 100644 --- a/dashboard/app/models/ability.rb +++ b/dashboard/app/models/ability.rb @@ -36,7 +36,12 @@ def initialize(user) Plc::LearningModule, Plc::Task, Plc::UserCourseEnrollment, - Plc::CourseUnit + Plc::CourseUnit, + # PD models + Pd::Workshop, + Pd::Enrollment, + Pd::Attendance, + Pd::DistrictPaymentTerm ] end @@ -66,6 +71,7 @@ def initialize(user) !user.students.where(id: user_level.user_id).empty? end can :read, Plc::UserCourseEnrollment + can :manage, Pd::Enrollment, teacher_id: user.id end if user.facilitator? @@ -80,6 +86,8 @@ def initialize(user) can :manage, Workshop do |workshop| workshop.facilitators.include? user end + can [:read, :start, :end], Pd::Workshop, facilitators: {id: user.id} + can :manage, Pd::Attendance, workshop: {facilitators: {id: user.id}} end if user.district_contact? @@ -90,6 +98,12 @@ def initialize(user) end end end + + if user.workshop_organizer? + can :create, Pd::Workshop + can :manage, Pd::Workshop, organizer_id: user.id + can :manage, Pd::Attendance, workshop: {organizer_id: user.id} + end end if user.id && user.admin? diff --git a/dashboard/app/models/activity.rb b/dashboard/app/models/activity.rb index e55264bd83790..f58657c6e8201 100644 --- a/dashboard/app/models/activity.rb +++ b/dashboard/app/models/activity.rb @@ -37,6 +37,11 @@ def Activity.best?(result) (result == BEST_PASS_RESULT) end + def Activity.perfect?(result) + return false if result.nil? + (result > MAXIMUM_NONOPTIMAL_RESULT) + end + def Activity.passing?(result) return false if result.nil? (result >= MINIMUM_PASS_RESULT) @@ -51,6 +56,10 @@ def best? Activity.best? test_result end + def perfect? + Activity.perfect? test_result + end + def passing? Activity.passing? test_result end diff --git a/dashboard/app/models/pd.rb b/dashboard/app/models/pd.rb new file mode 100644 index 0000000000000..234077c67febd --- /dev/null +++ b/dashboard/app/models/pd.rb @@ -0,0 +1,5 @@ +module Pd + def self.table_name_prefix + 'pd_' + end +end diff --git a/dashboard/app/models/pd/attendance.rb b/dashboard/app/models/pd/attendance.rb new file mode 100644 index 0000000000000..d275fa77c33ed --- /dev/null +++ b/dashboard/app/models/pd/attendance.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: pd_attendances +# +# id :integer not null, primary key +# pd_session_id :integer not null +# teacher_id :integer not null +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_pd_attendances_on_pd_session_id (pd_session_id) +# + +class Pd::Attendance < ActiveRecord::Base + belongs_to :session, class_name: 'Pd::Session', foreign_key: :pd_session_id + belongs_to :teacher, class_name: 'User', foreign_key: :teacher_id + + has_one :workshop, class_name: 'Pd::Workshop', through: :session + + def self.for_teacher_in_workshop(teacher, workshop) + joins(:workshop).where(teacher: teacher, pd_workshops: {id: workshop.id}) + end +end diff --git a/dashboard/app/models/pd/district_payment_term.rb b/dashboard/app/models/pd/district_payment_term.rb new file mode 100644 index 0000000000000..0799687f388b0 --- /dev/null +++ b/dashboard/app/models/pd/district_payment_term.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: pd_district_payment_terms +# +# id :integer not null, primary key +# district_id :integer +# course :string(255) not null +# rate_type :string(255) not null +# rate :decimal(8, 2) not null +# +# Indexes +# +# index_pd_district_payment_terms_on_district_id_and_course (district_id,course) +# + +class Pd::DistrictPaymentTerm < ActiveRecord::Base + RATE_TYPES = [ + RATE_HOURLY = 'hourly', + RATE_DAILY = 'daily' + ] + validates_inclusion_of :rate_type, in: RATE_TYPES + + belongs_to :district + +end diff --git a/dashboard/app/models/pd/district_report.rb b/dashboard/app/models/pd/district_report.rb new file mode 100644 index 0000000000000..43f4f9cc41bfc --- /dev/null +++ b/dashboard/app/models/pd/district_report.rb @@ -0,0 +1,56 @@ +class Pd::DistrictReport + + # Construct a report row for each teacher in the district + def self.generate_district_report(*districts) + [].tap do |rows| + districts.each do |district| + district.users.all.each do |teacher| + Pd::Workshop.attended_by(teacher).each do |workshop| + rows << generate_district_report_row(district, teacher, workshop) + end + end + end + end + end + + def self.generate_district_report_row(district, teacher, workshop) + payment_term = Pd::DistrictPaymentTerm.where(district_id: district.id, course: workshop.course).first + attendances = Pd::Attendance.for_teacher_in_workshop(teacher, workshop) + hours = attendances.map(&:session).map(&:hours).reduce(&:+) + days = attendances.count + qualified = (payment_term && workshop.workshop_type == Pd::Workshop::TYPE_DISTRICT && workshop.course != Pd::Workshop::COURSE_CSF) + payment_type = payment_term ? payment_term.rate_type : nil + payment_rate = payment_term ? payment_term.rate : nil + payment_amount = !qualified ? 0 : + case payment_term.rate_type + when Pd::DistrictPaymentTerm::RATE_HOURLY + hours * payment_term.rate + when Pd::DistrictPaymentTerm::RATE_DAILY + days * payment_term.rate + else + raise "Unexpected district payment term rate type #{payment_term.rate_type} for id #{payment_term.id}" + end + + { + district_name: district.name, + workshop_organizer_name: workshop.organizer.name, + workshop_organizer_id: workshop.organizer.id, + facilitators: workshop.facilitators.map(&:name).join(','), + workshop_dates: workshop.sessions.map(&:formatted_date).join(','), + workshop_type: workshop.workshop_type, + course: workshop.course, + subject: workshop.subject, + school: teacher.school, + teacher_name: teacher.name, + teacher_id: teacher.id, + teacher_email: teacher.email, + year: workshop.year, + hours: hours, + days: days, + payment_type: payment_type, + payment_rate: payment_rate, + qualified: qualified, + payment_amount: payment_amount + } + end +end diff --git a/dashboard/app/models/pd/enrollment.rb b/dashboard/app/models/pd/enrollment.rb new file mode 100644 index 0000000000000..455c4db2658a3 --- /dev/null +++ b/dashboard/app/models/pd/enrollment.rb @@ -0,0 +1,26 @@ +# == Schema Information +# +# Table name: pd_enrollments +# +# id :integer not null, primary key +# pd_workshop_id :integer not null +# name :string(255) not null +# email :string(255) not null +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_pd_enrollments_on_pd_workshop_id (pd_workshop_id) +# + +class Pd::Enrollment < ActiveRecord::Base + belongs_to :workshop, class_name: 'Pd::Workshop', foreign_key: :pd_workshop_id + validates :name, :email, presence: true + + validates_confirmation_of :email + + def user + User.find_by_email_or_hashed_email self.email + end +end diff --git a/dashboard/app/models/pd/session.rb b/dashboard/app/models/pd/session.rb new file mode 100644 index 0000000000000..75ce4e01940d7 --- /dev/null +++ b/dashboard/app/models/pd/session.rb @@ -0,0 +1,28 @@ +# == Schema Information +# +# Table name: pd_sessions +# +# id :integer not null, primary key +# pd_workshop_id :integer +# start :datetime not null +# end :datetime not null +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_pd_sessions_on_pd_workshop_id (pd_workshop_id) +# + +class Pd::Session < ActiveRecord::Base + belongs_to :workshop, class_name: 'Pd::Workshop', foreign_key: 'pd_workshop_id' + has_many :attendances, class_name: 'Pd::Attendance', foreign_key: 'pd_session_id', dependent: :destroy + + def formatted_date + self.start.strftime('%m/%d/%Y') + end + + def hours + (self.end - self.start) / 1.hour + end +end diff --git a/dashboard/app/models/pd/workshop.rb b/dashboard/app/models/pd/workshop.rb new file mode 100644 index 0000000000000..b0b3a391a963f --- /dev/null +++ b/dashboard/app/models/pd/workshop.rb @@ -0,0 +1,112 @@ +# == Schema Information +# +# Table name: pd_workshops +# +# id :integer not null, primary key +# workshop_type :string(255) not null +# organizer_id :integer not null +# location_name :string(255) +# location_address :string(255) +# course :string(255) not null +# subject :string(255) +# capacity :integer not null +# notes :string(255) +# section_id :integer +# started_at :datetime +# ended_at :datetime +# created_at :datetime +# updated_at :datetime +# +# Indexes +# +# index_pd_workshops_on_organizer_id (organizer_id) +# + +class Pd::Workshop < ActiveRecord::Base + TYPES = [ + TYPE_PUBLIC = 'Public', + TYPE_PRIVATE = 'Private', + TYPE_DISTRICT = 'District' + ] + + COURSES = [ + COURSE_CSF = 'CSF', + COURSE_CSP = 'CSP', + COURSE_ECS = 'ECS', + COURSE_CS_IN_A = 'CSinA', + COURSE_CS_IN_S = 'CSinS', + COURSE_CSD = 'CSD' + ] + + STATE_NOT_STARTED = 'Not Started' + STATE_IN_PROGRESS = 'In Progress' + STATE_ENDED = 'Ended' + + validates_inclusion_of :workshop_type, in: TYPES + validates_inclusion_of :course, in: COURSES + validates :capacity, numericality: {only_integer: true, greater_than: 0} + + belongs_to :organizer, class_name: 'User' + has_and_belongs_to_many :facilitators, class_name: 'User', join_table: 'pd_workshops_facilitators', foreign_key: 'pd_workshop_id', association_foreign_key: 'user_id' + + has_many :sessions, -> {order :start}, class_name: 'Pd::Session', dependent: :destroy, foreign_key: 'pd_workshop_id' + accepts_nested_attributes_for :sessions, allow_destroy: true + + has_many :enrollments, class_name: 'Pd::Enrollment', dependent: :destroy, foreign_key: 'pd_workshop_id' + belongs_to :section + + def self.organized_by(organizer) + where(organizer_id: organizer.id) + end + + def self.facilitated_by(facilitator) + joins(:facilitators).where(users: {id: facilitator.id}).distinct + end + + def self.enrolled_in_by(teacher) + joins(:enrollments).where(pd_enrollments: {email: teacher.email}).distinct + end + + def self.attended_by(teacher) + joins(sessions: :attendances).where(pd_attendances: {teacher_id: teacher.id}).distinct + end + + def friendly_name + start_time = sessions.empty? ? '' : sessions.first.start.strftime('%m/%d/%y') + "Workshop #{start_time} at #{location_name}" + end + + def start! + return unless self.started_at.nil? + raise 'Workshop must have at least one session to start.' if self.sessions.empty? + + self.started_at = DateTime.now + self.section = Section.create!( + name: friendly_name, + user_id: self.organizer_id + ) + self.save! + end + + def end! + return unless self.ended_at.nil? + self.ended_at = DateTime.now + self.save! + end + + def state + case + when self.started_at.nil? + STATE_NOT_STARTED + when self.ended_at.nil? + STATE_IN_PROGRESS + else + STATE_ENDED + end + end + + def year + return nil if sessions.empty? + sessions.order(:start).first.start.strftime('%Y') + end +end diff --git a/dashboard/app/models/user.rb b/dashboard/app/models/user.rb index 06ccd7d3ea36b..dfa24de804209 100644 --- a/dashboard/app/models/user.rb +++ b/dashboard/app/models/user.rb @@ -128,6 +128,10 @@ def facilitator? permission? UserPermission::FACILITATOR end + def workshop_organizer? + permission? UserPermission::WORKSHOP_ORGANIZER + end + def delete_permission(permission) permission = permissions.find_by(permission: permission) permissions.delete permission if permission diff --git a/dashboard/app/models/user_level.rb b/dashboard/app/models/user_level.rb index daf3c8cd1ed98..1257179857e7f 100644 --- a/dashboard/app/models/user_level.rb +++ b/dashboard/app/models/user_level.rb @@ -37,6 +37,10 @@ def best? Activity.best? best_result end + def perfect? + Activity.perfect? best_result + end + def finished? Activity.finished? best_result end diff --git a/dashboard/app/models/user_permission.rb b/dashboard/app/models/user_permission.rb index 30bedf86104c1..6a59d87e3e2d7 100644 --- a/dashboard/app/models/user_permission.rb +++ b/dashboard/app/models/user_permission.rb @@ -16,4 +16,5 @@ class UserPermission < ActiveRecord::Base FACILITATOR = 'facilitator' DISTRICT_CONTACT = 'district_contact' + WORKSHOP_ORGANIZER = 'workshop_organizer' end diff --git a/dashboard/app/views/levels/_admin.html.haml b/dashboard/app/views/levels/_admin.html.haml index 994b2369a5cff..4e960f928367c 100644 --- a/dashboard/app/views/levels/_admin.html.haml +++ b/dashboard/app/views/levels/_admin.html.haml @@ -8,25 +8,28 @@ %li= link_to level_path(@level), @level %li= link_to "see callouts (#{@level.available_callouts(@script_level).count})", show_callouts: '1' - if can? :edit, @level - %li - = link_to 'edit', edit_level_path(@level) - - if @level.is_a? Blockly - %ul - %li= link_to 'solution', level_edit_blocks_path(@level, :solution_blocks) - %li= link_to 'toolbox', level_edit_blocks_path(@level, :toolbox_blocks) - %li= link_to "start (#{Blockly.count_xml_blocks(@level.start_blocks)})", level_edit_blocks_path(@level, :start_blocks) - %li= link_to "required (#{Blockly.count_xml_blocks(@level.required_blocks)})", level_edit_blocks_path(@level, :required_blocks) - %li= link_to "recommended (#{Blockly.count_xml_blocks(@level.recommended_blocks)})", level_edit_blocks_path(@level, :recommended_blocks) - - if @level.is_a? Artist - %li= link_to 'pre-draw', level_edit_blocks_path(@level, :predraw_blocks) - %li= link_to 'delete', @level, method: :delete, data: { confirm: t('crud.confirm') }, style: 'color: red' - %li - = link_to 'clone', '', onclick: "$('#clone_#{@level.id}').toggle(); return false;" - %div{class: 'clone_level', id: "clone_#{@level.id}", style: 'display: none;'} - = form_tag level_clone_path(@level), method: :post, remote: true do - = label_tag 'New name:' - = text_field_tag :name, @level.name - = submit_tag 'Clone' + - if Rails.application.config.levelbuilder_mode + %li + = link_to 'edit', edit_level_path(@level) + - if @level.is_a? Blockly + %ul + %li= link_to 'solution', level_edit_blocks_path(@level, :solution_blocks) + %li= link_to 'toolbox', level_edit_blocks_path(@level, :toolbox_blocks) + %li= link_to "start (#{Blockly.count_xml_blocks(@level.start_blocks)})", level_edit_blocks_path(@level, :start_blocks) + %li= link_to "required (#{Blockly.count_xml_blocks(@level.required_blocks)})", level_edit_blocks_path(@level, :required_blocks) + %li= link_to "recommended (#{Blockly.count_xml_blocks(@level.recommended_blocks)})", level_edit_blocks_path(@level, :recommended_blocks) + - if @level.is_a? Artist + %li= link_to 'pre-draw', level_edit_blocks_path(@level, :predraw_blocks) + %li= link_to 'delete', @level, method: :delete, data: { confirm: t('crud.confirm') }, style: 'color: red' + %li + = link_to 'clone', '', onclick: "$('#clone_#{@level.id}').toggle(); return false;" + %div{class: 'clone_level', id: "clone_#{@level.id}", style: 'display: none;'} + = form_tag level_clone_path(@level), method: :post, remote: true do + = label_tag 'New name:' + = text_field_tag :name, @level.name + = submit_tag 'Clone' + - elsif @script_level + %li= link_to 'edit on levelbuilder', URI.join("https://levelbuilder-studio.code.org/", build_script_level_path(@script_level)).to_s - else %li (Cannot edit) diff --git a/dashboard/config/locales/data.en.yml b/dashboard/config/locales/data.en.yml index 072309132a6f2..566e1d9cbb6a6 100644 --- a/dashboard/config/locales/data.en.yml +++ b/dashboard/config/locales/data.en.yml @@ -476,6 +476,7 @@ en: 'infinity_playlab_intro' : 'Play Lab - Introduction' 'infinity_playlab_events' : 'Play Lab - Events' 'infinity_playlab_repeat_forever' : 'Play Lab - Repeat Forever' + 'csp_applab_objects' : 'Introduction to Objects' 'csp_applab_databases_1' : 'Introduction to Databases - Part 1' 'csp_applab_databases_2' : 'Introduction to Databases - Part 2' 'csp_applab_processing_lists' : 'Processing Lists with Loops' diff --git a/dashboard/config/scripts/challenges_sample_4.external b/dashboard/config/scripts/challenges_sample_4.external index fff0886a3dba6..177f119a3da51 100644 --- a/dashboard/config/scripts/challenges_sample_4.external +++ b/dashboard/config/scripts/challenges_sample_4.external @@ -1,7 +1,6 @@ name 'challenges sample 4' title 'Life Science Challenges' description 'Give these a try' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/csd_u3_variables_reassign_predict.text_match b/dashboard/config/scripts/csd_u3_variables_reassign_predict.text_match index e952c5847ea75..854f859f63d56 100644 --- a/dashboard/config/scripts/csd_u3_variables_reassign_predict.text_match +++ b/dashboard/config/scripts/csd_u3_variables_reassign_predict.text_match @@ -1,4 +1,9 @@ name 'CSD U3 Variables Reassign Predict' -title 'Make a Prediction' css 'unplugged' -content1 'Enter prompt here' \ No newline at end of file +markdown < diff --git a/dashboard/config/scripts/ecs_phase_3_overview.external b/dashboard/config/scripts/ecs_phase_3_overview.external index bb0ca00056c97..8da63cda07cf7 100644 --- a/dashboard/config/scripts/ecs_phase_3_overview.external +++ b/dashboard/config/scripts/ecs_phase_3_overview.external @@ -1,5 +1,4 @@ name 'ECS Phase 3 Overview' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecs_phase_3_unit3_welcome.external b/dashboard/config/scripts/ecs_phase_3_unit3_welcome.external index 6d6668b7cb588..a611ec289e034 100644 --- a/dashboard/config/scripts/ecs_phase_3_unit3_welcome.external +++ b/dashboard/config/scripts/ecs_phase_3_unit3_welcome.external @@ -1,5 +1,4 @@ name 'ECS Phase 3 Unit3 Welcome' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_1_page_template.external b/dashboard/config/scripts/ecspd_1_page_template.external index ecb0cd168a352..533bfc5b9e860 100644 --- a/dashboard/config/scripts/ecspd_1_page_template.external +++ b/dashboard/config/scripts/ecspd_1_page_template.external @@ -1,5 +1,4 @@ name 'ECSPD 1 page template' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_challenge_intro.external b/dashboard/config/scripts/ecspd_challenge_intro.external index 1dd00c8fd495f..a97adb05c6816 100644 --- a/dashboard/config/scripts/ecspd_challenge_intro.external +++ b/dashboard/config/scripts/ecspd_challenge_intro.external @@ -1,6 +1,5 @@ name 'ECSPD Challenge Intro' -href 'path/to/html/in/asset/folder' markdown < ## Close & Next Steps > Keep Learning diff --git a/dashboard/config/scripts/ecspd_challenge_rubric.external b/dashboard/config/scripts/ecspd_challenge_rubric.external index 431ce5d419f0b..859880f64831e 100644 --- a/dashboard/config/scripts/ecspd_challenge_rubric.external +++ b/dashboard/config/scripts/ecspd_challenge_rubric.external @@ -1,7 +1,6 @@ name 'ECSPD Challenge Rubric' title 'title' description 'description here' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_growth_mindset_student_video.external b/dashboard/config/scripts/ecspd_growth_mindset_student_video.external index 4b058cea8c6c9..8401693fd4966 100644 --- a/dashboard/config/scripts/ecspd_growth_mindset_student_video.external +++ b/dashboard/config/scripts/ecspd_growth_mindset_student_video.external @@ -1,5 +1,4 @@ name 'ECSPD Growth Mindset Student Video' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_linear_vs_binary_search_video.external b/dashboard/config/scripts/ecspd_linear_vs_binary_search_video.external index a50a0eed5bb68..3438753a918e7 100644 --- a/dashboard/config/scripts/ecspd_linear_vs_binary_search_video.external +++ b/dashboard/config/scripts/ecspd_linear_vs_binary_search_video.external @@ -1,5 +1,4 @@ name 'ECSPD Linear vs Binary Search Video' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_p3unit2_go.external b/dashboard/config/scripts/ecspd_p3unit2_go.external index d51f6e15d7d8a..b2b3945b6cd62 100644 --- a/dashboard/config/scripts/ecspd_p3unit2_go.external +++ b/dashboard/config/scripts/ecspd_p3unit2_go.external @@ -1,5 +1,4 @@ name 'ECSPD P3Unit2 Go' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_phase_3_unit_2_teaching_practice.external b/dashboard/config/scripts/ecspd_phase_3_unit_2_teaching_practice.external index 73aeabe9b8eac..1cb9b7a3864e8 100644 --- a/dashboard/config/scripts/ecspd_phase_3_unit_2_teaching_practice.external +++ b/dashboard/config/scripts/ecspd_phase_3_unit_2_teaching_practice.external @@ -1,6 +1,5 @@ name 'ECSPD Phase 3 - Unit 2 - Teaching Practice' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_pick_challenge.external b/dashboard/config/scripts/ecspd_pick_challenge.external index 4b60264492ffa..2548fd3f24d82 100644 --- a/dashboard/config/scripts/ecspd_pick_challenge.external +++ b/dashboard/config/scripts/ecspd_pick_challenge.external @@ -1,5 +1,4 @@ name 'ECSPD Pick Challenge' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_problem_solving_strategies.external b/dashboard/config/scripts/ecspd_problem_solving_strategies.external index 517162cd291a1..0e1720f2c0e59 100644 --- a/dashboard/config/scripts/ecspd_problem_solving_strategies.external +++ b/dashboard/config/scripts/ecspd_problem_solving_strategies.external @@ -1,6 +1,5 @@ name 'ECSPD Problem Solving Strategies' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_start_challenge.external b/dashboard/config/scripts/ecspd_start_challenge.external index 8313ef01fe50c..704911d7f5d4b 100644 --- a/dashboard/config/scripts/ecspd_start_challenge.external +++ b/dashboard/config/scripts/ecspd_start_challenge.external @@ -1,5 +1,4 @@ name 'ECSPD Start Challenge' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_teaching_strategies_intro.external b/dashboard/config/scripts/ecspd_teaching_strategies_intro.external index 1a823e2cdd29a..2dada5ef30ad5 100644 --- a/dashboard/config/scripts/ecspd_teaching_strategies_intro.external +++ b/dashboard/config/scripts/ecspd_teaching_strategies_intro.external @@ -1,5 +1,4 @@ name 'ECSPD Teaching Strategies Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u2_challenge_decision.external b/dashboard/config/scripts/ecspd_u2_challenge_decision.external index 2fa4457a873c2..62eb4d94c18dd 100644 --- a/dashboard/config/scripts/ecspd_u2_challenge_decision.external +++ b/dashboard/config/scripts/ecspd_u2_challenge_decision.external @@ -1,6 +1,5 @@ name 'ECSPD U2 Challenge Decision' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u2_challenge_plan.external b/dashboard/config/scripts/ecspd_u2_challenge_plan.external index 3d43c43cd208e..6c4ca9bbf354e 100644 --- a/dashboard/config/scripts/ecspd_u2_challenge_plan.external +++ b/dashboard/config/scripts/ecspd_u2_challenge_plan.external @@ -1,7 +1,6 @@ name 'ECSPD U2 Challenge Plan' title 'title' description 'description here' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u2_d3_forum_q.external b/dashboard/config/scripts/ecspd_u2_d3_forum_q.external index d28ab9365b451..8002d5584bcb8 100644 --- a/dashboard/config/scripts/ecspd_u2_d3_forum_q.external +++ b/dashboard/config/scripts/ecspd_u2_d3_forum_q.external @@ -1,5 +1,4 @@ name 'ECSPD U2 D3 Forum Q' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_14_intro.external b/dashboard/config/scripts/ecspd_u4_d_14_intro.external index ebb61467556ac..0d4a202562504 100644 --- a/dashboard/config/scripts/ecspd_u4_d_14_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_14_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 14 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_15_intro.external b/dashboard/config/scripts/ecspd_u4_d_15_intro.external index ea4a0409e597a..ba4623b2cb551 100644 --- a/dashboard/config/scripts/ecspd_u4_d_15_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_15_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 15 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_16_17_intro.external b/dashboard/config/scripts/ecspd_u4_d_16_17_intro.external index c05c7c2aea9b0..14c561aa9f2e0 100644 --- a/dashboard/config/scripts/ecspd_u4_d_16_17_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_16_17_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 16-17 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_18_intro.external b/dashboard/config/scripts/ecspd_u4_d_18_intro.external index 15e3ae2153028..44fe0ae60e506 100644 --- a/dashboard/config/scripts/ecspd_u4_d_18_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_18_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 18 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_19_intro.external b/dashboard/config/scripts/ecspd_u4_d_19_intro.external index ff5f0494f75ac..7daddd07c7109 100644 --- a/dashboard/config/scripts/ecspd_u4_d_19_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_19_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 19 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_1_intro.external b/dashboard/config/scripts/ecspd_u4_d_1_intro.external index af8f8d35b8222..d9910ec207d42 100644 --- a/dashboard/config/scripts/ecspd_u4_d_1_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_1_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 1 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_20_23_intro.external b/dashboard/config/scripts/ecspd_u4_d_20_23_intro.external index 6eddde8a43597..e860fdef376ff 100644 --- a/dashboard/config/scripts/ecspd_u4_d_20_23_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_20_23_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 20-23 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_24_intro.external b/dashboard/config/scripts/ecspd_u4_d_24_intro.external index 1f1cdc82ae769..f98ce0555ff64 100644 --- a/dashboard/config/scripts/ecspd_u4_d_24_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_24_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 24 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_25_30_intro.external b/dashboard/config/scripts/ecspd_u4_d_25_30_intro.external index fba208a8cfe9b..880226f225616 100644 --- a/dashboard/config/scripts/ecspd_u4_d_25_30_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_25_30_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 25-30 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_2_3_intro.external b/dashboard/config/scripts/ecspd_u4_d_2_3_intro.external index eeb562e4e0fe8..d6f2aae90a082 100644 --- a/dashboard/config/scripts/ecspd_u4_d_2_3_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_2_3_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 2-3 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_4_intro.external b/dashboard/config/scripts/ecspd_u4_d_4_intro.external index f26fbf9d4d55e..90cb63a69f1df 100644 --- a/dashboard/config/scripts/ecspd_u4_d_4_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_4_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 4 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_5_6_intro.external b/dashboard/config/scripts/ecspd_u4_d_5_6_intro.external index 93f23dcbd10f8..97c53942922d2 100644 --- a/dashboard/config/scripts/ecspd_u4_d_5_6_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_5_6_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 5-6 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_7_8_intro.external b/dashboard/config/scripts/ecspd_u4_d_7_8_intro.external index 9ffd0a82c93f3..da8fa2f5ff4c0 100644 --- a/dashboard/config/scripts/ecspd_u4_d_7_8_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_7_8_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 7-8 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_u4_d_9_intro.external b/dashboard/config/scripts/ecspd_u4_d_9_intro.external index 0216217333e99..9c327aee73825 100644 --- a/dashboard/config/scripts/ecspd_u4_d_9_intro.external +++ b/dashboard/config/scripts/ecspd_u4_d_9_intro.external @@ -1,6 +1,5 @@ name 'ECSPD U4 D 9 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit2_connections.external b/dashboard/config/scripts/ecspd_unit2_connections.external index 64724dddb7a86..7574e4176c017 100644 --- a/dashboard/config/scripts/ecspd_unit2_connections.external +++ b/dashboard/config/scripts/ecspd_unit2_connections.external @@ -1,5 +1,4 @@ name 'ECSPD Unit2 Connections' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_break.external b/dashboard/config/scripts/ecspd_unit_2_break.external index d7a8d84a3ef16..c3f07a813e76a 100644 --- a/dashboard/config/scripts/ecspd_unit_2_break.external +++ b/dashboard/config/scripts/ecspd_unit_2_break.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Break' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_challenge_preview.external b/dashboard/config/scripts/ecspd_unit_2_challenge_preview.external index 049897a1a427c..ef98f619f238a 100644 --- a/dashboard/config/scripts/ecspd_unit_2_challenge_preview.external +++ b/dashboard/config/scripts/ecspd_unit_2_challenge_preview.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Challenge Preview' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_cornrow_forum.external b/dashboard/config/scripts/ecspd_unit_2_cornrow_forum.external index a4c83f3365ee9..579d236d187c2 100644 --- a/dashboard/config/scripts/ecspd_unit_2_cornrow_forum.external +++ b/dashboard/config/scripts/ecspd_unit_2_cornrow_forum.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Cornrow Forum' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_growth.external b/dashboard/config/scripts/ecspd_unit_2_growth.external index e1b3ee90c7cb4..5712166aeeb3b 100644 --- a/dashboard/config/scripts/ecspd_unit_2_growth.external +++ b/dashboard/config/scripts/ecspd_unit_2_growth.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Growth' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_intro.external b/dashboard/config/scripts/ecspd_unit_2_intro.external index 38292e29c0dba..20789b031e9ee 100644 --- a/dashboard/config/scripts/ecspd_unit_2_intro.external +++ b/dashboard/config/scripts/ecspd_unit_2_intro.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Intro' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_lesson_overview.external b/dashboard/config/scripts/ecspd_unit_2_lesson_overview.external index b7649abaff7fb..d15d30b81b651 100644 --- a/dashboard/config/scripts/ecspd_unit_2_lesson_overview.external +++ b/dashboard/config/scripts/ecspd_unit_2_lesson_overview.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Lesson Overview' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_outline.external b/dashboard/config/scripts/ecspd_unit_2_outline.external index 96eeff0d902d3..16ede439e99ac 100644 --- a/dashboard/config/scripts/ecspd_unit_2_outline.external +++ b/dashboard/config/scripts/ecspd_unit_2_outline.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Outline' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_pick2.external b/dashboard/config/scripts/ecspd_unit_2_pick2.external index a56c71bb86937..993febeee2175 100644 --- a/dashboard/config/scripts/ecspd_unit_2_pick2.external +++ b/dashboard/config/scripts/ecspd_unit_2_pick2.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Pick2' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage10.external b/dashboard/config/scripts/ecspd_unit_2_stage10.external index 09248541be5f3..4a6e24b271476 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage10.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage10.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage10' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage11.external b/dashboard/config/scripts/ecspd_unit_2_stage11.external index a5f4e3cb1adca..93d801f2d2497 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage11.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage11.external @@ -1,7 +1,6 @@ name 'ECSPD Unit 2 Stage11' title 'title' description 'description here' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage12.external b/dashboard/config/scripts/ecspd_unit_2_stage12.external index f6f65cd2c90d0..76ad53d2eb85b 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage12.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage12.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage12' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage13.external b/dashboard/config/scripts/ecspd_unit_2_stage13.external index ff38b72284b6c..59a0383f591d4 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage13.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage13.external @@ -1,7 +1,6 @@ name 'ECSPD Unit 2 Stage13' title 'title' description 'description here' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage4review.external b/dashboard/config/scripts/ecspd_unit_2_stage4review.external index c05633fb4732d..981198c8b5ca6 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage4review.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage4review.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage4Review' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage5.external b/dashboard/config/scripts/ecspd_unit_2_stage5.external index e15a571b134cb..196b2fdca7e68 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage5.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage5.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage5' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage6.external b/dashboard/config/scripts/ecspd_unit_2_stage6.external index fea6867c4a0f6..27fb828b5ac58 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage6.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage6.external @@ -1,7 +1,6 @@ name 'ECSPD Unit 2 Stage6' title 'title' description 'description here' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage7.external b/dashboard/config/scripts/ecspd_unit_2_stage7.external index 128bcd9454992..20eff73cc81bd 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage7.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage7.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage7' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage8.external b/dashboard/config/scripts/ecspd_unit_2_stage8.external index db2697ec83b3a..1d9fa0b152428 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage8.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage8.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage8' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_unit_2_stage9.external b/dashboard/config/scripts/ecspd_unit_2_stage9.external index 1ee4d30315733..479a42f62b693 100644 --- a/dashboard/config/scripts/ecspd_unit_2_stage9.external +++ b/dashboard/config/scripts/ecspd_unit_2_stage9.external @@ -1,5 +1,4 @@ name 'ECSPD Unit 2 Stage9' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_what_is_problem_solving.external b/dashboard/config/scripts/ecspd_what_is_problem_solving.external index 2b3bbf0223787..dc33b2aa239a5 100644 --- a/dashboard/config/scripts/ecspd_what_is_problem_solving.external +++ b/dashboard/config/scripts/ecspd_what_is_problem_solving.external @@ -1,5 +1,4 @@ name 'ECSPD What Is Problem Solving' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_what_is_the_challenge.external b/dashboard/config/scripts/ecspd_what_is_the_challenge.external index 08d79e7526f44..ad5435b145fed 100644 --- a/dashboard/config/scripts/ecspd_what_is_the_challenge.external +++ b/dashboard/config/scripts/ecspd_what_is_the_challenge.external @@ -1,5 +1,4 @@ name 'ECSPD What is the Challenge' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/ecspd_why_binary_the_internet_video.external b/dashboard/config/scripts/ecspd_why_binary_the_internet_video.external index 64af4bf320c86..c977098913147 100644 --- a/dashboard/config/scripts/ecspd_why_binary_the_internet_video.external +++ b/dashboard/config/scripts/ecspd_why_binary_the_internet_video.external @@ -1,5 +1,4 @@ name 'ECSPD Why Binary - The Internet Video' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/forum_ecspd_unit_2_teaching_practice.external b/dashboard/config/scripts/forum_ecspd_unit_2_teaching_practice.external index c99caea93aa08..1cd6214ec5bf6 100644 --- a/dashboard/config/scripts/forum_ecspd_unit_2_teaching_practice.external +++ b/dashboard/config/scripts/forum_ecspd_unit_2_teaching_practice.external @@ -1,6 +1,5 @@ name 'Forum-ECSPD-Unit 2-Teaching Practice' -href 'path/to/html/in/asset/folder' markdown < diff --git a/dashboard/config/scripts/goodbadvisualizations_v1.external b/dashboard/config/scripts/goodbadvisualizations_v1.external index faee2aa175323..0c13789792992 100644 --- a/dashboard/config/scripts/goodbadvisualizations_v1.external +++ b/dashboard/config/scripts/goodbadvisualizations_v1.external @@ -2,7 +2,6 @@ name 'GoodBadVisualizations_v1' title 'title' css 'unplugged' description 'description here' -href 'path/to/html/in/asset/folder' markdown < + + + \ No newline at end of file diff --git a/dashboard/config/scripts/levels/CSD U3 Random Intro.level b/dashboard/config/scripts/levels/CSD U3 Random Intro.level new file mode 100644 index 0000000000000..086902e82e602 --- /dev/null +++ b/dashboard/config/scripts/levels/CSD U3 Random Intro.level @@ -0,0 +1,46 @@ + + + + \ No newline at end of file diff --git a/dashboard/config/scripts/levels/CSD U3 Random Min Max.level b/dashboard/config/scripts/levels/CSD U3 Random Min Max.level new file mode 100644 index 0000000000000..709ae80031227 --- /dev/null +++ b/dashboard/config/scripts/levels/CSD U3 Random Min Max.level @@ -0,0 +1,45 @@ + + + + \ No newline at end of file diff --git a/dashboard/config/scripts/levels/Course 4 Artist 2.level b/dashboard/config/scripts/levels/Course 4 Artist 2.level index 8368302286913..b72c195ff1c29 100644 --- a/dashboard/config/scripts/levels/Course 4 Artist 2.level +++ b/dashboard/config/scripts/levels/Course 4 Artist 2.level @@ -66,8 +66,6 @@ - - @@ -85,11 +83,11 @@ 4 - + moveForward 300 - + turnRight 90 diff --git a/dashboard/config/scripts/levels/Course 4 Artist For Loops 4.level b/dashboard/config/scripts/levels/Course 4 Artist For Loops 4.level index 6e783c3912800..1949b19ad4288 100644 --- a/dashboard/config/scripts/levels/Course 4 Artist For Loops 4.level +++ b/dashboard/config/scripts/levels/Course 4 Artist For Loops 4.level @@ -23,7 +23,7 @@ "impressive": "false", "disable_sharing": "false", "callout_json": "[\r\n {\"localization_key\":\"place_counter_variable\",\"callout_text\":\"Drag your counter variable inside this block\",\"element_id\":\"#place_counter_variable_qtip\",\"qtip_config\":{\"position\":{\"adjust\":{\"x\":null,\"y\":null},\"my\":\"top center\",\"at\":\"bottom center\"},\"style\":{\"classes\":\"\"}}}\r\n]", - "markdown_instructions": "How would you modify what you've learned to draw these squares? They start at 15 pixels long, the largest is 300 pixels long, and each square is 15 pixels larger than the last. \r\n\r\n
\r\n
\r\nHelp me with angles\r\n![](https://images.code.org/dede4ee3f1698a385a3a8e404d5758b4-image-1439254128944.gif)\r\n![](https://images.code.org/c24a3fdc9e5e31b4e943f749a18b7996-image-1439254361981.png)\r\n
\r\n
", + "markdown_instructions": "How would you modify what you've learned to draw these squares? They start at 15 pixels long, the largest is 300 pixels long, and each square is 15 pixels larger than the last. \r\n\r\n
\r\n
\r\n
\r\nHelp me with angles\r\n![](https://images.code.org/dede4ee3f1698a385a3a8e404d5758b4-image-1439254128944.gif)\r\n![](https://images.code.org/c24a3fdc9e5e31b4e943f749a18b7996-image-1439254361981.png)\r\n
\r\n
", "contract_highlight": "false", "contract_collapse": "false", "examples_highlight": "false", @@ -32,7 +32,8 @@ "definition_collapse": "false", "disable_examples": "false", "examples_required": "false", - "never_autoplay_video": "false" + "never_autoplay_video": "false", + "authored_hints": "[\r\n {\r\n \"hint_class\": \"content\",\r\n \"hint_markdown\": \"**What is the smallest number you will need? \\nWhat is the largest number? \\nHow big is the increase each time?**\\n\\nThose are the questions you need to ask to fill in the blanks of the `for` loop.\",\r\n \"hint_id\": \"Course_4_Artist_For_Loops_4_a\",\r\n \"hint_type\": \"general\"\r\n }\r\n]" }, "published": true, "notes": "" @@ -114,13 +115,6 @@
- - - - 1 - - - @@ -164,6 +158,28 @@ + + + + + + + 15 + + + + + 300 + + + + + 15 + + + + + diff --git a/dashboard/config/scripts/levels/Course 4 Bee Params 8.level b/dashboard/config/scripts/levels/Course 4 Bee Params 8.level index d475f186e479d..2282ca6372ae2 100644 --- a/dashboard/config/scripts/levels/Course 4 Bee Params 8.level +++ b/dashboard/config/scripts/levels/Course 4 Bee Params 8.level @@ -32,7 +32,9 @@ "never_autoplay_video": "false", "examples_required": "false", "fast_get_nectar_animation": "false" - } + }, + "published": true, + "notes": "" }]]> @@ -235,10 +237,160 @@ right + + + + + + + + + + + + + + left + + + 0 + + + + + right + + + 1 + + + + + + + + + + + moveForward + + + + + + + + + + right + + + + + 0 + + + + + 2 + + + + + moveForward + + + + + + + + + + left + + + + + 2 + + + + + 0 + + + + + moveForward + + + + + + + + + + right + + + + + 3 + + + + + 0 + + + + + moveForward + + + + + + + + + + left + + + + + 0 + + + + + 3 + + + + + + + + + + + + + + + + + + + @@ -352,144 +504,6 @@
- - - - left - - - 0 - - - - - right - - - 1 - - - - - moveForward - - - - - - - - - - right - - - - - 0 - - - - - 2 - - - - - moveForward - - - - - - - - - - left - - - - - 2 - - - - - 0 - - - - - moveForward - - - - - - - - - - right - - - - - 3 - - - - - 0 - - - - - moveForward - - - - - - - - - - left - - - - - 0 - - - - - 3 - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/dashboard/config/scripts/levels/NEW Course 4 Artist Vars 2.level b/dashboard/config/scripts/levels/NEW Course 4 Artist Vars 2.level index d7b3992bdb47d..8e0789681fd14 100644 --- a/dashboard/config/scripts/levels/NEW Course 4 Artist Vars 2.level +++ b/dashboard/config/scripts/levels/NEW Course 4 Artist Vars 2.level @@ -96,11 +96,13 @@ ??? - + + length + + + length + - - - diff --git a/dashboard/config/scripts/levels/U3 - Simple Drawing - Background.level b/dashboard/config/scripts/levels/U3 - Simple Drawing - Background.level new file mode 100644 index 0000000000000..d00f1b174c997 --- /dev/null +++ b/dashboard/config/scripts/levels/U3 - Simple Drawing - Background.level @@ -0,0 +1,53 @@ + + + + \ No newline at end of file diff --git a/dashboard/config/scripts/levels/U3 - Simple Drawing - Fill.level b/dashboard/config/scripts/levels/U3 - Simple Drawing - Fill.level new file mode 100644 index 0000000000000..eec636797bc93 --- /dev/null +++ b/dashboard/config/scripts/levels/U3 - Simple Drawing - Fill.level @@ -0,0 +1,44 @@ + + + + \ No newline at end of file diff --git a/dashboard/config/scripts/levels/U3 - Simple Drawing - Rectangle.level b/dashboard/config/scripts/levels/U3 - Simple Drawing - Rectangle.level new file mode 100644 index 0000000000000..b81d3bf005b6a --- /dev/null +++ b/dashboard/config/scripts/levels/U3 - Simple Drawing - Rectangle.level @@ -0,0 +1,42 @@ + + + + \ No newline at end of file diff --git a/dashboard/config/scripts/levels/allthethings design mode elements.level b/dashboard/config/scripts/levels/allthethings design mode elements.level index f3f81b9a395c8..59843bd643b28 100644 --- a/dashboard/config/scripts/levels/allthethings design mode elements.level +++ b/dashboard/config/scripts/levels/allthethings design mode elements.level @@ -6,7 +6,7 @@ "user_id": 18, "properties": { "skin": "applab", - "markdown_instructions": "Please sign in to test applab levels (By design, applab levels only work if signed in. Normally, we make people sign in, but this is only enforceable for a whole script and would be annoying to do for the allthethings script).", + "markdown_instructions": "Please sign in to test applab levels (By design, applab levels only work if signed in. Normally, we make people sign in, but this is only enforceable for a whole script and would be annoying to do for the allthethings script).\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\n.\r\n\r\nEnd of instructions that are long.", "instructions": "Please sign in to test applab levels (By design, applab levels only work if signed in. Normally, we make people sign in, but this is only enforceable for a whole script and would be annoying to do for the allthethings script).", "code_functions": { "onEvent": null, @@ -132,7 +132,8 @@ "callMyFunction": null, "callMyFunction_n": null, "return": null, - "drawImageURL": null + "drawImageURL": null, + "comment": null }, "edit_code": true, "embed": "false", @@ -163,8 +164,16 @@ "submittable": "false", "hide_view_data_button": "false", "debugger_disabled": "false", - "start_blocks": "// make sure dynamically generated elements appear on\r\n// a different screen from design mode elements\r\nsetScreen(\"screen2\");\r\n\r\nbutton(\"buttonid\", \"button text\");\r\ntextInput(\"inputid\", \"input text\");\r\ntextLabel(\"labelid\", \"label text\");\r\ndropdown(\"dropdownid\", \"option1\", \"option2\", \"option3\");\r\ncheckbox(\"checkboxid\", false);\r\nradioButton(\"radioid\", false, \"group\");\r\n" - } + "start_blocks": "// make sure dynamically generated elements appear on\r\n// a different screen from design mode elements\r\nsetScreen(\"screen2\");\r\n\r\nbutton(\"buttonid\", \"button text\");\r\ntextInput(\"inputid\", \"input text\");\r\ntextLabel(\"labelid\", \"label text\");\r\ndropdown(\"dropdownid\", \"option1\", \"option2\", \"option3\");\r\ncheckbox(\"checkboxid\", false);\r\nradioButton(\"radioid\", false, \"group\");\r\n", + "encrypted_examples": [ + + ], + "lock_zero_param_functions": "false", + "execute_palette_apis_only": "false", + "fail_on_lint_errors": "false" + }, + "published": true, + "notes": "" }]]> - + \ No newline at end of file diff --git a/dashboard/config/scripts/pdalg_teacher_dashboard.external b/dashboard/config/scripts/pdalg_teacher_dashboard.external index 39347898fcdaa..b87ed80254d3b 100644 --- a/dashboard/config/scripts/pdalg_teacher_dashboard.external +++ b/dashboard/config/scripts/pdalg_teacher_dashboard.external @@ -1,7 +1,6 @@ name 'PDAlg Teacher Dashboard' title 'Course Progress' css 'unplugged' -skip_dialogue true markdown < diff --git a/dashboard/config/scripts/pdk5_why_k5_cs.external b/dashboard/config/scripts/pdk5_why_k5_cs.external index 5b1cdbb3a2b14..39d8b4d300163 100644 --- a/dashboard/config/scripts/pdk5_why_k5_cs.external +++ b/dashboard/config/scripts/pdk5_why_k5_cs.external @@ -1,7 +1,6 @@ name 'PDK5 Why K5 CS' title 'Why teach computer science in elementary school?' css 'unplugged' -skip_dialogue true markdown < diff --git a/dashboard/config/scripts/u3_conditionals_introducing_else.external b/dashboard/config/scripts/u3_conditionals_introducing_else.external index 629b71a5f3908..45e91a53a60fc 100644 --- a/dashboard/config/scripts/u3_conditionals_introducing_else.external +++ b/dashboard/config/scripts/u3_conditionals_introducing_else.external @@ -1,5 +1,4 @@ name 'U3 - Conditionals - Introducing Else' -href 'path/to/html/in/asset/folder' css 'unplugged' markdown < To: `dev+build@code.org`
Subject: `/home/ubuntu/staging/aws/build_and_mail_log websites` | | `ssh ubuntu@staging.code.org` `~/staging/code-dot-org/pegasus/log/*.log` | `ssh ubuntu@staging.code.org` `~/staging/code-dot-org/dashboard/log/staging.log` | -| test | email
To: `dev+build@code.org`
Subject: `/home/ubuntu/test/aws/build_and_mail_log test-websites` | `ssh ubuntu@test.code.org`
`~/test/code-dot-org/dashboard/test/ui/*.log`
also on browserstack at https://www.browserstack.com/automate | `ssh ubuntu@test.code.org`
`~/test/code-dot-org/pegasus/log/*.log` | `ssh ubuntu@test.code.org`
`~/test/code-dot-org/dashboard/log/test.log` | +| test | email
To: `dev+build@code.org`
Subject: `/home/ubuntu/test/aws/build_and_mail_log test-websites` | `ssh ubuntu@test.code.org`
`~/test/code-dot-org/dashboard/test/ui/*.log`
also on Sauce Labs at https://saucelabs.com/beta/dashboard/tests | `ssh ubuntu@test.code.org`
`~/test/code-dot-org/pegasus/log/*.log` | `ssh ubuntu@test.code.org`
`~/test/code-dot-org/dashboard/log/test.log` | | production | email
To: `dev+build@code.org`
Subject: `/home/ubuntu/production/aws/build_and_mail_log websites` | | on logentries: https://logentries.com | on logentries: https://logentries.com | diff --git a/i18n/locales/source/blockly-mooc/common.json b/i18n/locales/source/blockly-mooc/common.json index e34d67089233d..badf860a44d7c 100644 --- a/i18n/locales/source/blockly-mooc/common.json +++ b/i18n/locales/source/blockly-mooc/common.json @@ -24,6 +24,7 @@ "completedWithoutRecommendedBlock": "Congratulations! You completed Puzzle {puzzleNumber}. (But you could use a different block for stronger code.)", "continue": "Continue", "copy": "Copy", + "currentVersion": "Current Version", "debugConsoleHeader": "Debug Console", "debugCommandsHeaderWhenOpen": "Debug Commands", "debugCommandsHeaderWhenClosed": "Show Debug Commands", @@ -184,6 +185,7 @@ "recommendedBlockContextualHintTitle": "Try using a block like this to solve the puzzle.", "repeat": "repeat", "resetProgram": "Reset", + "restoreThisVersion": "Restore this Version", "rotateText": "Rotate your device.", "runProgram": "Run", "runTooltip": "Run the program defined by the blocks in the workspace.", diff --git a/lib/cdo/workshop_constants.rb b/lib/cdo/workshop_constants.rb index de8057cda40ae..097f1dececc38 100644 --- a/lib/cdo/workshop_constants.rb +++ b/lib/cdo/workshop_constants.rb @@ -35,6 +35,8 @@ module WorkshopConstants 8 => {id: 8, short_name: PHASE_4, long_name: 'Phase 4: Summer Wrap Up'} } + # These are ids for google form surveys sent in email at the completion of various workshops. + # See Workshop.exit_survey_url EXIT_SURVEY_IDS = { CS_IN_S => { PHASE_2 => '1QG9eCbKJD26UNvTC0C9ZZyrp63WjzQSK5gQPP4lsZ2c', @@ -51,6 +53,7 @@ module WorkshopConstants PHASE_3A => '19Cfv0tzvBh7JOUWhFWv5phDvcDkxYWm1nBLKXESVyB4', PHASE_3B => '1U92feb_2lJ8lg2uU4dLeabK9zbmznkNogYtEP-N5IH4', PHASE_3C => '156hOBI42fcQx6rYsuMmdMH5NqElbAsCTd5MXDs70_Vc', + PHASE_3D => '10_5kTEDVNn46HleKyDIEjWrk6U1iiHHPIgpZATwL4vw', PHASE_4 => '1KygTcSauHkt5AA_RF_PM5OIYVpvXPLOlG8O79yVRi48' }, CSP => { @@ -62,5 +65,4 @@ module WorkshopConstants PHASE_4 => '1aMKclWosHmvn5GH0KaVxBGvxh7KeLYQTtKiHf-lC0X0' } } - end