mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-11 02:24:13 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d6abf484d |
+74
-17
@@ -328,6 +328,7 @@
|
||||
"not_authenticated": "You are not authenticated to perform this action.",
|
||||
"not_authorized": "Not authorized",
|
||||
"not_connected": "Not Connected",
|
||||
"not_set": "Not set",
|
||||
"note": "Note",
|
||||
"notifications": "Notifications",
|
||||
"number": "Number",
|
||||
@@ -366,7 +367,7 @@
|
||||
"please_upgrade_your_plan": "Please upgrade your plan",
|
||||
"powered_by_formbricks": "Powered by Formbricks",
|
||||
"preview": "Preview",
|
||||
"preview_survey": "Preview Survey",
|
||||
"preview_survey": "Preview survey",
|
||||
"privacy": "Privacy Policy",
|
||||
"product_manager": "Product Manager",
|
||||
"production": "Production",
|
||||
@@ -389,6 +390,7 @@
|
||||
"report_survey": "Report Survey",
|
||||
"request_trial_license": "Request trial license",
|
||||
"reset_to_default": "Reset to default",
|
||||
"resize": "Resize",
|
||||
"response": "Response",
|
||||
"response_id": "Response ID",
|
||||
"responses": "Responses",
|
||||
@@ -427,6 +429,7 @@
|
||||
"some_files_failed_to_upload": "Some files failed to upload",
|
||||
"something_went_wrong": "Something went wrong",
|
||||
"something_went_wrong_please_try_again": "Something went wrong. Please try again.",
|
||||
"soon": "Soon",
|
||||
"sort_by": "Sort by",
|
||||
"start_free_trial": "Start free trial",
|
||||
"status": "Status",
|
||||
@@ -1762,9 +1765,11 @@
|
||||
"please_select_dashboard": "Please select a dashboard",
|
||||
"predefined_measures": "Predefined Measures",
|
||||
"preset": "Preset",
|
||||
"preview_chart": "Preview chart",
|
||||
"query_executed_successfully": "Query executed successfully",
|
||||
"reset_to_ai_suggestion": "Reset to AI suggestion",
|
||||
"save_chart": "Save Chart",
|
||||
"save_and_add_to_dashboard": "Save & add to dashboard",
|
||||
"save_chart": "Save chart",
|
||||
"save_chart_dialog_title": "Save Chart",
|
||||
"select_data_source": "Select a data source",
|
||||
"select_data_source_first": "Please select a data source first",
|
||||
@@ -1776,7 +1781,8 @@
|
||||
"start_date": "Start date",
|
||||
"time_dimension": "Time Dimension",
|
||||
"time_dimension_title": "Add time-based grouping",
|
||||
"time_dimension_toggle_description": "Monitor trends over time."
|
||||
"time_dimension_toggle_description": "Monitor trends over time.",
|
||||
"update_chart": "Update chart"
|
||||
},
|
||||
"dashboards": {
|
||||
"add_count_charts": "Add {count} chart(s)",
|
||||
@@ -1807,7 +1813,9 @@
|
||||
"no_dashboards_found": "No dashboards found.",
|
||||
"no_data_message": "No Data. There is currently no information to display. Add charts to build your dashboard.",
|
||||
"please_enter_name": "Please enter a dashboard name"
|
||||
}
|
||||
},
|
||||
"no_feedback_records_message": "You don't have Feedback Records to report on. Setup Feedback Sources to feed data into the system.",
|
||||
"setup_feedback_source": "Setup feedback sources"
|
||||
},
|
||||
"api_keys": {
|
||||
"add_api_key": "Add API Key",
|
||||
@@ -1820,7 +1828,7 @@
|
||||
"api_key_updated": "API Key updated",
|
||||
"delete_api_key_confirmation": "Any applications using this key will no longer be able to access your Formbricks data.",
|
||||
"duplicate_access": "Duplicate workspace access not allowed",
|
||||
"no_api_keys_yet": "You do not have any API keys yet",
|
||||
"no_api_keys_yet": "No API keys found. Create an API key to get started.",
|
||||
"no_env_permissions_found": "No environment permissions found",
|
||||
"organization_access": "Organization Access",
|
||||
"organization_access_description": "Select read or write privileges for organization-wide resources.",
|
||||
@@ -2506,14 +2514,14 @@
|
||||
"archive_directory": "Archive Directory",
|
||||
"archive_not_allowed": "You are not allowed to archive this directory.",
|
||||
"are_you_sure_you_want_to_archive": "Are you sure you want to archive this directory? Workspaces will no longer have access to it.",
|
||||
"assign_workspaces_description": "Control which workspaces can access this feedback record directory.",
|
||||
"assign_workspaces_description": "Control which workspaces can access this directory. Each workspace can only access one directory.",
|
||||
"connectors_description": "Connectors that send feedback records to this directory.",
|
||||
"create_feedback_directory": "Create feedback directory",
|
||||
"description": "Manage feedback record directories and their workspace assignments.",
|
||||
"directory_archived_successfully": "Directory archived successfully",
|
||||
"directory_created_successfully": "Directory created successfully",
|
||||
"directory_id": "Directory ID",
|
||||
"directory_name": "Directory Name",
|
||||
"directory_name": "Directory name",
|
||||
"directory_settings_description": "Manage directory name, workspace assignments, and more.",
|
||||
"directory_settings_title": "{directoryName} Settings",
|
||||
"directory_unarchived_successfully": "Directory unarchived successfully",
|
||||
@@ -2523,13 +2531,19 @@
|
||||
"error_directory_name_duplicate": "A feedback record directory with this name already exists.",
|
||||
"error_directory_name_required": "Directory name is required.",
|
||||
"error_directory_workspaces_invalid_org": "Some specified workspaces do not belong to this organization.",
|
||||
"has_access": "Has access",
|
||||
"nav_label": "Feedback Directories",
|
||||
"no_access": "You do not have permission to manage feedback record directories.",
|
||||
"no_connectors": "No connectors linked to this directory yet.",
|
||||
"pause_connectors_confirmation_description": "{count, plural, one {1 connector will be paused because its workspace no longer has access to this directory. Continue?} other {{count} connectors will be paused because their workspaces no longer have access to this directory. Continue?}}",
|
||||
"pause_connectors_confirmation_title": "Pause affected connectors?",
|
||||
"select_workspaces_placeholder": "Select workspaces...",
|
||||
"show_archived": "Show archived",
|
||||
"title": "Feedback Record Directories",
|
||||
"unarchive": "Unarchive"
|
||||
"unarchive": "Unarchive",
|
||||
"unarchive_workspace_conflict": "Cannot unarchive this directory because one or more workspaces are already assigned to another Feedback Directory.",
|
||||
"workspace_access": "Workspace access",
|
||||
"workspace_already_assigned_to_directory": "Already assigned to \"{directoryName}\""
|
||||
},
|
||||
"general": {
|
||||
"ai_data_analysis_disabled_for_organization": "AI data analysis is disabled for this organization.",
|
||||
@@ -3551,7 +3565,7 @@
|
||||
"add_tag": "Add Tag",
|
||||
"count": "Count",
|
||||
"delete_tag_confirmation": "Are you sure you want to delete this tag?",
|
||||
"manage_tags": "Manage Tags",
|
||||
"manage_tags": "Manage tags",
|
||||
"manage_tags_description": "Merge and remove response tags.",
|
||||
"merge": "Merge",
|
||||
"no_tag_found": "No tag found",
|
||||
@@ -3570,15 +3584,21 @@
|
||||
"team_settings_description": "See which teams can access this workspace."
|
||||
},
|
||||
"unify": {
|
||||
"add_feedback_record": "Add feedback record",
|
||||
"add_feedback_record_description": "Create a feedback record manually.",
|
||||
"add_feedback_source": "Add Feedback Source",
|
||||
"add_source": "Add source",
|
||||
"allowed_values": "Allowed values: {values}",
|
||||
"api_ingestion": "API ingestion",
|
||||
"api_ingestion_manage_api_keys": "Manage API keys",
|
||||
"api_ingestion_settings_description": "Send feedback records directly to Formbricks via HTTP.",
|
||||
"auto_generated": "Auto-generated",
|
||||
"change_file": "Change file",
|
||||
"click_load_sample_csv": "Click 'Load sample CSV' to see columns",
|
||||
"click_to_upload": "Click to upload",
|
||||
"collected_at": "Collected At",
|
||||
"configure_import": "Configure import",
|
||||
"configure_mapping": "Configure Mapping",
|
||||
"configure_mapping": "Configure mapping",
|
||||
"connection": "Connection",
|
||||
"connector_created_successfully": "Connector created successfully",
|
||||
"connector_deleted_successfully": "Connector deleted successfully",
|
||||
@@ -3593,14 +3613,18 @@
|
||||
"csv_empty_column_headers": "CSV contains empty column headers. All columns must have a name.",
|
||||
"csv_file_too_large": "CSV file is too large. Maximum size is 2MB.",
|
||||
"csv_files_only": "CSV files only",
|
||||
"csv_import": "CSV Import",
|
||||
"csv_import": "CSV import",
|
||||
"csv_import_complete": "CSV import complete: {successes} succeeded, {failures} failed, {skipped} skipped",
|
||||
"csv_import_duplicate_warning": "Importing data twice will create duplicate records.",
|
||||
"csv_inconsistent_columns": "Row {row} has inconsistent columns. All rows must have the same headers.",
|
||||
"csv_max_records": "Maximum {max} records allowed.",
|
||||
"custom_source_type": "Custom source type",
|
||||
"custom_source_type_placeholder": "Enter custom source type",
|
||||
"default_connector_name_csv": "CSV Import",
|
||||
"default_connector_name_formbricks": "Formbricks Survey Connection",
|
||||
"deselect_all": "Deselect all",
|
||||
"discard_feedback_record_changes_description": "Your changes will be lost if you close this drawer.",
|
||||
"discard_feedback_record_changes_title": "Discard unsaved changes?",
|
||||
"drop_a_field_here": "Drop a field here",
|
||||
"drop_field_or": "Drop field or",
|
||||
"edit_csv_mapping": "Edit CSV mapping",
|
||||
@@ -3610,24 +3634,46 @@
|
||||
"enum": "enum",
|
||||
"failed_to_load_feedback_records": "Failed to load feedback records",
|
||||
"feedback_date": "Current date",
|
||||
"feedback_record_created_successfully": "Feedback record created successfully",
|
||||
"feedback_record_details": "Feedback record details",
|
||||
"feedback_record_details_description": "Review and update feedback record fields.",
|
||||
"feedback_record_directory": "Feedback Record Directory",
|
||||
"feedback_record_fields": "Feedback Record Fields",
|
||||
"feedback_record_mcp": "Feedback Record MCP",
|
||||
"feedback_record_updated_successfully": "Feedback record updated successfully",
|
||||
"feedback_record_value_required": "A value is required for the selected field type",
|
||||
"feedback_records": "Feedback Records",
|
||||
"feedback_records_refreshed": "Feedback records refreshed",
|
||||
"feedback_sources": "Feedback Sources",
|
||||
"feedback_sources_directory_access_multiple": "This workspace has access to the {directoryNames} feedback directories.",
|
||||
"feedback_sources_directory_access_single": "This workspace has access to the {directoryNames} feedback directory.",
|
||||
"feedback_sources_settings_description": "Connect and manage the sources that feed your feedback records.",
|
||||
"field_group_id": "Field Group ID",
|
||||
"field_group_label": "Field Group Label",
|
||||
"field_id": "Field ID",
|
||||
"field_label": "Field Label",
|
||||
"field_type": "Field Type",
|
||||
"formbricks_surveys": "Formbricks Surveys",
|
||||
"frd_cannot_be_changed": "Feedback directory cannot be changed after creation.",
|
||||
"formbricks_surveys": "Formbricks survey",
|
||||
"go_to_feedback_record_directories": "Go to directories settings",
|
||||
"historical_import_complete": "Import complete: {successes} succeeded, {failures} failed, {skipped} skipped (no data)",
|
||||
"import_csv_data": "Import feedback",
|
||||
"import_feedback": "Import feedback",
|
||||
"import_historical_responses": "Import historical responses",
|
||||
"import_historical_responses_description": "Creates one feedback record for each answer to each question.",
|
||||
"import_rows": "Import {count} rows",
|
||||
"import_via_source_name": "Import via \"{sourceName}\"",
|
||||
"importing_data": "Importing data...",
|
||||
"importing_historical_data": "Importing historical data...",
|
||||
"invalid_enum_values": "Invalid values in column mapped to {field}",
|
||||
"invalid_values_found": "Found: {values} (rows: {rows}) {extra}",
|
||||
"load_sample_csv": "Load sample CSV",
|
||||
"manage_directories": "Manage directories",
|
||||
"manage_feedback_sources": "Manage feedback sources",
|
||||
"metadata": "Metadata",
|
||||
"metadata_key": "Metadata key",
|
||||
"metadata_read_only_entries": "Read-only metadata values (non-string)",
|
||||
"metadata_value": "Metadata value",
|
||||
"missing_feedback_source_title": "Missing a feedback source?",
|
||||
"n_supported_questions": "{count} supported questions",
|
||||
"no_feedback_record_directory_available": "No feedback record directory assigned to this workspace. Create or assign one first.",
|
||||
"no_feedback_records": "No feedback records yet. Records will appear here once your connectors start sending data.",
|
||||
@@ -3639,15 +3685,16 @@
|
||||
"question_selected": "<strong>{count}</strong> question selected. Each response to these questions will create a new Feedback Record.",
|
||||
"question_type_not_supported": "This question type is not supported",
|
||||
"questions_selected": "<strong>{count}</strong> questions selected. Each response to these questions will create a new Feedback Record.",
|
||||
"records_will_go_to": "Records will go to",
|
||||
"refresh_feedback_records": "Refresh feedback records",
|
||||
"refreshing_feedback_records": "Refreshing feedback records...",
|
||||
"request_feedback_source": "Request it and we will build it!",
|
||||
"required": "Required",
|
||||
"save_changes": "Save changes",
|
||||
"select_a_survey_to_see_questions": "Select a survey to see its questions",
|
||||
"select_a_value": "Select a value...",
|
||||
"select_all": "Select all",
|
||||
"select_feedback_record_directory": "Select a directory",
|
||||
"select_feedback_record_source_type": "Select source type",
|
||||
"select_questions": "Select questions",
|
||||
"select_source_type_description": "Select the type of feedback source you want to connect.",
|
||||
"select_source_type_prompt": "Select the type of feedback source you want to connect:",
|
||||
@@ -3656,12 +3703,14 @@
|
||||
"select_survey_questions_description": "Choose which survey questions should create FeedbackRecords.",
|
||||
"set_value": "set value",
|
||||
"setup_connection": "Setup connection",
|
||||
"showing_count_loaded": "Showing {count} records",
|
||||
"showing_count_loaded": "Showing {count} records from Feedback Directory {directoryName}",
|
||||
"showing_rows": "Showing 3 of {count} rows",
|
||||
"source": "source",
|
||||
"source_connect_csv_description": "Import feedback from CSV files",
|
||||
"source_connect_formbricks_description": "Connect feedback from your Formbricks surveys",
|
||||
"source_connect_feedback_record_mcp_description": "Connect feedback records via the Formbricks MCP.",
|
||||
"source_connect_formbricks_description": "Connect feedback from your Formbricks survey",
|
||||
"source_fields": "Source Fields",
|
||||
"source_id": "Source ID",
|
||||
"source_name": "Source Name",
|
||||
"source_type": "Source Type",
|
||||
"source_type_cannot_be_changed": "Source type cannot be changed",
|
||||
@@ -3670,9 +3719,13 @@
|
||||
"status_completed": "Completed",
|
||||
"status_draft": "Draft",
|
||||
"status_error": "Error",
|
||||
"status_live_sync": "Live Sync",
|
||||
"status_paused": "Paused",
|
||||
"status_ready": "Ready",
|
||||
"submission_id": "Submission ID",
|
||||
"survey_has_no_questions": "This survey has no questions",
|
||||
"survey_import_line": "{surveyName}: {responseCount} responses × {questionCount} questions = {total} Feedback Records",
|
||||
"topics_and_subtopics": "Topics & Subtopics",
|
||||
"total_feedback_records": "Total: {checked} of {total} Feedback Records selected across {surveyCount} surveys",
|
||||
"unify_feedback": "Unify Feedback",
|
||||
"update_mapping_description": "Update the mapping configuration for this source.",
|
||||
@@ -3680,7 +3733,11 @@
|
||||
"upload_csv_data_description": "Upload a CSV file to import feedback data.",
|
||||
"upload_csv_file": "Upload CSV File",
|
||||
"user_identifier": "User",
|
||||
"value": "Value"
|
||||
"value": "Value",
|
||||
"value_boolean": "Value (Boolean)",
|
||||
"value_date": "Value (Date)",
|
||||
"value_number": "Value (Number)",
|
||||
"value_text": "Value (Text)"
|
||||
},
|
||||
"xm-templates": {
|
||||
"ces": "CES",
|
||||
|
||||
@@ -160,65 +160,8 @@ export const EditAPIKeys = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="grid h-12 grid-cols-10 content-center rounded-t-lg bg-slate-100 px-6 text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-4 sm:col-span-2">{t("common.label")}</div>
|
||||
<div className="col-span-4 hidden sm:col-span-5 sm:block">{t("workspace.api_keys.api_key")}</div>
|
||||
<div className="col-span-4 sm:col-span-2">{t("common.created_at")}</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="grid-cols-9">
|
||||
{apiKeysLocal?.length === 0 ? (
|
||||
<div className="flex h-12 items-center justify-center whitespace-nowrap px-6 text-sm font-medium text-slate-400">
|
||||
{t("workspace.api_keys.no_api_keys_yet")}
|
||||
</div>
|
||||
) : (
|
||||
apiKeysLocal?.map((apiKey) => (
|
||||
<div
|
||||
role="button"
|
||||
className="grid h-12 w-full grid-cols-10 content-center items-center rounded-lg px-6 text-left text-sm text-slate-900 hover:bg-slate-50 focus:bg-slate-50 focus:outline-none"
|
||||
onClick={() => {
|
||||
setActiveKey(apiKey);
|
||||
setViewPermissionsOpen(true);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setActiveKey(apiKey);
|
||||
setViewPermissionsOpen(true);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
data-testid="api-key-row"
|
||||
key={apiKey.id}>
|
||||
<div className="col-span-4 font-semibold sm:col-span-2">{apiKey.label}</div>
|
||||
<div className="col-span-4 hidden pr-4 sm:col-span-5 sm:block">
|
||||
<ApiKeyDisplay apiKey={apiKey.actualKey ?? ""} />
|
||||
</div>
|
||||
<div className="col-span-4 sm:col-span-2">
|
||||
{timeSince(apiKey.createdAt.toString(), locale)}
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="col-span-1 text-center">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
handleOpenDeleteKeyModal(e, apiKey);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isReadOnly && (
|
||||
<div>
|
||||
<div className="absolute right-4 top-4">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
@@ -228,6 +171,65 @@ export const EditAPIKeys = ({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<div className="grid h-12 grid-cols-10 content-center border-b border-slate-200 px-6 text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-4 sm:col-span-2">{t("common.label")}</div>
|
||||
<div className="col-span-4 hidden sm:col-span-5 sm:block">{t("workspace.api_keys.api_key")}</div>
|
||||
<div className="col-span-4 sm:col-span-2">{t("common.created_at")}</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div>
|
||||
{apiKeysLocal?.length === 0 ? (
|
||||
<div className="flex h-12 items-center justify-center whitespace-nowrap px-6 text-sm text-slate-400">
|
||||
{t("workspace.api_keys.no_api_keys_yet")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-slate-100">
|
||||
{apiKeysLocal?.map((apiKey) => (
|
||||
<div
|
||||
role="button"
|
||||
className="grid h-12 w-full grid-cols-10 content-center items-center px-6 text-left text-sm text-slate-900 transition-colors hover:bg-slate-50 focus:bg-slate-50 focus:outline-none"
|
||||
onClick={() => {
|
||||
setActiveKey(apiKey);
|
||||
setViewPermissionsOpen(true);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setActiveKey(apiKey);
|
||||
setViewPermissionsOpen(true);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
data-testid="api-key-row"
|
||||
key={apiKey.id}>
|
||||
<div className="col-span-4 font-semibold sm:col-span-2">{apiKey.label}</div>
|
||||
<div className="col-span-4 hidden pr-4 sm:col-span-5 sm:block">
|
||||
<ApiKeyDisplay apiKey={apiKey.actualKey ?? ""} />
|
||||
</div>
|
||||
<div className="col-span-4 sm:col-span-2">
|
||||
{timeSince(apiKey.createdAt.toString(), locale)}
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="col-span-1 text-center">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
handleOpenDeleteKeyModal(e, apiKey);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<AddApiKeyModal
|
||||
open={isAddAPIKeyModalOpen}
|
||||
setOpen={setIsAddAPIKeyModalOpen}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Question Bank - Linear Ticket Package (3 Scopes)
|
||||
|
||||
## Problem statement
|
||||
Teams currently recreate the same survey questions across workspaces, which slows authors down, creates inconsistent wording, and makes reporting harder to standardize. We need a shared question bank at the organization level so authenticated creators can reuse high-quality questions quickly without breaking existing surveys.
|
||||
|
||||
## Success metrics
|
||||
- Time to add a reusable question into a survey decreases by at least 50%.
|
||||
- At least 40% of newly created surveys use one or more question bank entries within 60 days.
|
||||
- Duplicate-question creation rate declines over time (measured by title/similarity heuristics).
|
||||
- Permission failures are explicit and non-destructive (0 ambiguous auth errors in tracked flows).
|
||||
|
||||
## Locked auth model for all scopes
|
||||
- Sharing boundary: all workspaces in the same organization.
|
||||
- Publish eligibility: authenticated users with `readWrite` or `manage` in at least one workspace.
|
||||
- Edit/delete: creator plus org `Owner/Manager`.
|
||||
- Unpublish: org `Owner/Manager`.
|
||||
- API keys: keep existing explicit workspace/org scopes (no owner-role inheritance change).
|
||||
- Deprovisioned creators: ownership transfers to org admin group.
|
||||
- Audit baseline: `createdBy`, `updatedBy`, timestamps.
|
||||
|
||||
---
|
||||
|
||||
## Ticket 1 - Scope 1 Core MVP
|
||||
|
||||
### Goal
|
||||
Launch a usable organization-level question bank that supports the full core lifecycle and detached insertion into surveys.
|
||||
|
||||
### In scope
|
||||
- Browse and insert global questions from survey creation/editing flow.
|
||||
- Create and publish global questions with locked auth model.
|
||||
- Edit/delete with creator + org admin override model.
|
||||
- Unpublish with org admin permissions.
|
||||
- Insert behavior is copy-detached (future question bank edits do not affect already inserted survey questions).
|
||||
- Basic metadata in UI: title, category, creator, updated time.
|
||||
- Empty state, unauthorized state, and no-results state messaging.
|
||||
|
||||
### Explicitly out of scope
|
||||
- Approval workflows.
|
||||
- Certified/locked question sets.
|
||||
- Sensitive-category permission differences.
|
||||
- Version history, diff, restore.
|
||||
- Live-linked question sync into existing surveys.
|
||||
|
||||
### Acceptance criteria
|
||||
- A user with `readWrite/manage` in at least one workspace can create and publish.
|
||||
- A user without required workspace permission cannot publish and gets clear guidance.
|
||||
- Creator can edit/delete own global questions.
|
||||
- Org `Owner/Manager` can edit/delete/unpublish any global question.
|
||||
- Inserting a question creates an independent survey copy that does not change on later bank edits.
|
||||
- Unpublishing hides the question from new insertions but does not alter surveys that already copied it.
|
||||
- All successful mutations capture attribution metadata (`createdBy`/`updatedBy` and timestamps).
|
||||
|
||||
### Scope 1 edge-case handling
|
||||
- Creator loses workspace write access after publishing: question remains available; creator can no longer mutate unless still authorized by current rules.
|
||||
- Creator is removed/deactivated: ownership is transferred to org admin group without content loss.
|
||||
- Admin edits creator-owned content: latest updater attribution is visible.
|
||||
- Cross-workspace discoverability remains consistent inside the same org.
|
||||
|
||||
---
|
||||
|
||||
## Ticket 2 - Scope 2 Adoption and Operational Quality
|
||||
|
||||
### Goal
|
||||
Increase discoverability and operational reliability so the bank is easy to use at scale for both UI users and scoped API clients.
|
||||
|
||||
### In scope
|
||||
- Search, category filtering, and sorting.
|
||||
- API-key read/write support under current explicit workspace/org scope model.
|
||||
- Ownership transfer flow for deprovisioned creators (admin stewardship).
|
||||
- Basic moderation operations for org admins focused on unpublish/recoverability.
|
||||
- Audit visibility in product UX (created by, last updated by, published status timestamps).
|
||||
|
||||
### Acceptance criteria
|
||||
- Users can find bank questions by keyword, category, and sort order with predictable results.
|
||||
- API key mutations respect explicit scope boundaries; out-of-scope writes are rejected clearly.
|
||||
- Admins can view and complete ownership transfer for deprovisioned creators.
|
||||
- Admin can recover previously unpublished items using a clear operational flow.
|
||||
- Audit fields are visible and understandable in bank listing/detail surfaces.
|
||||
|
||||
### Scope 2 edge-case handling
|
||||
- API key with partial workspace grants attempts org-wide mutation: request is denied with explicit reason.
|
||||
- Large volume of near-duplicate questions: list remains navigable via filters and sort defaults.
|
||||
- Simultaneous edits by creator and admin: user-facing result is deterministic and auditable.
|
||||
|
||||
---
|
||||
|
||||
## Ticket 3 - Scope 3 Governance and Enterprise Depth (Optional/Future)
|
||||
|
||||
### Goal
|
||||
Introduce governance controls for organizations that need stronger quality gates and policy enforcement.
|
||||
|
||||
### In scope (optional controls)
|
||||
- Approval gate before global publication.
|
||||
- Curator workflow for elevated stewardship.
|
||||
- Certified/locked question sets (or semi-locked variants).
|
||||
- Sensitive-category restrictions (if compliance needs require).
|
||||
- Extended versioning: history, fork, restore.
|
||||
- Org policy controls for publishing standards and lifecycle rules.
|
||||
|
||||
### Acceptance criteria
|
||||
- Governance controls are configurable per organization and can be rolled out progressively.
|
||||
- Approval workflow supports clear pending/approved/rejected states.
|
||||
- Certified or locked items are visibly distinct and enforce expected edit restrictions.
|
||||
- Version history supports safe recovery without impacting existing detached survey copies.
|
||||
- Policy changes are auditable and reversible.
|
||||
|
||||
### Scope 3 edge-case handling
|
||||
- Approval backlog delays publication: pending state remains visible and actionable.
|
||||
- Policy changes after content is already published: no retroactive corruption of survey copies.
|
||||
- Governance disabled for some orgs: core flows continue without governance regressions.
|
||||
|
||||
---
|
||||
|
||||
## Prioritization rationale (Qualtrics-informed)
|
||||
- Start with the minimum reusable-library primitives users expect: browse, search, insert, categorize.
|
||||
- Keep insertion detached by default to prevent retroactive survey changes.
|
||||
- Defer certified and strict governance controls until adoption data justifies added complexity.
|
||||
|
||||
Reference: [Qualtrics pre-made library questions](https://www.qualtrics.com/support/survey-platform/survey-module/editing-questions/question-types-guide/pre-made-qualtrics-library-questions/)
|
||||
Reference in New Issue
Block a user