fix #9265 feat(cirrus): Demo app integration with fml and cirrus (#9289)

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:
Yashika Khurana 2023-08-18 11:46:05 -07:00 коммит произвёл GitHub
Родитель bb15bc85b4
Коммит d73217d069
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 237 добавлений и 45 удалений

Просмотреть файл

@ -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

6
.env.test Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -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",