Merge branch 'main' into feature/published-dashboard

This commit is contained in:
Lone Iversen
2022-10-07 11:06:34 +02:00
committed by GitHub
12 changed files with 380 additions and 3 deletions

View File

@@ -347,6 +347,58 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
/telemetry/ConsentLevel:
get:
operationId: GetConsentLevel
responses:
'200':
description: 200 response
content:
application/json:
schema:
$ref: '#/components/schemas/ConsentLevelSettings'
default:
description: default response
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
post:
operationId: PostConsentLevel
parameters: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ConsentLevelSettings'
required: true
responses:
'201':
description: 201 response
'400':
description: 400 response
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
/telemetry/ConsentLevels:
get:
operationId: ConsentLevels
responses:
'200':
description: 200 response
content:
application/json:
schema:
type: array
items:
type: string
default:
description: default response
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
components:
schemas:
ConsentLevel:
@@ -1135,3 +1187,10 @@ components:
type: string
required:
- sections
ConsentLevelSettings:
type: object
properties:
telemetryLevel:
$ref: '#/components/schemas/ConsentLevel'
required:
- telemetryLevel

View File

@@ -67,6 +67,13 @@ export interface paths {
"/user/sections": {
get: operations["GetAllowedSections"];
};
"/telemetry/ConsentLevel": {
get: operations["GetConsentLevel"];
post: operations["PostConsentLevel"];
};
"/telemetry/ConsentLevels": {
get: operations["ConsentLevels"];
};
}
export interface components {
@@ -380,6 +387,9 @@ export interface components {
AllowedSectionsResponse: {
sections: string[];
};
ConsentLevelSettings: {
telemetryLevel: components["schemas"]["ConsentLevel"];
};
};
}
@@ -723,6 +733,56 @@ export interface operations {
};
};
};
GetConsentLevel: {
responses: {
/** 200 response */
200: {
content: {
"application/json": components["schemas"]["ConsentLevelSettings"];
};
};
/** default response */
default: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
PostConsentLevel: {
parameters: {};
responses: {
/** 201 response */
201: unknown;
/** 400 response */
400: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
requestBody: {
content: {
"application/json": components["schemas"]["ConsentLevelSettings"];
};
};
};
ConsentLevels: {
responses: {
/** 200 response */
200: {
content: {
"application/json": string[];
};
};
/** default response */
default: {
content: {
"application/json": components["schemas"]["ProblemDetails"];
};
};
};
};
}
export interface external {}

View File

@@ -1,15 +1,150 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { customElement, state } from 'lit/decorators.js';
import type { TelemetryModel } from '../../../core/models';
import { getConsentLevel, getConsentLevels, postConsentLevel } from '../../../core/api/fetcher';
export type SettingOption = 'Minimal' | 'Basic' | 'Detailed';
@customElement('umb-dashboard-telemetry')
export class UmbDashboardTelemetryElement extends LitElement {
static styles = [UUITextStyles, css``];
static styles = [
UUITextStyles,
css`
.italic {
font-style: italic;
}
`,
];
@state()
private _telemetryFormData: TelemetryModel['level'] = 'Basic';
@state()
private _telemetryLevels: TelemetryModel['level'][] = [];
@state()
private _errorMessage = '';
constructor() {
super();
}
connectedCallback(): void {
super.connectedCallback();
this._setup();
}
private async _setup() {
try {
const consentLevels = await getConsentLevels({});
this._telemetryLevels = consentLevels.data as TelemetryModel['level'][];
} catch (e) {
this._errorMessage;
}
try {
const consentSetting = await getConsentLevel({});
this._telemetryFormData = consentSetting.data.telemetryLevel as TelemetryModel['level'];
} catch (e) {
if (e instanceof getConsentLevel.Error) {
this._errorMessage = e.data.detail;
}
}
}
private _handleSubmit = async (e: CustomEvent<SubmitEvent>) => {
e.stopPropagation();
try {
await postConsentLevel({ telemetryLevel: this._telemetryFormData });
} catch (e) {
if (e instanceof postConsentLevel.Error) {
const error = e.getActualType();
if (error.status === 400) {
this._errorMessage = error.data.detail || 'Unknown error, please try again';
}
} else {
this._errorMessage = 'Unknown error, please try again';
}
}
};
disconnectedCallback(): void {
super.disconnectedCallback();
}
private _handleChange(e: InputEvent) {
const target = e.target as HTMLInputElement;
this._telemetryFormData = this._telemetryLevels[parseInt(target.value) - 1];
}
private get _selectedTelemetryIndex() {
return this._telemetryLevels?.findIndex((x) => x === this._telemetryFormData) ?? 0;
}
private get _selectedTelemetry() {
return this._telemetryLevels?.find((x) => x === this._telemetryFormData) ?? this._telemetryLevels[0];
}
private get _selectedTelemetryDescription() {
switch (this._selectedTelemetry) {
case 'Minimal':
return 'We will only send an anonymized site ID to let us know that the site exists.';
case 'Basic':
return 'We will send an anonymized site ID, Umbraco version, and packages installed.';
case 'Detailed':
return `We will send:<ul>
<li>Anonymized site ID, Umbraco version, and packages installed.</li>
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.</li>
<li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
<li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.</li>
</ul>
<i>We might change what we send on the Detailed level in the future. If so, it will be listed above.
By choosing "Detailed" you agree to current and future anonymized information being collected.</i>`;
default:
return 'Could not find description for this setting';
}
}
private _renderSettingSlider() {
if (!this._telemetryLevels || this._telemetryLevels.length < 1) return;
return html`
<uui-slider
@input=${this._handleChange}
name="telemetryLevel"
label="telemetry level"
value=${this._selectedTelemetryIndex + 1}
min="1"
max=${this._telemetryLevels.length}
hide-step-values></uui-slider>
<h2>${this._selectedTelemetry}</h2>
<p>${unsafeHTML(this._selectedTelemetryDescription)}</p>
`;
}
render() {
return html`
<uui-box>
<h1>Telemetry</h1>
<h1>Consent for telemetry data</h1>
<div style="max-width:580px">
<p>
In order to improve Umbraco and add new functionality based on as relevant information as possible, we would
like to collect system- and usage information from your installation. Aggregate data will be shared on a
regular basis as well as learnings from these metrics. Hopefully, you will help us collect some valuable
data.
<br /><br />
We <strong>WILL NOT</strong> collect any personal data such as content, code, user information, and all data
will be fully anonymized.
</p>
${this._renderSettingSlider()}
<uui-button look="primary" color="positive" label="Save telemetry settings" @click="${this._handleSubmit}">
Save
</uui-button>
</div>
</uui-box>
`;
}

View File

@@ -0,0 +1,19 @@
import { expect, fixture, html } from '@open-wc/testing';
import { defaultA11yConfig } from '../../../core/helpers/chai';
import { UmbDashboardTelemetryElement } from './dashboard-telemetry.element';
describe('UmbDashboardTelemetryElement', () => {
let element: UmbDashboardTelemetryElement;
beforeEach(async () => {
element = await fixture(html`<umb-dashboard-telemetry></umb-dashboard-telemetry>`);
});
it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbDashboardTelemetryElement);
});
it('passes the a11y audit', async () => {
await expect(element).to.be.accessible(defaultA11yConfig);
});
});

