Skip to content

Commit 53870b5

Browse files
committed
Create Luminork top-level search endpoint
1 parent 90e3ceb commit 53870b5

File tree

13 files changed

+223
-19
lines changed

13 files changed

+223
-19
lines changed

lib/luminork-server/src/search/component.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use si_id::{
2020
WorkspacePk,
2121
};
2222
use telemetry::prelude::*;
23+
use utoipa::ToSchema;
2324

2425
use crate::search::{
2526
Error,
@@ -34,7 +35,7 @@ pub async fn search(
3435
workspace_id: WorkspacePk,
3536
change_set_id: ChangeSetId,
3637
query: &Arc<SearchQuery>,
37-
) -> Result<Vec<ComponentId>> {
38+
) -> Result<Vec<ComponentSearchResult>> {
3839
// Grab the index first
3940
let attribute_trees = attribute_tree_mv_index(frigg, workspace_id, change_set_id).await?;
4041

@@ -53,17 +54,40 @@ pub async fn search(
5354
match_tasks.into_iter().flatten_ok().try_collect()
5455
}
5556

57+
/// Component data in search results.
58+
#[derive(Serialize, Deserialize, Debug, ToSchema)]
59+
pub struct ComponentSearchResult {
60+
#[schema(value_type = String, example = "01H9ZQD35JPMBGHH69BT0Q79AA")]
61+
pub id: ComponentId,
62+
#[schema(value_type = String, example = "MyInstance")]
63+
pub name: String,
64+
pub schema: ComponentSearchResultSchema,
65+
}
66+
67+
/// The schema for a component in search results.
68+
#[derive(Serialize, Deserialize, Debug, ToSchema)]
69+
pub struct ComponentSearchResultSchema {
70+
#[schema(value_type = String, example = "AWS::EC2::Instance")]
71+
pub name: String,
72+
}
73+
5674
/// Fetch the AttributeTree MV for a component and match it against the query.
5775
#[instrument(level = "debug", skip_all, fields(id))]
5876
async fn match_component(
5977
frigg: frigg::FriggStore,
6078
workspace_pk: WorkspacePk,
6179
index_ref: IndexReference,
6280
query: Arc<SearchQuery>,
63-
) -> Result<Option<ComponentId>> {
81+
) -> Result<Option<ComponentSearchResult>> {
6482
let attribute_tree = attribute_tree_mv(frigg, workspace_pk, index_ref).await?;
6583
if match_attribute_tree(&attribute_tree, &query) {
66-
Ok(Some(attribute_tree.id))
84+
Ok(Some(ComponentSearchResult {
85+
id: attribute_tree.id,
86+
name: attribute_tree.component_name,
87+
schema: ComponentSearchResultSchema {
88+
name: attribute_tree.schema_name,
89+
},
90+
}))
6791
} else {
6892
Ok(None)
6993
}

lib/luminork-server/src/service/v1.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod components;
1010
mod funcs;
1111
mod management_funcs;
1212
mod schemas;
13+
mod search;
1314
mod secrets;
1415
mod user;
1516
mod workspaces;
@@ -159,6 +160,10 @@ pub use schemas::{
159160
unlock_schema::UnlockedSchemaV1Response,
160161
update_schema_variant::UpdateSchemaVariantV1Request,
161162
};
163+
pub use search::{
164+
SearchV1Request,
165+
SearchV1Response,
166+
};
162167
pub use workspaces::WorkspaceError;
163168

164169
pub use crate::api_types::func_run::v1::{
@@ -228,6 +233,7 @@ pub use crate::api_types::func_run::v1::{
228233
secrets::delete_secret::delete_secret,
229234
secrets::update_secret::update_secret,
230235
secrets::get_secrets::get_secrets,
236+
search::search,
231237
),
232238
components(
233239
schemas(
@@ -314,6 +320,8 @@ pub use crate::api_types::func_run::v1::{
314320
CreateVariantManagementFuncV1Request,
315321
CreateVariantManagementFuncV1Response,
316322
UpdateSchemaVariantV1Request,
323+
SearchV1Request,
324+
SearchV1Response,
317325
)
318326
),
319327
tags(

lib/luminork-server/src/service/v1/components/search_components.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ pub async fn search_components(
7070

7171
let query: SearchQuery = payload.query_string.as_deref().unwrap_or("").parse()?;
7272
let query = Arc::new(query);
73-
let mut component_ids =
74-
search::component::search(frigg, workspace_id, change_set_id, &query).await?;
73+
let mut component_ids = search::component::search(frigg, workspace_id, change_set_id, &query)
74+
.await?
75+
.into_iter()
76+
.map(|component| component.id)
77+
.collect();
7578

7679
if let Some(schema_name) = payload.schema_name.clone() {
7780
component_ids = apply_schema_filter(ctx, component_ids, schema_name).await?;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::sync::Arc;
2+
3+
use axum::{
4+
Router,
5+
extract::Query,
6+
response::Json,
7+
routing::get,
8+
};
9+
use sdf_core::app_state::AppState;
10+
use sdf_extract::{
11+
FriggStore,
12+
change_set::ChangeSetAuthorization,
13+
};
14+
use serde::{
15+
Deserialize,
16+
Serialize,
17+
};
18+
use serde_json::json;
19+
use utoipa::{
20+
self,
21+
ToSchema,
22+
};
23+
24+
use crate::{
25+
extract::PosthogEventTracker,
26+
search::{
27+
self,
28+
component::ComponentSearchResult,
29+
},
30+
service::v1::ComponentsError,
31+
};
32+
33+
pub fn routes() -> Router<AppState> {
34+
Router::new().route("/", get(search))
35+
}
36+
37+
#[utoipa::path(
38+
post,
39+
path = "/v1/w/{workspace_id}/change-sets/{change_set_id}/search",
40+
params(
41+
("workspace_id" = String, Path, description = "Workspace identifier"),
42+
("change_set_id" = String, Path, description = "Change Set identifier"),
43+
),
44+
request_body = SearchV1Request,
45+
summary = "Complex search for components",
46+
responses(
47+
(status = 200, description = "Components retrieved successfully", body = SearchV1Response),
48+
(status = 401, description = "Unauthorized - Invalid or missing token"),
49+
(status = 500, description = "Internal server error", body = crate::service::v1::common::ApiError)
50+
)
51+
)]
52+
pub async fn search(
53+
FriggStore(ref frigg): FriggStore,
54+
ChangeSetAuthorization {
55+
workspace_id,
56+
change_set_id,
57+
..
58+
}: ChangeSetAuthorization,
59+
tracker: PosthogEventTracker,
60+
Query(SearchV1Request { q }): Query<SearchV1Request>,
61+
) -> Result<Json<SearchV1Response>, ComponentsError> {
62+
let query = Arc::new(q.parse()?);
63+
let components = search::component::search(frigg, workspace_id, change_set_id, &query).await?;
64+
65+
tracker.track_no_ctx(
66+
workspace_id,
67+
change_set_id,
68+
"api_search",
69+
json!({
70+
"q": q,
71+
"components": components.len(),
72+
}),
73+
);
74+
75+
Ok(Json(SearchV1Response { components }))
76+
}
77+
78+
#[derive(Deserialize, Serialize, Debug, ToSchema)]
79+
#[serde(rename_all = "camelCase")]
80+
pub struct SearchV1Request {
81+
#[schema(example = "AWS::EC2::Instance region:us-east-1")]
82+
pub q: String,
83+
}
84+
85+
#[derive(Deserialize, Serialize, Debug, ToSchema)]
86+
#[serde(rename_all = "camelCase")]
87+
pub struct SearchV1Response {
88+
#[schema(example = json!(["01H9ZQD35JPMBGHH69BT0Q79AA", "01H9ZQD35JPMBGHH69BT0Q79BB", "01H9ZQD35JPMBGHH69BT0Q79CC"]))]
89+
pub components: Vec<ComponentSearchResult>,
90+
}

lib/luminork-server/src/service/v1/workspaces.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub fn routes(state: AppState) -> Router<AppState> {
6767
Router::new()
6868
.route("/", get(super::change_sets::get::get_change_set))
6969
.route("/", delete(super::change_sets::delete::abandon_change_set))
70+
.nest("/search", super::search::routes())
7071
.nest("/components", super::components::routes())
7172
.nest("/schemas", super::schemas::routes())
7273
.nest("/funcs", super::funcs::routes())

lib/sdf-core/src/tracking.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use dal::DalContext;
22
use hyper::Uri;
3+
use si_id::{
4+
ChangeSetId,
5+
WorkspacePk,
6+
};
37
use telemetry::prelude::*;
48

59
use crate::app_state::PosthogClient;
@@ -20,7 +24,7 @@ pub fn track(
2024
.map(|workspace_pk| workspace_pk.to_string())
2125
.unwrap_or_else(|| "unknown".to_string());
2226
let changeset_id = ctx.change_set_id().to_string();
23-
track_no_ctx(
27+
_track(
2428
posthog_client,
2529
original_uri,
2630
host_name,
@@ -37,6 +41,57 @@ pub fn track(
3741
/// (e.g., for admin routes that operate across workspaces)
3842
#[allow(clippy::too_many_arguments)]
3943
pub fn track_no_ctx(
44+
posthog_client: &PosthogClient,
45+
original_uri: &Uri,
46+
host_name: &String,
47+
distinct_id: String,
48+
workspace_id: WorkspacePk,
49+
changeset_id: ChangeSetId,
50+
event_name: impl AsRef<str>,
51+
properties: serde_json::Value,
52+
) {
53+
_track(
54+
posthog_client,
55+
original_uri,
56+
host_name,
57+
distinct_id,
58+
Some(workspace_id.to_string()),
59+
Some(changeset_id.to_string()),
60+
event_name,
61+
properties,
62+
)
63+
}
64+
65+
/// Send tracking events to PostHog when you either don't have a DalContext
66+
/// or when the DalContext does not have the correct workspace and change_set id
67+
/// (e.g., for admin routes that operate across workspaces)
68+
#[allow(clippy::too_many_arguments)]
69+
pub fn track_no_ctx_workspace(
70+
posthog_client: &PosthogClient,
71+
original_uri: &Uri,
72+
host_name: &String,
73+
distinct_id: String,
74+
workspace_id: WorkspacePk,
75+
event_name: impl AsRef<str>,
76+
properties: serde_json::Value,
77+
) {
78+
_track(
79+
posthog_client,
80+
original_uri,
81+
host_name,
82+
distinct_id,
83+
Some(workspace_id.to_string()),
84+
None,
85+
event_name,
86+
properties,
87+
)
88+
}
89+
90+
/// Send tracking events to PostHog when you either don't have a DalContext
91+
/// or when the DalContext does not have the correct workspace and change_set id
92+
/// (e.g., for admin routes that operate across workspaces)
93+
#[allow(clippy::too_many_arguments)]
94+
pub fn _track(
4095
posthog_client: &PosthogClient,
4196
original_uri: &Uri,
4297
host_name: &String,

lib/sdf-extract/src/services.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ use axum::{
1111
request::Parts,
1212
},
1313
};
14-
use dal::DalContext;
14+
use dal::{
15+
ChangeSetId,
16+
DalContext,
17+
WorkspacePk,
18+
};
1519
use derive_more::{
1620
Deref,
1721
Into,
@@ -104,6 +108,25 @@ impl PosthogEventTracker {
104108
properties,
105109
)
106110
}
111+
112+
pub fn track_no_ctx(
113+
&self,
114+
workspace_id: WorkspacePk,
115+
change_set_id: ChangeSetId,
116+
event_name: impl AsRef<str>,
117+
properties: serde_json::Value,
118+
) {
119+
sdf_core::tracking::track_no_ctx(
120+
&self.posthog_client,
121+
&self.original_uri,
122+
&self.host,
123+
"anonymous".to_string(),
124+
workspace_id,
125+
change_set_id,
126+
event_name,
127+
properties,
128+
)
129+
}
107130
}
108131

109132
#[async_trait]

lib/sdf-server/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub(crate) use self::{
7979
tracking::{
8080
track,
8181
track_no_ctx,
82+
track_no_ctx_workspace,
8283
},
8384
};
8485

lib/sdf-server/src/service/v2/admin/get_cas_data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ pub async fn get_cas_data(
8383
&original_uri,
8484
&host_name,
8585
ctx.history_actor().distinct_id(),
86-
Some(workspace_id.to_string()),
87-
Some(change_set_id.to_string()),
86+
workspace_id,
87+
change_set_id,
8888
"admin.get_cas_data",
8989
serde_json::json!({}),
9090
);

lib/sdf-server/src/service/v2/admin/get_snapshot.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ pub async fn get_snapshot(
6464
&original_uri,
6565
&host_name,
6666
ctx.history_actor().distinct_id(),
67-
Some(workspace_id.to_string()),
68-
Some(change_set_id.to_string()),
67+
workspace_id,
68+
change_set_id,
6969
"admin.get_snapshot",
7070
serde_json::json!({
7171
"workspace_snapshot_address": snap_addr.to_string(),

0 commit comments

Comments
 (0)