v10: Set telemetry level in installer (#12365)

* Add slider to user view

* fix typo 'constentLevels'

* Add TelemetryLevel to UserModel

* Extend NewInstallStep to include descriptions of each TelemetryLevel

* Add nouislider as dependency for installer view

* Add telemetry levels

Add raw nouislider to select a telemetry level + set chosen level onChange to the user model + show detailed description of each level as a tooltip

* Copy over basic styling of nouislider

* Save consent level in installer

* Fix detailed key to not contain <br>

* Fix MetricsConsentService to log correctly when we set analyticsLevel in installer

* Fix breaking change and obsolete messages

* reinstate saved value of the subscription field if you traverse the steps

* calculate the initial slider level based on the saved value of the model

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
This commit is contained in:
Nikolaj Geisle
2022-05-19 15:49:08 +02:00
committed by GitHub
parent f7bf6d5959
commit 9a4daa451a
10 changed files with 454 additions and 228 deletions

View File

@@ -1,24 +1,25 @@
LazyLoad.js([
'lib/jquery/jquery.min.js',
'lib/jquery/jquery.min.js',
'lib/angular/angular.min.js',
'lib/angular-cookies/angular-cookies.min.js',
'lib/angular-touch/angular-touch.min.js',
'lib/angular-sanitize/angular-sanitize.min.js',
'lib/angular-messages/angular-messages.min.js',
'lib/angular-aria/angular-aria.min.js',
'lib/underscore/underscore-min.js',
'lib/angular-ui-sortable/sortable.min.js',
'lib/angular/angular.min.js',
'lib/angular-cookies/angular-cookies.min.js',
'lib/angular-touch/angular-touch.min.js',
'lib/angular-sanitize/angular-sanitize.min.js',
'lib/angular-messages/angular-messages.min.js',
'lib/angular-aria/angular-aria.min.js',
'lib/underscore/underscore-min.js',
'lib/angular-ui-sortable/sortable.min.js',
'lib/nouislider/nouislider.min.js',
'js/utilities.min.js',
'js/utilities.min.js',
'js/installer.app.min.js',
'js/umbraco.directives.min.js',
'js/umbraco.installer.min.js'
'js/installer.app.min.js',
'js/umbraco.directives.min.js',
'js/umbraco.installer.min.js'
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['umbraco']);
});
jQuery(document).ready(function () {
angular.bootstrap(document, ['umbraco']);
});
}
);

View File

@@ -1,27 +1,106 @@
angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) {
angular.module("umbraco.install").controller("Umbraco.Install.UserController", function ($scope, $sce, installerService) {
$scope.majorVersion = Umbraco.Sys.ServerVariables.application.version;
$scope.passwordPattern = /.*/;
$scope.installer.current.model.subscribeToNewsLetter = false;
$scope.majorVersion = Umbraco.Sys.ServerVariables.application.version;
$scope.passwordPattern = /.*/;
$scope.installer.current.model.subscribeToNewsLetter = $scope.installer.current.model.subscribeToNewsLetter || false;
$scope.installer.current.model.telemetryLevel = $scope.installer.current.model.telemetryLevel || $scope.installer.current.model.consentLevels[1].level;
if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
var exp = "";
for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) {
exp += ".*[\\W].*";
}
//replace duplicates
exp = exp.replace(".*.*", ".*");
$scope.passwordPattern = new RegExp(exp);
if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
var exp = "";
for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) {
exp += ".*[\\W].*";
}
//replace duplicates
exp = exp.replace(".*.*", ".*");
$scope.passwordPattern = new RegExp(exp);
}
$scope.consentTooltip = {
show: false,
event: null
};
if ('noUiSlider' in window) {
let consentSliderStartLevel = 2;
const initialConsentLevel = $scope.installer.current.model.consentLevels.findIndex(x => x.level === $scope.installer.current.model.telemetryLevel);
if (initialConsentLevel !== -1) {
consentSliderStartLevel = initialConsentLevel + 1;
}
$scope.validateAndInstall = function() {
installerService.install();
};
const sliderOptions =
{
"start": consentSliderStartLevel,
"step": 1,
"tooltips": [false],
"range": {
"min": 1,
"max": 3
},
pips: {
mode: 'values',
density: 50,
values: [1, 2, 3],
"format": {
to: function (value) {
return $scope.installer.current.model.consentLevels[value - 1].level;
},
from: function (value) {
return Number(value);
}
}
}
};
$scope.validateAndForward = function(){
if (this.myForm.$valid) {
installerService.forward();
}
};
const consentSlider = document.getElementById("consentSlider");
if (consentSlider) {
window.noUiSlider.create(consentSlider, sliderOptions);
consentSlider.noUiSlider.on('change', onChangeConsent);
const pips = consentSlider.querySelectorAll('.noUi-value');
$(consentSlider).on('$destroy', function () {
consentSlider.noUiSlider.off();
});
pips.forEach(function (pip) {
pip.addEventListener('mouseenter', function (e) {
$scope.$apply(function () {
const value = pip.getAttribute('data-value');
$scope.consentTooltip.show = true;
$scope.consentTooltip.event = e;
$scope.consentTooltip.description = $sce.trustAsHtml($scope.installer.current.model.consentLevels[value - 1].description);
});
});
pip.addEventListener('mouseleave', function () {
$scope.$apply(function () {
$scope.consentTooltip.show = false;
$scope.consentTooltip.event = null;
$scope.consentTooltip.description = '';
});
});
pip.addEventListener('click', function () {
const value = pip.getAttribute('data-value');
consentSlider.noUiSlider.set(value);
});
});
}
}
$scope.validateAndInstall = function () {
installerService.install();
};
$scope.validateAndForward = function () {
if (this.myForm.$valid) {
installerService.forward();
}
};
function onChangeConsent(values) {
const result = Number(values[0]);
$scope.installer.current.model.telemetryLevel = $scope.installer.current.model.consentLevels[result - 1].level;
};
});

