Skip to content

Commit

Permalink
ui: add surveys to querybook (#1375)
Browse files Browse the repository at this point in the history
* ui: add surveys to querybok

* feat: add surveys in Querybook

* Address comments

* remove dev code
  • Loading branch information
czgu authored Nov 28, 2023
1 parent e2efe8e commit e332fc5
Show file tree
Hide file tree
Showing 33 changed files with 979 additions and 45 deletions.
44 changes: 44 additions & 0 deletions docs_website/docs/integrations/add_surveys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
id: add_surveys
title: Add Surveys
sidebar_label: Add Surveys
---

Product surveys serve as an excellent tool to gather user feedback. Querybook supports several kinds of surveys out-of-the-box, including:

1. **Table search**: Users can indicate if the table search results matched their expectations.
2. **Table trust**: Users can rate their trust in the provided table metadata.
3. **Text to SQL**: Users can evaluate the quality of AI-generated SQL code.
4. **Query authoring**: Users can rate their experience of writing queries on Querybook.

Each of these surveys follows the same 1-5 rating format, complemented by an optional text field for additional comments.
By default, the surveys are disabled. If you wish to enable them, override the `querybook/config/querybook_public_config.yaml` file.

Below is an example of a setting that enables all surveys:

```yaml
survey:
global_response_cooldown: 2592000 # 30 days
global_trigger_cooldown: 600 # 10 minutes
global_max_per_week: 6
global_max_per_day: 3

surfaces:
- surface: table_search
max_per_week: 5
- surface: table_view
max_per_day: 4
- surface: text_to_sql
response_cooldown: 24000
- surface: query_authoring
```
To activate a survey for a specific surface, you need to include the relevant surface key under `surfaces`.
You can find out the list of all support surfaces in `SurveyTypeToQuestion` located under `querybook/webapp/const/survey.ts`.

There are 4 variables that you can configure either for eaceh individual surface or globally for surveys, they are:

- **response_cooldown**: Time (in seconds) the system waits before showing the same survey to a user who has already responded.
- **trigger_cooldown**: Waiting period before the same survey is shown to the same user.
- **max_per_week**: Maximum number of surveys shown to a user per week (per surface type).
- **max_per_day**: Daily limit for the number of surveys shown to a user (per surface type).
1 change: 1 addition & 0 deletions docs_website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"integrations/add_table_upload",
"integrations/add_event_logger",
"integrations/add_stats_logger",
"integrations/add_surveys",
"integrations/add_ai_assistant",
"integrations/customize_html",
"integrations/embedded_iframe"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "querybook",
"version": "3.28.3",
"version": "3.28.4",
"description": "A Big Data Webapp",
"private": true,
"scripts": {
Expand Down
16 changes: 16 additions & 0 deletions querybook/config/querybook_public_config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Public Configs are shared between backend and frontend and are not sensitive
# --------------- AI Assistant ---------------
ai_assistant:
enabled: false
Expand All @@ -13,3 +14,18 @@ ai_assistant:

table_vector_search:
enabled: false

survey:
global_response_cooldown: 2592000 # 30 days
global_trigger_cooldown: 600 # 10 minutes
global_max_per_week: 6
global_max_per_day: 3

surfaces: []

# Uncomment to enable survey on all surfaces
# surfaces:
# - surface: table_search
# - surface: table_view
# - surface: text_to_sql
# - surface: query_authoring
41 changes: 41 additions & 0 deletions querybook/migrations/versions/c00f08f16065_add_surveys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Add surveys
Revision ID: c00f08f16065
Revises: 4c70dae378f2
Create Date: 2023-11-20 22:40:36.139101
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = "c00f08f16065"
down_revision = "4c70dae378f2"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"survey",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("updated_at", sa.DateTime(), nullable=True),
sa.Column("uid", sa.Integer(), nullable=True),
sa.Column("rating", sa.Integer(), nullable=False),
sa.Column("comment", sa.String(length=5000), nullable=True),
sa.Column("surface", sa.String(length=255), nullable=False),
sa.Column("surface_metadata", sa.JSON(), nullable=False),
sa.ForeignKeyConstraint(["uid"], ["user.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("survey")
# ### end Alembic commands ###
2 changes: 2 additions & 0 deletions querybook/server/datasources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from . import event_log
from . import data_element
from . import comment
from . import survey

# Keep this at the end of imports to make sure the plugin APIs override the default ones
try:
Expand All @@ -42,4 +43,5 @@
event_log
data_element
comment
survey
api_plugin
27 changes: 27 additions & 0 deletions querybook/server/datasources/survey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from flask_login import current_user

from app.datasource import register
from logic import survey as logic


@register("/survey/", methods=["POST"])
def create_survey(
rating: int, surface: str, surface_metadata: dict[str, str], comment: str = None
):
return logic.create_survey(
uid=current_user.id,
rating=rating,
surface=surface,
surface_metadata=surface_metadata,
comment=comment,
)


@register("/survey/<int:survey_id>/", methods=["PUT"])
def update_survey(survey_id: int, rating: int = None, comment: str = None):
return logic.update_survey(
uid=current_user.id,
survey_id=survey_id,
rating=rating,
comment=comment,
)
52 changes: 52 additions & 0 deletions querybook/server/logic/survey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import datetime

from app.db import with_session
from models.survey import Survey


@with_session
def create_survey(
uid: int,
rating: int,
surface: str,
surface_metadata: dict[str, str] = {},
comment: str = None,
commit: bool = True,
session=None,
):
return Survey.create(
{
"uid": uid,
"rating": rating,
"surface": surface,
"surface_metadata": surface_metadata,
"comment": comment,
},
commit=commit,
session=session,
)


@with_session
def update_survey(
uid: int,
survey_id: int,
rating: int = None,
comment: str = None,
commit: bool = True,
session=None,
):
survey = Survey.get(id=survey_id, session=session)
assert survey.uid == uid, "User does not own this survey"

return Survey.update(
id=survey_id,
fields={
"rating": rating,
"comment": comment,
"updated_at": datetime.datetime.now(),
},
skip_if_value_none=True,
commit=commit,
session=session,
)
1 change: 1 addition & 0 deletions querybook/server/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
from .event_log import *
from .data_element import *
from .comment import *
from .survey import *
27 changes: 27 additions & 0 deletions querybook/server/models/survey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import sqlalchemy as sql

from app import db
from const.db import (
description_length,
name_length,
now,
)
from lib.sqlalchemy import CRUDMixin

Base = db.Base


class Survey(CRUDMixin, Base):
__tablename__ = "survey"

id = sql.Column(sql.Integer, primary_key=True)
created_at = sql.Column(sql.DateTime, default=now)
updated_at = sql.Column(sql.DateTime, default=now)

uid = sql.Column(sql.Integer, sql.ForeignKey("user.id", ondelete="CASCADE"))

rating = sql.Column(sql.Integer, nullable=False)
comment = sql.Column(sql.String(length=description_length), nullable=True)

surface = sql.Column(sql.String(length=name_length), nullable=False)
surface_metadata = sql.Column(sql.JSON, default={}, nullable=False)
21 changes: 18 additions & 3 deletions querybook/webapp/components/AIAssistant/QueryGenerationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { QueryComparison } from 'components/TranspileQueryModal/QueryComparison'
import { AICommandType } from 'const/aiAssistant';
import { ComponentType, ElementType } from 'const/analytics';
import { IQueryEngine } from 'const/queryEngine';
import { SurveySurfaceType } from 'const/survey';
import { useSurveyTrigger } from 'hooks/ui/useSurveyTrigger';
import { useAISocket } from 'hooks/useAISocket';
import { trackClick } from 'lib/analytics';
import { TableToken } from 'lib/sql-helper/sql-lexer';
Expand Down Expand Up @@ -115,7 +117,7 @@ export const QueryGenerationModal = ({

useEffect(() => {
if (!generating) {
setTables(uniq([...tablesInQuery, ...tables]));
setTables((tables) => uniq([...tablesInQuery, ...tables]));
}
}, [tablesInQuery, generating]);

Expand All @@ -125,12 +127,25 @@ export const QueryGenerationModal = ({
setNewQuery(trimSQLQuery(rawNewQuery));
}, [rawNewQuery]);

const triggerSurvey = useSurveyTrigger();
useEffect(() => {
if (!newQuery || generating) {
return;
}
triggerSurvey(SurveySurfaceType.TEXT_TO_SQL, {
question,
tables,
query: newQuery,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newQuery, triggerSurvey, generating]);

const onGenerate = useCallback(() => {
setFoundTables([]);
generateSQL({
query_engine_id: engineId,
tables: tables,
question: question,
tables,
question,
original_query: query,
});
trackClick({
Expand Down
16 changes: 13 additions & 3 deletions querybook/webapp/components/DataDocQueryCell/DataDocQueryCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { UDFForm } from 'components/UDFForm/UDFForm';
import { ComponentType, ElementType } from 'const/analytics';
import { IDataQueryCellMeta, TDataDocMetaVariables } from 'const/datadoc';
import type { IQueryEngine, IQueryTranspiler } from 'const/queryEngine';
import { SurveySurfaceType } from 'const/survey';
import { triggerSurvey } from 'hooks/ui/useSurveyTrigger';
import { trackClick } from 'lib/analytics';
import CodeMirror from 'lib/codemirror';
import { createSQLLinter } from 'lib/codemirror/codemirror-lint';
Expand Down Expand Up @@ -418,14 +420,22 @@ class DataDocQueryCellComponent extends React.PureComponent<IProps, IState> {
return runQuery(
await this.getTransformedQuery(),
this.engineId,
async (query, engineId) =>
(
async (query, engineId) => {
const queryId = (
await this.props.createQueryExecution(
query,
engineId,
this.props.cellId
)
).id
).id;

triggerSurvey(SurveySurfaceType.QUERY_AUTHORING, {
query_execution_id: queryId,
cell_id: this.props.cellId,
});

return queryId;
}
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { OptionTypeBase } from 'react-select';

export interface IDataDocBoardsSelectProps {
onChange: (params: OptionTypeBase[]) => void;
value: IOption<number>[];
value: Array<IOption<number>>;
label?: string;
name: string;
}
Expand All @@ -23,14 +23,16 @@ export const DataDocBoardsSelect: React.FC<IDataDocBoardsSelectProps> = ({
(state: IStoreState) => state.board.boardById
);

const boardOptions: IOption<number>[] = useMemo(() => {
return Object.values(boardById).map((board) => ({
value: board.id,
label: board.name,
}));
}, [boardById]);
const boardOptions: Array<IOption<number>> = useMemo(
() =>
Object.values(boardById).map((board) => ({
value: board.id,
label: board.name,
})),
[boardById]
);

const selectedBoards: IOption<number>[] = useMemo(
const selectedBoards: Array<IOption<number>> = useMemo(
() =>
boardOptions.filter((board) =>
value.map((v) => v.value).includes(board.value)
Expand Down
Loading

0 comments on commit e332fc5

Please sign in to comment.