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(webserver): support filter by users for userEvents api #1956

Merged
merged 3 commits into from
Apr 24, 2024
Merged
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
2 changes: 2 additions & 0 deletions ee/tabby-db/migrations/0025_user-events.down.sql
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
DROP INDEX idx_user_events_user_id;
DROP INDEX idx_user_events_created_at;
DROP TABLE user_events;
1 change: 1 addition & 0 deletions ee/tabby-db/migrations/0025_user-events.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ CREATE TABLE user_events (
payload BLOB NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_user_events_user_id ON user_events(user_id);
CREATE INDEX idx_user_events_created_at ON user_events(created_at);
Binary file modified ee/tabby-db/schema.sqlite
Binary file not shown.
1 change: 1 addition & 0 deletions ee/tabby-db/schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ CREATE TABLE user_events(
payload BLOB NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_user_events_user_id ON user_events(user_id);
CREATE INDEX idx_user_events_created_at ON user_events(created_at);
CREATE TABLE refresh_tokens(
id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand Down
10 changes: 8 additions & 2 deletions ee/tabby-db/src/user_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@ impl DbConn {
limit: Option<usize>,
skip_id: Option<i32>,
backwards: bool,
users: Vec<i64>,
start: DateTimeUtc,
end: DateTimeUtc,
) -> Result<Vec<UserEventDAO>> {
let users = users
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.join(",");
let no_selected_users = users.is_empty();
let condition = Some(format!(
"created_at >= '{}' AND created_at < '{}'",
start, end,
"created_at >= '{start}' AND created_at < '{end}' AND ({no_selected_users} OR user_id IN ({users}))"
));
let events = query_paged_as!(
UserEventDAO,
Expand Down
2 changes: 1 addition & 1 deletion ee/tabby-webserver/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ type Query {
jobs: [String!]!
dailyStatsInPastYear(users: [ID!]): [CompletionStats!]!
dailyStats(start: DateTimeUtc!, end: DateTimeUtc!, users: [ID!], languages: [Language!]): [CompletionStats!]!
userEvents(after: String, before: String, first: Int, last: Int, start: DateTimeUtc!, end: DateTimeUtc!): UserEventConnection!
userEvents(after: String, before: String, first: Int, last: Int, users: [ID!], start: DateTimeUtc!, end: DateTimeUtc!): UserEventConnection!
}

input NetworkSettingInput {
Expand Down
15 changes: 14 additions & 1 deletion ee/tabby-webserver/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,15 @@

async fn user_events(
ctx: &Context,

// pagination arguments
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,

// filter arguments
users: Option<Vec<ID>>,
start: DateTime<Utc>,
end: DateTime<Utc>,
) -> Result<Connection<UserEvent>> {
Expand All @@ -438,7 +443,15 @@
|after, before, first, last| async move {
ctx.locator
.user_event()
.list(after, before, first, last, start, end)
.list(
after,
before,
first,
last,
users.unwrap_or_default(),
start,
end,
)

Check warning on line 454 in ee/tabby-webserver/src/schema/mod.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/schema/mod.rs#L446-L454

Added lines #L446 - L454 were not covered by tests
.await
},
)
Expand Down
3 changes: 2 additions & 1 deletion ee/tabby-webserver/src/schema/user_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use super::Context;
use crate::{juniper::relay::NodeType, schema::Result};

#[derive(GraphQLEnum)]
#[derive(GraphQLEnum, Debug)]

Check warning on line 8 in ee/tabby-webserver/src/schema/user_event.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/schema/user_event.rs#L8

Added line #L8 was not covered by tests
pub enum EventKind {
Completion,
Select,
Expand Down Expand Up @@ -41,12 +41,13 @@

#[async_trait]
pub trait UserEventService: Send + Sync {
async fn list(

Check warning on line 44 in ee/tabby-webserver/src/schema/user_event.rs

View workflow job for this annotation

GitHub Actions / autofix

this function has too many arguments (8/7)
&self,
after: Option<String>,
before: Option<String>,
first: Option<usize>,
last: Option<usize>,
users: Vec<ID>,
start: DateTime<Utc>,
end: DateTime<Utc>,
) -> Result<Vec<UserEvent>>;
Expand Down
79 changes: 77 additions & 2 deletions ee/tabby-webserver/src/service/user_event.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use juniper::ID;
use tabby_db::DbConn;
use tracing::warn;

use super::graphql_pagination_to_filter;
use super::{graphql_pagination_to_filter, AsRowid};
use crate::schema::{
user_event::{UserEvent, UserEventService},
Result,
Expand All @@ -24,17 +26,90 @@
before: Option<String>,
first: Option<usize>,
last: Option<usize>,
users: Vec<ID>,
start: DateTime<Utc>,
end: DateTime<Utc>,
) -> Result<Vec<UserEvent>> {
let users = convert_ids(users);
let (limit, skip_id, backwards) = graphql_pagination_to_filter(after, before, first, last)?;
let events = self
.db
.list_user_events(limit, skip_id, backwards, start.into(), end.into())
.list_user_events(limit, skip_id, backwards, users, start.into(), end.into())
.await?;
Ok(events
.into_iter()
.map(UserEvent::try_from)
.collect::<Result<_, _>>()?)
}
}

fn convert_ids(ids: Vec<ID>) -> Vec<i64> {
ids.into_iter()
.filter_map(|id| match id.as_rowid() {
Ok(rowid) => Some(rowid),
Err(_) => {
warn!("Ignoring invalid ID: {}", id);
None

Check warning on line 52 in ee/tabby-webserver/src/service/user_event.rs

View check run for this annotation

Codecov / codecov/patch

ee/tabby-webserver/src/service/user_event.rs#L51-L52

Added lines #L51 - L52 were not covered by tests
}
})
.collect()
}

#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use chrono::{Days, Duration};

use super::*;
use crate::{schema::user_event::EventKind, service::AsID};

fn timestamp() -> u128 {
use std::time::{SystemTime, UNIX_EPOCH};
let start = SystemTime::now();
start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis()
}

#[tokio::test]
async fn test_list_user_events() {
let db = DbConn::new_in_memory().await.unwrap();
let user1 = db
.create_user("[email protected]".into(), Some("pass".into()), true)
.await
.unwrap();

db.create_user_event(user1, "view".into(), timestamp(), "".into())
.await
.unwrap();

let user2 = db
.create_user("[email protected]".into(), Some("pass".into()), true)
.await
.unwrap();

db.create_user_event(user2, "select".into(), timestamp(), "".into())
.await
.unwrap();

let svc = create(db);
let end = Utc::now() + Duration::days(1);
let start = end.checked_sub_days(Days::new(100)).unwrap();

// List without users should return all events
let events = svc
.list(None, None, None, None, vec![], start, end)
.await
.unwrap();
assert_eq!(events.len(), 2);

// Filter with user should return only events for that user
let events = svc
.list(None, None, None, None, vec![user1.as_id()], start, end)
.await
.unwrap();
assert_eq!(events.len(), 1);
assert_matches!(events[0].kind, EventKind::View);
}
}
Loading