Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(graphQL): support query commit history #3849

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 136 additions & 0 deletions crates/tabby-common/src/api/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use async_trait::async_trait;
use chrono::{DateTime, TimeZone, Utc};
use tantivy::{
schema::{self, document::CompactDocValue, Value},
DateTime as TantivyDateTime, TantivyDocument,
};

use super::Result;
use crate::index::{commit::fields, IndexSchema};

#[async_trait]
pub trait CommitHistorySearch: Send + Sync {
/// Search git commit history from underlying index.
///
/// * `source_id`: Filter documents by source ID.
async fn search(
&self,
source_id: &str,
q: &str,
limit: usize,
) -> Result<CommitHistorySearchResponse>;
}

pub struct CommitHistorySearchResponse {
pub hits: Vec<CommitHistorySearchHit>,
}

#[derive(Clone, Debug)]
pub struct CommitHistorySearchHit {
pub score: f32,
pub commit: CommitHistoryDocument,
}

#[derive(Debug, Clone)]
pub struct CommitHistoryDocument {
pub git_url: String,
pub sha: String,
pub message: String,
pub author_email: String,
pub author_at: DateTime<Utc>,
pub committer: String,
pub commit_at: DateTime<Utc>,

pub diff: Option<String>,
pub changed_file: Option<String>,
}

impl CommitHistoryDocument {
pub fn from_tantivy_document(doc: &TantivyDocument, chunk: &TantivyDocument) -> Option<Self> {
let schema = IndexSchema::instance();
let git_url =
get_json_text_field(doc, schema.field_attributes, fields::GIT_URL).to_string();
let sha = get_json_text_field(doc, schema.field_attributes, fields::SHA).to_string();
let message =
get_json_text_field(doc, schema.field_attributes, fields::MESSAGE).to_string();
let author_email =
get_json_text_field(doc, schema.field_attributes, fields::AUTHOR_EMAIL).to_string();
let author_at = get_json_date_field(doc, schema.field_attributes, fields::AUTHOR_AT)
.unwrap()
.into_timestamp_secs();
let committer =
get_json_text_field(doc, schema.field_attributes, fields::COMMITTER).to_string();
let commit_at = get_json_date_field(doc, schema.field_attributes, fields::COMMIT_AT)
.unwrap()
.into_timestamp_secs();
let diff =
get_json_option_text_field(chunk, schema.field_chunk_attributes, fields::CHUNK_DIFF)
.map(|s| s.to_string());
let changed_file = get_json_option_text_field(
chunk,
schema.field_chunk_attributes,
fields::CHUNK_FILEPATH,
)
.map(|s| s.to_string());

Some(Self {
git_url,
sha,
message,
author_email,
author_at: Utc.timestamp_opt(author_at, 0).single().unwrap_or_default(),
committer,
commit_at: Utc.timestamp_opt(commit_at, 0).single().unwrap_or_default(),

diff,
changed_file,
})
}

Check warning on line 88 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L49-L88

Added lines #L49 - L88 were not covered by tests
}

fn get_json_field<'a>(
doc: &'a TantivyDocument,
field: schema::Field,
name: &str,
) -> CompactDocValue<'a> {
doc.get_first(field)
.unwrap()
.as_object()
.unwrap()
.find(|(k, _)| *k == name)
.unwrap()
.1
}

Check warning on line 103 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L91-L103

Added lines #L91 - L103 were not covered by tests

fn get_json_text_field<'a>(doc: &'a TantivyDocument, field: schema::Field, name: &str) -> &'a str {
get_json_field(doc, field, name).as_str().unwrap()
}

Check warning on line 107 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L105-L107

Added lines #L105 - L107 were not covered by tests

fn get_json_option_field<'a>(
doc: &'a TantivyDocument,
field: schema::Field,
name: &str,
) -> Option<CompactDocValue<'a>> {
Some(
doc.get_first(field)?
.as_object()?
.find(|(k, _)| *k == name)?

Check warning on line 117 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L109-L117

Added lines #L109 - L117 were not covered by tests
.1,
)
}

