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 2 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
92 changes: 75 additions & 17 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}`}`
eldu marked this conversation as resolved.
Show resolved Hide resolved
);
}

/** -------------------- 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 != true) {
console.error(hasStudy);
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(STUDY_ID);
if (hasParticipant != true) {
console.error(hasParticipant);
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,19 +242,19 @@ async function deploymentPrompt() {
}

/** Prompt the user to enter the ID of a study */
async function studyIDPrompt() {
// helper to check if the given study (input) is in firestore
const validateStudyFirebase = async (input) => {
eldu marked this conversation as resolved.
Show resolved Hide resolved
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;
};
// 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;
Copy link
Member

Choose a reason for hiding this comment

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

Hmm... I know this is code I wrote but it looks a little funky to me. Are you okay changing it here?

I would return true or false from this function and then in main where you have that console.error I would just call the string itself - console.error("Please enter a valid study from your Firestore database")

};

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

switch (DEPLOYMENT) {
case "firebase":
return validateStudyFirebase(input);
Expand All @@ -209,14 +266,15 @@ async function studyIDPrompt() {
}

/** Prompt the user to enter the ID of a participant on the STUDY_ID study */
async function participantIDPrompt() {
// helper to check if the given participant (input) is in firestore under study
const validateParticipantFirebase = async (input) => {
eldu marked this conversation as resolved.
Show resolved Hide resolved
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;
};
// 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;
};

async function participantIDPrompt() {
return await input({
message: "Select a participant:",
validate: async (input) => {
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