feat: centralize workspace profile DTOs
This commit is contained in:
@@ -222,6 +222,100 @@ pub struct WorkspaceResponse {
|
|||||||
pub extension_points: WorkspaceExtensionPoints,
|
pub extension_points: WorkspaceExtensionPoints,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Workspace identity metadata exposed by the current settings resource.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct WorkspaceMetadataSettingsResponse {
|
||||||
|
pub workspace_id: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub created_at: String,
|
||||||
|
pub revision: String,
|
||||||
|
pub source: String,
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare-and-swap update for Workspace identity display metadata.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct UpdateWorkspaceMetadataRequest {
|
||||||
|
pub display_name: String,
|
||||||
|
pub revision: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct WorkspaceMetadataMutationResponse {
|
||||||
|
pub workspace: WorkspaceMetadataSettingsResponse,
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read-only Profile catalog projected from one active Workspace config revision.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[cfg_attr(feature = "typescript", ts(optional_fields = nullable))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct ProfileSettingsResponse {
|
||||||
|
pub workspace_id: String,
|
||||||
|
pub registry_revision: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[cfg_attr(feature = "typescript", ts(optional, type = "number | null"))]
|
||||||
|
pub config_revision: Option<u64>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub tree_digest: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub projection_digest: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub default_profile: Option<String>,
|
||||||
|
pub profiles: Vec<WorkspaceProfileSummary>,
|
||||||
|
pub sources: Vec<WorkspaceProfileSourceSummary>,
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[cfg_attr(feature = "typescript", ts(optional_fields = nullable))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct WorkspaceProfileSummary {
|
||||||
|
pub profile_id: String,
|
||||||
|
pub selector: String,
|
||||||
|
pub label: String,
|
||||||
|
pub source_kind: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub profile_source_id: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub editable: bool,
|
||||||
|
pub is_default: bool,
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct WorkspaceProfileSourceSummary {
|
||||||
|
pub profile_source_id: String,
|
||||||
|
pub display_path: String,
|
||||||
|
pub kind: String,
|
||||||
|
pub content_type: String,
|
||||||
|
pub content_digest: String,
|
||||||
|
pub provenance: WorkspaceProfileSourceProvenance,
|
||||||
|
pub editable: bool,
|
||||||
|
pub revision: String,
|
||||||
|
#[cfg_attr(feature = "typescript", ts(type = "number"))]
|
||||||
|
pub size_bytes: u64,
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum WorkspaceProfileSourceProvenance {
|
||||||
|
ProjectProfileSourceTree,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
@@ -1124,6 +1218,13 @@ pub fn catalog_typescript() -> String {
|
|||||||
WorkspaceExtensionPointState::decl(&config),
|
WorkspaceExtensionPointState::decl(&config),
|
||||||
WorkspaceExtensionPoints::decl(&config),
|
WorkspaceExtensionPoints::decl(&config),
|
||||||
WorkspaceResponse::decl(&config),
|
WorkspaceResponse::decl(&config),
|
||||||
|
WorkspaceMetadataSettingsResponse::decl(&config),
|
||||||
|
UpdateWorkspaceMetadataRequest::decl(&config),
|
||||||
|
WorkspaceMetadataMutationResponse::decl(&config),
|
||||||
|
ProfileSettingsResponse::decl(&config),
|
||||||
|
WorkspaceProfileSummary::decl(&config),
|
||||||
|
WorkspaceProfileSourceSummary::decl(&config),
|
||||||
|
WorkspaceProfileSourceProvenance::decl(&config),
|
||||||
RepositorySourceKind::decl(&config),
|
RepositorySourceKind::decl(&config),
|
||||||
RepositorySource::decl(&config),
|
RepositorySource::decl(&config),
|
||||||
RepositoryObservedStatus::decl(&config),
|
RepositoryObservedStatus::decl(&config),
|
||||||
@@ -1322,6 +1423,14 @@ mod tests {
|
|||||||
assert!(output.contains("export type RepositoryListResponse ="));
|
assert!(output.contains("export type RepositoryListResponse ="));
|
||||||
assert!(output.contains("items: Array<RepositorySummary>"));
|
assert!(output.contains("items: Array<RepositorySummary>"));
|
||||||
assert!(output.contains("observed_at?: string | null"));
|
assert!(output.contains("observed_at?: string | null"));
|
||||||
|
assert!(output.contains("export type WorkspaceMetadataSettingsResponse ="));
|
||||||
|
assert!(output.contains("export type WorkspaceMetadataMutationResponse ="));
|
||||||
|
assert!(output.contains("export type ProfileSettingsResponse ="));
|
||||||
|
assert!(output.contains("config_revision?: number | null"));
|
||||||
|
assert!(output.contains("provenance: WorkspaceProfileSourceProvenance"));
|
||||||
|
assert!(output.contains(
|
||||||
|
"export type WorkspaceProfileSourceProvenance = \"project_profile_source_tree\""
|
||||||
|
));
|
||||||
assert!(!output.contains("repository_id: string, display_name"));
|
assert!(!output.contains("repository_id: string, display_name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1354,6 +1463,83 @@ mod tests {
|
|||||||
assert_eq!(decoded, value);
|
assert_eq!(decoded, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn workspace_metadata_and_profile_projection_fixtures_round_trip() {
|
||||||
|
let diagnostic = Diagnostic {
|
||||||
|
code: "profile_projection_warning".to_string(),
|
||||||
|
severity: DiagnosticSeverity::Warning,
|
||||||
|
message: "projected from the active config revision".to_string(),
|
||||||
|
};
|
||||||
|
let metadata = WorkspaceMetadataSettingsResponse {
|
||||||
|
workspace_id: "workspace-test".to_string(),
|
||||||
|
display_name: "Test".to_string(),
|
||||||
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||||
|
revision: "sha256:metadata".to_string(),
|
||||||
|
source: "workspace-config".to_string(),
|
||||||
|
diagnostics: vec![diagnostic.clone()],
|
||||||
|
};
|
||||||
|
round_trip(metadata.clone());
|
||||||
|
round_trip(UpdateWorkspaceMetadataRequest {
|
||||||
|
display_name: "Renamed".to_string(),
|
||||||
|
revision: metadata.revision.clone(),
|
||||||
|
});
|
||||||
|
round_trip(WorkspaceMetadataMutationResponse {
|
||||||
|
workspace: metadata,
|
||||||
|
diagnostics: vec![],
|
||||||
|
});
|
||||||
|
|
||||||
|
round_trip(ProfileSettingsResponse {
|
||||||
|
workspace_id: "workspace-test".to_string(),
|
||||||
|
registry_revision: "config-source:7:sha256:tree:sha256:projection".to_string(),
|
||||||
|
config_revision: Some(7),
|
||||||
|
tree_digest: Some("sha256:tree".to_string()),
|
||||||
|
projection_digest: Some("sha256:projection".to_string()),
|
||||||
|
default_profile: Some("workspace:coder".to_string()),
|
||||||
|
profiles: vec![WorkspaceProfileSummary {
|
||||||
|
profile_id: "workspace:coder".to_string(),
|
||||||
|
selector: "workspace:coder".to_string(),
|
||||||
|
label: "Coder".to_string(),
|
||||||
|
source_kind: "project".to_string(),
|
||||||
|
profile_source_id: Some("profile-source-1".to_string()),
|
||||||
|
description: None,
|
||||||
|
editable: true,
|
||||||
|
is_default: true,
|
||||||
|
diagnostics: vec![diagnostic.clone()],
|
||||||
|
}],
|
||||||
|
sources: vec![WorkspaceProfileSourceSummary {
|
||||||
|
profile_source_id: "profile-source-1".to_string(),
|
||||||
|
display_path: "profiles/coder.dcdl".to_string(),
|
||||||
|
kind: "profile".to_string(),
|
||||||
|
content_type: "text/x-decodal".to_string(),
|
||||||
|
content_digest: "sha256:source".to_string(),
|
||||||
|
provenance: WorkspaceProfileSourceProvenance::ProjectProfileSourceTree,
|
||||||
|
editable: false,
|
||||||
|
revision: "config-source:7".to_string(),
|
||||||
|
size_bytes: 128,
|
||||||
|
diagnostics: vec![],
|
||||||
|
}],
|
||||||
|
diagnostics: vec![diagnostic],
|
||||||
|
});
|
||||||
|
|
||||||
|
let absent_optional_fields = serde_json::json!({
|
||||||
|
"workspace_id": "workspace-test",
|
||||||
|
"registry_revision": "builtin",
|
||||||
|
"profiles": [],
|
||||||
|
"sources": [],
|
||||||
|
"diagnostics": []
|
||||||
|
});
|
||||||
|
let decoded: ProfileSettingsResponse =
|
||||||
|
serde_json::from_value(absent_optional_fields.clone()).unwrap();
|
||||||
|
assert_eq!(decoded.config_revision, None);
|
||||||
|
assert_eq!(decoded.tree_digest, None);
|
||||||
|
assert_eq!(decoded.projection_digest, None);
|
||||||
|
assert_eq!(decoded.default_profile, None);
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_value(decoded).unwrap(),
|
||||||
|
absent_optional_fields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn companion_worker() -> WorkspaceWorkerDiscoveryItem {
|
fn companion_worker() -> WorkspaceWorkerDiscoveryItem {
|
||||||
WorkspaceWorkerDiscoveryItem {
|
WorkspaceWorkerDiscoveryItem {
|
||||||
subject: WorkspaceWorkerSubject::RuntimeWorker {
|
subject: WorkspaceWorkerSubject::RuntimeWorker {
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ use worker_runtime::config_bundle::{
|
|||||||
ConfigBundle, ConfigBundleMetadata, ConfigBundleProvenance, ConfigProfileDescriptor,
|
ConfigBundle, ConfigBundleMetadata, ConfigBundleProvenance, ConfigProfileDescriptor,
|
||||||
};
|
};
|
||||||
use worker_runtime::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveInput};
|
use worker_runtime::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveInput};
|
||||||
|
use workspace_api::{
|
||||||
|
Diagnostic, DiagnosticSeverity, ProfileSettingsResponse, UpdateWorkspaceMetadataRequest,
|
||||||
|
WorkspaceMetadataSettingsResponse, WorkspaceProfileSourceProvenance,
|
||||||
|
WorkspaceProfileSourceSummary, WorkspaceProfileSummary,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::config_source::{
|
use crate::config_source::{
|
||||||
WorkspaceConfigSchemaProvider, WorkspaceConfigState, evaluate_workspace_config_state,
|
WorkspaceConfigSchemaProvider, WorkspaceConfigState, evaluate_workspace_config_state,
|
||||||
};
|
};
|
||||||
use crate::hosts::{DiagnosticSeverity, RuntimeDiagnostic};
|
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
|
||||||
const PROFILE_SCHEMA_SOURCE: &str = r#"{
|
const PROFILE_SCHEMA_SOURCE: &str = r#"{
|
||||||
@@ -427,81 +431,6 @@ fn build_virtual_profile_archive(
|
|||||||
.map_err(|error| profile_validation_error("profile_source_archive_invalid", &error.to_string()))
|
.map_err(|error| profile_validation_error("profile_source_archive_invalid", &error.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct WorkspaceMetadataSettingsResponse {
|
|
||||||
pub workspace_id: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub created_at: String,
|
|
||||||
pub revision: String,
|
|
||||||
pub source: String,
|
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct UpdateWorkspaceMetadataRequest {
|
|
||||||
pub display_name: String,
|
|
||||||
pub revision: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct WorkspaceMetadataMutationResponse {
|
|
||||||
pub workspace: WorkspaceMetadataSettingsResponse,
|
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ProfileSettingsResponse {
|
|
||||||
pub workspace_id: String,
|
|
||||||
pub registry_revision: String,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub config_revision: Option<u64>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub tree_digest: Option<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub projection_digest: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub default_profile: Option<String>,
|
|
||||||
pub profiles: Vec<WorkspaceProfileSummary>,
|
|
||||||
pub sources: Vec<WorkspaceProfileSourceSummary>,
|
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct WorkspaceProfileSummary {
|
|
||||||
pub profile_id: String,
|
|
||||||
pub selector: String,
|
|
||||||
pub label: String,
|
|
||||||
pub source_kind: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub profile_source_id: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub editable: bool,
|
|
||||||
pub is_default: bool,
|
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct WorkspaceProfileSourceSummary {
|
|
||||||
pub profile_source_id: String,
|
|
||||||
pub display_path: String,
|
|
||||||
pub kind: String,
|
|
||||||
pub content_type: String,
|
|
||||||
pub content_digest: String,
|
|
||||||
pub provenance: WorkspaceProfileSourceProvenance,
|
|
||||||
pub editable: bool,
|
|
||||||
pub revision: String,
|
|
||||||
pub size_bytes: u64,
|
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum WorkspaceProfileSourceProvenance {
|
|
||||||
ProjectProfileSourceTree,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
struct WorkspaceIdentityFile {
|
struct WorkspaceIdentityFile {
|
||||||
@@ -804,8 +733,8 @@ fn diagnostic(
|
|||||||
code: impl Into<String>,
|
code: impl Into<String>,
|
||||||
severity: DiagnosticSeverity,
|
severity: DiagnosticSeverity,
|
||||||
message: impl Into<String>,
|
message: impl Into<String>,
|
||||||
) -> RuntimeDiagnostic {
|
) -> Diagnostic {
|
||||||
RuntimeDiagnostic {
|
Diagnostic {
|
||||||
code: code.into(),
|
code: code.into(),
|
||||||
severity,
|
severity,
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
|
|||||||
@@ -61,20 +61,22 @@ use workspace_api::{
|
|||||||
CreateWorkspaceRepositoryRequest, CreateWorkspaceRepositoryResponse,
|
CreateWorkspaceRepositoryRequest, CreateWorkspaceRepositoryResponse,
|
||||||
DeleteRepositorySshCredentialRequest, DeleteRepositorySshHostTrustRequest,
|
DeleteRepositorySshCredentialRequest, DeleteRepositorySshHostTrustRequest,
|
||||||
ObjectiveCreateRequest, ObjectiveEditRequest, ObjectiveLinkTicketRequest,
|
ObjectiveCreateRequest, ObjectiveEditRequest, ObjectiveLinkTicketRequest,
|
||||||
ObjectiveStateRequest, PutRepositorySshHostTrustRequest, RepositoryAccessProjection,
|
ObjectiveStateRequest, ProfileSettingsResponse, PutRepositorySshHostTrustRequest,
|
||||||
RepositoryDetailResponse, RepositoryListResponse, RepositoryLogResponse,
|
RepositoryAccessProjection, RepositoryDetailResponse, RepositoryListResponse,
|
||||||
RepositorySshCredential, RepositorySshHostTrust, RotateRepositorySshCredentialRequest,
|
RepositoryLogResponse, RepositorySshCredential, RepositorySshHostTrust,
|
||||||
RuntimeConnectionTestResponse, RuntimeManagementSummary, TICKET_ORCHESTRATION_PLANS_QUERY_PATH,
|
RotateRepositorySshCredentialRequest, RuntimeConnectionTestResponse, RuntimeManagementSummary,
|
||||||
TICKET_RELATIONS_QUERY_PATH,
|
TICKET_ORCHESTRATION_PLANS_QUERY_PATH, TICKET_RELATIONS_QUERY_PATH,
|
||||||
|
UpdateWorkspaceMetadataRequest,
|
||||||
WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest,
|
WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest,
|
||||||
WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse,
|
WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse,
|
||||||
WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse,
|
WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse,
|
||||||
WorkingDirectoryListResponse as BrowserWorkingDirectoryListResponse,
|
WorkingDirectoryListResponse as BrowserWorkingDirectoryListResponse,
|
||||||
WorkingDirectoryRemovalDisposition, WorkingDirectoryRemovalRequest,
|
WorkingDirectoryRemovalDisposition, WorkingDirectoryRemovalRequest,
|
||||||
WorkingDirectoryRemovalResponse, WorkspaceCatalogListResponse, WorkspaceCreateResponse,
|
WorkingDirectoryRemovalResponse, WorkspaceCatalogListResponse, WorkspaceCreateResponse,
|
||||||
WorkspaceExtensionPointState, WorkspaceExtensionPoints, WorkspacePermissionSummary,
|
WorkspaceExtensionPointState, WorkspaceExtensionPoints, WorkspaceMetadataMutationResponse,
|
||||||
WorkspaceRepositoryRecord, WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary,
|
WorkspaceMetadataSettingsResponse, WorkspacePermissionSummary, WorkspaceRepositoryRecord,
|
||||||
WorkspaceWorkerDiscoveryItem, WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
|
WorkspaceResponse, WorkspaceRuntimeResource, WorkspaceSummary, WorkspaceWorkerDiscoveryItem,
|
||||||
|
WorkspaceWorkerDiscoveryPage, WorkspaceWorkerSubject,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::auth::{
|
use crate::auth::{
|
||||||
@@ -114,7 +116,6 @@ use crate::observation::{
|
|||||||
BackendObservationProxy, ObservationProxyError, RuntimeObservationClient,
|
BackendObservationProxy, ObservationProxyError, RuntimeObservationClient,
|
||||||
RuntimeObservationSource, RuntimeObservationSourceConfig,
|
RuntimeObservationSource, RuntimeObservationSourceConfig,
|
||||||
};
|
};
|
||||||
use crate::profile_settings::UpdateWorkspaceMetadataRequest;
|
|
||||||
use crate::records::{
|
use crate::records::{
|
||||||
MergeRequestListItem, MergeRequestListResponse, ObjectiveDetail, ObjectiveQueryRequest,
|
MergeRequestListItem, MergeRequestListResponse, ObjectiveDetail, ObjectiveQueryRequest,
|
||||||
ObjectiveQueryResponse, ObjectiveShowRequest, ProjectRecordList, TicketDetail,
|
ObjectiveQueryResponse, ObjectiveShowRequest, ProjectRecordList, TicketDetail,
|
||||||
@@ -3360,7 +3361,7 @@ async fn scoped_get_workspace(
|
|||||||
async fn scoped_get_workspace_settings(
|
async fn scoped_get_workspace_settings(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
) -> ApiResult<Json<crate::profile_settings::WorkspaceMetadataSettingsResponse>> {
|
) -> ApiResult<Json<WorkspaceMetadataSettingsResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
Ok(Json(crate::profile_settings::workspace_metadata_settings(
|
Ok(Json(crate::profile_settings::workspace_metadata_settings(
|
||||||
&api.config.workspace_root,
|
&api.config.workspace_root,
|
||||||
@@ -3374,20 +3375,18 @@ async fn scoped_update_workspace_settings(
|
|||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
Json(request): Json<UpdateWorkspaceMetadataRequest>,
|
Json(request): Json<UpdateWorkspaceMetadataRequest>,
|
||||||
) -> ApiResult<Json<crate::profile_settings::WorkspaceMetadataMutationResponse>> {
|
) -> ApiResult<Json<WorkspaceMetadataMutationResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let workspace =
|
let workspace =
|
||||||
crate::profile_settings::update_workspace_metadata(&api.config.workspace_root, request)?;
|
crate::profile_settings::update_workspace_metadata(&api.config.workspace_root, request)?;
|
||||||
Ok(Json(
|
Ok(Json(WorkspaceMetadataMutationResponse {
|
||||||
crate::profile_settings::WorkspaceMetadataMutationResponse {
|
workspace,
|
||||||
workspace,
|
diagnostics: vec![workspace_api::Diagnostic {
|
||||||
diagnostics: vec![RuntimeDiagnostic {
|
code: "workspace_metadata_updated".to_string(),
|
||||||
code: "workspace_metadata_updated".to_string(),
|
severity: workspace_api::DiagnosticSeverity::Info,
|
||||||
severity: DiagnosticSeverity::Info,
|
message: "Workspace display metadata was updated.".to_string(),
|
||||||
message: "Workspace display metadata was updated.".to_string(),
|
}],
|
||||||
}],
|
}))
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -3763,7 +3762,7 @@ async fn scoped_commit_workspace_config_tree(
|
|||||||
async fn scoped_get_profile_settings(
|
async fn scoped_get_profile_settings(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
) -> ApiResult<Json<crate::profile_settings::ProfileSettingsResponse>> {
|
) -> ApiResult<Json<ProfileSettingsResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let state = api
|
let state = api
|
||||||
.config_store
|
.config_store
|
||||||
@@ -14353,7 +14352,7 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> ApiResult<WorkerLaunchO
|
|||||||
!profile
|
!profile
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.any(|diagnostic| diagnostic.severity == DiagnosticSeverity::Error)
|
.any(|diagnostic| diagnostic.severity == workspace_api::DiagnosticSeverity::Error)
|
||||||
})
|
})
|
||||||
.map(|profile| WorkerLaunchProfileCandidate {
|
.map(|profile| WorkerLaunchProfileCandidate {
|
||||||
id: profile.profile_id,
|
id: profile.profile_id,
|
||||||
@@ -26640,6 +26639,13 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3);
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn profile_settings_are_read_only_virtual_config_projection() {
|
async fn profile_settings_are_read_only_virtual_config_projection() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let legacy_profile_dir = dir.path().join(".yoi/profiles");
|
||||||
|
std::fs::create_dir_all(&legacy_profile_dir).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
legacy_profile_dir.join("legacy.dcdl"),
|
||||||
|
"LegacyFilesystemProfile = { label = 'must not be projected' }\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let app = test_app(dir.path()).await;
|
let app = test_app(dir.path()).await;
|
||||||
let path = format!("/api/w/{TEST_WORKSPACE_ID}/settings/profiles");
|
let path = format!("/api/w/{TEST_WORKSPACE_ID}/settings/profiles");
|
||||||
let settings = get_json(app.clone(), &path).await;
|
let settings = get_json(app.clone(), &path).await;
|
||||||
@@ -26654,6 +26660,8 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3);
|
|||||||
.iter()
|
.iter()
|
||||||
.all(|profile| profile["editable"] == false)
|
.all(|profile| profile["editable"] == false)
|
||||||
);
|
);
|
||||||
|
assert!(!settings.to_string().contains("LegacyFilesystemProfile"));
|
||||||
|
assert!(!settings.to_string().contains("legacy.dcdl"));
|
||||||
let mutation = app
|
let mutation = app
|
||||||
.oneshot(
|
.oneshot(
|
||||||
Request::builder()
|
Request::builder()
|
||||||
|
|||||||
@@ -81,6 +81,64 @@ export type WorkspaceResponse = {
|
|||||||
extension_points: WorkspaceExtensionPoints;
|
extension_points: WorkspaceExtensionPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMetadataSettingsResponse = {
|
||||||
|
workspace_id: string;
|
||||||
|
display_name: string;
|
||||||
|
created_at: string;
|
||||||
|
revision: string;
|
||||||
|
source: string;
|
||||||
|
diagnostics: Array<Diagnostic>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateWorkspaceMetadataRequest = {
|
||||||
|
display_name: string;
|
||||||
|
revision: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMetadataMutationResponse = {
|
||||||
|
workspace: WorkspaceMetadataSettingsResponse;
|
||||||
|
diagnostics: Array<Diagnostic>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProfileSettingsResponse = {
|
||||||
|
workspace_id: string;
|
||||||
|
registry_revision: string;
|
||||||
|
config_revision?: number | null;
|
||||||
|
tree_digest?: string | null;
|
||||||
|
projection_digest?: string | null;
|
||||||
|
default_profile?: string | null;
|
||||||
|
profiles: Array<WorkspaceProfileSummary>;
|
||||||
|
sources: Array<WorkspaceProfileSourceSummary>;
|
||||||
|
diagnostics: Array<Diagnostic>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceProfileSummary = {
|
||||||
|
profile_id: string;
|
||||||
|
selector: string;
|
||||||
|
label: string;
|
||||||
|
source_kind: string;
|
||||||
|
profile_source_id?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
editable: boolean;
|
||||||
|
is_default: boolean;
|
||||||
|
diagnostics: Array<Diagnostic>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceProfileSourceSummary = {
|
||||||
|
profile_source_id: string;
|
||||||
|
display_path: string;
|
||||||
|
kind: string;
|
||||||
|
content_type: string;
|
||||||
|
content_digest: string;
|
||||||
|
provenance: WorkspaceProfileSourceProvenance;
|
||||||
|
editable: boolean;
|
||||||
|
revision: string;
|
||||||
|
size_bytes: number;
|
||||||
|
diagnostics: Array<Diagnostic>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceProfileSourceProvenance = "project_profile_source_tree";
|
||||||
|
|
||||||
export type RepositorySourceKind =
|
export type RepositorySourceKind =
|
||||||
| "local_path"
|
| "local_path"
|
||||||
| "file"
|
| "file"
|
||||||
|
|||||||
Reference in New Issue
Block a user