Check warning on line 120 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L120

Added line #L120 was not covered by tests

fn get_json_date_field(
doc: &TantivyDocument,
field: schema::Field,
name: &str,
) -> Option<TantivyDateTime> {
get_json_option_field(doc, field, name).and_then(|field| field.as_datetime())
}

Check warning on line 128 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L122-L128

Added lines #L122 - L128 were not covered by tests

fn get_json_option_text_field<'a>(
doc: &'a TantivyDocument,
field: schema::Field,
name: &str,
) -> Option<&'a str> {
get_json_option_field(doc, field, name).and_then(|field| field.as_str())
}

Check warning on line 136 in crates/tabby-common/src/api/commit.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/commit.rs#L130-L136

Added lines #L130 - L136 were not covered by tests
20 changes: 20 additions & 0 deletions crates/tabby-common/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
pub mod code;
pub mod commit;
pub mod event;
pub mod server_setting;
pub mod structured_doc;

use thiserror::Error;

pub type Result<T> = std::result::Result<T, SearchError>;

#[derive(Error, Debug)]

Check warning on line 11 in crates/tabby-common/src/api/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/api/mod.rs#L11

Added line #L11 was not covered by tests
pub enum SearchError {
#[error("index not ready")]
NotReady,

#[error(transparent)]
QueryParserError(#[from] tantivy::query::QueryParserError),

#[error(transparent)]
TantivyError(#[from] tantivy::TantivyError),

#[error(transparent)]
Other(#[from] anyhow::Error),
}
14 changes: 14 additions & 0 deletions crates/tabby-common/src/index/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub mod fields {
// === Doc level fields ===
pub const GIT_URL: &str = "git_url";
pub const SHA: &str = "sha";
pub const MESSAGE: &str = "message";
pub const AUTHOR_EMAIL: &str = "author_email";
pub const AUTHOR_AT: &str = "author_at";
pub const COMMITTER: &str = "committer";
pub const COMMIT_AT: &str = "commit_at";

// === Chunk level fields ===
pub const CHUNK_FILEPATH: &str = "chunk_filepath";
pub const CHUNK_DIFF: &str = "chunk_diff";
}
23 changes: 23 additions & 0 deletions crates/tabby-common/src/index/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod code;
pub mod commit;
pub mod structured_doc;

use std::borrow::Cow;
Expand Down Expand Up @@ -82,6 +83,7 @@
pub mod corpus {
pub const CODE: &str = "code";
pub const STRUCTURED_DOC: &str = "structured_doc";
pub const COMMIT_HISTORY: &str = "commit_history";
}

impl IndexSchema {
Expand Down Expand Up @@ -278,6 +280,27 @@
])
}

/// Build a query the documents have specific attribute field and value.
pub fn doc_with_attribute_field(&self, corpus: &str, field: &str, value: &str) -> impl Query {
let mut term = Term::from_field_json_path(self.field_attributes, field, false);
term.append_type_and_str(value);

BooleanQuery::new(vec![
// Must match the corpus
(Occur::Must, self.corpus_query(corpus)),
// The attributes.field must have the value
(
Occur::Must,
Box::new(TermQuery::new(term, IndexRecordOption::Basic)),
),
// Exclude chunk documents
(
Occur::MustNot,
Box::new(ExistsQuery::new_exists_query(FIELD_CHUNK_ID.into())),
),
])
}

Check warning on line 302 in crates/tabby-common/src/index/mod.rs

View check run for this annotation

Codecov / codecov/patch

crates/tabby-common/src/index/mod.rs#L284-L302

Added lines #L284 - L302 were not covered by tests

pub fn corpus_query(&self, corpus: &str) -> Box<dyn Query> {
Box::new(TermQuery::new(
Term::from_field_text(self.field_corpus, corpus),
Expand Down
1 change: 1 addition & 0 deletions crates/tabby-git/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tokio.workspace = true
tracing.workspace = true
ignore.workspace = true
grep = "0.3.1"
chrono.workspace = true

[dev-dependencies]
assert_matches.workspace = true
Loading
Loading