Skip to content
Open
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
87 changes: 48 additions & 39 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,56 +1,65 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import globals from 'globals';
import tsParser from '@typescript-eslint/parser';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import js from '@eslint/js';
import {FlatCompat} from '@eslint/eslintrc';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});

export default [{
export default [
{
ignores: [
"**/dist/",
"**/node_modules/",
"**/webpack.config.js",
"**/lit-css-loader.js",
'**/dist/',
'**/node_modules/',
'**/webpack.config.js',
'**/lit-css-loader.js',
],
}, ...compat.extends(
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
), {
},
...compat.extends(
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
),
{
plugins: {
"@typescript-eslint": typescriptEslint,
'@typescript-eslint': typescriptEslint,
},

languageOptions: {
globals: {
...globals.browser,
},
globals: {
...globals.browser,
},

parser: tsParser,
ecmaVersion: 2020,
sourceType: "module",
parser: tsParser,
ecmaVersion: 2020,
sourceType: 'module',
},

rules: {
"no-case-declarations": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": ["warn", {
argsIgnorePattern: "^_",
}],
'no-case-declarations': 'off',
'no-prototype-builtins': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
}];
},
];
20 changes: 13 additions & 7 deletions firestore/firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ service cloud.firestore {
match /agents/{agentId} {
allow get: if isExperimenter();
allow list: if isExperimenter();
// Experimenters can use cloud function endpoints
// Experimenters can use cloud function endpoints
allow write: if false;

match /chatPrompts/{promptId} {
Expand All @@ -131,13 +131,13 @@ service cloud.firestore {
allow get: if true; // Public read
allow list: if canEditExperiment(experimentId);

// Experimenters can use cloud function endpoints
// Experimenters can use cloud function endpoints
allow write: if false;

match /publicStageData/{stageId} {
allow read: if true;

// TODO: Triggered by cloud function triggers
// TODO: Triggered by cloud function triggers
allow write: if false;

match /chats/{chatId} {
Expand Down Expand Up @@ -169,7 +169,7 @@ service cloud.firestore {
allow get: if true;
allow list: if true;

// Use cloud function endpoints
// Use cloud function endpoints
allow update: if false;
}

Expand All @@ -180,27 +180,33 @@ service cloud.firestore {
allow list: if true;
allow get: if true; // Public read

// Participants can use cloud function endpoints
// Participants can use cloud function endpoints
allow update: if false;

match /stageData/{stageId} {
allow read: if true;

// Participants can use cloud function endpoints
// Participants can use cloud function endpoints
allow write: if false;
}

match /alerts/{alertId} {
allow read: if true;
allow write: if false; // Use cloud functions
}

// Append-only participant behavior events
match /behavior/{eventId} {
allow read: if isExperimenter();
allow write: if false; // Use cloud functions
}
}

match /participantPublicData/{participantPublicId} {
allow list: if true;
allow get: if true;

// Triggered by cloud function triggers
// Triggered by cloud function triggers
allow write: if false;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import {MobxLitElement} from '@adobe/lit-mobx';
import {CSSResultGroup, html, nothing} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {customElement} from 'lit/decorators.js';

import '@material/web/textfield/filled-text-field.js';
import '@material/web/checkbox/checkbox.js';

import {core} from '../../core/core';
import {ButtonClick, AnalyticsService} from '../../services/analytics.service';
import {AuthService} from '../../services/auth.service';
import {HomeService} from '../../services/home.service';
import {Pages, RouterService} from '../../services/router.service';
import {ExperimentEditor} from '../../services/experiment.editor';
import {ExperimentManager} from '../../services/experiment.manager';

Expand All @@ -22,7 +18,6 @@ import {styles} from './experiment_settings_editor.scss';
export class ExperimentSettingsEditor extends MobxLitElement {
static override styles: CSSResultGroup = [styles];

private readonly analyticsService = core.getService(AnalyticsService);
private readonly experimentEditor = core.getService(ExperimentEditor);
private readonly experimentManager = core.getService(ExperimentManager);

Expand Down Expand Up @@ -92,9 +87,16 @@ export class ExperimentSettingsEditor extends MobxLitElement {
this.experimentEditor.updatePermissions({visibility});
};

const isBehaviorEnabled =
this.experimentEditor.experiment.collectBehaviorData;

const updateBehavior = () => {
this.experimentEditor.updateCollectBehaviorData(!isBehaviorEnabled);
};

return html`
<div class="section">
<div class="checkbox-wrapper">
<div class="checkbox-wrapper" style="margin-top: 8px;">
<md-checkbox
touch-target="wrapper"
?checked=${isPublic}
Expand All @@ -107,6 +109,19 @@ export class ExperimentSettingsEditor extends MobxLitElement {
manage the experiment dashboard if you share the link with them)
</div>
</div>
<div class="checkbox-wrapper">
<md-checkbox
touch-target="wrapper"
?checked=${isBehaviorEnabled}
?disabled=${!this.experimentEditor.isCreator}
@click=${updateBehavior}
>
</md-checkbox>
<div>
Collect behavior data for bot detection (may incur additional
database costs)
</div>
</div>
</div>
`;
}
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/components/participant_view/cohort_landing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {AnalyticsService, ButtonClick} from '../../services/analytics.service';
import {ExperimentService} from '../../services/experiment.service';
import {FirebaseService} from '../../services/firebase.service';
import {Pages, RouterService} from '../../services/router.service';
import {BehaviorService} from '../../services/behavior.service';

import {StageKind} from '@deliberation-lab/utils';

Expand All @@ -26,8 +27,24 @@ export class CohortLanding extends MobxLitElement {
private readonly experimentService = core.getService(ExperimentService);
private readonly firebaseService = core.getService(FirebaseService);
private readonly routerService = core.getService(RouterService);
private readonly behaviorService = core.getService(BehaviorService);

@state() isLoading = false;
private hasLoggedCtaRender = false;

override updated() {
// Log first time the join CTA is actually available to the user
const params = this.routerService.activeRoute.params;
const exp = this.experimentService.experiment;
const isLocked = !!(exp && exp.cohortLockMap[params['cohort']]);
if (!isLocked && !this.hasLoggedCtaRender) {
this.behaviorService.log('cta_render', {
cta: 'join_experiment',
page: 'cohort_landing',
});
this.hasLoggedCtaRender = true;
}
}

override render() {
const isLockedCohort = () => {
Expand Down Expand Up @@ -67,6 +84,11 @@ export class CohortLanding extends MobxLitElement {
}

private async joinExperiment() {
// Log CTA click for time-to-first-interaction
this.behaviorService.log('cta_click', {
cta: 'join_experiment',
page: 'cohort_landing',
});
this.isLoading = true;
this.analyticsService.trackButtonClick(ButtonClick.PARTICIPANT_JOIN);

Expand Down
28 changes: 28 additions & 0 deletions frontend/src/components/stages/chat_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import './chat_message';

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

import {core} from '../../core/core';
Expand All @@ -17,6 +18,7 @@ import {CohortService} from '../../services/cohort.service';
import {ExperimentService} from '../../services/experiment.service';
import {ParticipantService} from '../../services/participant.service';
import {ParticipantAnswerService} from '../../services/participant.answer';
import {BehaviorService} from '../../services/behavior.service';
import {RouterService} from '../../services/router.service';

import {
Expand All @@ -41,6 +43,7 @@ export class ChatInterface extends MobxLitElement {
private readonly participantAnswerService = core.getService(
ParticipantAnswerService,
);
private readonly behaviorService = core.getService(BehaviorService);
private readonly routerService = core.getService(RouterService);

@property() stage: ChatStageConfig | undefined = undefined;
Expand All @@ -49,6 +52,30 @@ export class ChatInterface extends MobxLitElement {
@state() readyToEndDiscussionLoading = false;
@state() isAlertLoading = false;

private _chatInputUnsub: (() => void) | null = null;

private onChatRef = (el: Element | undefined) => {
// Detach previous listener if any
if (this._chatInputUnsub) {
this._chatInputUnsub();
this._chatInputUnsub = null;
}
if (!el) return;
const stageId = this.stage?.id ?? 'unknown';
this._chatInputUnsub = this.behaviorService.attachTextInput(
el,
`chat_stage:${stageId}`,
);
};

override disconnectedCallback(): void {
if (this._chatInputUnsub) {
this._chatInputUnsub();
this._chatInputUnsub = null;
}
super.disconnectedCallback();
}

private sendUserInput() {
if (!this.stage) return;

Expand Down Expand Up @@ -210,6 +237,7 @@ export class ChatInterface extends MobxLitElement {
this.isConversationOver()}
@keyup=${handleKeyUp}
@input=${handleInput}
${ref(this.onChatRef)}
>
</pr-textarea>
<pr-tooltip
Expand Down
Loading