Skip to content

Commit

Permalink
Jspsych Surveys (#24)
Browse files Browse the repository at this point in the history
* Update readme

* Update eslint package to fix warning message

* Add exit survey pacakge

* exit survey

* Use survey_function to format survey text

* edit exit survey question titles and descriptions

* change question names and MC values to match EFP names

* add withdrawal and feedback questions

* Change package name, exit survey parameters

* Fixed typing

* Added privacy and databrary params

* Update API to get a study

* Get study to add to withdraw copu

* Add MD link

* disable the video sharing questions if video withdrawal is true

* add support for private level only option for media use question

* fix private_level_only so that value is private in data

* Add global data store

* fix withdrawal question value in data and logic for enabling/disabling the other video-related questions

* add validation for child birthdate: cannot be a future date

* Combine helpers and api into new package called data

* Update root package

* Update initjspych to use new data structure

* Update survey to use new data structure

* Moved exit survey function to utils

* First pass at tests

* Add consent servey plugin

* These packages are needed when building in linux environment

* Add data finish function

* Remove unneeded comments

* Added text markdown to consent survey

* Update tests

* Update versions of deps

* Update to getUuids

* freeze loaded data

* install jspsych/plugin-survey from npm and fix compatibility (pass object instead of JSON string)

* replace exit.json with exit_json.ts and fix import

* fix linting errors

* fix failing data test due to syntax error in untranspiled deep-freeze node module

* fix linting error

* Needed to adjust ts config to build.

---------

Co-authored-by: Becky Gilbert <[email protected]>
  • Loading branch information
okaycj and becky-gilbert authored May 6, 2024
1 parent 82e56d2 commit cbf6ae7
Show file tree
Hide file tree
Showing 46 changed files with 2,198 additions and 1,381 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ module.exports = {
},
plugins: ["@typescript-eslint"],
ignorePatterns: ["packages/**/dist/*"],
rules: { "require-await": "error" },
};
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"jsonRecursiveSort": true,
"plugins": [
"prettier-plugin-packagejson",
"prettier-plugin-sort-json",
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Add build and test script to new package's `package.json`:
```json
"scripts": {
"test": "jest --coverage",
"dev": "rollup --config rollup.config.dev.mjs --watch",
"build": "rollup --config"
},
```
Expand Down Expand Up @@ -53,8 +54,8 @@ Edit `tsconfig.json`:
```json
{
"compilerOptions": {
"strict": true,
"baseUrl": "."
"baseUrl": ".",
"strict": true
},
"extends": "@jspsych/config/tsconfig.core.json",
"include": ["src"]
Expand Down
2,405 changes: 1,356 additions & 1,049 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
"scripts": {
"build": "npm run build --workspaces",
"changeset": "changeset",
"fix": "eslint ./**/*.{ts,mjs} --fix && prettier . -l --write",
"fix": "prettier . -l --write && eslint ./**/*.{ts,mjs} --fix",
"format": "prettier . -c",
"lint": "eslint ./packages/**/src/**/*.ts --quiet",
"test": "npm test --workspaces"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"jest-fetch-mock": "^3.0.3",
Expand Down
6 changes: 6 additions & 0 deletions packages/data/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const config = require("@jspsych/config/jest").makePackageConfig(__dirname);
module.exports = {
...config,
moduleNameMapper: {},
transformIgnorePatterns: ["node_modules/(?!deep-freeze-es6)"],
};
11 changes: 7 additions & 4 deletions packages/lookit-api/package.json → packages/data/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@lookit/lookit-api",
"name": "@lookit/data",
"version": "0.0.1",
"description": "This is a JS implementation of lookit's RESTful API.",
"homepage": "https://github.com/lookit/lookit-jspsych#readme",
Expand All @@ -9,7 +9,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/lookit/lookit-jspsych.git",
"directory": "packages/lookit-api"
"directory": "packages/data"
},
"license": "ISC",
"author": "Christopher J Green <[email protected]> (https://github.com/okaycj)",
Expand All @@ -20,9 +20,12 @@
"dev": "rollup --config rollup.config.dev.mjs --watch",
"test": "jest --coverage"
},
"dependencies": {},
"dependencies": {
"deep-freeze-es6": "^3.0.2"
},
"devDependencies": {
"@jspsych/config": "^2.0.0"
"@jspsych/config": "^2.0.0",
"jest-fetch-mock": "^3.0.3"
},
"peerDependencies": {
"jspsych": "^7.3.4"
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { makeRollupConfig } from "@jspsych/config/rollup";

export default makeRollupConfig("lookitAPI");
export default makeRollupConfig("chsData");
45 changes: 45 additions & 0 deletions packages/data/src/api.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
finish,
retrieveChild,
retrievePastSessions,
retrieveResponse,
retrieveStudy,
updateResponse,
} from "./api";

jest.mock("./utils", () => ({
...jest.requireActual("./utils"),
getUuids: jest.fn(),
get: jest.fn().mockReturnValue("get response"),
patch: jest.fn().mockReturnValue("patch response"),
}));

test("Api call to get Child", async () => {
expect(await retrieveChild()).toStrictEqual("get response");
});

test("Api call to get Past Sessions", async () => {
expect(await retrievePastSessions("some uuid")).toStrictEqual("get response");
});

test("Api call to get Study", async () => {
expect(await retrieveStudy()).toStrictEqual("get response");
});

test("Api call to get Response", async () => {
expect(await retrieveResponse("some uuid")).toStrictEqual("get response");
});

test("Api call to patch Response", async () => {
expect(await updateResponse("some uuid", {})).toStrictEqual("patch response");
});

test("Check that all calls to API have finished", async () => {
expect(await finish()).toStrictEqual([
"get response",
"get response",
"get response",
"get response",
"patch response",
]);
});
48 changes: 48 additions & 0 deletions packages/data/src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
Child,
PastSession,
Promises,
Response,
ResponseAttrsUpdate,
ResponseUpdate,
Study,
} from "./types";
import { get, getUuids, patch } from "./utils";

const CONFIG = <const>{ ...getUuids() };
const promises: Promises[] = [];

function deposit<T extends Promises>(promise: T) {
promises.push(promise);
return promise;
}

export function finish() {
return Promise.all(promises);
}

export function retrieveChild() {
return deposit(get<Child>(`children/${CONFIG.child}/`));
}

export function retrievePastSessions(uuid: string) {
return deposit(get<PastSession[]>(`past-sessions/${uuid}/`));
}

export function retrieveStudy() {
return deposit(get<Study>(`studies/${CONFIG.study}/`));
}

export function retrieveResponse(uuid: string) {
return deposit(get<Response>(`responses/${uuid}/`));
}

export function updateResponse(uuid: string, data: ResponseAttrsUpdate) {
return deposit(
patch<ResponseUpdate, Response>(`responses/${uuid}/`, {
id: uuid,
type: "responses",
attributes: data,
}),
);
}
25 changes: 25 additions & 0 deletions packages/data/src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Api from "./index";

jest.mock("./utils", () => ({
...jest.requireActual("./utils"),
getUuids: jest.fn(),
}));

jest.mock("./api", () => ({
...jest.requireActual("./api"),
retrieveStudy: jest.fn().mockReturnValue("Study"),
retrieveChild: jest.fn().mockReturnValue("Child"),
retrievePastSessions: jest.fn().mockReturnValue("PastSessions"),
retrieveResponse: jest.fn().mockReturnValue("Response"),
}));

test("Load data for this study into window.chs", async () => {
expect(Object.hasOwn(window, "chs")).toBeFalsy();
await Api.load("response uuid");
expect(window.chs).toEqual({
study: "Study",
child: "Child",
pastSessions: "PastSessions",
response: "Response",
});
});
37 changes: 37 additions & 0 deletions packages/data/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import deepFreeze from "deep-freeze-es6";
import {
finish,
retrieveChild,
retrievePastSessions,
retrieveResponse,
retrieveStudy,
updateResponse,
} from "./api";
import { Child, PastSession, Response, Study } from "./types";

declare global {
interface Window {
chs: {
study: Study;
child: Child;
pastSessions: PastSession[];
response: Response;
};
}
}

async function load(response_uuid: string) {
if (!window.chs) {
Object.assign(window, {
chs: {
study: await retrieveStudy(),
child: await retrieveChild(),
pastSessions: await retrievePastSessions(response_uuid),
response: await retrieveResponse(response_uuid),
},
});
deepFreeze(window.chs);
}
}

export default { load, retrieveResponse, updateResponse, finish };
67 changes: 66 additions & 1 deletion packages/lookit-api/src/types.ts → packages/data/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { DataCollection } from "jspsych/dist/modules/data/DataCollection";

export type Promises = Promise<Data<Attributes> | Data<Attributes>[]>;

export type Relationship = {
links: {
related: string;
Expand All @@ -11,6 +15,36 @@ export type Attributes = {

export interface Relationships {}

export interface StudyAttrs extends Attributes {
name: string;
short_description: string;
purpose: string;
criteria: string;
duration: string;
contact_info: string;
/** Format: binary */
image?: string | null;
structure?: Record<string, never>;
generator?: string;
use_generator?: boolean;
display_full_screen?: boolean;
/** Format: uri */
exit_url?: string;
/** @enum {string} */
state?:
| "created"
| "submitted"
| "rejected"
| "retracted"
| "approved"
| "active"
| "paused"
| "deactivated"
| "archived";
public?: boolean;
responses: string[];
}

export interface ChildAttrs extends Attributes {
given_name: string;
birthday: string;
Expand All @@ -27,7 +61,7 @@ export interface ChildAttrs extends Attributes {
export interface PastSessionAttrs extends Attributes {
conditions?: Record<string, never>;
global_event_timings?: Record<string, never>;
exp_data?: Record<string, never>;
exp_data?: DataCollection[];
sequence?: string[];
completed?: boolean;
completed_consent_frame?: boolean;
Expand Down Expand Up @@ -59,12 +93,20 @@ export interface ApiResponse<Data> {
data: Data;
}

export interface Study extends Data<StudyAttrs> {
type: "studies";
relationships: {
responses: Relationship;
};
}

export interface Child extends Data<ChildAttrs> {
type: "children";
relationships: {
user: Relationship;
};
}

export interface PastSession extends Data<PastSessionAttrs> {
type: "past_sessions";
relationships: {
Expand All @@ -74,3 +116,26 @@ export interface PastSession extends Data<PastSessionAttrs> {
demographic_snapshot: Relationship;
};
}

export interface Response extends Data<PastSessionAttrs> {
type: "responses";
relationships: {
child: Relationship;
user: Relationship;
study: Relationship;
demographic_snapshot: Relationship;
};
}

export interface ResponseUpdate {
type: "responses";
id: string;
attributes: ResponseAttrsUpdate;
}

export interface ResponseAttrsUpdate {
exp_data?: DataCollection[];
completed?: boolean;
survey_consent?: boolean;
completed_consent_frame?: boolean;
}
Loading

0 comments on commit cbf6ae7

Please sign in to comment.