Skip to content

Commit

Permalink
refactor: Rename more instances of "experiment" to "block"
Browse files Browse the repository at this point in the history
  • Loading branch information
drikusroor committed Jun 28, 2024
1 parent 0b6d60f commit 070fde6
Show file tree
Hide file tree
Showing 23 changed files with 204 additions and 193 deletions.
22 changes: 11 additions & 11 deletions frontend/src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ export const useConsent = (slug: string) =>
useGet(API_BASE_URL + URLS.result.get('consent_' + slug));

interface CreateConsentParams {
experiment: Block;
block: Block;
participant: Participant;
}

// Create consent for given experiment
export const createConsent = async ({ experiment, participant }: CreateConsentParams) => {
/** Create consent for given experiment */
export const createConsent = async ({ block, participant }: CreateConsentParams) => {
try {
const response = await axios.post(
API_BASE_URL + URLS.result.consent,
qs.stringify({
json_data: JSON.stringify(
{
key: "consent_" + experiment.slug,
key: "consent_" + block.slug,
value: true,
}
),
Expand All @@ -88,18 +88,18 @@ export const createConsent = async ({ experiment, participant }: CreateConsentPa
};

interface CreateSessionParams {
experiment: Block;
block: Block;
participant: Participant;
playlist: { current: string };
}

// Create a new session for given experiment
export const createSession = async ({experiment, participant, playlist}: CreateSessionParams) => {
export const createSession = async ({ block, participant, playlist }: CreateSessionParams) => {
try {
const response = await axios.post(
API_BASE_URL + URLS.session.create,
qs.stringify({
experiment_id: experiment.id,
experiment_id: block.id,
playlist_id: playlist.current,
csrfmiddlewaretoken: participant.csrf_token,
})
Expand Down Expand Up @@ -186,7 +186,7 @@ export const scoreIntermediateResult = async ({
interface GetNextRoundParams {
session: Session;
}


// Get next_round from server
export const getNextRound = async ({ session }: GetNextRoundParams) => {
Expand Down Expand Up @@ -249,14 +249,14 @@ export const shareParticipant = async ({ email, participant }: ShareParticipantP
};

interface PostFeedbackParams {
experimentSlug: string;
blockSlug: string;
feedback: string;
participant: Participant;
}

// Collect user feedback
export const postFeedback = async({ experimentSlug, feedback, participant }: PostFeedbackParams) => {
const endpoint = API_BASE_URL + URLS.block.feedback(experimentSlug)
export const postFeedback = async ({ blockSlug, feedback, participant }: PostFeedbackParams) => {
const endpoint = API_BASE_URL + URLS.block.feedback(blockSlug)
try {
const response = await axios.post(
endpoint,
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/Block/Block.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const Block = ({ match }) => {
return session;
}
try {
const newSession = await createSession({ experiment: block, participant, playlist })
const newSession = await createSession({ block, participant, playlist })
setSession(newSession);
return newSession;
}
Expand Down Expand Up @@ -165,7 +165,7 @@ const Block = ({ match }) => {
const render = (view) => {
// Default attributes for every view
const attrs = {
experiment: block,
block,
participant,
loadingText,
onResult,
Expand Down Expand Up @@ -260,7 +260,7 @@ const Block = ({ match }) => {
{block?.feedback_info?.show_float_button && (
<FloatingActionButton>
<UserFeedback
experimentSlug={block.slug}
blockSlug={block.slug}
participant={participant}
feedbackInfo={block.feedback_info}
inline={false} />
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/Consent/Consent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import Loading from "../Loading/Loading";
import { createConsent, useConsent } from "../../API";
import classNames from "classnames";

// Consent is an experiment view that shows the consent text, and handles agreement/stop actions
const Consent = ({ title, text, experiment, participant, onNext, confirm, deny }) => {
const [consent, loadingConsent] = useConsent(experiment.slug);
/** Consent is an block view that shows the consent text, and handles agreement/stop actions */
const Consent = ({ title, text, block, participant, onNext, confirm, deny }) => {
const [consent, loadingConsent] = useConsent(block.slug);
const urlQueryString = window.location.search;

// Listen for consent, and auto advance if already given
Expand All @@ -22,7 +22,7 @@ const Consent = ({ title, text, experiment, participant, onNext, confirm, deny }
// Click on agree button
const onAgree = async () => {
// Store consent
await createConsent({ experiment, participant });
await createConsent({ block, participant });

// Next!
onNext();
Expand All @@ -31,14 +31,14 @@ const Consent = ({ title, text, experiment, participant, onNext, confirm, deny }
const onDownload = async () => {
const doc = new DOMParser().parseFromString(text, 'text/html');
const txt = doc.body.textContent.split(' ').join('');
const blob = new Blob([txt], {type: "text/plain;charset=utf-8"});
const blob = new Blob([txt], { type: "text/plain;charset=utf-8" });
saveAs(blob, 'consent.txt');
}

// Loader in case consent is being loaded
// or it was already given
if (loadingConsent || consent) {
return <Loading loadingText={experiment.loading_text} />;
return <Loading loadingText={block.loading_text} />;
}

// Calculate height for consent text to prevent overlapping browser chrome
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/components/Consent/Consent.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,28 @@ const mockBlock = {
describe('Consent', () => {
it('renders loading state correctly', () => {
useConsent.mockReturnValue([null, true]); // Mock loading state
const { getByText } = render(<Consent experiment={{ slug: 'test-experiment', loading_text: 'Loading...' }} />);
const { getByText } = render(<Consent block={{ slug: 'test-experiment', loading_text: 'Loading...' }} />);
expect(document.body.contains(getByText('Loading...'))).to.be.true;
});

it('renders consent text when not loading', () => {
useConsent.mockReturnValue([null, false]);
const { getByText } = render(<Consent text="<p>Consent Text</p>" experiment={mockBlock} />);
const { getByText } = render(<Consent text="<p>Consent Text</p>" block={mockBlock} />);
expect(document.body.contains(getByText('Consent Text'))).to.be.true;
});

it('calls onNext when Agree button is clicked', async () => {
useConsent.mockReturnValue([null, false]);
const onNext = vi.fn();
const { getByText } = render(<Consent onNext={onNext} confirm="Agree" experiment={mockBlock} />);
const { getByText } = render(<Consent onNext={onNext} confirm="Agree" block={mockBlock} />);
fireEvent.click(getByText('Agree'));

await waitFor(() => expect(onNext).toHaveBeenCalled());
});

it('triggers download when Download button is clicked', async () => {
useConsent.mockReturnValue([null, false]);
const { getByTestId } = render(<Consent text="<p>Consent Text</p>" experiment={mockBlock} />);
const { getByTestId } = render(<Consent text="<p>Consent Text</p>" block={mockBlock} />);
fireEvent.click(getByTestId('download-button'));

await waitFor(() => expect(saveAs).toHaveBeenCalled());
Expand All @@ -59,14 +59,14 @@ describe('Consent', () => {
it('auto advances if consent is already given', () => {
useConsent.mockReturnValue([true, false]);
const onNext = vi.fn();
render(<Consent onNext={onNext} experiment={mockBlock} />);
render(<Consent onNext={onNext} block={mockBlock} />);
expect(onNext).toHaveBeenCalled();
});

it('calculates style for consent text correctly', () => {
useConsent.mockReturnValue([null, false]);
Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 800 });
const { getByTestId } = render(<Consent text="<p>Consent Text</p>" experiment={mockBlock} />);
const { getByTestId } = render(<Consent text="<p>Consent Text</p>" block={mockBlock} />);
const consentText = getByTestId('consent-text');
expect(consentText.style.height).toBe('500px');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import axios from 'axios';
import ExperimentCollection from './ExperimentCollection';
let mock = new MockAdapter(axios);

const getExperiment = (overrides = {}) => {
const getBlock = (overrides = {}) => {
return {
slug: 'some_slug',
name: 'Some Experiment',
name: 'Some Block',
...overrides
};
}

const experiment1 = getExperiment({
const block1 = getBlock({
slug: 'some_slug',
name: 'Some Experiment'
name: 'Some Block'
});

const theme = {
Expand Down Expand Up @@ -48,74 +48,74 @@ const theme = {
}
}

const experimentWithAllProps = getExperiment({ image: 'some_image.jpg', description: 'Some description' });
const blockWithAllProps = getBlock({ image: 'some_image.jpg', description: 'Some description' });

describe('ExperimentCollection', () => {

it('forwards to a single experiment if it receives an empty dashboard array', async () => {
mock.onGet().replyOnce(200, {dashboard: [], nextExperiment: experiment1});
it('forwards to a single block if it receives an empty dashboard array', async () => {
mock.onGet().replyOnce(200, { dashboard: [], nextExperiment: block1 });
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
</MemoryRouter>);
<MemoryRouter>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>);
await waitFor(() => {
expect(screen.queryByRole('menu')).toBeFalsy();
})
});

it('shows a loading spinner while loading', () => {
mock.onGet().replyOnce(200, new Promise(() => {}));
mock.onGet().replyOnce(200, new Promise(() => { }));
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
</MemoryRouter>
<MemoryRouter>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>
);
waitFor(() => {
expect(document.querySelector('.loader-container')).not.toBeNull();
})
});

it('shows a placeholder if no image is available', () => {
mock.onGet().replyOnce(200, { dashboard: [experiment1], nextExperiment: experiment1 });
mock.onGet().replyOnce(200, { dashboard: [block1], nextExperiment: block1 });
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
</MemoryRouter>
<MemoryRouter>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>
);
waitFor(() => {
expect(document.querySelector('.loader-container')).not.toBeNull();
})
});

it('shows the image if it is available', () => {
mock.onGet().replyOnce(200, { dashboard: [experimentWithAllProps], nextExperiment: experiment1 });
mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextExperiment: block1 });
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
</MemoryRouter>
<MemoryRouter>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>
);
waitFor(() => {
expect(document.querySelector('img')).not.toBeNull();
})
});

it('shows the description if it is available', () => {
mock.onGet().replyOnce(200, { dashboard: [experimentWithAllProps], nextExperiment: experiment1 });
mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextExperiment: block1 });
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
</MemoryRouter>
<MemoryRouter>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>
);
waitFor(() => {
expect(screen.getByText('Some description')).toBeInTheDocument();
})
});

it('shows consent first if available', async () => {
mock.onGet().replyOnce(200, { consent: '<p>This is our consent form!</p>', dashboard: [experimentWithAllProps], nextExperiment: experiment1} );
mock.onGet().replyOnce(200, { consent: '<p>This is our consent form!</p>', dashboard: [blockWithAllProps], nextExperiment: block1 });
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>
);
await waitFor(() => {
Expand All @@ -124,14 +124,14 @@ describe('ExperimentCollection', () => {
});

it('shows a footer if a theme with footer is available', async () => {
mock.onGet().replyOnce(200, { dashboard: [experimentWithAllProps], nextExperiment: experiment1, theme });
mock.onGet().replyOnce(200, { dashboard: [blockWithAllProps], nextExperiment: block1, theme });
render(
<MemoryRouter>
<ExperimentCollection match={{params: {slug: 'some_collection'}}}/>
<ExperimentCollection match={{ params: { slug: 'some_collection' } }} />
</MemoryRouter>
);
await waitFor( () => {
await waitFor(() => {
expect(document.querySelector('.aha__footer')).not.toBeNull();
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => {
const attrs = {
participant,
onNext,
experiment: experimentCollection,
block: experimentCollection,
...experimentCollection.consent,
}
return (
<DefaultPage className='aha__consent-wrapper' title={experimentCollection.name}>
<Consent {...attrs}/>
<Consent {...attrs} />
</DefaultPage>
)
}
Expand All @@ -95,4 +95,4 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => {
)
}

export default ExperimentCollection;
export default ExperimentCollection;
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ interface ExperimentCollectionDashboardProps {
export const ExperimentCollectionDashboard: React.FC<ExperimentCollectionDashboardProps> = ({ experimentCollection, participantIdUrl, totalScore }) => {

const dashboard = experimentCollection.dashboard;
const nextExperimentSlug = experimentCollection.nextExperiment?.slug;
const nextBlockSlug = experimentCollection.nextExperiment?.slug;

const headerProps = experimentCollection.theme?.header ? {
nextExperimentSlug,
nextBlockSlug,
collectionSlug: experimentCollection.slug,
...experimentCollection.theme.header,
totalScore,
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/components/Explainer/Explainer.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React, {useEffect} from "react";
import React, { useEffect } from "react";
import Button from "../Button/Button";

// Explainer is an experiment view that shows a list of steps
// If the button has not been clicked, onNext will be called automatically after the timer expires (in milliseconds). If timer == null, onNext will only be called after the button is clicked.
/**
* Explainer is an block view that shows a list of steps
* If the button has not been clicked, onNext will be called automatically after the timer expires (in milliseconds).
* If timer == null, onNext will only be called after the button is clicked.
*/
const Explainer = ({ instruction, button_label, steps = [], timer = null, onNext }) => {

useEffect( () => {
useEffect(() => {
if (timer != null) {
const id = setTimeout(onNext, timer);
return () => {clearTimeout(id)}; // if button has been clicked, clear timeout
return () => { clearTimeout(id) }; // if button has been clicked, clear timeout
}
}, [onNext, timer])

Expand Down
Loading

0 comments on commit 070fde6

Please sign in to comment.