Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 2 additions & 16 deletions source/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ import "./css/App.scss";
import { signInAnonymously } from "firebase/auth";
import { getSoundProfileStatement } from "./components/firebase_soundProfile";
import { captureError } from "./sentry";
import { fetchGlossaryData } from "./components/glossaryApi";
import { initGlossary } from "../threshold/parameters/glossaryRegistry";
import { getGlossaryFull } from "../threshold/parameters/glossaryRegistry";

// Utility function to create empty resources object from constants
const createEmptyResourcesObject = () => {
Expand Down Expand Up @@ -102,7 +101,6 @@ export default class App extends Component {
profileStatement: "Loading ...",
isCompiledFromArchiveBool: false,
archivedZip: null,
glossaryData: null,
compileWarnings: [],
};

Expand Down Expand Up @@ -145,13 +143,6 @@ export default class App extends Component {
}

async componentDidMount() {
fetchGlossaryData()
.then((data) => {
initGlossary(data);
this.setState({ glossaryData: data });
})
.catch((error) => console.warn("Failed to fetch glossary data:", error));

// get the actual changes from GitHub
try {
const websiteGitHubRepo = await fetch(
Expand Down Expand Up @@ -707,12 +698,9 @@ export default class App extends Component {
isCompiledFromArchiveBool,
archivedZip,
resourcesLoaded,
glossaryData,
compileWarnings,
} = this.state;

if (glossaryData === null) return null;

const steps = [];

const viewingPreviousExperiment =
Expand Down Expand Up @@ -742,7 +730,6 @@ export default class App extends Component {
isCompiledFromArchiveBool={isCompiledFromArchiveBool}
archivedZip={archivedZip}
resourcesLoaded={resourcesLoaded}
glossaryData={glossaryData}
/>,
);
else
Expand All @@ -765,7 +752,6 @@ export default class App extends Component {
isCompiledFromArchiveBool={isCompiledFromArchiveBool}
archivedZip={archivedZip}
resourcesLoaded={resourcesLoaded}
glossaryData={glossaryData}
compileWarnings={compileWarnings}
/>,
);
Expand All @@ -776,7 +762,7 @@ export default class App extends Component {
<Suspense fallback={<></>}>
<Glossary
closeGlossary={this.closeGlossary}
glossaryFull={glossaryData?.glossaryFull ?? []}
glossaryFull={getGlossaryFull()}
/>
</Suspense>
)}
Expand Down
1 change: 0 additions & 1 deletion source/Running.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,6 @@ export default class Running extends Component {
incompatibleCompletionCode,
abortedCompletionCode,
prolificToken,
this.props.glossaryData,
);

if (result?.status === "UNPUBLISHED" && result.id) {
Expand Down
50 changes: 30 additions & 20 deletions source/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,36 +69,46 @@ export default class Table extends Component {
}

async handleTable(file) {
const { user } = this.props;

// The glossary is fetched lazily on first compile (no longer at app launch).
// handleDrop has already opened a "Compiling ..." dialog before calling us;
// we relabel that same dialog for each phase instead of firing/closing our
// own, so the modal stays open continuously — closing it would leave a blank
// screen through the rest of the compile.
let shouldFetch = true;
let serverVersion = null;
try {
let shouldFetch = true;
let serverVersion = null;
try {
({ version: serverVersion } = await fetchGlossaryVersion());
const cachedVersion = getGlossaryVersion();
if (
serverVersion !== null &&
cachedVersion !== null &&
serverVersion === cachedVersion
) {
shouldFetch = false;
}
} catch {
// fall through to full fetch
({ version: serverVersion } = await fetchGlossaryVersion());
const cachedVersion = getGlossaryVersion();
if (
serverVersion !== null &&
cachedVersion !== null &&
serverVersion === cachedVersion
) {
shouldFetch = false;
}
} catch {
// fall through to full fetch
}

if (shouldFetch) {
if (shouldFetch) {
// The glossary isn't ready yet; tell the scientist we're waiting on it.
manuallySetSwalTitle("Loading glossary …");
Swal.showLoading(null);
try {
// Fetch by explicit version so the CDN returns the just-published
// glossary (new version = new URL = cache miss), never a stale copy.
// If the probe failed, serverVersion is null → falls back to current.
const data = await fetchGlossaryData(serverVersion);
initGlossary(data);
} catch (err) {
Swal.close();
console.error("Failed to refresh glossary:", err);
return;
}
} catch (err) {
console.error("Failed to refresh glossary:", err);
return;
}
// Restore the compiling status before handing off to the resource/compile
// flow, which manages its own status dialog.
manuallySetSwalTitle("Compiling ...");

let resolvedResources;

Expand Down
112 changes: 0 additions & 112 deletions source/__tests__/App.test.js

This file was deleted.

77 changes: 77 additions & 0 deletions source/__tests__/Table.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from "react";
import { render } from "@testing-library/react";
import Table from "../Table";
import Swal from "sweetalert2";
import { preprocessExperimentFile } from "../../threshold/preprocess/main";

jest.mock("sweetalert2", () => ({
fire: jest.fn(),
Expand Down Expand Up @@ -221,3 +223,78 @@ describe("Table.handleTable", () => {
expect(initGlossary).toHaveBeenCalledWith(mockGlossaryData);
});
});

describe("Table.handleTable glossary loading dialog", () => {
beforeEach(() => {
jest.clearAllMocks();
});

const swalTitles = () => {
const { manuallySetSwalTitle } = require("../../threshold/preprocess/gitlabUtils");
return manuallySetSwalTitle.mock.calls.map(([title]) => title);
};

it("relabels the open dialog to 'Glossary …' while downloading, then restores 'Compiling ...' without closing it", async () => {
const { fetchGlossaryData, fetchGlossaryVersion } = require("../components/glossaryApi");
const { getGlossaryVersion } = require("../../threshold/parameters/glossaryRegistry");
const { manuallySetSwalTitle } = require("../../threshold/preprocess/gitlabUtils");
fetchGlossaryVersion.mockResolvedValue({ version: "2.0" });
getGlossaryVersion.mockReturnValue(null);
fetchGlossaryData.mockResolvedValue(mockGlossaryData);

const ref = React.createRef();
render(<Table ref={ref} {...makeProps()} />);

await ref.current.handleTable(new File(["a,b"], "exp.csv"));

const titles = swalTitles();
// The dialog opened by handleDrop is relabeled to show the glossary download...
expect(titles).toContain("Glossary …");
// ...before the download starts...
const glossaryTitleOrder =
manuallySetSwalTitle.mock.invocationCallOrder[titles.indexOf("Glossary …")];
const fetchOrder = fetchGlossaryData.mock.invocationCallOrder[0];
expect(glossaryTitleOrder).toBeLessThan(fetchOrder);
// ...and is restored to "Compiling ..." (never closed) before preprocessing.
expect(titles).toContain("Compiling ...");
expect(Swal.close).not.toHaveBeenCalled();
expect(preprocessExperimentFile).toHaveBeenCalledTimes(1);
});

it("does not relabel to 'Glossary …' when the cached version is current", async () => {
const { fetchGlossaryVersion } = require("../components/glossaryApi");
const { getGlossaryVersion } = require("../../threshold/parameters/glossaryRegistry");
fetchGlossaryVersion.mockResolvedValue({ version: "2.0" });
getGlossaryVersion.mockReturnValue("2.0");

const ref = React.createRef();
render(<Table ref={ref} {...makeProps()} />);

await ref.current.handleTable(new File(["a,b"], "exp.csv"));

// No download, so no glossary status; the shared dialog stays open (never closed).
expect(swalTitles()).not.toContain("Glossary …");
expect(Swal.close).not.toHaveBeenCalled();
});

it("closes the dialog when the glossary download fails", async () => {
const { fetchGlossaryData, fetchGlossaryVersion } = require("../components/glossaryApi");
const { getGlossaryVersion } = require("../../threshold/parameters/glossaryRegistry");
fetchGlossaryVersion.mockResolvedValue({ version: "2.0" });
getGlossaryVersion.mockReturnValue(null);
fetchGlossaryData.mockRejectedValue(new Error("network down"));
const consoleError = jest.spyOn(console, "error").mockImplementation(() => {});

const ref = React.createRef();
render(<Table ref={ref} {...makeProps()} />);

await ref.current.handleTable(new File(["a,b"], "exp.csv"));

expect(swalTitles()).toContain("Glossary …");
// The error path closes the dialog instead of leaving it spinning forever.
expect(Swal.close).toHaveBeenCalledTimes(1);
expect(preprocessExperimentFile).not.toHaveBeenCalled();

consoleError.mockRestore();
});
});
6 changes: 6 additions & 0 deletions source/__tests__/prolificIntegration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import {
COMPLETION_CODE_ACTION,
COMPLETION_CODE_TYPE,
} from "../components/prolificConstants";
import { getGlossary } from "../../threshold/parameters/glossaryRegistry";

jest.mock("../../threshold/parameters/glossaryRegistry", () => ({
getGlossary: jest.fn(),
}));

// Mock global fetch
global.fetch = jest.fn();
Expand All @@ -33,6 +38,7 @@ describe("Prolific Integration - New Parameters", () => {
_online2Description: { default: "Default Description" },
},
};
getGlossary.mockReturnValue(mockGlossaryData.glossary);
mockInternalName = "TestExperiment";
mockCompletionCode = "COMPLETED123";
mockIncompatibleCode = "INCOMPATIBLE456";
Expand Down
6 changes: 3 additions & 3 deletions source/components/prolificIntegration.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
VR_HEADSET_USAGE_PROLIFIC_MAPPING,
} from "./prolificConstants";
import { captureError } from "../sentry";
import { getGlossary } from "../../threshold/parameters/glossaryRegistry";

const prolificStudySubmissionStatus = {
RESERVED: "RESERVED",
Expand Down Expand Up @@ -360,7 +361,6 @@ export const prolificCreateDraft = async (
incompatibleCompletionCode,
abortedCompletionCode,
token,
glossaryData,
) => {
// const prolificStudyDraftApiUrl = "https://api.prolific.com/api/v1/studies/";
const prolificStudyDraftApiUrl = "/.netlify/functions/prolific/studies/";
Expand Down Expand Up @@ -516,7 +516,7 @@ export const prolificCreateDraft = async (
user.currentExperiment.titleOfStudy &&
user.currentExperiment.titleOfStudy !== ""
? user.currentExperiment.titleOfStudy
: glossaryData.glossary["_online1Title"].default,
: getGlossary()["_online1Title"].default,
internal_name:
user.currentExperiment._online1InternalName &&
user.currentExperiment._online1InternalName !== ""
Expand All @@ -526,7 +526,7 @@ export const prolificCreateDraft = async (
user.currentExperiment.descriptionOfStudy &&
user.currentExperiment.descriptionOfStudy !== ""
? user.currentExperiment.descriptionOfStudy
: glossaryData.glossary["_online2Description"].default,
: getGlossary()["_online2Description"].default,
external_study_url: user.currentExperiment.experimentUrl,
prolific_id_option: "url_parameters",
completion_option: "url",
Expand Down
Loading