nimbus_cli/
feature_utils.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::{
6    path::{Path, PathBuf},
7    time::{SystemTime, UNIX_EPOCH},
8};
9
10use anyhow::Result;
11use heck::ToKebabCase;
12use serde_json::{json, Value};
13
14use crate::{value_utils, NimbusApp};
15
16pub(crate) fn create_experiment(
17    app: &NimbusApp,
18    feature_id: &str,
19    files: &Vec<PathBuf>,
20) -> Result<Value> {
21    let mut branches = Vec::new();
22    for f in files {
23        branches.push(branch(feature_id, f)?);
24    }
25
26    let control = slug(files.first().unwrap())?;
27
28    let start = SystemTime::now();
29    let now = start
30        .duration_since(UNIX_EPOCH)
31        .expect("Time went backawards");
32
33    let user = whoami::username();
34    let slug = format!(
35        "{} test {} {:x}",
36        user,
37        feature_id,
38        now.as_secs() & ((1u64 << 16) - 1)
39    )
40    .to_kebab_case();
41
42    let app_name = app
43        .app_name()
44        .expect("An app name is expected. This is a bug in nimbus-cli");
45    Ok(json!({
46        "appId": &app_name,
47        "appName": &app_name,
48        "application": &app_name,
49        "arguments": {},
50        "branches": branches,
51        "bucketConfig": {
52            "count": 10_000,
53            "namespace": format!("{}-1", &slug),
54            "randomizationUnit": "nimbus_id",
55            "start": 0,
56            "total": 10_000
57        },
58        "channel": app.channel,
59        "endDate": null,
60        "enrollmentEndDate": null,
61        "featureIds": [
62          feature_id,
63        ],
64        "featureValidationOptOut": false,
65        "id": &slug,
66        "isEnrollmentPaused": false,
67        "isRollout": false,
68        "last_modified": now.as_secs() * 1000,
69        "outcomes": [],
70        "probeSets": [],
71        "proposedDuration": 7,
72        "proposedEnrollment": 7,
73        "referenceBranch": control,
74        "schemaVersion": "1.11.0",
75        "slug": &slug,
76        "startDate": null,
77        "targeting": "true",
78        "userFacingDescription": format!("Testing the {} feature from nimbus-cli", feature_id),
79        "userFacingName": format!("[{}] Testing {}", &user, feature_id)
80    }))
81}
82
83pub(crate) fn slug(path: &Path) -> Result<String> {
84    let filename = path
85        .file_stem()
86        .ok_or_else(|| anyhow::Error::msg("File has no filename"))?;
87    Ok(filename.to_string_lossy().to_string().to_kebab_case())
88}
89
90fn branch(feature_id: &str, file: &Path) -> Result<Value> {
91    let value: Value = value_utils::read_from_file(file)?;
92
93    let config = value.as_object().ok_or_else(|| {
94        anyhow::Error::msg(format!(
95            "{} does not contain a JSON object",
96            file.to_str().unwrap()
97        ))
98    })?;
99
100    Ok(json!({
101      "feature": {
102        "enabled": true,
103        "featureId": feature_id,
104        "value": config,
105      },
106      "slug": slug(file)?,
107    }))
108}