Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ updates:
interval: "weekly"
cooldown:
default-days: 7

- package-ecosystem: "npm"
directory: "/src/Exceptionless.EmailTemplates"
schedule:
interval: "weekly"
cooldown:
default-days: 7
51 changes: 50 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,55 @@ jobs:
- name: Run Integration Tests
run: echo "npm run test:integration"

test-email-templates:
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: src/Exceptionless.EmailTemplates

steps:
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ (github.event_name == 'repository_dispatch' && github.event.client_payload.head_sha) || github.event.pull_request.head.sha || github.sha }}

- name: Setup Node.js environment
uses: actions/setup-node@v6
with:
node-version: 24

- name: Cache npm packages
uses: actions/cache@v6
id: cache-email-template-npm
with:
path: ~/.npm
key: npm-email-${{ runner.os }}-${{ hashFiles('src/Exceptionless.EmailTemplates/package-lock.json') }}

- name: Install Npm Packages
run: npm ci

- name: Lint
run: npm run lint

- name: Type Check
run: npm run check

- name: Build Templates
run: npm run build

- name: Build Storybook
run: npm run build-storybook

- name: Verify Generated HTML is Up-To-Date
run: |
changes="$(git status --short --untracked-files=all -- ../Exceptionless.Core/Mail/Templates/ generated-templates.json)"
if [ -n "$changes" ]; then
echo "$changes"
echo "::error::Generated email artifacts are stale. Run 'npm run build' in src/Exceptionless.EmailTemplates and commit the updated files."
exit 1
fi

test-e2e:
runs-on: ubuntu-latest
timeout-minutes: 45
Expand Down Expand Up @@ -437,7 +486,7 @@ jobs:
docker-publish:
if: ${{ needs.version.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
needs: [version, docker-build, test-api, test-client, test-e2e]
needs: [version, docker-build, test-api, test-client, test-email-templates, test-e2e]
timeout-minutes: 30
env:
VERSION: ${{ needs.version.outputs.version }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ docs/_cache/
.devcontainer/devcontainer-lock.json

*.lscache
.gstack/
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 7 additions & 3 deletions src/Exceptionless.AppHost/Extensions/WorktreeScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public sealed record WorktreePorts(
int OldAppHttp,
int OldAppHttps,
int OldAppLiveReload,
int AppHttps)
int AppHttps,
int Storybook,
int EmailStorybook)
{
public string ApiHttpUrl => $"http://localhost:{ApiHttp}";
public string ApiHttpsUrl => $"https://localhost:{ApiHttps}";
Expand Down Expand Up @@ -49,7 +51,7 @@ public static class WorktreeScope

public static WorktreePorts AssignFreePorts()
{
int[] ports = FreePorts(11);
int[] ports = FreePorts(13);
var assignments = new WorktreePorts(
ports[0],
ports[1],
Expand All @@ -61,7 +63,9 @@ public static WorktreePorts AssignFreePorts()
ports[7],
ports[8],
ports[9],
ports[10]);
ports[10],
ports[11],
ports[12]);

Environment.SetEnvironmentVariable("ASPNETCORE_URLS", $"https://localhost:{assignments.DashboardHttps};http://localhost:{assignments.DashboardHttp}");
Environment.SetEnvironmentVariable("DOTNET_DASHBOARD_OTLP_ENDPOINT_URL", $"https://localhost:{assignments.DashboardOtlp}");
Expand Down
27 changes: 27 additions & 0 deletions src/Exceptionless.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
int oldAppLiveReloadPort = worktreePorts?.OldAppLiveReload ?? 35729;
string oldAppAspNetCoreUrls = String.Concat("http://localhost:", oldAppHttpPort);
int appPort = worktreePorts?.AppHttps ?? 7131;
int storybookPort = worktreePorts?.Storybook ?? 6006;
int emailStorybookPort = worktreePorts?.EmailStorybook ?? 6008;
const int DefaultApiHttpsPort = 7111;
string exceptionlessServerUrl = worktreePorts?.ApiHttpsUrl ?? $"https://api-ex.dev.localhost:{DefaultApiHttpsPort}";
const string SharedEmailConnectionString = "smtp://localhost:1025";
Expand Down Expand Up @@ -207,6 +209,31 @@
.WithEnvironment("OLDAPP_HTTPS", worktreePorts.OldAppHttpsUrl);
}
#pragma warning restore ASPIREBROWSERLOGS001

if (includeDevTools)
{
builder.AddJavaScriptApp("Storybook", "../Exceptionless.Web/ClientApp", "storybook")
.WithRunScript("storybook", ["--", "--port", storybookPort.ToString(), "--no-open"])
.WithHttpEndpoint(port: storybookPort, targetPort: storybookPort, name: "http", isProxied: false)
.WithUrlForEndpoint("http", u =>
{
u.DisplayText = "Component Library";
u.DisplayOrder = 200;
})
.WithHttpHealthCheck("/")
.WithParentRelationship(api);

builder.AddJavaScriptApp("EmailStorybook", "../Exceptionless.EmailTemplates", "storybook")
.WithRunScript("storybook", ["--", "--port", emailStorybookPort.ToString(), "--no-open"])
.WithHttpEndpoint(port: emailStorybookPort, targetPort: emailStorybookPort, name: "http", isProxied: false)
.WithUrlForEndpoint("http", u =>
{
u.DisplayText = "Email Templates";
u.DisplayOrder = 300;
})
.WithHttpHealthCheck("/")
.WithParentRelationship(api);
}
}

await builder.Build().RunAsync();
Expand Down
12 changes: 11 additions & 1 deletion src/Exceptionless.Core/Mail/Mailer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ private void AddUserInfo(PersistentEvent ev, Dictionary<string, object?> data)
data["UserDescription"] = ud.Description;

if (!String.IsNullOrEmpty(ud?.EmailAddress))
{
data["UserEmail"] = ud.EmailAddress;
data["UserEmailHref"] = BuildMailtoHref(ud.EmailAddress, ud.Description);
}

string? displayName = null;
if (!String.IsNullOrEmpty(ui?.Identity))
Expand All @@ -135,6 +138,12 @@ private void AddUserInfo(PersistentEvent ev, Dictionary<string, object?> data)
data["HasUserInfo"] = ud is not null || ui is not null;
}

private static string BuildMailtoHref(string emailAddress, string? body)
{
string href = $"mailto:{Uri.EscapeDataString(emailAddress)}";
return String.IsNullOrEmpty(body) ? href : $"{href}?body={Uri.EscapeDataString(body)}";
}

private static void AddDefaultFields(PersistentEvent ev, Dictionary<string, object?> data)
{
if (ev.Tags?.Count > 0)
Expand Down Expand Up @@ -269,7 +278,8 @@ private static IEnumerable<object> GetStackTemplateData(IEnumerable<Stack> stack
StackId = s.Id,
Title = s.Title.Truncate(50),
TypeName = s.GetTypeName()?.Truncate(50),
s.Status
s.Status,
IsRegressed = s.Status == StackStatus.Regressed
});
}

Expand Down
Loading
Loading