Skip to main content

pipedash_plugin_api/plugin/
defaults.rs

1use serde_json::{
2    Map,
3    Value,
4};
5
6use crate::actions::{
7    ActionAvailability,
8    PipelineActionState,
9    RunActionState,
10};
11use crate::metadata::PluginCapabilities;
12use crate::presentation::{
13    DisplayField,
14    PipelineFacts,
15    RunDisplay,
16    RunFacts,
17};
18use crate::types::{
19    PipelineStatus,
20    WorkflowParameter,
21    WorkflowParameterType,
22};
23
24fn flag(metadata: &std::collections::HashMap<String, Value>, key: &str) -> bool {
25    metadata.get(key) == Some(&Value::Bool(true))
26}
27
28fn truthy(value: Option<&Value>) -> bool {
29    match value {
30        None | Some(Value::Null) => false,
31        Some(Value::Bool(value)) => *value,
32        Some(Value::String(value)) => !value.is_empty(),
33        Some(Value::Number(value)) => value.as_f64().is_some_and(|value| value != 0.0),
34        Some(_) => true,
35    }
36}
37
38pub fn pipeline_actions(
39    capabilities: &PluginCapabilities, pipeline: &PipelineFacts<'_>,
40) -> PipelineActionState {
41    PipelineActionState {
42        trigger: ActionAvailability::allowed(capabilities.trigger),
43        cancel: ActionAvailability::allowed(capabilities.cancel),
44        force_cancel: ActionAvailability::allowed(capabilities.force_cancel),
45        enable: ActionAvailability::allowed(flag(pipeline.metadata, "can_enable")),
46        target: namespace_target(pipeline.metadata),
47        ..PipelineActionState::default()
48    }
49}
50
51fn namespace_target(metadata: &std::collections::HashMap<String, Value>) -> Option<DisplayField> {
52    let namespace = metadata.get("namespace").and_then(Value::as_str)?.trim();
53    (!namespace.is_empty()).then(|| DisplayField {
54        label: "Namespace".to_string(),
55        value: namespace.to_string(),
56    })
57}
58
59pub fn run_actions(capabilities: &PluginCapabilities, run: &RunFacts<'_>) -> RunActionState {
60    let active = matches!(
61        run.status,
62        PipelineStatus::Running | PipelineStatus::Pending
63    );
64    let awaiting_review = flag(run.metadata, "awaiting_review");
65
66    RunActionState {
67        cancel: ActionAvailability::allowed(active && capabilities.cancel && !awaiting_review),
68        force_cancel: ActionAvailability::allowed(
69            run.status == PipelineStatus::Running && capabilities.force_cancel,
70        ),
71        rerun: ActionAvailability::allowed(!active && capabilities.trigger),
72        rerun_failed: ActionAvailability::allowed(
73            !active && flag(run.metadata, "can_rerun_failed_jobs"),
74        ),
75        review: ActionAvailability::allowed(awaiting_review),
76        watch: active,
77    }
78}
79
80pub fn run_display(run: &RunFacts<'_>) -> RunDisplay {
81    let context = [("Branch", run.branch), ("Actor", run.actor)]
82        .into_iter()
83        .filter_map(|(label, value)| {
84            value
85                .filter(|value| !value.is_empty())
86                .map(|value| DisplayField {
87                    label: label.to_string(),
88                    value: value.to_string(),
89                })
90        })
91        .collect();
92
93    RunDisplay {
94        title: format!("#{}", run.run_number),
95        message: None,
96        context,
97        started_label: "Started".to_string(),
98    }
99}
100
101pub fn trigger_values(
102    previous: Option<&RunFacts<'_>>, parameters: &[WorkflowParameter],
103) -> Map<String, Value> {
104    let mut inputs = previous
105        .and_then(|run| run.inputs)
106        .and_then(Value::as_object)
107        .cloned()
108        .unwrap_or_default();
109
110    if let Some(branch) = previous
111        .and_then(|run| run.branch)
112        .filter(|branch| !branch.is_empty())
113    {
114        if !truthy(inputs.get("branch")) && !truthy(inputs.get("ref")) {
115            inputs.insert("ref".to_string(), Value::String(branch.to_string()));
116        }
117    }
118
119    let mut values = Map::new();
120    for parameter in parameters {
121        if let Some(replayed) = inputs.get(&parameter.name) {
122            values.insert(parameter.name.clone(), replayed.clone());
123        } else if let Some(default) = default_value(&parameter.param_type) {
124            values.insert(parameter.name.clone(), default);
125        }
126    }
127    values
128}
129
130fn default_value(parameter_type: &WorkflowParameterType) -> Option<Value> {
131    match parameter_type {
132        WorkflowParameterType::String { default }
133        | WorkflowParameterType::Choice { default, .. } => default
134            .as_deref()
135            .filter(|default| !default.trim().is_empty())
136            .map(Value::from),
137        WorkflowParameterType::Boolean { default } => Some(Value::Bool(*default)),
138        WorkflowParameterType::Number { default } => default.map(Value::from),
139    }
140}