Skip to content

Commit 4ced2e7

Browse files
committed
Merge remote-tracking branch 'origin/feature/issue-427' into feature/issue-427
2 parents 19857da + c3435c7 commit 4ced2e7

15 files changed

Lines changed: 639 additions & 190 deletions

File tree

crates/pet-conda/src/environments.rs

Lines changed: 151 additions & 137 deletions
Large diffs are not rendered by default.

crates/pet-conda/src/package.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,16 @@ pub struct CondaPackageInfo {
6262

6363
impl CondaPackageInfo {
6464
pub fn from(path: &Path, package: &Package) -> Option<Self> {
65-
get_conda_package_info(path, package)
65+
let history = fs::read_to_string(path.join("conda-meta").join("history")).ok();
66+
Self::from_history(path, package, history.as_deref())
67+
}
68+
69+
pub(crate) fn from_history(
70+
path: &Path,
71+
package: &Package,
72+
history: Option<&str>,
73+
) -> Option<Self> {
74+
get_conda_package_info(path, package, history)
6675
}
6776
}
6877

@@ -73,8 +82,14 @@ struct CondaMetaPackageStructure {
7382
}
7483

7584
/// Get the details of a conda package from the 'conda-meta' directory.
76-
fn get_conda_package_info(path: &Path, name: &Package) -> Option<CondaPackageInfo> {
77-
if let Some(info) = get_conda_package_info_from_history(path, name) {
85+
fn get_conda_package_info(
86+
path: &Path,
87+
name: &Package,
88+
history: Option<&str>,
89+
) -> Option<CondaPackageInfo> {
90+
if let Some(info) =
91+
history.and_then(|history| get_conda_package_info_from_history(path, name, history))
92+
{
7893
Some(info)
7994
} else {
8095
warn!(
@@ -86,14 +101,15 @@ fn get_conda_package_info(path: &Path, name: &Package) -> Option<CondaPackageInf
86101
}
87102
}
88103

89-
fn get_conda_package_info_from_history(path: &Path, name: &Package) -> Option<CondaPackageInfo> {
104+
fn get_conda_package_info_from_history(
105+
path: &Path,
106+
name: &Package,
107+
history_contents: &str,
108+
) -> Option<CondaPackageInfo> {
90109
// conda-meta is in the root of the conda installation folder
91110
let path = path.join("conda-meta");
92-
let history = path.join("history");
93111
let package_entry = format!(":{}-", name.to_name());
94112

95-
let history_contents = fs::read_to_string(history).ok()?;
96-
97113
// Filter to only include lines that:
98114
// 1. Start with '+' (installed packages, not '-' for removed packages)
99115
// 2. Contain the package entry (e.g., ":python-")
@@ -107,13 +123,9 @@ fn get_conda_package_info_from_history(path: &Path, name: &Package) -> Option<Co
107123
// ...
108124
// -defaults::python-3.9.18-h123456_0 <- removed during upgrade
109125
// +defaults::python-3.9.21-h789abc_0 <- current version (we want this)
110-
let matching_lines: Vec<&str> = history_contents
126+
let line = history_contents
111127
.lines()
112-
.filter(|l| l.starts_with('+') && l.contains(&package_entry))
113-
.collect();
114-
115-
// Get the last matching line (most recent installation)
116-
let line = matching_lines.last()?;
128+
.rfind(|line| line.starts_with('+') && line.contains(&package_entry))?;
117129

118130
// Sample entry in the history file
119131
// +conda-forge/osx-arm64::psutil-5.9.8-py312he37b823_0

crates/pet-core/src/telemetry/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ use inaccurate_python_info::InaccuratePythonEnvironmentInfo;
55
use missing_conda_info::MissingCondaEnvironments;
66
use missing_poetry_info::MissingPoetryEnvironments;
77
use refresh_performance::RefreshPerformance;
8+
use refresh_progress::RefreshProgress;
89
use serde::{Deserialize, Serialize};
910

1011
pub mod inaccurate_python_info;
1112
pub mod missing_conda_info;
1213
pub mod missing_poetry_info;
1314
pub mod refresh_performance;
15+
pub mod refresh_progress;
1416

1517
pub type NumberOfCustomSearchPaths = u32;
1618

@@ -38,6 +40,8 @@ pub enum TelemetryEvent {
3840
MissingPoetryEnvironments(MissingPoetryEnvironments),
3941
/// Telemetry with metrics for finding all environments as a result of refresh.
4042
RefreshPerformance(RefreshPerformance),
43+
/// Progress through a refresh operation, including per-locator timing.
44+
RefreshProgress(RefreshProgress),
4145
}
4246

4347
pub fn get_telemetry_event_name(event: &TelemetryEvent) -> &'static str {
@@ -57,5 +61,6 @@ pub fn get_telemetry_event_name(event: &TelemetryEvent) -> &'static str {
5761
TelemetryEvent::MissingCondaEnvironments(_) => "MissingCondaEnvironments",
5862
TelemetryEvent::MissingPoetryEnvironments(_) => "MissingPoetryEnvironments",
5963
TelemetryEvent::RefreshPerformance(_) => "RefreshPerformance",
64+
TelemetryEvent::RefreshProgress(_) => "RefreshProgress",
6065
}
6166
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7+
#[serde(rename_all = "camelCase")]
8+
pub enum RefreshProgressPhase {
9+
Locators,
10+
Path,
11+
GlobalVirtualEnvs,
12+
Workspaces,
13+
}
14+
15+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16+
#[serde(rename_all = "camelCase")]
17+
pub enum RefreshProgressStatus {
18+
Started,
19+
Completed,
20+
}
21+
22+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23+
#[serde(rename_all = "camelCase")]
24+
pub struct RefreshProgress {
25+
pub refresh_id: u64,
26+
pub phase: RefreshProgressPhase,
27+
pub status: RefreshProgressStatus,
28+
pub elapsed_ms: u128,
29+
#[serde(skip_serializing_if = "Option::is_none")]
30+
pub phase_elapsed_ms: Option<u128>,
31+
#[serde(skip_serializing_if = "Option::is_none")]
32+
pub locator_name: Option<String>,
33+
#[serde(skip_serializing_if = "Option::is_none")]
34+
pub locator_elapsed_ms: Option<u128>,
35+
}

crates/pet-reporter/src/jsonrpc.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,43 @@ mod tests {
173173
assert_eq!(value["data"]["refreshPerformance"]["total"], json!(10));
174174
}
175175

176+
#[test]
177+
fn refresh_progress_serializes_privacy_safe_fields() {
178+
use pet_core::telemetry::refresh_progress::{
179+
RefreshProgress, RefreshProgressPhase, RefreshProgressStatus,
180+
};
181+
182+
let event = TelemetryEvent::RefreshProgress(RefreshProgress {
183+
refresh_id: 42,
184+
phase: RefreshProgressPhase::Locators,
185+
status: RefreshProgressStatus::Completed,
186+
elapsed_ms: 15,
187+
phase_elapsed_ms: None,
188+
locator_name: Some("Conda".to_string()),
189+
locator_elapsed_ms: Some(10),
190+
});
191+
let payload = TelemetryData {
192+
event: get_telemetry_event_name(&event).to_string(),
193+
data: event,
194+
};
195+
196+
assert_eq!(
197+
serde_json::to_value(payload).unwrap(),
198+
json!({
199+
"event": "RefreshProgress",
200+
"data": {
201+
"refreshProgress": {
202+
"refreshId": 42,
203+
"phase": "locators",
204+
"status": "completed",
205+
"elapsedMs": 15,
206+
"locatorName": "Conda",
207+
"locatorElapsedMs": 10
208+
}
209+
}
210+
})
211+
);
212+
}
176213
#[test]
177214
fn log_payload_uses_camel_case_fields_and_level_renames() {
178215
let payload = Log {

0 commit comments

Comments
 (0)