Skip to content
25 changes: 14 additions & 11 deletions docs/researchers/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ Experiment templates allow you to quickly create new experiments based on pre-de

When building an experiment, you can load a template to populate the stages and settings.

1. In the experiment builder, click the **Load template** button in the left sidebar.
2. Browse the gallery of available templates.
* **Built-in Templates**: Pre-defined templates included with Deliberate Lab.
* **Saved Templates**: Templates you or your team have created and saved.
3. Click on a template to apply it to your current experiment. **Warning: This will overwrite your current stages and settings.**
1. In the experiment builder, click the **Load template** button in the top header.
2. Browse the gallery of available templates:
* **Recommended**: Includes "Quick Start" templates (e.g., Blank, Group Chat, Private Chat) and other default templates.
* **Owned by me**: Templates you have created.
* **Shared with me**: Templates shared with you by other researchers.
3. You can search for templates by name or description using the search bar.
4. Click on a template card to apply it to your current experiment. **Warning: This will overwrite your current stages and settings.**

## Saving a new template

Expand All @@ -27,9 +29,9 @@ You can save your current experiment configuration as a new template for future

## Updating a template

If you have loaded a template and made changes, you can update the original template with your new configuration.
If you have loaded a template that you own and have made changes, you can update the original template.

1. Load a template.
1. Load a template you own.
2. Make your desired changes to the stages or settings.
3. Click the **Update template** button in the top right corner.
4. Confirm the update in the dialog.
Expand All @@ -38,7 +40,8 @@ If you have loaded a template and made changes, you can update the original temp

You can delete saved templates that are no longer needed.

1. Click the **Load template** button in the left sidebar.
2. Find the template you want to delete in the **Saved Templates** section.
3. Click the delete icon (trash can) on the template card.
4. Confirm the deletion.
1. Click the **Load template** button in the top header to open the gallery.
2. Navigate to the **Owned by me** tab.
3. Find the template you want to delete.
4. Click the delete icon (trash can) on the template card.
5. Confirm the deletion.
10 changes: 8 additions & 2 deletions firestore/firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,17 @@ service cloud.firestore {
allow update: if isAdmin();
}

// Experiment templates (experimenter only)
// Experiment templates (experimenter only)
match /experimentTemplates/{templateId} {
allow list: if isExperimenter();
allow get: if isExperimenter() && (isPublic() || isCreator() || isReader() || isAdmin());

allow get: if isExperimenter() && (
resource.data.visibility == "public" ||
lowerEquals(resource.data.experiment.metadata.creator, request.auth.token.email) ||
(resource.data.sharedWith != null && resource.data.sharedWith.hasAny([request.auth.token.email])) ||
isAdmin()
);

// Experiments can use `writeExperiment`, `deleteExperiment` endpoints
allow write: if false;
}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import './components/login/login';
import './components/participant_view/cohort_landing';
import './components/participant_view/participant_view';
import './components/settings/settings';
import './components/templates/templates_dialog';

import {MobxLitElement} from '@adobe/lit-mobx';
import {CSSResultGroup, html, nothing, TemplateResult} from 'lit';
Expand Down Expand Up @@ -55,6 +56,7 @@ export class App extends MobxLitElement {
<page-header></page-header>
<quick-start-gallery></quick-start-gallery>
<home-gallery></home-gallery>
<templates-dialog></templates-dialog>
`;
case Pages.ADMIN:
return html`
Expand All @@ -63,6 +65,7 @@ export class App extends MobxLitElement {
<admin-dashboard></admin-dashboard>
</div>
`;

case Pages.SETTINGS:
return html`
<page-header></page-header>
Expand All @@ -71,6 +74,7 @@ export class App extends MobxLitElement {
</div>
`;
case Pages.EXPERIMENT:
case Pages.EXPERIMENT_EDIT:
if (!this.authService.isExperimenter) {
return this.render403();
}
Expand All @@ -79,6 +83,8 @@ export class App extends MobxLitElement {

return html` <experiment-dashboard></experiment-dashboard> `;
case Pages.EXPERIMENT_CREATE:
case Pages.TEMPLATE_CREATE:
case Pages.TEMPLATE_EDIT:
if (!this.authService.isExperimenter) {
return this.render403();
}
Expand Down
50 changes: 49 additions & 1 deletion frontend/src/components/experiment_builder/experiment_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import './variable_editor';

import {MobxLitElement} from '@adobe/lit-mobx';
import {CSSResultGroup, html, nothing} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {customElement, state} from 'lit/decorators.js';

import '@material/web/checkbox/checkbox.js';

Expand All @@ -51,6 +51,11 @@ import {
validateTemplateVariables,
} from '@deliberation-lab/utils';

import {
DEFAULT_TEMPLATES,
RESEARCH_TEMPLATES,
} from '../../shared/default_templates';

import {styles} from './experiment_builder.scss';

enum PanelView {
Expand All @@ -77,6 +82,49 @@ export class ExperimentBuilder extends MobxLitElement {

@state() panelView: PanelView = PanelView.STAGES;

override async firstUpdated() {
const params = this.routerService.activeRoute.params;
const isTemplateEdit =
this.routerService.activePage === Pages.TEMPLATE_EDIT;
const templateId = params['template'];

if (templateId) {
if (this.experimentEditor.savedTemplates.length === 0) {
await this.experimentEditor.loadTemplates();
}

let template = this.experimentEditor.savedTemplates.find(
(t) => t.id === templateId,
);

if (!template) {
const defaultTemplate = [
...DEFAULT_TEMPLATES,
...RESEARCH_TEMPLATES,
].find((t) => t.id === templateId);

if (defaultTemplate) {
template = defaultTemplate.factory();
}
}

if (template) {
this.experimentEditor.loadTemplate(template);
if (isTemplateEdit) {
// TODO: Set a flag in experimentEditor to indicate we are directly editing a template
// For now, we just load it. The save button logic needs to know this.
this.experimentEditor.setLoadedTemplateId(template.id);
}

if (this.experimentEditor.stages.length > 0) {
this.experimentEditor.setCurrentStageId(
this.experimentEditor.stages[0].id,
);
}
}
}
}

override render() {
return html`
${this.renderPanelView()} ${this.renderExperimentConfigBuilder()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ export class ExperimentBuilderNav extends MobxLitElement {

override render() {
return html`
<div class="buttons header">
${this.renderAddStageButton()} ${this.renderLoadTemplateButton()}
</div>
<div class="buttons header">${this.renderAddStageButton()}</div>
<div class="nav-items-wrapper">
${this.experimentEditor.stages.map((stage, index) =>
this.renderStageItem(stage, index),
Expand Down Expand Up @@ -126,21 +124,6 @@ export class ExperimentBuilderNav extends MobxLitElement {
</pr-button>
`;
}

private renderLoadTemplateButton() {
return html`
<pr-button
color="neutral"
variant="default"
?disabled=${!this.experimentEditor.canEditStages}
@click=${() => {
this.experimentEditor.toggleStageBuilderDialog(true);
}}
>
Load template
</pr-button>
`;
}
}

declare global {
Expand Down
Loading