Skip to content

Commit

Permalink
Make some data in a dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
Rockerby committed Mar 6, 2025
1 parent 9488291 commit cfbe050
Show file tree
Hide file tree
Showing 25 changed files with 647 additions and 422 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
]
},
"ConnectionStrings": {
"umbracoDbDSN": "Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Shared;Foreign Keys=True;Pooling=True",
"umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite"
"umbracoDbDSN": "server=MSI\\SQLEXPRESS;database=emaillog;Integrated Security=True;TrustServerCertificate=true;",
"umbracoDbDSN_ProviderName": "Microsoft.Data.SqlClient"
},
"Umbraco": {
"CMS": {
Expand Down
7 changes: 6 additions & 1 deletion src/Umbraco.Community.EmailLogger.TestSite/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
"CMS": {
"Global": {
"Id": "bf3f8803-86eb-4dfd-8a01-7fd91f2f6f4a",
"SanitizeTinyMce": true
"SanitizeTinyMce": true,
"Smtp": {
"Host": "localhost",
"Port": 587,
"From": "test@test.com"
}
},
"Content": {
"AllowEditInvariantFromNonDefault": true,
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Community.EmailLogger/Client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"watch": "tsc && vite build --watch",
"build": "tsc && vite build",
"generate-client": "node scripts/generate-openapi.js 'https://localhost:44324'/umbraco/swagger/umbracocommunityemaillogger/swagger.json"
"generate-client": "node scripts/generate-openapi.js https://localhost:44324/umbraco/swagger/umbracocommunityemaillogger/swagger.json"
},
"devDependencies": {
"@hey-api/client-fetch": "^0.4.2",
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Community.EmailLogger/Client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts
export * from './schemas.gen';
export * from './services.gen';
export * from './types.gen';
export * from './types.gen';
28 changes: 28 additions & 0 deletions src/Umbraco.Community.EmailLogger/Client/src/api/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ export const DocumentGranularPermissionModelSchema = {
additionalProperties: false
} as const;

export const EmailLogSchema = {
required: ['emailLogUmbracoKey', 'id', 'isSuccessful', 'message', 'recipients', 'subject'],
type: 'object',
properties: {
id: {
type: 'integer',
format: 'int32'
},
emailLogUmbracoKey: {
type: 'string',
format: 'uuid'
},
recipients: {
type: 'string'
},
subject: {
type: 'string'
},
message: {
type: 'string'
},
isSuccessful: {
type: 'boolean'
}
},
additionalProperties: false
} as const;

export const ReadOnlyUserGroupModelSchema = {
required: ['alias', 'allowedLanguages', 'allowedSections', 'granularPermissions', 'hasAccessToAllLanguages', 'id', 'key', 'name', 'permissions'],
type: 'object',
Expand Down
15 changes: 12 additions & 3 deletions src/Umbraco.Community.EmailLogger/Client/src/api/services.gen.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
// This file is auto-generated by @hey-api/openapi-ts

import { createClient, createConfig, type Options } from '@hey-api/client-fetch';
import type { PingError, PingResponse, WhatsMyNameError, WhatsMyNameResponse, WhatsTheTimeMrWolfError, WhatsTheTimeMrWolfResponse, WhoAmIError, WhoAmIResponse } from './types.gen';
import type { AllError, AllResponse, PingError, PingResponse, WhatsMyNameError, WhatsMyNameResponse, WhatsTheTimeMrWolfError, WhatsTheTimeMrWolfResponse, WhoAmIError, WhoAmIResponse } from './types.gen';

export const client = createClient(createConfig());

export class UmbracoCommunityEmailLoggerService {
public static all<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
return (options?.client ?? client).get<AllResponse, AllError, ThrowOnError>({
...options,
url: '/umbraco/umbracocommunityemaillogger/api/v1/all'
});
}

public static ping<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
return (options?.client ?? client).get<PingResponse, PingError, ThrowOnError>({
...options,
url: '/umbraco/umbracocommunityemaillogger/api/v1/ping'
url: '/umbraco/umbracocommunityemaillogger/api/v1/ping'
});
}

public static whatsMyName<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
return (options?.client ?? client).get<WhatsMyNameResponse, WhatsMyNameError, ThrowOnError>({
...options,
Expand All @@ -32,4 +40,5 @@ export class UmbracoCommunityEmailLoggerService {
url: '/umbraco/umbracocommunityemaillogger/api/v1/whoAmI'
});
}
}

}
18 changes: 17 additions & 1 deletion src/Umbraco.Community.EmailLogger/Client/src/api/types.gen.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
// This file is auto-generated by @hey-api/openapi-ts

export type DocumentGranularPermissionModel = {
key: string;
readonly context: string;
permission: string;
};

export type EmailLog = {
id: number;
emailLogUmbracoKey: string;
recipients: string;
subject: string;
message: string;
isSuccessful: boolean;
};

export type ReadOnlyUserGroupModel = {
id: number;
key: string;
Expand Down Expand Up @@ -87,9 +97,15 @@ export type UserProfileModel = {
};

export type UserStateModel = 'Active' | 'Disabled' | 'LockedOut' | 'Invited' | 'Inactive' | 'All';

export type AllResponse = (Array<(EmailLog)>);

export type AllError = (unknown);

export type PingResponse = (string);

export type PingError = (unknown);

export type WhatsMyNameResponse = (string);

export type WhatsMyNameError = (unknown);
Expand All @@ -100,4 +116,4 @@ export type WhatsTheTimeMrWolfError = (unknown);

export type WhoAmIResponse = ((UserModel));

export type WhoAmIError = (unknown);
export type WhoAmIError = (unknown);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LitElement, css, html, customElement, state } from "@umbraco-cms/backoffice/external/lit";
import { LitElement, css, html, customElement, state, repeat } from "@umbraco-cms/backoffice/external/lit";
import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { UmbracoCommunityEmailLoggerService, UserModel } from "../api";
import { EmailLog, UmbracoCommunityEmailLoggerService, UserModel } from "../api";
import { UUIButtonElement } from "@umbraco-cms/backoffice/external/uui";
import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from "@umbraco-cms/backoffice/notification";
import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from "@umbraco-cms/backoffice/current-user";
Expand All @@ -17,9 +17,15 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) {
@state()
private _serverUserData: UserModel | undefined = undefined;

@state()
private _userData: Array<EmailLog> = [];

@state()
private _contextCurrentUser: UmbCurrentUserModel | undefined = undefined;

@state()
private _showCode: boolean = true;

constructor() {
super();

Expand All @@ -42,9 +48,10 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) {

#onClickWhoAmI = async (ev: Event) => {
const buttonElement = ev.target as UUIButtonElement;
const prettyButton = document.getElementById('btnTogglePretty') as UUIButtonElement;
buttonElement.state = "waiting";

const { data, error } = await UmbracoCommunityEmailLoggerService.whoAmI();
const { data, error } = await UmbracoCommunityEmailLoggerService.all();

if (error) {
buttonElement.state = "failed";
Expand All @@ -53,18 +60,22 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) {
}

if (data !== undefined) {
this._serverUserData = data;
this._userData = data;
buttonElement.state = "success";
prettyButton.disabled = false;
}

if (this.#notificationContext) {
/*if (this.#notificationContext) {
this.#notificationContext.peek("warning", {
data: {
headline: `You are ${this._serverUserData?.name}`,
message: `Your email is ${this._serverUserData?.email}`,
}
})
}
}*/
}
#togglePretty = async (ev: Event) => {
this._showCode = !this._showCode;
}

#onClickWhatsTheTimeMrWolf = async (ev: Event) => {
Expand Down Expand Up @@ -104,46 +115,50 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) {

render() {
return html`
<uui-box headline="Who am I?">
<uui-box headline="Want to see some logs?">
<div slot="header">[Server]</div>
<h2><uui-icon name="icon-user"></uui-icon>${this._serverUserData?.email ? this._serverUserData.email : 'Press the button!'}</h2>
<ul>
${this._serverUserData?.groups.map(group => html`<li>${group.name}</li>`)}
</ul>
<uui-button color="default" look="primary" @click="${this.#onClickWhoAmI}">
Who am I?
Go get data
</uui-button>
<p>This endpoint gets your current user from the server and displays your email and list of user groups.
It also displays a Notification with your details.</p>
</uui-box>
<uui-box headline="What's my Name?">
<div slot="header">[Server]</div>
<h2><uui-icon name="icon-user"></uui-icon> ${this._yourName }</h2>
<uui-button color="default" look="primary" @click="${this.#onClickWhatsMyName}">
Whats my name?
<uui-button color="default" look="secondary" @click="${this.#togglePretty}">
Toggle pretty HTML
</uui-button>
<p>This endpoint has a forced delay to show the button 'waiting' state for a few seconds before completing the request.</p>
</uui-box>
<uui-box headline="What's the Time?">
<div slot="header">[Server]</div>
<h2><uui-icon name="icon-alarm-clock"></uui-icon> ${this._timeFromMrWolf ? this._timeFromMrWolf.toLocaleString() : 'Press the button!'}</h2>
<uui-button color="default" look="primary" @click="${this.#onClickWhatsTheTimeMrWolf}">
Whats the time Mr Wolf?
</uui-button>
<p>This endpoint gets the current date and time from the server.</p>
<uui-box headline="Email Logs" class="wide">
<uui-table id="users-wrapper">
<uui-table-row>
<uui-table-head-cell>Recipient</uui-table-head-cell>
<uui-table-head-cell>Subject</uui-table-head-cell>
<uui-table-head-cell>Sent</uui-table-head-cell>
<uui-table-head-cell>Message</uui-table-head-cell>
</uui-table-row>
${repeat(this._userData, (user) => user.id, (user) => this._renderEmailLog(user))}
</uui-table>
</uui-box>
<uui-box headline="Who am I?" class="wide">
<div slot="header">[Context]</div>
<p>Current user email: <b>${this._contextCurrentUser?.email}</b></p>
<p>This is the JSON object available by consuming the 'UMB_CURRENT_USER_CONTEXT' context:</p>
<umb-code-block language="json" copy>${JSON.stringify(this._contextCurrentUser, null, 2)}</umb-code-block>
</uui-box>
`;
}

private _renderEmailLog(user: EmailLog) {
if (!user) return;
return html`<uui-table-row class="user">
<uui-table-cell>${user.recipients}</uui-table-cell>
<uui-table-cell>${user.subject}</uui-table-cell>
<uui-table-cell>${user.isSuccessful ? 'YES' : 'No'}</uui-table-cell>
<uui-table-cell><div class="htmlbox">${this.rawHTML(user.message)}</div></uui-table-cell>
</uui-table-row>`;
}

private rawHTML(html: string) {
if (this._showCode) {
return html;
}

var frag = document.createRange().createContextualFragment(`${html}`);
return frag;
}

static styles = [
css`
:host {
Expand All @@ -164,6 +179,11 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) {
.wide {
grid-column: span 3;
}
.htmlbox {
max-width:800px;
height:300px;
overflow-y: scroll;
}
`];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
using Umbraco.Cms.Infrastructure.Mail.Interfaces;
using Umbraco.Community.EmailLogger.BackOffice.Services;
using Umbraco.Cms.Core.Mail;
using Umbraco.Extensions;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Persistence.EFCore.Scoping;
using Umbraco.Community.EmailLogger.Context;

namespace Umbraco.Community.EmailLogger.Composers
{
Expand All @@ -24,14 +28,24 @@ public class UmbracoCommunityEmailLoggerApiComposer : IComposer
public void Compose(IUmbracoBuilder builder)
{

builder.Services.AddUmbracoDbContext<EmailLogContext>((serviceProvider, options) =>
{
options.UseUmbracoDatabaseProvider(serviceProvider);
});

//builder.Services.Remove<IEmailSender>();
//var serviceDescriptor = builder.Services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IEmailSender));
//builder.Services.Remove(serviceDescriptor);
builder.Services.AddSingleton<IEmailSender, EmailLogSender>(
services => new EmailLogSender(
services.GetRequiredService<ILogger<EmailLogSender>>(),
services.GetRequiredService<IOptionsMonitor<GlobalSettings>>(),
services.GetRequiredService<IEventAggregator>(),
services.GetRequiredService<IEmailSenderClient>(),
services.GetService<INotificationHandler<SendEmailNotification>>(),
services.GetService<INotificationAsyncHandler<SendEmailNotification>>()));
services.GetService<INotificationAsyncHandler<SendEmailNotification>>(),
services.GetService<IEFCoreScopeProvider<EmailLogContext>>()
));

builder.Services.AddSingleton<IOperationIdHandler, CustomOperationHandler>();

Expand Down
32 changes: 32 additions & 0 deletions src/Umbraco.Community.EmailLogger/Context/EmailLogContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Community.EmailLogger.Models;
using Microsoft.EntityFrameworkCore;

namespace Umbraco.Community.EmailLogger.Context
{
public class EmailLogContext : DbContext
{
public EmailLogContext(DbContextOptions<EmailLogContext> options)
: base(options)
{
}

public required DbSet<EmailLog> EmailLogs { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<EmailLog>(entity =>
{
entity.ToTable("emailLogger");
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.EmailLogUmbracoKey).HasColumnName("emailLogUmbracoKey");
entity.Property(e => e.Message).HasColumnName("message");
entity.Property(e => e.Subject).HasColumnName("subject");
entity.Property(e => e.Recipients).HasColumnName("recipients");
});
}
}
Loading

0 comments on commit cfbe050

Please sign in to comment.