Skip to content
Closed
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 lib/luminork-server/src/service/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub use components::{
list_components::{
ComponentDetailsV1,
ListComponentsV1Response,
QualificationStatusV1 as ListComponentsQualificationStatusV1,
},
manage_component::{
ManageComponentV1Request,
Expand Down Expand Up @@ -313,6 +314,7 @@ pub use crate::api_types::func_run::v1::{
SourceViewV1,
ComponentDetailsV1,
ListComponentsV1Response,
ListComponentsQualificationStatusV1,
FindComponentV1Params,
FuncRunV1RequestPath,
FuncRunLogViewV1,
Expand Down
4 changes: 4 additions & 0 deletions lib/luminork-server/src/service/v1/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,8 @@ pub struct PaginationParams {
pub cursor: Option<String>,
#[schema(value_type = Option<bool>)]
pub include_codegen: Option<bool>,
#[schema(value_type = Option<bool>, example = "true")]
pub include_qualifications: Option<bool>,
#[schema(value_type = Option<bool>, example = "true")]
pub include_upgrade_status: Option<bool>,
}
89 changes: 72 additions & 17 deletions lib/luminork-server/src/service/v1/components/list_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,22 @@ pub struct ListComponentsV1Response {
value_type = Vec<ComponentDetailsV1>,
example = json!([
{
"component_id": "01H9ZQD35JPMBGHH69BT0Q79AA",
"componentId": "01H9ZQD35JPMBGHH69BT0Q79AA",
"name": "my-vpc",
"schema_name": "AWS::EC2::VPC"
"schemaName": "AWS::EC2::VPC"
},
{
"component_id": "01H9ZQD35JPMBGHH69BT0Q79BB",
"componentId": "01H9ZQD35JPMBGHH69BT0Q79BB",
"name": "Public 1",
"schema_name": "AWS::EC2::Subnet"
"schemaName": "AWS::EC2::Subnet",
"qualificationStatus": {
"total": 2,
"succeeded": 1,
"warned": 0,
"failed": 0,
"running": 1
},
"canBeUpgraded": true
}
])
)]
Expand All @@ -56,6 +64,31 @@ pub struct ComponentDetailsV1 {
pub name: String,
pub schema_name: String,
pub codegen: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qualification_status: Option<QualificationStatusV1>,
#[serde(skip_serializing_if = "Option::is_none")]
pub can_be_upgraded: Option<bool>,
}

#[derive(Serialize, Debug, ToSchema)]
pub struct QualificationStatusV1 {
pub total: u64,
pub succeeded: u64,
pub warned: u64,
pub failed: u64,
pub running: u64,
}

impl From<si_frontend_types::ComponentQualificationStats> for QualificationStatusV1 {
fn from(stats: si_frontend_types::ComponentQualificationStats) -> Self {
Self {
total: stats.total,
succeeded: stats.succeeded,
warned: stats.warned,
failed: stats.failed,
running: stats.running,
}
}
}

#[utoipa::path(
Expand All @@ -67,21 +100,31 @@ pub struct ComponentDetailsV1 {
("limit" = Option<String>, Query, description = "Maximum number of results to return (default: 50, max: 300)"),
("cursor" = Option<String>, Query, description = "Cursor for pagination (ComponentId of the last item from previous page)"),
("includeCodegen" = Option<bool>, Query, description = "Allow returning the codegen for the cloudformation template for the component (if it exists)"),
("includeQualifications" = Option<bool>, Query, description = "Include real-time qualification status"),
("includeUpgradeStatus" = Option<bool>, Query, description = "Include upgrade-ability information"),
),
summary = "List all components",
tag = "components",
responses(
(status = 200, description = "Components retrieved successfully", body = ListComponentsV1Response, example = json!({
"componentDetails": [
{
"component_id": "01H9ZQD35JPMBGHH69BT0Q79AA",
"componentId": "01H9ZQD35JPMBGHH69BT0Q79AA",
"name": "my-vpc",
"schema_name": "AWS::EC2::VPC"
"schemaName": "AWS::EC2::VPC"
},
{
"component_id": "01H9ZQD35JPMBGHH69BT0Q79BB",
"componentId": "01H9ZQD35JPMBGHH69BT0Q79BB",
"name": "Public 1",
"schema_name": "AWS::EC2::Subnet"
"schemaName": "AWS::EC2::Subnet",
"qualificationStatus": {
"total": 2,
"succeeded": 1,
"warned": 0,
"failed": 0,
"running": 1
},
"canBeUpgraded": true
}
],
"nextCursor": null
Expand Down Expand Up @@ -143,21 +186,33 @@ pub async fn list_components(
name,
schema_name,
codegen: None,
qualification_status: None,
can_be_upgraded: None,
};

if let Some(codegen) = params.include_codegen {
if codegen {
let code_map_av_id =
Component::find_code_map_attribute_value_id(ctx, component.id()).await?;
// Handle existing includeCodegen parameter for backward compatibility
if let Some(true) = params.include_codegen {
let code_map_av_id =
Component::find_code_map_attribute_value_id(ctx, component.id()).await?;

let view = AttributeValue::view(ctx, code_map_av_id).await?;
if let Some(v) = view {
let details = v.get("awsCloudFormationLint");
comp_response.codegen = details.cloned();
}
let view = AttributeValue::view(ctx, code_map_av_id).await?;
if let Some(v) = view {
let details = v.get("awsCloudFormationLint");
comp_response.codegen = details.cloned();
}
}

// Handle new inclusion parameters
if let Some(true) = params.include_qualifications {
let stats =
super::get_qualification_stats_with_realtime_running(ctx, component.id()).await?;
comp_response.qualification_status = Some(stats);
}

if let Some(true) = params.include_upgrade_status {
comp_response.can_be_upgraded = Some(component.can_be_upgraded(ctx).await?);
}

comp_details.push(comp_response);
}

Expand Down
65 changes: 65 additions & 0 deletions lib/luminork-server/src/service/v1/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,67 @@ pub mod search_components;
pub mod update_component;
pub mod upgrade_component;

// Shared utility for real-time qualification tracking
pub async fn get_qualification_stats_with_realtime_running(
ctx: &dal::DalContext,
component_id: ComponentId,
) -> ComponentsResult<list_components::QualificationStatusV1> {
use dal::qualification::QualificationSummary;
use si_events::FuncRunState;

// Get base stats using existing logic
let base_stats = QualificationSummary::individual_stats(ctx, component_id).await?;

// Count qualifications that are actively executing
let qualification_avs = dal::Component::list_qualification_avs(ctx, component_id).await?;
let mut active_running_count = 0;

for qualification_av in qualification_avs {
if let Some(func_run) = ctx
.layer_db()
.func_run()
.get_last_qualification_for_attribute_value_id(
ctx.events_tenancy().workspace_pk,
qualification_av.id(),
)
.await?
{
// Check if function is currently executing
match func_run.state() {
FuncRunState::Created
| FuncRunState::Dispatched
| FuncRunState::Running
| FuncRunState::PostProcessing => {
active_running_count += 1;
}
FuncRunState::Success | FuncRunState::Failure | FuncRunState::Killed => {
// Completed - don't count as running
}
}
}
}

// If we have actively running qualifications, adjust the counts
let mut result_stats = list_components::QualificationStatusV1::from(base_stats);

if active_running_count > 0 {
// Override the running count with actual execution state
result_stats.running = active_running_count;

// Optionally adjust other counts to maintain total consistency
let accounted_for = result_stats.succeeded
+ result_stats.warned
+ result_stats.failed
+ result_stats.running;
if accounted_for > result_stats.total {
// Some qualifications are re-running, adjust previous counts
result_stats.running = active_running_count.min(result_stats.total);
}
}

Ok(result_stats)
}

#[remain::sorted]
#[derive(Debug, Error)]
pub enum ComponentsError {
Expand Down Expand Up @@ -125,6 +186,8 @@ pub enum ComponentsError {
InputSocket(#[from] dal::socket::input::InputSocketError),
#[error("invalid secret value: {0}")]
InvalidSecretValue(String),
#[error("layer db error: {0}")]
LayerDb(#[from] si_layer_cache::LayerDbError),
#[error("management func error: {0}")]
ManagementFuncExecution(#[from] si_db::ManagementFuncExecutionError),
#[error("management function already running for this component")]
Expand All @@ -147,6 +210,8 @@ pub enum ComponentsError {
OutputSocket(#[from] dal::socket::output::OutputSocketError),
#[error("prop error: {0}")]
Prop(#[from] dal::prop::PropError),
#[error("qualification summary error: {0}")]
QualificationSummary(#[from] dal::qualification::QualificationSummaryError),
#[error("schema error: {0}")]
Schema(#[from] dal::SchemaError),
#[error("schema not found by name error: {0}")]
Expand Down