From 64bb52d433bc2b38800b5fc1077c716c69ed9e80 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Mon, 27 Jan 2025 21:30:59 +0000 Subject: [PATCH 01/10] 1113 Register must include location and made available to local leaders (#1279) * store in progress register * access locations on register * use same course code as data files * add locations to all courses * update autocomplete * Rename module data to match expected module name * Allow configuring module hierarchy depth * Update ITP frequency * Update fields --------- Co-authored-by: Daniel Wagner-Hall --- common-theme/layouts/_default/day-plan.html | 11 ++++++++++- .../layouts/partials/register-attendance.html | 18 +++++++++++++----- org-cyf-itp/content/welcome/_index.md | 1 + org-cyf-itp/hugo.toml | 2 +- .../netlify/functions/submission-created.js | 5 +++-- org-cyf-piscine/content/sprints/_index.md | 12 ++++++++++++ .../courses/{piscine.toml => cyf-piscine.toml} | 3 ++- org-cyf-theme/data/courses/itd.toml | 1 + org-cyf-theme/data/courses/itp.toml | 3 ++- org-cyf-theme/data/courses/launch.toml | 3 ++- org-cyf-theme/data/courses/sdc.toml | 3 ++- org-cyf-theme/data/courses/systems.toml | 3 ++- 12 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 org-cyf-piscine/content/sprints/_index.md rename org-cyf-theme/data/courses/{piscine.toml => cyf-piscine.toml} (70%) diff --git a/common-theme/layouts/_default/day-plan.html b/common-theme/layouts/_default/day-plan.html index 307a8a50d..ede7743a6 100644 --- a/common-theme/layouts/_default/day-plan.html +++ b/common-theme/layouts/_default/day-plan.html @@ -15,7 +15,16 @@
{{ if ne .Params.noRegister true }} - {{ partial "register-attendance.html" (dict "course" site.Title "module" $.Page.CurrentSection.Parent.Title "day" $.Page.CurrentSection.Title) }} + {{ $moduleSection := $.Page.CurrentSection }} + {{/* Most of our modules have two levels of hierarchy - course > module > sprint. But some go straight course > sprint or module > sprint - allow opting into this with a param. */}} + {{ $parentsToTraverseToModule := $moduleSection.Params.parentsToTraverseToModule }} + {{ if eq $parentsToTraverseToModule nil }} + {{ $parentsToTraverseToModule = 1 }} + {{ end }} + {{ range seq $parentsToTraverseToModule }} + {{ $moduleSection = $moduleSection.Parent }} + {{ end }} + {{ partial "register-attendance.html" (dict "course" site.Title "module" $moduleSection.Title "day" $.Page.CurrentSection.Title) }} {{ end }} {{ range $index, $block := .Params.blocks }} diff --git a/common-theme/layouts/partials/register-attendance.html b/common-theme/layouts/partials/register-attendance.html index 17ae9c5f2..8fdf8da0a 100644 --- a/common-theme/layouts/partials/register-attendance.html +++ b/common-theme/layouts/partials/register-attendance.html @@ -13,6 +13,9 @@ {{ $module := .module }} {{ $day := .day | urlize}} +{{ $orgData := index site.Data.courses $course }} +{{ $locations := $orgData.locations }} + {{/* if any of these params are null, error */}} {{ if or (not $course) (not $module) (not $day) }} {{ errorf "register must go on a day plan and capture course, module, and day correctly" }} @@ -37,15 +40,20 @@

-
- -
-
- +
+
+ {{ with $locations }} +
+ + + {{ range . }}{{ end }} + +
+ {{ end }} diff --git a/org-cyf-itp/content/welcome/_index.md b/org-cyf-itp/content/welcome/_index.md index 276571e1b..8c5f83697 100644 --- a/org-cyf-itp/content/welcome/_index.md +++ b/org-cyf-itp/content/welcome/_index.md @@ -5,4 +5,5 @@ layout = 'module' emoji= '🫢🏽' menu = ['syllabus', 'course schedule'] weight='1' +parentsToTraverseToModule = 0 +++ diff --git a/org-cyf-itp/hugo.toml b/org-cyf-itp/hugo.toml index 7fff66829..f61904254 100644 --- a/org-cyf-itp/hugo.toml +++ b/org-cyf-itp/hugo.toml @@ -1,4 +1,4 @@ -title = "CYF ITP" +title = "ITP" baseURL = "https://programming.codeyourfuture.io/" [module] diff --git a/org-cyf-itp/tooling/netlify/functions/submission-created.js b/org-cyf-itp/tooling/netlify/functions/submission-created.js index 88d82b77e..32eddd505 100644 --- a/org-cyf-itp/tooling/netlify/functions/submission-created.js +++ b/org-cyf-itp/tooling/netlify/functions/submission-created.js @@ -16,6 +16,7 @@ import {Auth, google} from "googleapis"; // Each spreadsheet must also give write access to the email listed below in CREDENTIALS. const COURSE_TO_SPREADSHEET_ID = { "cyf-itp": "1YHKPCCN55PJD-o1jg4wbVKI3kbhB-ULiwB5hhG17DcA", + "cyf-piscine": "1XabWuYqvOUiY7HpUra0Vdic4pSxmXmNRZHMR72I1bjk", }; const CREDENTIALS = { @@ -47,7 +48,7 @@ const handler = async (event, context) => { } const request = body.payload.data; - for (const requiredField of ["course", "module", "givenName", "familyName", "email", "day", "buildTime"]) { + for (const requiredField of ["course", "module", "name", "email", "day", "location", "buildTime"]) { if (!(requiredField in request)) { const error = `Request missing field ${requiredField}`; console.error(error, request); @@ -83,7 +84,7 @@ const handler = async (event, context) => { insertDataOption: "INSERT_ROWS", resource: { values: [ - [request.givenName, request.familyName, request.email, new Date().toISOString(), request.course, request.module, request.day, request.buildTime], + [request.name, request.email, new Date().toISOString(), request.course, request.module, request.day, request.location, request.buildTime], ], }, }); diff --git a/org-cyf-piscine/content/sprints/_index.md b/org-cyf-piscine/content/sprints/_index.md new file mode 100644 index 000000000..f8ab8df76 --- /dev/null +++ b/org-cyf-piscine/content/sprints/_index.md @@ -0,0 +1,12 @@ ++++ +title = 'Piscine' +description = 'In teams and on your own, build working software with tests. Explain your work to others.' +layout = 'module' +emoji= '🐠' +menu = ['syllabus', 'next steps'] +menus_to_map=['entry', 'sprints', 'assessment'] +[build] + render = "never" + list = "local" + publishResources = false ++++ diff --git a/org-cyf-theme/data/courses/piscine.toml b/org-cyf-theme/data/courses/cyf-piscine.toml similarity index 70% rename from org-cyf-theme/data/courses/piscine.toml rename to org-cyf-theme/data/courses/cyf-piscine.toml index b2424b7d6..9f0a9b30b 100644 --- a/org-cyf-theme/data/courses/piscine.toml +++ b/org-cyf-theme/data/courses/cyf-piscine.toml @@ -7,4 +7,5 @@ menu="selection" url="https://piscine.codeyourfuture.io/" days="22" commitment="part time" -frequency=4 \ No newline at end of file +frequency=3 +locations=["Birmingham", "Capetown", "Glasgow", "London", "Manchester", "Online", "Sheffield"] diff --git a/org-cyf-theme/data/courses/itd.toml b/org-cyf-theme/data/courses/itd.toml index 4f61f3925..70a0ca0b5 100644 --- a/org-cyf-theme/data/courses/itd.toml +++ b/org-cyf-theme/data/courses/itd.toml @@ -8,3 +8,4 @@ description="Meet the world of tech: write a CV with AI; evaluate data in spread days=30 commitment="part time" frequency=6 +locations=["Birmingham", "Capetown", "Glasgow", "London", "Manchester", "Online", "Sheffield"] diff --git a/org-cyf-theme/data/courses/itp.toml b/org-cyf-theme/data/courses/itp.toml index 8c339e619..1578bf39f 100644 --- a/org-cyf-theme/data/courses/itp.toml +++ b/org-cyf-theme/data/courses/itp.toml @@ -7,4 +7,5 @@ description="Programming with JavaScript, Python, and SQL; collaborate to delive emoji="🐣" days=84 commitment="part time" -frequency=4 \ No newline at end of file +frequency=3 +locations=["Birmingham", "Capetown", "Glasgow", "London", "Manchester", "Online", "Sheffield"] diff --git a/org-cyf-theme/data/courses/launch.toml b/org-cyf-theme/data/courses/launch.toml index 9ca6a3478..16733194a 100644 --- a/org-cyf-theme/data/courses/launch.toml +++ b/org-cyf-theme/data/courses/launch.toml @@ -7,4 +7,5 @@ description="In a cross functional Agile team, develop and deliver a real produc emoji="πŸš€" commitment="part time" days=35 -frequency=4 \ No newline at end of file +frequency=4 +locations=["Online"] \ No newline at end of file diff --git a/org-cyf-theme/data/courses/sdc.toml b/org-cyf-theme/data/courses/sdc.toml index 38f39e4c0..17c11de75 100644 --- a/org-cyf-theme/data/courses/sdc.toml +++ b/org-cyf-theme/data/courses/sdc.toml @@ -7,4 +7,5 @@ weight=4 description="A secure grounding in software: binary, logic, systems, and problem solving" days="84" commitment="part time" -frequency=3 \ No newline at end of file +frequency=3 +locations=["Birmingham", "Capetown", "Glasgow", "London", "Manchester", "Online", "Sheffield"] \ No newline at end of file diff --git a/org-cyf-theme/data/courses/systems.toml b/org-cyf-theme/data/courses/systems.toml index 6fbfdbc43..175017ac8 100644 --- a/org-cyf-theme/data/courses/systems.toml +++ b/org-cyf-theme/data/courses/systems.toml @@ -5,4 +5,5 @@ description="An immersive, full time, systems engineering programme" url="https://systems.codeyourfuture.io/" menu="fellowships" days=260 -commitment="full time" \ No newline at end of file +commitment="full time" +locations=["Online"] \ No newline at end of file From 162acc764298b413c54eb0195aa5affa7c58fa3d Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 28 Jan 2025 12:13:23 +0000 Subject: [PATCH 02/10] Fix up register (#1323) * Rename courses to remove cyf- prefixes everywhere Also add a check that courses have matching data. * Symlink submission-created across courses Otherwise the Piscine doesn't get register events handled --- .../layouts/partials/register-attendance.html | 8 ++ org-cyf-itd/hugo.toml | 4 +- .../netlify/functions/submission-created.js | 109 +----------------- org-cyf-launch/hugo.toml | 4 +- org-cyf-piscine/hugo.toml | 2 +- .../netlify/functions/submission-created.js | 1 + org-cyf-sdc/hugo.toml | 2 +- .../{cyf-piscine.toml => piscine.toml} | 0 .../netlify/functions/submission-created.js | 108 +++++++++++++++++ 9 files changed, 124 insertions(+), 114 deletions(-) mode change 100644 => 120000 org-cyf-itp/tooling/netlify/functions/submission-created.js create mode 120000 org-cyf-piscine/tooling/netlify/functions/submission-created.js rename org-cyf-theme/data/courses/{cyf-piscine.toml => piscine.toml} (100%) create mode 100644 tooling/netlify/functions/submission-created.js diff --git a/common-theme/layouts/partials/register-attendance.html b/common-theme/layouts/partials/register-attendance.html index 8fdf8da0a..91bc86d0a 100644 --- a/common-theme/layouts/partials/register-attendance.html +++ b/common-theme/layouts/partials/register-attendance.html @@ -13,7 +13,15 @@ {{ $module := .module }} {{ $day := .day | urlize}} +{{/* Note: These keys are hard-coded in submission-created.js - if you change them, you need to change them there as well. */}} {{ $orgData := index site.Data.courses $course }} +{{ if eq $orgData nil }} + {{ $keys := slice }} + {{ range $key, $value := site.Data.courses }} + {{ $keys = append $key $keys }} + {{ end }} + {{ errorf "Couldn't find course data for course %s - knew about %s" $course $keys }} +{{ end }} {{ $locations := $orgData.locations }} {{/* if any of these params are null, error */}} diff --git a/org-cyf-itd/hugo.toml b/org-cyf-itd/hugo.toml index 6208a1e87..8c3b20bea 100644 --- a/org-cyf-itd/hugo.toml +++ b/org-cyf-itd/hugo.toml @@ -1,4 +1,4 @@ -title = "Intro to Digital" +title = "ITD" description="Meet the world of tech in 30 days" [module] @@ -33,4 +33,4 @@ description="Meet the world of tech in 30 days" # because of this 'unexpected behaviour' https://gohugo.io/methods/page/nextinsection/ [page] nextPrevInSectionSortOrder = 'asc' - nextPrevSortOrder = 'asc' \ No newline at end of file + nextPrevSortOrder = 'asc' diff --git a/org-cyf-itp/tooling/netlify/functions/submission-created.js b/org-cyf-itp/tooling/netlify/functions/submission-created.js deleted file mode 100644 index 32eddd505..000000000 --- a/org-cyf-itp/tooling/netlify/functions/submission-created.js +++ /dev/null @@ -1,108 +0,0 @@ -// This function handles forwards netlify form submissions to our register spreadsheets. -// Note that errors are reported in the netlify console, but are _not_ exposed to users who submit the form - they will see successful submission even if this function fails. -// -// This function is automatically called for all form submissions, because it is named exactly submission-create. -// It cannot be manually called by users, and is only callable by Netlify itself. -// If we end up adding multiple forms to the site, we'll need to demux them here in some way and filter out other forms. -// -// The underlying form data is in the body (which is a JSON object) under .payload.data. - -import {Auth, google} from "googleapis"; - -// Each sheet listed here was manually crated, and its spreadsheet ID is taken from its URL. -// Each spreadsheet is expected to contain one sheet per module, named for the module. -// Each module sheet is expected to contain the following columns, in order: -// Given Name | Family Name | Email | Timestamp | Course | Module | Day | Build Time -// Each spreadsheet must also give write access to the email listed below in CREDENTIALS. -const COURSE_TO_SPREADSHEET_ID = { - "cyf-itp": "1YHKPCCN55PJD-o1jg4wbVKI3kbhB-ULiwB5hhG17DcA", - "cyf-piscine": "1XabWuYqvOUiY7HpUra0Vdic4pSxmXmNRZHMR72I1bjk", -}; - -const CREDENTIALS = { - // This was generated by: - // 1. Visit https://console.cloud.google.com/apis/credentials?project=cyf-syllabus - // 2. Generate or find the service account. - // 3. Download its credentials as a JSON file. - // 4. Fetch the keys listed below - the non-env-var ones are not really secret. - // 5. For private_key, this is read from an env var on netlify, and env vars cannot contain newlines, so we put the string "\n" where we need newlines, accordingly, we undo this below. - // The env var was constructed by running: `jq '.private_key' { - console.log("Got request with body", event.body); - let body; - try { - // TODO: Check for structure - body = JSON.parse(event.body); - } catch (error) { - console.error(`Failed to parse request as valid JSON: ${event.body}: ${error}`); - return {statusCode: 400, body: JSON.stringify("Failed to parse body as JSON")}; - } - if (!("payload" in body) || !("data" in body.payload)) { - console.error(`Failed to parse request - missing .payload.data: ${event.body}`); - return {statusCode: 400, body: JSON.stringify("Failed to parse body as JSON")}; - } - const request = body.payload.data; - - for (const requiredField of ["course", "module", "name", "email", "day", "location", "buildTime"]) { - if (!(requiredField in request)) { - const error = `Request missing field ${requiredField}`; - console.error(error, request); - return {statusCode: 400, body: JSON.stringify(error)}; - } - const type = typeof request[requiredField]; - if (type !== "string") { - const error = `Field ${requiredField} must be of type string but was ${type}`; - console.error(error, request); - return {statusCode: 400, body: JSON.stringify(error)}; - } - } - - if (!(request.course in COURSE_TO_SPREADSHEET_ID)) { - console.error(`Didn't recognise course ${request.course}`); - return {statusCode: 404, body: JSON.stringify(`Unknown course: ${request.course}`)}; - } - - const auth = new Auth.GoogleAuth({ - credentials: CREDENTIALS, - scopes: "https://www.googleapis.com/auth/spreadsheets", - }); - - const sheets = google.sheets({version: "v4", auth}); - - try { - const response = await sheets.spreadsheets.values.append({ - spreadsheetId: COURSE_TO_SPREADSHEET_ID[request.course], - // Trust that the user-supplied module exists. - // If it's wrong, we'll get a 400 from the API and pass it on to the user without much useful detail. - range: request.module, - valueInputOption: "RAW", - insertDataOption: "INSERT_ROWS", - resource: { - values: [ - [request.name, request.email, new Date().toISOString(), request.course, request.module, request.day, request.location, request.buildTime], - ], - }, - }); - } catch (error) { - console.error("Error from Google API", error); - let message = "An error occurred signing the register"; - if (error.errors) { - if ("message" in error.errors[0]) { - message += ": " + error.errors[0].message; - } - } - return {statusCode: 400, body: JSON.stringify(message)}; - } - - return { - statusCode: 200, - body: JSON.stringify("You have successfully signed the register"), - }; -}; - -export { handler }; diff --git a/org-cyf-itp/tooling/netlify/functions/submission-created.js b/org-cyf-itp/tooling/netlify/functions/submission-created.js new file mode 120000 index 000000000..bd794c774 --- /dev/null +++ b/org-cyf-itp/tooling/netlify/functions/submission-created.js @@ -0,0 +1 @@ +../../../../tooling/netlify/functions/submission-created.js \ No newline at end of file diff --git a/org-cyf-launch/hugo.toml b/org-cyf-launch/hugo.toml index 44b506e83..1a9bdcf13 100644 --- a/org-cyf-launch/hugo.toml +++ b/org-cyf-launch/hugo.toml @@ -1,4 +1,4 @@ -title = "The Launch" +title = "Launch" baseURL = "https://launch.codeyourfuture.io/" [module] @@ -35,4 +35,4 @@ baseURL = "https://launch.codeyourfuture.io/" # because of this 'unexpected behaviour' https://gohugo.io/methods/page/nextinsection/ [page] nextPrevInSectionSortOrder = 'asc' - nextPrevSortOrder = 'asc' \ No newline at end of file + nextPrevSortOrder = 'asc' diff --git a/org-cyf-piscine/hugo.toml b/org-cyf-piscine/hugo.toml index fbee95829..9beee90e4 100644 --- a/org-cyf-piscine/hugo.toml +++ b/org-cyf-piscine/hugo.toml @@ -1,4 +1,4 @@ -title = "CYF Piscine" +title = "Piscine" [module] [[module.imports]] diff --git a/org-cyf-piscine/tooling/netlify/functions/submission-created.js b/org-cyf-piscine/tooling/netlify/functions/submission-created.js new file mode 120000 index 000000000..bd794c774 --- /dev/null +++ b/org-cyf-piscine/tooling/netlify/functions/submission-created.js @@ -0,0 +1 @@ +../../../../tooling/netlify/functions/submission-created.js \ No newline at end of file diff --git a/org-cyf-sdc/hugo.toml b/org-cyf-sdc/hugo.toml index 44c3b5a66..157fb43b7 100644 --- a/org-cyf-sdc/hugo.toml +++ b/org-cyf-sdc/hugo.toml @@ -1,4 +1,4 @@ -title = "CYF SDC Curriculum" +title = "SDC" baseURL = "https://sdc.codeyourfuture.io/" [module] diff --git a/org-cyf-theme/data/courses/cyf-piscine.toml b/org-cyf-theme/data/courses/piscine.toml similarity index 100% rename from org-cyf-theme/data/courses/cyf-piscine.toml rename to org-cyf-theme/data/courses/piscine.toml diff --git a/tooling/netlify/functions/submission-created.js b/tooling/netlify/functions/submission-created.js new file mode 100644 index 000000000..837a21b66 --- /dev/null +++ b/tooling/netlify/functions/submission-created.js @@ -0,0 +1,108 @@ +// This function handles forwards netlify form submissions to our register spreadsheets. +// Note that errors are reported in the netlify console, but are _not_ exposed to users who submit the form - they will see successful submission even if this function fails. +// +// This function is automatically called for all form submissions, because it is named exactly submission-create. +// It cannot be manually called by users, and is only callable by Netlify itself. +// If we end up adding multiple forms to the site, we'll need to demux them here in some way and filter out other forms. +// +// The underlying form data is in the body (which is a JSON object) under .payload.data. + +import {Auth, google} from "googleapis"; + +// Each sheet listed here was manually crated, and its spreadsheet ID is taken from its URL. +// Each spreadsheet is expected to contain one sheet per module, named for the module. +// Each module sheet is expected to contain the following columns, in order: +// Given Name | Family Name | Email | Timestamp | Course | Module | Day | Build Time +// Each spreadsheet must also give write access to the email listed below in CREDENTIALS. +const COURSE_TO_SPREADSHEET_ID = { + "itp": "1YHKPCCN55PJD-o1jg4wbVKI3kbhB-ULiwB5hhG17DcA", + "piscine": "1XabWuYqvOUiY7HpUra0Vdic4pSxmXmNRZHMR72I1bjk", +}; + +const CREDENTIALS = { + // This was generated by: + // 1. Visit https://console.cloud.google.com/apis/credentials?project=cyf-syllabus + // 2. Generate or find the service account. + // 3. Download its credentials as a JSON file. + // 4. Fetch the keys listed below - the non-env-var ones are not really secret. + // 5. For private_key, this is read from an env var on netlify, and env vars cannot contain newlines, so we put the string "\n" where we need newlines, accordingly, we undo this below. + // The env var was constructed by running: `jq '.private_key' { + console.log("Got request with body", event.body); + let body; + try { + // TODO: Check for structure + body = JSON.parse(event.body); + } catch (error) { + console.error(`Failed to parse request as valid JSON: ${event.body}: ${error}`); + return {statusCode: 400, body: JSON.stringify("Failed to parse body as JSON")}; + } + if (!("payload" in body) || !("data" in body.payload)) { + console.error(`Failed to parse request - missing .payload.data: ${event.body}`); + return {statusCode: 400, body: JSON.stringify("Failed to parse body as JSON")}; + } + const request = body.payload.data; + + for (const requiredField of ["course", "module", "name", "email", "day", "location", "buildTime"]) { + if (!(requiredField in request)) { + const error = `Request missing field ${requiredField}`; + console.error(error, request); + return {statusCode: 400, body: JSON.stringify(error)}; + } + const type = typeof request[requiredField]; + if (type !== "string") { + const error = `Field ${requiredField} must be of type string but was ${type}`; + console.error(error, request); + return {statusCode: 400, body: JSON.stringify(error)}; + } + } + + if (!(request.course in COURSE_TO_SPREADSHEET_ID)) { + console.error(`Didn't recognise course ${request.course}`); + return {statusCode: 404, body: JSON.stringify(`Unknown course: ${request.course}`)}; + } + + const auth = new Auth.GoogleAuth({ + credentials: CREDENTIALS, + scopes: "https://www.googleapis.com/auth/spreadsheets", + }); + + const sheets = google.sheets({version: "v4", auth}); + + try { + const response = await sheets.spreadsheets.values.append({ + spreadsheetId: COURSE_TO_SPREADSHEET_ID[request.course], + // Trust that the user-supplied module exists. + // If it's wrong, we'll get a 400 from the API and pass it on to the user without much useful detail. + range: request.module, + valueInputOption: "RAW", + insertDataOption: "INSERT_ROWS", + resource: { + values: [ + [request.name, request.email, new Date().toISOString(), request.course, request.module, request.day, request.location, request.buildTime], + ], + }, + }); + } catch (error) { + console.error("Error from Google API", error); + let message = "An error occurred signing the register"; + if (error.errors) { + if ("message" in error.errors[0]) { + message += ": " + error.errors[0].message; + } + } + return {statusCode: 400, body: JSON.stringify(message)}; + } + + return { + statusCode: 200, + body: JSON.stringify("You have successfully signed the register"), + }; +}; + +export { handler }; From ad31478c9e1917914d11be3abeecfc319f2b05ff Mon Sep 17 00:00:00 2001 From: MitchLloyd Date: Tue, 28 Jan 2025 12:38:23 +0000 Subject: [PATCH 03/10] Change scheme for fetch (#1307) * move more details on events from asychrony to capturing-events * shuffle explanation into re-rendering * add first pass of file protocol explanation into using-fetch * tidy the explanation * Update common-content/en/module/js3/re-rendering/index.md Co-authored-by: Daniel Wagner-Hall * Update common-content/en/module/js3/using-fetch/index.md Co-authored-by: Daniel Wagner-Hall * Update common-content/en/module/js3/using-fetch/index.md Co-authored-by: Daniel Wagner-Hall * rename async block * tweak the text * rm a learning objective * simplify copy with hemmingway * rename blocks * Update common-content/en/module/js3/capturing-events/index.md Co-authored-by: Daniel Wagner-Hall * Update common-content/en/module/js3/synchronous-execution/index.md Co-authored-by: Daniel Wagner-Hall --------- Co-authored-by: Dedekind561 Co-authored-by: Daniel Wagner-Hall --- .../en/module/js3/asynchrony/index.md | 84 ------------------- .../en/module/js3/capturing-events/index.md | 36 ++------ .../en/module/js3/re-rendering/index.md | 31 +++++++ .../module/js3/synchronous-execution/index.md | 43 ++++++++++ .../en/module/js3/using-fetch/index.md | 52 +++++++++--- .../data-flows/sprints/3/prep/index.md | 4 +- 6 files changed, 122 insertions(+), 128 deletions(-) delete mode 100644 common-content/en/module/js3/asynchrony/index.md create mode 100644 common-content/en/module/js3/synchronous-execution/index.md diff --git a/common-content/en/module/js3/asynchrony/index.md b/common-content/en/module/js3/asynchrony/index.md deleted file mode 100644 index b57c9840b..000000000 --- a/common-content/en/module/js3/asynchrony/index.md +++ /dev/null @@ -1,84 +0,0 @@ -+++ -title = 'Asynchrony : outside time' - -time = 40 -emoji= '⏳' -[objectives] -1="Define asynchrony" -2="Explain why we need asynchrony" -3="Identify an asynchronous method we have already used" -[build] - render = 'never' - list = 'local' - publishResources = false - -+++ - -We can handle latency using {{}}Asynchronous execution is running code in a different order than it was written.{{}} To understand asynchrony we first need to be clear about {{}}Synchronous execution is running code in the order it is written.{{}}. - -We have written a lot of JavaScript programs that execute sequentially. This means that each line of code is run in order, one after the other. -{{}} - -#### For example: - -```js -console.log("first"); -console.log("second"); -console.log("third"); -``` - -<---> - -#### Outputs: - -```console -first -second -third -``` - -{{}} -Each line of code is run in order. This is synchronous execution. We do this because JavaScript is {{}} -A single thread can do one thing at a time. JavaScript is a single threaded language. -{{}}. - -When we call a function, the function will run to completion before the next line of code is executed. But what if we need to wait for something to happen? What if we need to wait for our data to arrive before we can show it? In this case, we can use **asynchronous execution**. - -### Event Loop - -We have already used asynchronous execution. We have defined `eventListener`s that _listen_ for events to happen, _then_ execute a callback function. - -```js -const search = document.getElementById("search"); -search.addEventListener("input", handleInput); -``` - -When we called `addEventListener` it didn't immediately call `handleInput`. - -But here's a new idea: eventListeners are part of the [Event API](https://developer.mozilla.org/en-US/docs/Web/API/Event). They are not part of the JavaScript language! 🀯 This means you can't use them in a Node REPL. But they are implemented in web browsers. The core of JavaScript (e.g. strings and functions) is the same everywhere, but different contexts may add extra APIs. - -When you set an eventListener you are really sending a call to a Web API and asking it do something for you. - -```js -const search = document.getElementById("search"); -search.addEventListener("input", handleInput); -``` - -The callback `handleInput` does not run until the user types. With `fetch`, the callback function does not run until the data arrives. In both cases, we are waiting for something to happen before we can run our code. - -We use a function as a way of wrapping up the code that needs to be run later on. This means we can tell the browser _what_ to do when we're done waiting. - -**πŸ‘‰πŸ½ [Visualise the Event Loop](http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIGNvbnNvbGUubG9nKCdZb3UgY2xpY2tlZCB0aGUgYnV0dG9uIScpOyAgICAKfSk7Cgpjb25zb2xlLmxvZygiSGkhIik7Cgpjb25zb2xlLmxvZygiV2VsY29tZSB0byB0aGUgZXZlbnQgbG9vcCIpOw%3D%3D!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D)** - -### 🧠 Recap our concept map - -```mermaid -graph LR - TimeProblem[πŸ—“οΈ Time Problem] --> |caused by| SingleThread[🧡 Single thread] - SingleThread --> |send tasks to| ClientAPIs - TimeProblem --> |solved by| Asynchrony[πŸ›ŽοΈ Asynchrony] - Asynchrony --> | delivered with | ClientAPIs{πŸ’» Client APIs} - ClientAPIs --> |like| setTimeout[(⏲️ setTimeout)] - ClientAPIs --> |like| eventListener[(🦻🏾 eventListener)] - ClientAPIs --> |like| fetch[(πŸ• fetch)] -``` diff --git a/common-content/en/module/js3/capturing-events/index.md b/common-content/en/module/js3/capturing-events/index.md index 5fae89415..faa91f875 100644 --- a/common-content/en/module/js3/capturing-events/index.md +++ b/common-content/en/module/js3/capturing-events/index.md @@ -1,7 +1,6 @@ +++ title = 'Capturing the user event' - -time = 15 +time = 20 emoji= '🦻🏻' [objectives] 1='Add an event listener to a user input' @@ -9,7 +8,6 @@ emoji= '🦻🏻' render = 'never' list = 'local' publishResources = false - +++ We've introduced our state, and our render works for different values of that state. But users of our website can't change the `searchTerm` state themselves. We need to introduce a way for them to change the `searchTerm` state via the UI. @@ -26,33 +24,13 @@ function handleSearchInput(event) { } ``` -When the "input" event fires, our handler function will run. Inside the handler we can access the updated input value: `const searchTerm = event.target.value;` - -So our key steps are: - -1. Add an input event listener to the search box -2. In the handler, get `value` of input element -3. Set the new state based on this value. -4. Call our `render` function again. - -{{}} -But we're not going to do all of these at once! Stop and implement just the first two steps (adding the event listener, and getting the value), and `console.log` the search term. - -{{}} - -We will make sure this works before we try to change the UI. Why? If we try to add the event listener and something _doesn't_ work, we will only have a little bit of code to debug. - -If we tried to solve the whole problem (updating the UI) and something didn't work, we would have a _lot_ of code to debug, which is harder! - -We've now demonstrated that we can capture search text on every keystroke: +These listeners wait for specific events to occur, and when they do, they trigger a callback function we've defined. This gives us a way to make our code respond to user actions rather than running all at once. ```js -const searchBox = document.getElementById("search"); +const search = document.getElementById("search"); +search.addEventListener("input", handleInput); +``` -searchBox.addEventListener("input", handleSearchInput); +When we call `addEventListener`, it doesn't immediately execute the `handleInput` function. Instead, it sets up a listener that will run this function later. Event listeners are part of the web browser's Event API. But event listeners themselves aren't part of the core JavaScript language! When you create an event listener, you're making a request to a Web API to handle this functionality for you. In this pattern, the callback function (`handleInput`) only executes when a user types. These callback functions need to execute in response to user interactions. This lets us tell the browser exactly what actions to take once a particular event occurs. -function handleSearchInput(event) { - const searchTerm = event.target.value; - console.log(searchTerm); -} -``` +Callback functions are essential for handling user interactions in web browsers. They allow our code to execute in response to an event. The browser listens for events and executes our callback functions at the right time. It is our job to define what should happen when those events occur. diff --git a/common-content/en/module/js3/re-rendering/index.md b/common-content/en/module/js3/re-rendering/index.md index df2aa4812..732301e99 100644 --- a/common-content/en/module/js3/re-rendering/index.md +++ b/common-content/en/module/js3/re-rendering/index.md @@ -13,6 +13,37 @@ emoji= 'πŸ”' +++ +When the "input" event fires, our handler function will run. Inside the handler we can access the updated input value: `const searchTerm = event.target.value;` + +So our key steps are: + +1. Add an input event listener to the search box. +2. In the handler, get the `value` of input element. +3. Set the new state based on this value. +4. Call our `render` function again. + +{{}} +But we're not going to do all of these at once! Stop and implement just the first two steps (adding the event listener, and getting the value), and `console.log` the search term. + +{{}} + +We will make sure this works before we try to change the UI. Why? If we try to add the event listener and something _doesn't_ work, we will only have a little bit of code to debug. + +If we tried to solve the whole problem (updating the UI) and something didn't work, we would have a _lot_ of code to debug, which is harder! + +We've now demonstrated that we can capture search text on every keystroke: + +```js +const searchBox = document.getElementById("search"); + +searchBox.addEventListener("input", handleSearchInput); + +function handleSearchInput(event) { + const searchTerm = event.target.value; + console.log(searchTerm); +} +``` + Now that we've shown we can log the search text, we can set the new value of the `searchTerm` state, and re-render the page. We should have a page like this: diff --git a/common-content/en/module/js3/synchronous-execution/index.md b/common-content/en/module/js3/synchronous-execution/index.md new file mode 100644 index 000000000..af2e17526 --- /dev/null +++ b/common-content/en/module/js3/synchronous-execution/index.md @@ -0,0 +1,43 @@ ++++ +title = 'Synchronous execution' + +time = 10 +emoji= '⏳' +[objectives] +1="Define asynchrony" +2="Explain how synchronous execution works" +[build] + render = 'never' + list = 'local' + publishResources = false ++++ + +We can handle latency using {{}}Asynchronous execution is running code in a different order than it was written.{{}} To understand asynchrony we first need to be clear about {{}}Synchronous execution is running code in the order it is written.{{}}. + +We have written a lot of JavaScript programs that execute sequentially. This means that each line of code is run in order, one after the other. +{{}} + +#### For example: + +```js +console.log("first"); +console.log("second"); +console.log("third"); +``` + +<---> + +#### Outputs: + +```console +first +second +third +``` + +{{}} +Each line of code is run in order. This is synchronous execution. We do this because JavaScript is {{}} +A single thread can do one thing at a time. JavaScript is a single threaded language. +{{}}. + +When we call a function, the function will run to completion before the next line of code is executed. But what if we need to wait for something to happen? What if we need to wait for our data to arrive before we can show it? In this case, we can use **asynchronous execution**. diff --git a/common-content/en/module/js3/using-fetch/index.md b/common-content/en/module/js3/using-fetch/index.md index b362a8f7a..3e6c3bb54 100644 --- a/common-content/en/module/js3/using-fetch/index.md +++ b/common-content/en/module/js3/using-fetch/index.md @@ -9,20 +9,43 @@ emoji= '🌐' render = 'never' list = 'local' publishResources = false - +++ -So now we have these pieces of our giant concept map +So now we have these pieces of our giant concept map: + +1. πŸ“€ We know that we can send a request using `fetch()` +2. πŸ• We know that `fetch` is a πŸ’» client-side 🧰 Web API that requires an HTTP connection +3. πŸ—“οΈ We know that sending requests over a network takes time +4. 🧡 We know that we should not stop our program to wait for data +5. πŸͺƒ We know that we can use Promises to manage asynchronous operations + +But we still don’t know how to use `fetch` to get data from a server side API. -1. πŸ“€ we know that we can send a request using `fetch()` -1. πŸ• we know that `fetch` is a πŸ’» client side 🧰 Web API -1. πŸ—“οΈ we know that sending requests over a network takes time -1. 🧡 we know that we should not stop our program to wait for data -1. πŸͺƒ we know that we can use callbacks to manage events +### Loading html files -But we still don’t know how to use `fetch` to get data from a server side API. Let’s find this out now. +When you double-click an HTML file in your file explorer to open it directly in your browser, you're using what's called the "file protocol" or "file scheme." In your browser's URL bar, you'll see something like: -Let's pick our film display exercise back up. Before we had a list of films hard-coded in our `state`. We're going to replace the films array with data fetched from a server. +``` +file:///Users/username/projects/my-website/index.html +``` + +The `file://` prefix indicates that your browser is reading the file directly from your computer's filesystem, without going through a web server. While this approach might seem convenient for simple HTML files, it will prevent us from using `fetch`. + +### Web Server Access: The HTTP Protocol + +Another approach involves using a local development server. You can create one using tools like [Python's built-in server](https://realpython.com/python-http-server/) or [npm's http-server](https://www.npmjs.com/package/http-server). These tools create a web server on your computer that serves your files using the HTTP protocol. Your browser will then access the files through a URL like: + +``` +http://localhost:8000/index.html +``` + +The `http://` prefix shows that you're accessing the file through a proper web server, even though that server is running on your own computer. + +You need to be using `http://` (or `https://`) _not_ `file://` in order to use `fetch`. + +## Using `fetch` + +Previously, we had a list of films hard-coded in our `state`. Now, let's continue using our concept map to fetch data from a server. ```js // Begin with an empty state @@ -36,15 +59,18 @@ const endpoint = "https://programming.codeyourfuture.io/dummy-apis/films.json"; const fetchFilms = async () => { const response = await fetch(endpoint); return await response.json(); -}; // Our async function returns a Promise +}; fetchFilms().then((films) => { - // When the fetchFilms Promise resolves, this callback will be called. state.films = films; render(); }); ``` -`fetch` returns a `Promise`; the `Promise` fulfils itself with a response; the response contains our data. +{{}} +Remember: If you see an error message about fetch not being allowed from `file://` URLs, that's your signal to serve your files through a local development server instead of opening them directly in the browser. +{{}} + +fetch returns a Promise; the Promise fulfils itself with a response; the response contains our data. -Next we will dig into `Promise`s, `async`, `await`, and `then`, and complete our concept map. +Next, we'll dig into `Promise`s, `async`, `await`, and `then` in more detail to complete our concept map. diff --git a/org-cyf-itp/content/data-flows/sprints/3/prep/index.md b/org-cyf-itp/content/data-flows/sprints/3/prep/index.md index 56a6999d8..0baf1f872 100644 --- a/org-cyf-itp/content/data-flows/sprints/3/prep/index.md +++ b/org-cyf-itp/content/data-flows/sprints/3/prep/index.md @@ -14,8 +14,8 @@ src="https://www.youtube.com/watch?v=J-0XkB54yp8" name="Latency" src="module/js3/latency" [[blocks]] -name="Asynchrony" -src="module/js3/asynchrony" +name="Synchronous execution" +src="module/js3/synchronous-execution" [[blocks]] name="Callbacks" src="module/js3/callbacks" From ce5d51b7309fde62d556f042648d01ca2a85d730 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 28 Jan 2025 12:44:27 +0000 Subject: [PATCH 04/10] Fix functions more (#1324) * Fix comment * Install googleapis in piscine This is required for the submission-created function. --- org-cyf-piscine/package-lock.json | 551 ++++++++++++++++++ org-cyf-piscine/package.json | 1 + .../netlify/functions/submission-created.js | 2 +- 3 files changed, 553 insertions(+), 1 deletion(-) diff --git a/org-cyf-piscine/package-lock.json b/org-cyf-piscine/package-lock.json index 175dfffcd..cb41cc913 100644 --- a/org-cyf-piscine/package-lock.json +++ b/org-cyf-piscine/package-lock.json @@ -12,6 +12,7 @@ "@netlify/functions": "^1.6.0", "cookie": "^0.5.0", "dotenv": "^16.3.1", + "googleapis": "^144.0.0", "joi": "^17.9.2", "lodash": "^4.17.21", "octokit": "^3.1.2", @@ -427,6 +428,15 @@ "undici-types": "~5.26.4" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -439,11 +449,40 @@ "node": ">=8" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", @@ -459,6 +498,35 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -489,6 +557,23 @@ "node": ">= 8" } }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -529,6 +614,20 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -537,6 +636,268 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis": { + "version": "144.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", + "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -550,6 +911,18 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -568,6 +941,15 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -656,6 +1038,15 @@ "node": "14 || >=16.14" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -670,6 +1061,38 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/octokit": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", @@ -735,6 +1158,21 @@ "prettier": "^2.0.0" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -800,6 +1238,84 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ulid": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz", @@ -827,6 +1343,41 @@ "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/org-cyf-piscine/package.json b/org-cyf-piscine/package.json index 1cbceb729..f98c84150 100644 --- a/org-cyf-piscine/package.json +++ b/org-cyf-piscine/package.json @@ -20,6 +20,7 @@ "@netlify/functions": "^1.6.0", "cookie": "^0.5.0", "dotenv": "^16.3.1", + "googleapis": "^144.0.0", "joi": "^17.9.2", "lodash": "^4.17.21", "octokit": "^3.1.2", diff --git a/tooling/netlify/functions/submission-created.js b/tooling/netlify/functions/submission-created.js index 837a21b66..75f9fc693 100644 --- a/tooling/netlify/functions/submission-created.js +++ b/tooling/netlify/functions/submission-created.js @@ -12,7 +12,7 @@ import {Auth, google} from "googleapis"; // Each sheet listed here was manually crated, and its spreadsheet ID is taken from its URL. // Each spreadsheet is expected to contain one sheet per module, named for the module. // Each module sheet is expected to contain the following columns, in order: -// Given Name | Family Name | Email | Timestamp | Course | Module | Day | Build Time +// Name | Email | Timestamp | Course | Module | Day | Location | Build Time // Each spreadsheet must also give write access to the email listed below in CREDENTIALS. const COURSE_TO_SPREADSHEET_ID = { "itp": "1YHKPCCN55PJD-o1jg4wbVKI3kbhB-ULiwB5hhG17DcA", From 1597b407849d4ac92dd77bd00b1b82ecf4bfbfca Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Tue, 28 Jan 2025 13:06:32 +0000 Subject: [PATCH 05/10] add DPG badge --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8b544505..c2457430c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# Curriculum Platform +# Common Curriculum Platform +[![DPG Badge](https://img.shields.io/badge/Verified-DPG-3333AB?logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iMzEiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMSAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0LjIwMDggMjEuMzY3OEwxMC4xNzM2IDE4LjAxMjRMMTEuNTIxOSAxNi40MDAzTDEzLjk5MjggMTguNDU5TDE5LjYyNjkgMTIuMjExMUwyMS4xOTA5IDEzLjYxNkwxNC4yMDA4IDIxLjM2NzhaTTI0LjYyNDEgOS4zNTEyN0wyNC44MDcxIDMuMDcyOTdMMTguODgxIDUuMTg2NjJMMTUuMzMxNCAtMi4zMzA4MmUtMDVMMTEuNzgyMSA1LjE4NjYyTDUuODU2MDEgMy4wNzI5N0w2LjAzOTA2IDkuMzUxMjdMMCAxMS4xMTc3TDMuODQ1MjEgMTYuMDg5NUwwIDIxLjA2MTJMNi4wMzkwNiAyMi44Mjc3TDUuODU2MDEgMjkuMTA2TDExLjc4MjEgMjYuOTkyM0wxNS4zMzE0IDMyLjE3OUwxOC44ODEgMjYuOTkyM0wyNC44MDcxIDI5LjEwNkwyNC42MjQxIDIyLjgyNzdMMzAuNjYzMSAyMS4wNjEyTDI2LjgxNzYgMTYuMDg5NUwzMC42NjMxIDExLjExNzdMMjQuNjI0MSA5LjM1MTI3WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==)](https://digitalpublicgoods.net/r/dpg-slug) + > [!TIP] -> Our curriculum in action: https://curriculum.codeyourfuture.io +> Our curriculum in action: https://curriculum.codeyourfuture.io > Curriculum platform docs: https://common.codeyourfuture.io ## What is this? From afddfc7ce3e6286070767a9fbfbd5c015ff12581 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Tue, 28 Jan 2025 13:19:18 +0000 Subject: [PATCH 06/10] add our slug we never had a slug before! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2457430c..b95e5b90c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Common Curriculum Platform -[![DPG Badge](https://img.shields.io/badge/Verified-DPG-3333AB?logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iMzEiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMSAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0LjIwMDggMjEuMzY3OEwxMC4xNzM2IDE4LjAxMjRMMTEuNTIxOSAxNi40MDAzTDEzLjk5MjggMTguNDU5TDE5LjYyNjkgMTIuMjExMUwyMS4xOTA5IDEzLjYxNkwxNC4yMDA4IDIxLjM2NzhaTTI0LjYyNDEgOS4zNTEyN0wyNC44MDcxIDMuMDcyOTdMMTguODgxIDUuMTg2NjJMMTUuMzMxNCAtMi4zMzA4MmUtMDVMMTEuNzgyMSA1LjE4NjYyTDUuODU2MDEgMy4wNzI5N0w2LjAzOTA2IDkuMzUxMjdMMCAxMS4xMTc3TDMuODQ1MjEgMTYuMDg5NUwwIDIxLjA2MTJMNi4wMzkwNiAyMi44Mjc3TDUuODU2MDEgMjkuMTA2TDExLjc4MjEgMjYuOTkyM0wxNS4zMzE0IDMyLjE3OUwxOC44ODEgMjYuOTkyM0wyNC44MDcxIDI5LjEwNkwyNC42MjQxIDIyLjgyNzdMMzAuNjYzMSAyMS4wNjEyTDI2LjgxNzYgMTYuMDg5NUwzMC42NjMxIDExLjExNzdMMjQuNjI0MSA5LjM1MTI3WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==)](https://digitalpublicgoods.net/r/dpg-slug) +[![DPG Badge](https://img.shields.io/badge/Verified-DPG-3333AB?logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iMzEiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMSAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0LjIwMDggMjEuMzY3OEwxMC4xNzM2IDE4LjAxMjRMMTEuNTIxOSAxNi40MDAzTDEzLjk5MjggMTguNDU5TDE5LjYyNjkgMTIuMjExMUwyMS4xOTA5IDEzLjYxNkwxNC4yMDA4IDIxLjM2NzhaTTI0LjYyNDEgOS4zNTEyN0wyNC44MDcxIDMuMDcyOTdMMTguODgxIDUuMTg2NjJMMTUuMzMxNCAtMi4zMzA4MmUtMDVMMTEuNzgyMSA1LjE4NjYyTDUuODU2MDEgMy4wNzI5N0w2LjAzOTA2IDkuMzUxMjdMMCAxMS4xMTc3TDMuODQ1MjEgMTYuMDg5NUwwIDIxLjA2MTJMNi4wMzkwNiAyMi44Mjc3TDUuODU2MDEgMjkuMTA2TDExLjc4MjEgMjYuOTkyM0wxNS4zMzE0IDMyLjE3OUwxOC44ODEgMjYuOTkyM0wyNC44MDcxIDI5LjEwNkwyNC42MjQxIDIyLjgyNzdMMzAuNjYzMSAyMS4wNjEyTDI2LjgxNzYgMTYuMDg5NUwzMC42NjMxIDExLjExNzdMMjQuNjI0MSA5LjM1MTI3WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==)](https://digitalpublicgoods.net/r/code-your-future) > [!TIP] From 8f074a2f410cfe4ce116a9f6017b30a454371821 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 28 Jan 2025 21:54:11 +0000 Subject: [PATCH 07/10] ~Final tweaks to Piscine (#1325) * Note that groups may be 2 people. * Add temporary block for talking through breaking down requirements. We hope to remove this in the future, but we would like our day plans to be accurate for the course we're delivering. * Move team-selection to happen before morning break. --- .../en/module/piscine/kickoff/index.md | 2 +- .../piscine/practice-break-down/index.md | 24 +++++++++++++++++++ .../content/sprints/1/day-plan/index.md | 13 ++++++---- .../content/sprints/2/day-plan/index.md | 6 ++--- 4 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 common-content/en/module/piscine/practice-break-down/index.md diff --git a/common-content/en/module/piscine/kickoff/index.md b/common-content/en/module/piscine/kickoff/index.md index 18057f7f6..ac20501e4 100644 --- a/common-content/en/module/piscine/kickoff/index.md +++ b/common-content/en/module/piscine/kickoff/index.md @@ -16,7 +16,7 @@ It's important that software works and the people can use it ## 🎯 Goal: -You will be split randomly into groups of 3-4. +You will be split randomly into groups of 2-4. {{}} diff --git a/common-content/en/module/piscine/practice-break-down/index.md b/common-content/en/module/piscine/practice-break-down/index.md new file mode 100644 index 000000000..accd056ff --- /dev/null +++ b/common-content/en/module/piscine/practice-break-down/index.md @@ -0,0 +1,24 @@ ++++ +title = "Practice breaking down a requirement" +time = 60 +objectives = [ + "Break down a requirement into approachable tasks.", + "Coordinate multiple team members in executing tasks in parallel.", +] +[build] + render = "never" + list = "local" + publishResources = false ++++ + +In team projects, it's important that we break large tasks down into small tasks. + +It's also important that we can coordinate across our team. This requires having shared understanding of who will do what, and how the work we do will interact. + +We will practice this together on the first two requirements of the project. + +{{/* + TODO: The Piscine is meant to be pure evaluation with no teaching. + Ideally ITP would provide this experience so we wouldn't need to teach it in the Piscine. + But right now ITP doesn't teach these skills, so we fill that gap so we're not testing people on things they don't know. +*/}} diff --git a/org-cyf-piscine/content/sprints/1/day-plan/index.md b/org-cyf-piscine/content/sprints/1/day-plan/index.md index e0d9af23c..477b79bff 100644 --- a/org-cyf-piscine/content/sprints/1/day-plan/index.md +++ b/org-cyf-piscine/content/sprints/1/day-plan/index.md @@ -12,20 +12,23 @@ time=15 name="Energiser" src="energisers/zip-zap-boing" [[blocks]] -name="Morning break" -src="blocks/morning-break" -[[blocks]] name="Kickoff" src="module/piscine/kickoff" time=20 [[blocks]] +name="Morning break" +src="blocks/morning-break" +[[blocks]] name="Group Project: Spaced Repetition Tracker" src="https://github.com/CodeYourFuture/The-Piscine/tree/main/Project-Spaced-Repetition-Tracker" -time=50 +time=30 +[[blocks]] +name="Practice breaking down a requirement" +src="module/piscine/practice-break-down" [[blocks]] name="Development" src="module/piscine/development" -time="60" +time="20" [[blocks]] name="lunch" src="blocks/lunch" diff --git a/org-cyf-piscine/content/sprints/2/day-plan/index.md b/org-cyf-piscine/content/sprints/2/day-plan/index.md index 191b0802f..2a13da0e7 100644 --- a/org-cyf-piscine/content/sprints/2/day-plan/index.md +++ b/org-cyf-piscine/content/sprints/2/day-plan/index.md @@ -11,13 +11,13 @@ src="energisers/blockers" name="Demo" src="module/piscine/demo" [[blocks]] -name="Morning break" -src="blocks/morning-break" -[[blocks]] name="Kickoff" src="module/piscine/kickoff" time=20 [[blocks]] +name="Morning break" +src="blocks/morning-break" +[[blocks]] name="Group Project: Days Calendar" src="https://github.com/CodeYourFuture/The-Piscine/tree/main/Project-Days-Calendar" time=40 From f027646fb9516eb071964c629f2ea92ec658e8d7 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Thu, 30 Jan 2025 10:49:46 +0000 Subject: [PATCH 08/10] Extend temp Piscine workshop with exercises to breaking down project requirements (#1326) --- .../piscine/practice-break-down/index.md | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/common-content/en/module/piscine/practice-break-down/index.md b/common-content/en/module/piscine/practice-break-down/index.md index accd056ff..221e290d8 100644 --- a/common-content/en/module/piscine/practice-break-down/index.md +++ b/common-content/en/module/piscine/practice-break-down/index.md @@ -15,7 +15,39 @@ In team projects, it's important that we break large tasks down into small tasks It's also important that we can coordinate across our team. This requires having shared understanding of who will do what, and how the work we do will interact. -We will practice this together on the first two requirements of the project. +Finally, it's important that we arrange tasks so your team isn't blocking yourselves, so you'll want to find ways of working on tasks in parallel. + +We will practice this together on the two of the requirements of the project: + +- Selecting a user must display the agenda for the relevant user (see manual testing below) +- Submitting the form adds a new topic to revise for the relevant user only + +To complete these requirements we'd need to build most of the project! So we'll focus on simplified versions of these requirements: + +- When the page loads, display one revision date for one topic from in User 1's stored agenda +- When clicking a button, store {{}}Hard-coding refers to when developers directly write values or data into code, often replacing variables or user input with static values.{{}} data for User 1's agenda + +To complete the full requirements, you can build on the tasks we decide on today. + +{{}} + +1. Set a timer for {{}}5{{}} +2. Individually, write down all the tasks that would be needed to complete the simplified requirements above +3. After the timer is up, go around the group and discuss the tasks you came up with. One of the volunteers should write the tasks on a whiteboard +4. Volunteers, discuss any that you think might be missing or that might need breaking down further + +{{}} + +Looking ahead to your coursework for the next week, one of your tasks will be to [Refine a ticket](https://github.com/CodeYourFuture/The-Piscine/issues/6). + +{{}} + +1. Everyone read the [Refine a ticket coursework task](https://github.com/CodeYourFuture/The-Piscine/issues/6), for {{}}3{{}} minutes. +2. Get into pairs. Each pair should then pick 2 of the tasks from the whiteboard (it doesn't matter if multiple pairs do the same task). Spend {{}}10{{}} minutes refining your tickets in your pairs. Make sure you work through **all** of the workflow points. +3. Go around each pair in the group. The pair should pick 1 of their tasks and say what they think the priority, estimate, schedule and type of work the task is, and if there is anything they would need to know before starting the ticket. (Hint: the last point is **important**! Don't skip it!) +4. As a group, pick a task (or two, depending on time) that needs more information. Spend {{}}10{{}} minutes discussing what actions you could take to ensure everyone has what they need to complete this task? + +{{}} {{/* TODO: The Piscine is meant to be pure evaluation with no teaching. From 1d1010a34bc114aacf107b261dfedaeb6a7ebcbb Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 31 Jan 2025 11:06:10 +0000 Subject: [PATCH 09/10] Link to the public view of the calendar (#1329) Previously we were linking to the sheet the public view is a proxy for, which means it didn't get updated when we updated the proxy. Also, remove 2024 from the link text, we're in 2025 now. --- org-cyf/content/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org-cyf/content/_index.md b/org-cyf/content/_index.md index 7d1377801..57ac64fc6 100644 --- a/org-cyf/content/_index.md +++ b/org-cyf/content/_index.md @@ -1,6 +1,6 @@ +++ title="Our Courses" menus_to_map=["start here", "selection", "trainees", "fellowships"] -description="Free training for good jobs in tech [πŸ“… 2024/25](https://docs.google.com/spreadsheets/d/1qNxf44_vbNKU1KeFozGX7Kt1QxmTtU6Bf9tOh1sTUuc/edit?gid=1346767713#gid=1346767713)" +description="Free training for good jobs in tech [πŸ“… 2025](https://docs.google.com/spreadsheets/d/1yI0msIwe8qYn2Nv9WPLwFMTM_Td0TtPvlv17u3B8sS8/edit)" emoji= "πŸ§‘πŸΏβ€πŸ«πŸ‘¨πŸ½β€πŸŽ“" +++ From 3f09fa1359d2c34a3fc95126eb2bdad541879e46 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 31 Jan 2025 11:20:12 +0000 Subject: [PATCH 10/10] Fix link and move timeline (#1330) --- org-cyf-guides/content/teaching/_index.md | 2 +- org-cyf-itp/content/guides/{teaching => }/timeline/index.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename org-cyf-itp/content/guides/{teaching => }/timeline/index.md (100%) diff --git a/org-cyf-guides/content/teaching/_index.md b/org-cyf-guides/content/teaching/_index.md index 69b67df1a..4ce945364 100644 --- a/org-cyf-guides/content/teaching/_index.md +++ b/org-cyf-guides/content/teaching/_index.md @@ -4,6 +4,6 @@ description = "Teaching strategies and guidelines" emoji = 'πŸ‘₯' +++ -You may also be interested in specific teaching activities. +You may also be interested in [specific teaching activities](../activities). Strategies and guidelines we've noticed help teaching: diff --git a/org-cyf-itp/content/guides/teaching/timeline/index.md b/org-cyf-itp/content/guides/timeline/index.md similarity index 100% rename from org-cyf-itp/content/guides/teaching/timeline/index.md rename to org-cyf-itp/content/guides/timeline/index.md