View File

@@ -1,85 +1,88 @@
<div ng-controller="Umbraco.Install.UserController">
<h1>Install Umbraco</h1>
<p>Enter your name, email and password for this Umbraco installation.</p>
<p>Enter your name, email and password to install Umbraco with its default settings, alternatively you can customize
your installation</p>
<form name="myForm" class="form-horizontal" novalidate ng-submit="validateAndInstall();">
<div class="row">
<div class="span8">
<div class="pull-right">
<div class="control-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" id="name" name="name" placeholder="Full name" required ng-model="installer.current.model.name" />
<input type="text" id="name" name="name" placeholder="Full name" required
ng-model="installer.current.model.name" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="email" id="email" name="email" placeholder="you@example.com" val-email required ng-model="installer.current.model.email" />
<input type="email" id="email" name="email" placeholder="you@example.com" val-email required
ng-model="installer.current.model.email" />
<small class="inline-help">Your email will be used as your login</small>
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">Password</label>
<div class="controls">
<!-- why isn't this masked: https://www.nngroup.com/articles/stop-password-masking/ -->
<input type="text" name="installer.current.model.password" ng-minlength="{{installer.current.model.minCharLength}}" ng-pattern="passwordPattern" autocorrect="off" autocapitalize="off" autocomplete="off" required ng-model="installer.current.model.password" id="password" />
<input type="text" name="installer.current.model.password"
ng-minlength="{{installer.current.model.minCharLength}}" ng-pattern="passwordPattern" autocorrect="off"
autocapitalize="off" autocomplete="off" required ng-model="installer.current.model.password"
id="password" />
<small class="inline-help">At least {{installer.current.model.minCharLength}} characters long</small>
<small ng-if="installer.current.model.minNonAlphaNumericLength > 0" class="inline-help">
At least {{installer.current.model.minNonAlphaNumericLength}} symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}}
At least {{installer.current.model.minNonAlphaNumericLength}}
symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}}
</small>
</div>
</div>
<div class="control-group">
<label class="control-label">Telemetry</label>
<div class="controls">
<div id="consentSliderWrapper">
<div id="consentSlider"></div>
<umb-tooltip ng-if="consentTooltip.show" event="consentTooltip.event">
<div ng-bind-html="consentTooltip.description"></div>
</umb-tooltip>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<label>
<input type="checkbox" id="subscribeToNewsLetter" name="subscribeToNewsLetter" ng-model="installer.current.model.subscribeToNewsLetter" />
<input type="checkbox" id="subscribeToNewsLetter" name="subscribeToNewsLetter"
ng-model="installer.current.model.subscribeToNewsLetter" />
Keep me updated on Umbraco Versions, Security Bulletins and Community News
</label>
</div>
</div>
</div>
</div>
</div>
<div class="row" ng-if="installer.current.model.quickInstallSettings && installer.current.model.customInstallAvailable" ng-style="{'opacity': myForm.$invalid ? '0.4' : ''}">
<legend>Database Configuration</legend>
<div class="span8">
<div class="pull-right">
<div class="span12">
<div class="control-group" style="margin-bottom: 10px;">
<label class="control-label" for="dbType">Database type</label>
<div class="controls">
<div class="input-readonly-text">{{installer.current.model.quickInstallSettings.displayName}}</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="Sql_databaseName">Database name</label>
<div class="controls">
<div class="input-readonly-text">{{installer.current.model.quickInstallSettings.defaultDatabaseName}}</div>
</div>
</div>
</div>
<div class="span12">
<div class="control-group">
<div class="controls">
<button class="btn btn-info control-customize" ng-disabled="myForm.$invalid" ng-click="validateAndForward()">Change Database Configuration</button>
</div>
</div>
</div>
</div>
</div>
<legend></legend>
</div>
<div class="row">
<div class="span8">
<div class="pull-right">
<div class="control-group" ng-class="{disabled:myForm.$invalid}">
<div class="controls">
<input ng-if="installer.current.model.quickInstallSettings" type="submit" ng-disabled="myForm.$invalid" value="Install" class="btn btn-success" />
<button ng-if="!installer.current.model.quickInstallSettings" class="btn btn-primary control-customize" ng-disabled="myForm.$invalid" ng-click="validateAndForward()">Next</button>
<input ng-if="installer.current.model.quickInstallAvailable" type="submit" ng-disabled="myForm.$invalid"
value="Install" class="btn btn-success" />
<button
ng-if="installer.current.model.quickInstallAvailable && installer.current.model.customInstallAvailable"
class="btn btn-info control-customize" ng-disabled="myForm.$invalid"
ng-click="validateAndForward()">Customize</button>
<button ng-if="!installer.current.model.quickInstallAvailable" class="btn btn-primary control-customize"
ng-disabled="myForm.$invalid" ng-click="validateAndForward()">Next</button>
</div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -17,146 +17,155 @@
// Umbraco Components
@import "components/umb-loader.less";
@import "components/tooltip/umb-tooltip.less";
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
[ng\:cloak],
[ng-cloak],
[data-ng-cloak],
[x-ng-cloak],
.ng-cloak,
.x-ng-cloak {
display: none !important;
}
html {
background: url('../img/installer.svg') no-repeat center center fixed;
background-size: cover;
background: url('../img/installer.svg') no-repeat center center fixed;
background-size: cover;
}
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
font-family: @baseFontFamily;
font-size: @baseFontSize;
line-height: @baseLineHeight;
color: @textColor;
vertical-align: middle;
text-align: center;
// better font rendering
-webkit-font-smoothing: antialiased;
font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
font-family: @baseFontFamily;
font-size: @baseFontSize;
line-height: @baseLineHeight;
color: @textColor;
vertical-align: middle;
text-align: center;
// better font rendering
-webkit-font-smoothing: antialiased;
font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#logo {
position: absolute;
top: 20px;
left: 20px;
opacity: 0.8;
z-index: 777;
position: absolute;
top: 20px;
left: 20px;
opacity: 0.8;
z-index: 777;
}
#installer {
margin: auto;
background: @white;
width: 80%;
max-width: 750px;
height: 640px;
text-align: left;
padding: 30px;
z-index: 667;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.16);
margin: auto;
background: @white;
width: 80%;
max-width: 750px;
height: 640px;
text-align: left;
padding: 30px;
z-index: 667;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.16);
}
#overlay {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: @purple-d2;
z-index: 666;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: @purple-d2;
z-index: 666;
}
.loading #overlay {
opacity: 0.5;
display: block !important;
opacity: 0.5;
display: block !important;
}
#fact {
color: @white;
text-shadow: 0px 0px 4px @black;
font-size: 25px;
text-align: left;
line-height: 35px;
z-index: 667;
height: 600px;
width: 750px;
h2 {
font-size: 35px;
border-bottom: 1px solid @white;
padding-bottom: 10px;
margin-bottom: 20px;
color: @white;
text-shadow: 0px 0px 4px @black;
font-size: 25px;
text-align: left;
line-height: 35px;
z-index: 667;
height: 600px;
width: 750px;
}
h2 {
font-size: 35px;
border-bottom: 1px solid @white;
padding-bottom: 10px;
margin-bottom: 20px;
color: @white;
}
a {
color: @white;
}
a {
color: @white;
}
}
#feedback {
color: @white;
text-shadow: 0px 0px 4px @black;
font-size: 14px;
text-align: center;
line-height: 20px;
z-index: 667;
bottom: 20px;
right: 0;
left: 0;
height: 25px;
position: absolute;
color: @white;
text-shadow: 0px 0px 4px @black;
font-size: 14px;
text-align: center;
line-height: 20px;
z-index: 667;
bottom: 20px;
right: 0;
left: 0;
height: 25px;
position: absolute;
}
h1 {
border-bottom: 1px solid @gray-10;
padding-bottom: 10px;
color: @gray-2;
border-bottom: 1px solid @gray-10;
padding-bottom: 10px;
color: @gray-2;
}
.error h1, .error .message, span.error {
color: @red;
.error h1,
.error .message,
span.error {
color: @red;
}
legend {
font-size: 14px;
font-weight: bold
font-size: 14px;
font-weight: bold
}
input.ng-dirty.ng-invalid {
border-color: @pink;
color: @pink;
border-color: @pink;
color: @pink;
}
.disabled {
opacity: 0.6;
opacity: 0.6;
}
#installer label.control-label,
#installer .constrol-label {
padding-top: 5px !important;
padding-top: 5px !important;
}
.controls {
text-align: left;
text-align: left;
small {
display: block;
color: @gray-3;
}
small {
display: block;
color: @gray-3;
}
}
#installer .input-readonly-text {
@@ -168,101 +177,153 @@ input.ng-dirty.ng-invalid {
}
.absolute-center {
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.fade-hide,
.fade-show {
transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
.fade-hide {
opacity: 1;
opacity: 1;
}
.fade-hide.fade-hide-active {
opacity: 0;
opacity: 0;
}
.fade-show {
opacity: 0;
opacity: 0;
}
.fade-show.fade-show-active {
opacity: 1;
opacity: 1;
}
.umb-installer-loader {
margin: 0;
width: 0;
z-index: 777;
margin: 0;
width: 0;
z-index: 777;
.umb-loader {
background-color: @white;
height: 5px;
}
.umb-loader {
background-color: @white;
height: 5px;
}
}
.permissions-report {
overflow: auto;
height: 320px;
margin: 0;
display: block;
padding: 0;
overflow: auto;
height: 320px;
margin: 0;
display: block;
padding: 0;
}
.permissions-report > li {
list-style: none;
.permissions-report>li {
list-style: none;
}
.permissions-report h4 {
margin: 7px;
margin: 7px;
}
.upgrade-report {
overflow: auto;
height: 280px;
display: block;
overflow: auto;
height: 280px;
display: block;
}
select {
width: 320px;
width: 320px;
}
#ysod {
height: 500px;
width: 100%;
overflow: auto;
border: none;
height: 500px;
width: 100%;
overflow: auto;
border: none;
}
#starterKits {
.thumbnails {
min-height: 128px;
padding-left: 0;
.thumbnails {
min-height: 128px;
padding-left: 0;
}
.thumbnail {
position: relative;
cursor: pointer;
small {
padding: 10px 10px 5px;
display: inline-block;
}
.thumbnail {
position: relative;
cursor: pointer;
small {
padding: 10px 10px 5px;
display: inline-block;
}
img {
position: relative;
z-index: 100;
}
img {
position: relative;
z-index: 100;
}
}
.btn-link {
padding-left: 0;
}
.btn-link {
padding-left: 0;
}
}
.umb-tooltip {
border: 1px solid @ui-action-border;
max-width: 500px;
}
#consentSliderWrapper {
margin-bottom: 40px;
}
#consentSlider {
.noUi-target {
background: linear-gradient(to bottom, @grayLighter 0%, @grayLighter 100%);
box-shadow: none;
border-radius: 20px;
height: 8px;
border: 1px solid @inputBorder;
&:focus,
&:focus-within {
border-color: @inputBorderFocus;
}
}
.noUi-handle {
cursor: grab;
border-radius: 100px;
border: none;
box-shadow: none;
width: 20px !important;
height: 20px !important;
right: -10px !important; // half the handle width
top: -1px;
background-color: @blueExtraDark;
}
.noUi-handle::before {
display: none;
}
.noUi-handle::after {
display: none;
}
.noUi-value {
cursor: pointer;
}
.noUi-pips-horizontal {
height: 40px;
}
}

View File

@@ -37,9 +37,9 @@
</div>
<div ng-if="vm.sliderVal === 'Detailed'">
<b>{{vm.sliderVal}}</b>
<br>
<localize key="analytics_detailedLevelDescription">
<br> We will send:
We will send:
<br>- Anonymized site ID, umbraco version, and packages installed.
<br>- Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.
<br>- System information: Webserver, server OS, server framework, server OS language, and database provider.