Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add: commander to optionally provide delete or download along with st… #516

Merged
merged 19 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 79 additions & 19 deletions cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fsExtra from "fs-extra";
// TODO @brown-ccv #183: Upgrade to modular SDK instead of compat
import { cert, initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { Command } from "commander";

/** -------------------- GLOBALS -------------------- */

Expand All @@ -18,16 +19,73 @@ let OUTPUT_ROOT; // The root in which data is saved
const INVALID_ACTION_ERROR = new Error("Invalid action: " + ACTION);
const INVALID_DEPLOYMENT_ERROR = new Error("Invalid deployment: " + DEPLOYMENT);

/** -------------------- COMMANDER -------------------- */
const commander = new Command();
// default: [download | delete ] not provided, run main() as usual continuing with prompting
commander.action(() => {});

// download: optional argument studyID and participantID skips relative prompts
commander
.command(`download`)
.argument(`[studyID]`)
.argument(`[participantID]`)
.description(`Download experiment data from Firebase provided study ID and participant ID`)
.action((studyID, participantID) => {
ACTION = "download";
STUDY_ID = studyID;
PARTICIPANT_ID = participantID;
});

// delete: optional argument studyID and participantID skips relative prompts
commander
.command(`delete`)
.argument(`[studyID]`)
.argument(`[participantID]`)
.description(`Delete experiment data from Firebase provided study ID and participant ID`)
.action((studyID, participantID) => {
ACTION = "delete";
STUDY_ID = studyID;
PARTICIPANT_ID = participantID;
});
commander.parse();

// print message if download or delete provided, along with optional args provided
if (ACTION != undefined) {
YUUU23 marked this conversation as resolved.
Show resolved Hide resolved
console.log(
`${ACTION} data from Firebase given ${STUDY_ID === undefined ? "" : `study ID: ${STUDY_ID}`} ${PARTICIPANT_ID === undefined ? "" : `and participant ID: ${PARTICIPANT_ID}`}`
);
}

/** -------------------- MAIN -------------------- */

// TODO @brown-ccv #289: Pass CLI arguments with commander (especially for action)
async function main() {
eldu marked this conversation as resolved.
Show resolved Hide resolved
ACTION = await actionPrompt();
if (ACTION == undefined) {
YUUU23 marked this conversation as resolved.
Show resolved Hide resolved
ACTION = await actionPrompt();
}
DEPLOYMENT = await deploymentPrompt();
// TODO @brown-ccv #291: Enable downloading all study data at once
STUDY_ID = await studyIDPrompt();
if (STUDY_ID == undefined) {
YUUU23 marked this conversation as resolved.
Show resolved Hide resolved
STUDY_ID = await studyIDPrompt();
} else {
// when args directly passed in through CLI, check if study is valid
const hasStudy = await validateStudyFirebase(STUDY_ID);
if (!hasStudy) {
console.error("Please enter a valid study from your Firestore database");
return;
eldu marked this conversation as resolved.
Show resolved Hide resolved
}
}
// TODO @brown-ccv #291: Enable downloading all participant data at once
PARTICIPANT_ID = await participantIDPrompt();
if (PARTICIPANT_ID == undefined) {
YUUU23 marked this conversation as resolved.
Show resolved Hide resolved
PARTICIPANT_ID = await participantIDPrompt();
} else {
// when args directly passed in through CLI, check if participant is valid
const hasParticipant = await validateParticipantFirebase(PARTICIPANT_ID);
if (!hasParticipant) {
console.error(`Please enter a valid participant on the study "${STUDY_ID}"`);
return;
}
}
EXPERIMENT_IDS = await experimentIDPrompt();

switch (ACTION) {
Expand Down Expand Up @@ -55,7 +113,6 @@ async function main() {
}
}
main();

/** -------------------- DOWNLOAD ACTION -------------------- */

/** Download data that's stored in Firebase */
Expand Down Expand Up @@ -185,22 +242,23 @@ async function deploymentPrompt() {
}

/** Prompt the user to enter the ID of a study */
// helper to check if the given study (input) is in firestore
async function validateStudyFirebase(input) {
// subcollection is programmatically generated, if it doesn't exist then input must not be a valid studyID
const studyIDCollections = await getStudyRef(input).listCollections();
return studyIDCollections.find((c) => c.id === PARTICIPANTS_COL);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this helper function (along with validateParticipantFirebase) into a new section for validation functions? It looks like some of the comments aren't above the correct functions anymore


async function studyIDPrompt() {
const invalidMessage = "Please enter a valid study from your Firestore database";
const validateStudyFirebase = async (input) => {
// subcollection is programmatically generated, if it doesn't exist then input must not be a valid studyID
const studyIDCollections = await getStudyRef(input).listCollections();
return studyIDCollections.find((c) => c.id === PARTICIPANTS_COL) ? true : invalidMessage;
};

return await input({
message: "Select a study:",
validate: async (input) => {
if (!input) return invalidMessage;

switch (DEPLOYMENT) {
case "firebase":
return validateStudyFirebase(input);
const res = await validateStudyFirebase(input);
return !res ? invalidMessage : true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're calling .find inside validateStudyFirebase so I would rename this to be studyCollection or something like that? Because the return of the function is really the Firebase study collection or undefined

default:
throw INVALID_DEPLOYMENT_ERROR;
}
Expand All @@ -209,14 +267,15 @@ async function studyIDPrompt() {
}

/** Prompt the user to enter the ID of a participant on the STUDY_ID study */
// helper to check if the given participant (input) is in firestore under study
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it looks like the comment on line 269 is for the function defined on line 277

async function validateParticipantFirebase(input) {
// subcollection is programmatically generated, if it doesn't exist then input must not be a valid participantID
const studyIDCollections = await getParticipantRef(STUDY_ID, input).listCollections();
return studyIDCollections.find((c) => c.id === DATA_COL);
}

async function participantIDPrompt() {
const invalidMessage = `Please enter a valid participant on the study "${STUDY_ID}"`;
const validateParticipantFirebase = async (input) => {
// subcollection is programmatically generated, if it doesn't exist then input must not be a valid participantID
const studyIDCollections = await getParticipantRef(STUDY_ID, input).listCollections();
return studyIDCollections.find((c) => c.id === DATA_COL) ? true : invalidMessage;
};

return await input({
message: "Select a participant:",
validate: async (input) => {
Expand All @@ -226,7 +285,8 @@ async function participantIDPrompt() {

switch (DEPLOYMENT) {
case "firebase":
return validateParticipantFirebase(input);
const res = await validateParticipantFirebase(input);
return !res ? invalidMessage : true;
default:
throw INVALID_DEPLOYMENT_ERROR;
}
Expand Down
46 changes: 31 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@jspsych/plugin-instructions": "^1.1.3",
"@jspsych/plugin-preload": "^1.1.2",
"@jspsych/plugin-survey": "^1.0.1",
"commander": "^12.1.0",
"electron-log": "^5.0.0",
"electron-squirrel-startup": "^1.0.0",
"execa": "^8.0.1",
Expand Down
Loading