Because - We want to test Cirrus end to end with the local remote setting, including fml and experimenter to launch an experiment for the Demo app This commit - Integrate fml, remote settings, experimenter, cirrus to demo app - Fixes RS pushing to web PS: currently it uses the static value for client and context, in the next ticket work, we will add an option in the frontend app to set the client value and context https://github.com/mozilla/experimenter/issues/9288 fixes #9265
This commit is contained in:
Родитель
bb15bc85b4
Коммит
d73217d069
10
.env.sample
10
.env.sample
|
@ -50,9 +50,9 @@ STATSD_PORT=
|
|||
STATSD_PREFIX=experimenter
|
||||
USE_GOOGLE_ANALYTICS=false
|
||||
UPLOADS_GS_BUCKET_NAME=
|
||||
REMOTE_SETTING_URL=https://firefox.settings.services.mozilla.com/v1/buckets/main/collections/nimbus-web-experiments/records
|
||||
REMOTE_SETTING_REFRESH_RATE_IN_SECONDS=10
|
||||
APP_ID=test_app_id
|
||||
APP_NAME=test_app_name
|
||||
CHANNEL=developer
|
||||
CIRRUS_FML_PATH=./tests/feature_manifest/sample.fml.yaml
|
||||
REMOTE_SETTING_URL=http://kinto:8888/v1/buckets/main/collections/nimbus-web-experiments/records
|
||||
APP_ID=demo-app-beta
|
||||
APP_NAME=demo_app
|
||||
CHANNEL=beta
|
||||
CIRRUS_FML_PATH=./feature_manifest/sample.yml
|
|
@ -0,0 +1,6 @@
|
|||
REMOTE_SETTING_URL=http://kinto:8888/v1/buckets/main/collections/nimbus-web-experiments/records
|
||||
REMOTE_SETTING_REFRESH_RATE_IN_SECONDS=10
|
||||
APP_ID=test_app_id
|
||||
APP_NAME=test_app_name
|
||||
CHANNEL=developer
|
||||
CIRRUS_FML_PATH=./tests/feature_manifest/sample.fml.yaml
|
4
Makefile
4
Makefile
|
@ -263,10 +263,8 @@ cirrus_generate_docs: cirrus_build
|
|||
$(COMPOSE) run cirrus sh -c '$(CIRRUS_GENERATE_DOCS)'
|
||||
|
||||
build_demo_app:
|
||||
$(COMPOSE) build demo-app-frontend demo-app-server
|
||||
$(COMPOSE_INTEGRATION) build demo-app-frontend demo-app-server
|
||||
|
||||
run_demo_app: build_demo_app
|
||||
$(COMPOSE) up demo-app-frontend demo-app-server
|
||||
|
||||
# nimbus schemas package
|
||||
SCHEMAS_VERSION_FILE := schemas/VERSION
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
about:
|
||||
description: Nimbus Feature Manifest for Python Testing
|
||||
channels:
|
||||
- nightly
|
||||
- developer
|
||||
- beta
|
||||
- release
|
||||
features:
|
||||
example-feature:
|
||||
description: An example feature
|
||||
|
@ -16,9 +16,9 @@ features:
|
|||
type: Option<String>
|
||||
default: null
|
||||
defaults:
|
||||
- channel: nightly
|
||||
- channel: beta
|
||||
value: { "enabled": true }
|
||||
- channel: developer
|
||||
- channel: release
|
||||
value: { "something": "wicked" }
|
||||
|
||||
types:
|
||||
|
|
|
@ -7,9 +7,6 @@ This App is designed to test the Cirrus functionality end-to-end.
|
|||
- To build the Demo App, including frontend and backend components, use the following command:
|
||||
`make build_demo_app`
|
||||
|
||||
- To run the Demo App, use the following command:
|
||||
`make run_demo_app`
|
||||
|
||||
## Frontend is available at
|
||||
|
||||
The frontend of the Demo App is accessible at: [http://localhost:8080](http://localhost:8080)
|
||||
|
@ -17,3 +14,13 @@ The frontend of the Demo App is accessible at: [http://localhost:8080](http://lo
|
|||
## Backend is available at
|
||||
|
||||
The backend of the Demo App is accessible at: [http://localhost:3002](http://localhost:3002)
|
||||
|
||||
## Env config used for the demo app
|
||||
|
||||
```
|
||||
REMOTE_SETTING_URL=http://kinto:8888/v1/buckets/main/collections/nimbus-web-experiments/records
|
||||
APP_ID=demo-app-beta
|
||||
APP_NAME=demo_app
|
||||
CHANNEL=beta
|
||||
CIRRUS_FML_PATH=./feature_manifest/sample.yml
|
||||
```
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import ReactDOM from 'react-dom';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
|
@ -10,20 +8,25 @@ root.render(
|
|||
</React.StrictMode>
|
||||
);
|
||||
|
||||
|
||||
function App() {
|
||||
const [message, setMessage] = useState('');
|
||||
const [message, setMessage] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/data')
|
||||
.then(response => response.json())
|
||||
.then(data => setMessage(data.message))
|
||||
.then(data => {
|
||||
setMessage(data);
|
||||
})
|
||||
.catch(error => console.error('Error fetching data:', error));
|
||||
}, []);
|
||||
|
||||
const displayText = message && message['example-feature'] && message['example-feature']['something']
|
||||
? message['example-feature']['something']
|
||||
: 'Not Enrolled';
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>{message}</h1>
|
||||
<h1>{displayText}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,54 @@
|
|||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const server = http.createServer(async (req, res) => {
|
||||
if (req.url === '/api/data' && req.method === 'GET') {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
const apiInput = {
|
||||
"client_id": "4a1d71ab-29a2-4c5f-9e1d-9d9df2e6e449",
|
||||
"context": {
|
||||
"key1": "value1",
|
||||
"key2": {
|
||||
"key2.1": "value2",
|
||||
"key2.2": "value3"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (req.url === '/api/data' && req.method === 'GET') {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
const jsonResponse = JSON.stringify({ message: 'Hello from the server!' });
|
||||
console.log('Server Response:', jsonResponse);
|
||||
res.end(jsonResponse);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end('Not Found');
|
||||
}
|
||||
const options = {
|
||||
hostname: 'cirrus', // Use the service name
|
||||
port: 8001,
|
||||
path: '/v1/features/',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const reqToOtherAPI = http.request(options, responseFromAPI => {
|
||||
let responseData = '';
|
||||
|
||||
responseFromAPI.on('data', chunk => {
|
||||
responseData += chunk;
|
||||
});
|
||||
|
||||
responseFromAPI.on('end', () => {
|
||||
res.end(responseData);
|
||||
});
|
||||
});
|
||||
|
||||
reqToOtherAPI.on('error', error => {
|
||||
console.error('Error sending request to API:', error);
|
||||
res.statusCode = 500;
|
||||
res.end('Internal Server Error');
|
||||
});
|
||||
|
||||
reqToOtherAPI.write(JSON.stringify(apiInput));
|
||||
reqToOtherAPI.end();
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end('Not Found');
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = 3002;
|
||||
|
|
|
@ -39,6 +39,18 @@ services:
|
|||
ports:
|
||||
- "5000:5000"
|
||||
|
||||
cirrus:
|
||||
build:
|
||||
context: cirrus/server/
|
||||
dockerfile: Dockerfile
|
||||
env_file: .env
|
||||
links:
|
||||
- kinto
|
||||
volumes:
|
||||
- ./cirrus/server/:/cirrus
|
||||
working_dir: /cirrus
|
||||
ports:
|
||||
- "8001:8001"
|
||||
demo-app-server:
|
||||
build:
|
||||
context: ./demo-app/server
|
||||
|
@ -46,6 +58,11 @@ services:
|
|||
ports:
|
||||
- '3002:3002'
|
||||
restart: always
|
||||
links:
|
||||
- cirrus
|
||||
environment:
|
||||
- CIRRUS=http://cirrus:8001
|
||||
|
||||
|
||||
demo-app-frontend:
|
||||
build:
|
||||
|
|
|
@ -20,7 +20,7 @@ services:
|
|||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
cirrus:
|
||||
env_file: .env.sample
|
||||
env_file: .env.test
|
||||
build:
|
||||
context: cirrus/server/
|
||||
dockerfile: Dockerfile
|
||||
|
|
|
@ -117,6 +117,8 @@ services:
|
|||
context: cirrus/server/
|
||||
dockerfile: Dockerfile
|
||||
env_file: .env
|
||||
links:
|
||||
- kinto
|
||||
volumes:
|
||||
- ./cirrus/server/:/cirrus
|
||||
working_dir: /cirrus
|
||||
|
@ -129,6 +131,11 @@ services:
|
|||
ports:
|
||||
- '3002:3002'
|
||||
restart: always
|
||||
links:
|
||||
- cirrus
|
||||
environment:
|
||||
- CIRRUS=http://cirrus:8001
|
||||
|
||||
|
||||
demo-app-frontend:
|
||||
build:
|
||||
|
|
|
@ -15,6 +15,7 @@ KINTO_BUCKET_MAIN = "main"
|
|||
KINTO_COLLECTION_NIMBUS_DESKTOP = "nimbus-desktop-experiments"
|
||||
KINTO_COLLECTION_NIMBUS_MOBILE = "nimbus-mobile-experiments"
|
||||
KINTO_COLLECTION_NIMBUS_PREVIEW = "nimbus-preview"
|
||||
KINTO_COLLECTION_NIMBUS_WEB = "nimbus-web-experiments"
|
||||
|
||||
|
||||
def create_user(user, passw):
|
||||
|
@ -47,6 +48,7 @@ def setup():
|
|||
KINTO_COLLECTION_NIMBUS_DESKTOP,
|
||||
KINTO_COLLECTION_NIMBUS_MOBILE,
|
||||
KINTO_COLLECTION_NIMBUS_PREVIEW,
|
||||
KINTO_COLLECTION_NIMBUS_WEB,
|
||||
]:
|
||||
print(">>>> Creating kinto group: editors")
|
||||
print(
|
||||
|
|
|
@ -289,7 +289,8 @@ class NimbusConfigurationDataClass:
|
|||
for f in NimbusFeatureConfig.objects.all()
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"schemas", queryset=NimbusVersionedSchema.objects.filter(version=None)
|
||||
"schemas",
|
||||
queryset=NimbusVersionedSchema.objects.filter(version=None),
|
||||
)
|
||||
)
|
||||
.order_by("name")
|
||||
|
@ -1481,6 +1482,9 @@ class NimbusReviewSerializer(serializers.ModelSerializer):
|
|||
is_rollout = data.get("is_rollout")
|
||||
application = data.get("application")
|
||||
|
||||
if NimbusExperiment.Application.is_web(application):
|
||||
return data
|
||||
|
||||
if min_version == "":
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
|
|
|
@ -139,6 +139,18 @@ APPLICATION_CONFIG_MONITOR_WEB = ApplicationConfig(
|
|||
randomization_unit=BucketRandomizationUnit.USER_ID,
|
||||
)
|
||||
|
||||
APPLICATION_CONFIG_DEMO_APP = ApplicationConfig(
|
||||
name="Demo App",
|
||||
slug="demo-app",
|
||||
app_name="demo_app",
|
||||
channel_app_id={
|
||||
Channel.BETA: "demo-app-beta",
|
||||
Channel.RELEASE: "demo-app-release",
|
||||
},
|
||||
kinto_collection=settings.KINTO_COLLECTION_NIMBUS_WEB,
|
||||
randomization_unit=BucketRandomizationUnit.USER_ID,
|
||||
)
|
||||
|
||||
|
||||
NO_FEATURE_SLUG = [
|
||||
"no-feature-focus-android",
|
||||
|
@ -176,6 +188,7 @@ class Application(models.TextChoices):
|
|||
APPLICATION_CONFIG_MONITOR_WEB.slug,
|
||||
APPLICATION_CONFIG_MONITOR_WEB.name,
|
||||
)
|
||||
DEMO_APP = (APPLICATION_CONFIG_DEMO_APP.slug, APPLICATION_CONFIG_DEMO_APP.name)
|
||||
|
||||
@staticmethod
|
||||
def is_mobile(application):
|
||||
|
@ -188,6 +201,13 @@ class Application(models.TextChoices):
|
|||
Application.KLAR_IOS,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_web(application):
|
||||
return application in (
|
||||
Application.DEMO_APP,
|
||||
Application.MONITOR,
|
||||
)
|
||||
|
||||
|
||||
class NimbusConstants(object):
|
||||
class Status(models.TextChoices):
|
||||
|
@ -233,6 +253,7 @@ class NimbusConstants(object):
|
|||
Application.FOCUS_IOS: APPLICATION_CONFIG_FOCUS_IOS,
|
||||
Application.KLAR_IOS: APPLICATION_CONFIG_KLAR_IOS,
|
||||
Application.MONITOR: APPLICATION_CONFIG_MONITOR_WEB,
|
||||
Application.DEMO_APP: APPLICATION_CONFIG_DEMO_APP,
|
||||
}
|
||||
|
||||
Channel = Channel
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# Generated by Django 3.2.20 on 2023-08-18 17:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("experiments", "0246_alter_nimbusexperiment_channel"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="nimbusexperiment",
|
||||
name="application",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("firefox-desktop", "Firefox Desktop"),
|
||||
("fenix", "Firefox for Android (Fenix)"),
|
||||
("ios", "Firefox for iOS"),
|
||||
("focus-android", "Focus for Android"),
|
||||
("klar-android", "Klar for Android"),
|
||||
("focus-ios", "Focus for iOS"),
|
||||
("klar-ios", "Klar for iOS"),
|
||||
("monitor-web", "Monitor Web"),
|
||||
("demo-app", "Demo App"),
|
||||
],
|
||||
max_length=255,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="nimbusfeatureconfig",
|
||||
name="application",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("firefox-desktop", "Firefox Desktop"),
|
||||
("fenix", "Firefox for Android (Fenix)"),
|
||||
("ios", "Firefox for iOS"),
|
||||
("focus-android", "Focus for Android"),
|
||||
("klar-android", "Klar for Android"),
|
||||
("focus-ios", "Focus for iOS"),
|
||||
("klar-ios", "Klar for iOS"),
|
||||
("monitor-web", "Monitor Web"),
|
||||
("demo-app", "Demo App"),
|
||||
],
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="nimbusisolationgroup",
|
||||
name="application",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("firefox-desktop", "Firefox Desktop"),
|
||||
("fenix", "Firefox for Android (Fenix)"),
|
||||
("ios", "Firefox for iOS"),
|
||||
("focus-android", "Focus for Android"),
|
||||
("klar-android", "Klar for Android"),
|
||||
("focus-ios", "Focus for iOS"),
|
||||
("klar-ios", "Klar for iOS"),
|
||||
("monitor-web", "Monitor Web"),
|
||||
("demo-app", "Demo App"),
|
||||
],
|
||||
max_length=255,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -2424,16 +2424,19 @@ class TestNimbusReviewSerializerSingleFeature(TestCase):
|
|||
).data,
|
||||
context={"user": self.user},
|
||||
)
|
||||
if NimbusExperiment.Application.is_web(application):
|
||||
self.assertTrue(serializer.is_valid())
|
||||
else:
|
||||
self.assertFalse(serializer.is_valid())
|
||||
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEquals(
|
||||
serializer.errors,
|
||||
{
|
||||
"firefox_min_version": [
|
||||
NimbusExperiment.ERROR_EXCLUDED_REQUIRED_MIN_VERSION
|
||||
],
|
||||
},
|
||||
)
|
||||
self.assertEquals(
|
||||
serializer.errors,
|
||||
{
|
||||
"firefox_min_version": [
|
||||
NimbusExperiment.ERROR_EXCLUDED_REQUIRED_MIN_VERSION
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def test_targeting_exclude_require_mutally_exclusive(self):
|
||||
other = NimbusExperimentFactory.create_with_lifecycle(
|
||||
|
@ -3023,7 +3026,11 @@ class TestNimbusReviewSerializerMultiFeature(TestCase):
|
|||
def test_minimum_version(self, application, firefox_min_version):
|
||||
valid_version = NimbusExperiment.Version.parse(
|
||||
firefox_min_version
|
||||
) >= NimbusExperiment.Version.parse(NimbusExperiment.MIN_REQUIRED_VERSION)
|
||||
) >= NimbusExperiment.Version.parse(
|
||||
NimbusExperiment.MIN_REQUIRED_VERSION
|
||||
) or NimbusExperiment.Application.is_web(
|
||||
application
|
||||
)
|
||||
|
||||
experiment = NimbusExperimentFactory.create_with_lifecycle(
|
||||
NimbusExperimentFactory.Lifecycles.CREATED,
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
example-feature:
|
||||
description: An example feature
|
||||
hasExposure: true
|
||||
exposureDescription: ""
|
||||
variables:
|
||||
enabled:
|
||||
type: boolean
|
||||
description: If the feature is enabled
|
||||
something:
|
||||
type: string
|
||||
description: Another variable
|
|
@ -256,6 +256,7 @@ enum NimbusExperimentApplicationEnum {
|
|||
FOCUS_IOS
|
||||
KLAR_IOS
|
||||
MONITOR
|
||||
DEMO_APP
|
||||
}
|
||||
|
||||
enum NimbusExperimentChannelEnum {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
//==============================================================
|
||||
|
||||
export enum NimbusExperimentApplicationEnum {
|
||||
DEMO_APP = "DEMO_APP",
|
||||
DESKTOP = "DESKTOP",
|
||||
FENIX = "FENIX",
|
||||
FOCUS_ANDROID = "FOCUS_ANDROID",
|
||||
|
|
Загрузка…
Ссылка в новой задаче