feat: migrations for document auto-sync [AI-141] (#14041)
# Pull Request Template ## Description Add migrations for document auto-sync Fixes # (issue) ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? locally ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
class AddEditedToCaptainAssistantResponses < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :captain_assistant_responses, :edited, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
class AddSyncColumnsToCaptainDocuments < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
change_table :captain_documents, bulk: true do |t|
|
||||||
|
t.integer :sync_status
|
||||||
|
t.datetime :last_synced_at
|
||||||
|
t.datetime :last_sync_attempted_at
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :captain_documents, [:account_id, :sync_status]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class BackfillEditedOnCaptainAssistantResponses < ActiveRecord::Migration[7.0]
|
||||||
|
def up
|
||||||
|
return unless ChatwootApp.enterprise?
|
||||||
|
|
||||||
|
# rubocop:disable Rails/SkipsModelValidations
|
||||||
|
# NOTE: Since there is no way of knowing currently which FAQs were edited by a human
|
||||||
|
# we use a heuristic based on time passed between created_at and updated_at.
|
||||||
|
# 15 days is arbitrary but seems reasonable for a user to go back and edit an FAQ.
|
||||||
|
Captain::AssistantResponse
|
||||||
|
.where('updated_at - created_at > make_interval(days := ?)', 15)
|
||||||
|
.in_batches(of: 1000) do |batch|
|
||||||
|
batch.update_all(edited: true)
|
||||||
|
end
|
||||||
|
# rubocop:enable Rails/SkipsModelValidations
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# no-op: rolling back migration of edited column will drop the edited column entirely
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2026_04_09_091202) do
|
ActiveRecord::Schema[7.1].define(version: 2026_04_10_092753) do
|
||||||
# These extensions should be enabled to support this database
|
# These extensions should be enabled to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
@@ -329,6 +329,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_04_09_091202) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "status", default: 1, null: false
|
t.integer "status", default: 1, null: false
|
||||||
t.string "documentable_type"
|
t.string "documentable_type"
|
||||||
|
t.boolean "edited", default: false, null: false
|
||||||
t.index ["account_id"], name: "index_captain_assistant_responses_on_account_id"
|
t.index ["account_id"], name: "index_captain_assistant_responses_on_account_id"
|
||||||
t.index ["assistant_id"], name: "index_captain_assistant_responses_on_assistant_id"
|
t.index ["assistant_id"], name: "index_captain_assistant_responses_on_assistant_id"
|
||||||
t.index ["documentable_id", "documentable_type"], name: "idx_cap_asst_resp_on_documentable"
|
t.index ["documentable_id", "documentable_type"], name: "idx_cap_asst_resp_on_documentable"
|
||||||
@@ -377,10 +378,14 @@ ActiveRecord::Schema[7.1].define(version: 2026_04_09_091202) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "status", default: 0, null: false
|
t.integer "status", default: 0, null: false
|
||||||
t.jsonb "metadata", default: {}
|
t.jsonb "metadata", default: {}
|
||||||
|
t.integer "sync_status"
|
||||||
|
t.datetime "last_synced_at"
|
||||||
|
t.datetime "last_sync_attempted_at"
|
||||||
t.index ["account_id"], name: "index_captain_documents_on_account_id"
|
t.index ["account_id"], name: "index_captain_documents_on_account_id"
|
||||||
t.index ["assistant_id", "external_link"], name: "index_captain_documents_on_assistant_id_and_external_link", unique: true
|
t.index ["assistant_id", "external_link"], name: "index_captain_documents_on_assistant_id_and_external_link", unique: true
|
||||||
t.index ["assistant_id"], name: "index_captain_documents_on_assistant_id"
|
t.index ["assistant_id"], name: "index_captain_documents_on_assistant_id"
|
||||||
t.index ["status"], name: "index_captain_documents_on_status"
|
t.index ["status"], name: "index_captain_documents_on_status"
|
||||||
|
t.index ["account_id", "sync_status"], name: "index_captain_documents_on_account_id_and_sync_status"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "captain_inboxes", force: :cascade do |t|
|
create_table "captain_inboxes", force: :cascade do |t|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# answer :text not null
|
# answer :text not null
|
||||||
# documentable_type :string
|
# documentable_type :string
|
||||||
|
# edited :boolean default(FALSE), not null
|
||||||
# embedding :vector(1536)
|
# embedding :vector(1536)
|
||||||
# question :string not null
|
# question :string not null
|
||||||
# status :integer default("approved"), not null
|
# status :integer default("approved"), not null
|
||||||
@@ -35,6 +36,7 @@ class Captain::AssistantResponse < ApplicationRecord
|
|||||||
|
|
||||||
before_validation :ensure_account
|
before_validation :ensure_account
|
||||||
before_validation :ensure_status
|
before_validation :ensure_status
|
||||||
|
before_validation :mark_as_edited, on: :update
|
||||||
after_commit :update_response_embedding
|
after_commit :update_response_embedding
|
||||||
|
|
||||||
scope :ordered, -> { order(created_at: :desc) }
|
scope :ordered, -> { order(created_at: :desc) }
|
||||||
@@ -55,6 +57,10 @@ class Captain::AssistantResponse < ApplicationRecord
|
|||||||
self.status ||= :approved
|
self.status ||= :approved
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mark_as_edited
|
||||||
|
self.edited = true if question_changed? || answer_changed?
|
||||||
|
end
|
||||||
|
|
||||||
def ensure_account
|
def ensure_account
|
||||||
self.account = assistant&.account
|
self.account = assistant&.account
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,20 +2,24 @@
|
|||||||
#
|
#
|
||||||
# Table name: captain_documents
|
# Table name: captain_documents
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# content :text
|
# content :text
|
||||||
# external_link :string not null
|
# external_link :string not null
|
||||||
# metadata :jsonb
|
# last_sync_attempted_at :datetime
|
||||||
# name :string
|
# last_synced_at :datetime
|
||||||
# status :integer default("in_progress"), not null
|
# metadata :jsonb
|
||||||
# created_at :datetime not null
|
# name :string
|
||||||
# updated_at :datetime not null
|
# status :integer default("in_progress"), not null
|
||||||
# account_id :bigint not null
|
# sync_status :integer
|
||||||
# assistant_id :bigint not null
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# assistant_id :bigint not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_captain_documents_on_account_id (account_id)
|
# index_captain_documents_on_account_id (account_id)
|
||||||
|
# index_captain_documents_on_account_id_and_sync_status (account_id,sync_status)
|
||||||
# index_captain_documents_on_assistant_id (assistant_id)
|
# index_captain_documents_on_assistant_id (assistant_id)
|
||||||
# index_captain_documents_on_assistant_id_and_external_link (assistant_id,external_link) UNIQUE
|
# index_captain_documents_on_assistant_id_and_external_link (assistant_id,external_link) UNIQUE
|
||||||
# index_captain_documents_on_status (status)
|
# index_captain_documents_on_status (status)
|
||||||
@@ -44,6 +48,8 @@ class Captain::Document < ApplicationRecord
|
|||||||
available: 1
|
available: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum :sync_status, { syncing: 0, synced: 1, failed: 2 }, prefix: :sync
|
||||||
|
|
||||||
before_create :ensure_within_plan_limit
|
before_create :ensure_within_plan_limit
|
||||||
after_create_commit :enqueue_crawl_job
|
after_create_commit :enqueue_crawl_job
|
||||||
after_create_commit :update_document_usage
|
after_create_commit :update_document_usage
|
||||||
@@ -68,6 +74,22 @@ class Captain::Document < ApplicationRecord
|
|||||||
pdf_file.blob.byte_size if pdf_file.attached?
|
pdf_file.blob.byte_size if pdf_file.attached?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def content_fingerprint
|
||||||
|
metadata&.dig('content_fingerprint')
|
||||||
|
end
|
||||||
|
|
||||||
|
def content_fingerprint=(value)
|
||||||
|
self.metadata = (metadata || {}).merge('content_fingerprint' => value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def last_sync_error_code
|
||||||
|
metadata&.dig('last_sync_error_code')
|
||||||
|
end
|
||||||
|
|
||||||
|
def last_sync_error_code=(value)
|
||||||
|
self.metadata = (metadata || {}).merge('last_sync_error_code' => value)
|
||||||
|
end
|
||||||
|
|
||||||
def openai_file_id
|
def openai_file_id
|
||||||
metadata&.dig('openai_file_id')
|
metadata&.dig('openai_file_id')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ json.id resource.id
|
|||||||
json.question resource.question
|
json.question resource.question
|
||||||
json.updated_at resource.updated_at.to_i
|
json.updated_at resource.updated_at.to_i
|
||||||
json.status resource.status
|
json.status resource.status
|
||||||
|
json.edited resource.edited
|
||||||
|
|||||||
@@ -11,4 +11,8 @@ json.file_size resource.file_size
|
|||||||
json.id resource.id
|
json.id resource.id
|
||||||
json.name resource.name
|
json.name resource.name
|
||||||
json.status resource.status
|
json.status resource.status
|
||||||
|
json.sync_status resource.sync_status
|
||||||
|
json.last_synced_at resource.last_synced_at&.to_i
|
||||||
|
json.last_sync_attempted_at resource.last_sync_attempted_at&.to_i
|
||||||
|
json.last_sync_error_code resource.last_sync_error_code
|
||||||
json.updated_at resource.updated_at.to_i
|
json.updated_at resource.updated_at.to_i
|
||||||
|
|||||||
Reference in New Issue
Block a user