View File

@@ -21,6 +21,10 @@ export const getUpgradeSettings = fetcher.path('/upgrade/settings').method('get'
export const PostUpgradeAuthorize = fetcher.path('/upgrade/authorize').method('post').create();
export const getManifests = fetcher.path('/manifests').method('get').create();
export const getPackagesInstalled = fetcher.path('/manifests/packages/installed').method('get').create();
export const getConsentLevels = fetcher.path('/telemetry/ConsentLevels').method('get').create();
export const getConsentLevel = fetcher.path('/telemetry/ConsentLevel').method('get').create();
export const postConsentLevel = fetcher.path('/telemetry/ConsentLevel').method('post').create();
// Property Editors
export const getPropertyEditorsList = fetcher.path('/property-editors/list').method('get').create();
@@ -36,3 +40,4 @@ export const getPublishedCacheStatus = fetcher.path('/published-cache/status').m
export const postPublishedCacheReload = fetcher.path('/published-cache/reload').method('post').create();
export const postPublishedCacheRebuild = fetcher.path('/published-cache/rebuild').method('post').create();
export const getPublishedCacheCollect = fetcher.path('/published-cache/collect').method('get').create();

View File

@@ -30,6 +30,7 @@ export type ManifestEntrypoint = components['schemas']['IManifestEntrypoint'];
export type ManifestCustom = components['schemas']['IManifestCustom'];
export type ManifestPackageView = components['schemas']['IManifestPackageView'];
export type PackageInstalled = components['schemas']['PackageInstalled'];
export type ConsentLevelSettings = components['schemas']['ConsentLevelSettings'];
// Property Editors
export type PropertyEditorsListResponse = components['schemas']['PropertyEditorsListResponse'];

View File

@@ -8,6 +8,7 @@ import { handlers as publishedStatusHandlers } from './domains/published-status.
import * as serverHandlers from './domains/server.handlers';
import { handlers as upgradeHandlers } from './domains/upgrade.handlers';
import { handlers as userHandlers } from './domains/user.handlers';
import { handlers as telemetryHandlers } from './domains/telemetry.handlers';
import { handlers as propertyEditorHandlers } from './domains/property-editor.handlers';
const handlers = [
@@ -21,6 +22,7 @@ const handlers = [
...treeHandlers,
...propertyEditorHandlers,
...manifestsHandlers.default,
...telemetryHandlers,
...publishedStatusHandlers,
];

View File

@@ -0,0 +1,33 @@
import { rest } from 'msw';
import umbracoPath from '../../core/helpers/umbraco-path';
import type { ConsentLevelSettings, TelemetryModel } from '../../core/models';
let telemetryLevel: TelemetryModel['level'] = 'Basic';
export const handlers = [
rest.get(umbracoPath('/telemetry/ConsentLevel'), (_req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
ctx.json<ConsentLevelSettings>({
telemetryLevel,
})
);
}),
rest.get(umbracoPath('/telemetry/ConsentLevels'), (_req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
ctx.json<TelemetryModel['level'][]>(['Minimal', 'Basic', 'Detailed'])
);
}),
rest.post<ConsentLevelSettings>(umbracoPath('/telemetry/ConsentLevel'), async (_req, res, ctx) => {
telemetryLevel = (await _req.json<ConsentLevelSettings>()).telemetryLevel;
return res(
// Respond with a 200 status code
ctx.status(201)
);
}),
];

