1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::{
    path::{Path, PathBuf},
    time::{SystemTime, UNIX_EPOCH},
};

use anyhow::Result;
use heck::ToKebabCase;
use serde_json::{json, Value};

use crate::{value_utils, NimbusApp};

pub(crate) fn create_experiment(
    app: &NimbusApp,
    feature_id: &str,
    files: &Vec<PathBuf>,
) -> Result<Value> {
    let mut branches = Vec::new();
    for f in files {
        branches.push(branch(feature_id, f)?);
    }

    let control = slug(files.first().unwrap())?;

    let start = SystemTime::now();
    let now = start
        .duration_since(UNIX_EPOCH)
        .expect("Time went backawards");

    let user = whoami::username();
    let slug = format!(
        "{} test {} {:x}",
        user,
        feature_id,
        now.as_secs() & ((1u64 << 16) - 1)
    )
    .to_kebab_case();

    let app_name = app
        .app_name()
        .expect("An app name is expected. This is a bug in nimbus-cli");
    Ok(json!({
        "appId": &app_name,
        "appName": &app_name,
        "application": &app_name,
        "arguments": {},
        "branches": branches,
        "bucketConfig": {
            "count": 10_000,
            "namespace": format!("{}-1", &slug),
            "randomizationUnit": "nimbus_id",
            "start": 0,
            "total": 10_000
        },
        "channel": app.channel,
        "endDate": null,
        "enrollmentEndDate": null,
        "featureIds": [
          feature_id,
        ],
        "featureValidationOptOut": false,
        "id": &slug,
        "isEnrollmentPaused": false,
        "isRollout": false,
        "last_modified": now.as_secs() * 1000,
        "outcomes": [],
        "probeSets": [],
        "proposedDuration": 7,
        "proposedEnrollment": 7,
        "referenceBranch": control,
        "schemaVersion": "1.11.0",
        "slug": &slug,
        "startDate": null,
        "targeting": "true",
        "userFacingDescription": format!("Testing the {} feature from nimbus-cli", feature_id),
        "userFacingName": format!("[{}] Testing {}", &user, feature_id)
    }))
}

pub(crate) fn slug(path: &Path) -> Result<String> {
    let filename = path
        .file_stem()
        .ok_or_else(|| anyhow::Error::msg("File has no filename"))?;
    Ok(filename.to_string_lossy().to_string().to_kebab_case())
}

fn branch(feature_id: &str, file: &Path) -> Result<Value> {
    let value: Value = value_utils::read_from_file(file)?;

    let config = value.as_object().ok_or_else(|| {
        anyhow::Error::msg(format!(
            "{} does not contain a JSON object",
            file.to_str().unwrap()
        ))
    })?;

    Ok(json!({
      "feature": {
        "enabled": true,
        "featureId": feature_id,
        "value": config,
      },
      "slug": slug(file)?,
    }))
}