View File

@@ -7,6 +7,7 @@ import { handlers as publishedStatusHandlers } from './domains/published-status.
import * as serverHandlers from './domains/server.handlers';
import { handlers as upgradeHandlers } from './domains/upgrade.handlers';
import { handlers as userHandlers } from './domains/user.handlers';
import { handlers as telemetryHandlers } from './domains/telemetry.handlers';
import { handlers as treeHandlers } from './domains/entity.handlers';
import { handlers as propertyEditorHandlers } from './domains/property-editor.handlers';
@@ -21,6 +22,7 @@ export const handlers = [
...dataTypeHandlers,
...documentTypeHandlers,
...manifestsHandlers.default,
...telemetryHandlers,
...publishedStatusHandlers,
...treeHandlers,
...propertyEditorHandlers,

View File

@@ -100,6 +100,19 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
weight: 10,
},
},
{
type: 'dashboard',
alias: 'Umb.Dashboard.Telemetry',
name: 'Telemetry',
elementName: 'umb-dashboard-telemetry',
loader: () => import('./backoffice/dashboards/telemetry/dashboard-telemetry.element'),
meta: {
label: 'Telemetry Data',
sections: ['Umb.Section.Settings'],
pathname: 'telemetry', // TODO: how do we want to support pretty urls?
weight: 0,
},
},
{
type: 'dashboard',
alias: 'Umb.Dashboard.ExamineManagement',

View File

@@ -4,6 +4,7 @@ import './publishedstatus';
import './server';
import './upgrader';
import './user';
import './telemetry';
import './property-editors';
import { api } from '@airtasker/spot';

View File

@@ -0,0 +1,47 @@
import { body, defaultResponse, endpoint, request, response } from '@airtasker/spot';
import { TelemetryModel } from './installer';
import { ProblemDetails } from './models';
@endpoint({
method: 'GET',
path: '/telemetry/ConsentLevel',
})
export class GetConsentLevel {
@response({ status: 200 })
success(@body body: ConsentLevelSettings) {}
@defaultResponse
default(@body body: ProblemDetails) {}
}
@endpoint({
method: 'GET',
path: '/telemetry/ConsentLevels',
})
export class ConsentLevels {
@response({ status: 200 })
success(@body body: string[]) {}
@defaultResponse
default(@body body: ProblemDetails) {}
}
@endpoint({
method: 'POST',
path: '/telemetry/ConsentLevel',
})
export class PostConsentLevel {
@request
request(@body body: ConsentLevelSettings) {}
@response({ status: 201 })
success() {}
@response({ status: 400 })
badRequest(@body body: ProblemDetails) {}
}
export interface ConsentLevelSettings {
telemetryLevel: TelemetryModel['level'];
}