Skip to content
Merged
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ This section documents the external interface of the software, including where i

- Required Outlook permission level for header retrieval: ReadWriteMailbox.
- The app consumes Outlook mailbox APIs and does not expose a separate public REST API.
- The standalone About dialog and Outlook add-in diagnostics identify the build and source commit.
- Build metadata is available as JSON at `/Pages/build-info.json`.
- Local builds display `Local` and identify the Git commit on which the working tree is based.

## Installation Procedure

Expand Down Expand Up @@ -112,7 +115,7 @@ For both IOS and Android click open an email, then press the three dots under th

### Add-in testing (VSCode)

- Follow the steps given [here](https://learn.microsoft.com/en-us/office/dev/add-ins/testing/debug-desktop-using-edge-chromium#use-the-visual-studio-code-debugger).
- Follow the steps for [using the Visual Studio Code debugger](https://learn.microsoft.com/en-us/office/dev/add-ins/testing/debug-desktop-using-edge-chromium#use-the-visual-studio-code-debugger).

### Add-in testing (Outlook Web App)

Expand Down
3 changes: 1 addition & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ export default [{
Office: "readonly",
// Webpack defined globals (replaced at build time)
__AIKEY__: "readonly",
__BUILDTIME__: "readonly",
__VERSION__: "readonly",
mhaBuildInfo: "readonly",
// Node.js/TypeScript globals used in specific contexts
process: "readonly",
global: "readonly",
Expand Down
6 changes: 6 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
declare module "*.png";
declare module "*.jpg";
declare module "*.css";

declare const mhaBuildInfo: Readonly<{
buildNumber: string;
commit: string;
builtAt: string;
}>;
8 changes: 7 additions & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ const config: Config = {
"^.+.tsx?$": ["ts-jest",{ diagnostics: { ignoreCodes: ["TS151001"] } }],
},
globals: {
"__AIKEY__": ""
// Stand-ins for webpack DefinePlugin constants so code runs under Jest.
"__AIKEY__": "",
"mhaBuildInfo": {
buildNumber: "local",
commit: "0".repeat(40),
builtAt: "1970-01-01T00:00:00.000Z"
}
},
collectCoverage: true,
collectCoverageFrom: ["./src/**"],
Expand Down
69 changes: 68 additions & 1 deletion src/Content/classicDesktopFrame.css
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,77 @@ button[aria-expanded="false"] .collapsibleSwitch::after {
box-sizing: border-box;
}

#feedbackLink {
#aboutLink {
float: right;
}

#aboutButton {
padding: 0;
border: 0;
background: transparent;
color: var(--primary-blue);
font: inherit;
text-decoration: underline;
cursor: pointer;
}

#aboutDialog {
--dialog-width: min(360px, calc(100vw - 32px));
overflow-x: hidden;
}

#aboutDialog::part(control),
#aboutDialog::part(positioning-region),
#aboutDialog::part(overlay),
#aboutDialog::part(dialog) {
overflow-x: hidden;
}

#aboutDialog[hidden] {
display: none;
}

#aboutDialog .dialog-header,
#aboutDialog .dialog-content,
#aboutDialog .dialog-actions {
overflow-x: hidden;
}

#aboutDialog .dialog-content {
padding: 16px 20px;
}

#aboutDialog dl {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 8px 16px;
margin: 0 0 16px;
min-width: 0;
}

#aboutDialog dt {
font-weight: 600;
}

#aboutDialog dd {
min-width: 0;
margin: 0;
overflow-wrap: anywhere;
word-break: break-word;
}

#aboutDialog p {
margin: 8px 0 0;
overflow-wrap: anywhere;
}

#aboutDialog .dialog-actions {
display: flex;
justify-content: flex-end;
padding: 12px 20px 16px;
border-top: 1px solid var(--border-gray);
}

.commandBar {
height: 44px;
background-color: #f4f4f4;
Expand Down
25 changes: 23 additions & 2 deletions src/Pages/mha.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ <h2 id="inputSection" class="sr-only">Input Headers</h2>
<div class="status-overlay status-overlay-inline" id="copyStatusMessage" role="status" aria-live="polite">
</div>
</fluent-button>
<div id="feedbackLink">
<a href="https://github.com/microsoft/MHA" target="_blank" rel="noopener">Submit feedback on github</a>
<div id="aboutLink">
<button type="button" id="aboutButton">About</button>
</div>
</section>

Expand All @@ -56,6 +56,27 @@ <h2 id="resultsSection" class="sr-only">Analysis Results</h2>
</section>
</main>

<fluent-dialog id="aboutDialog" aria-label="About Message Header Analyzer" hidden>
<header class="dialog-header">
<h2>About</h2>
</header>
<section class="dialog-content">
<dl>
<dt>Build</dt>
<dd id="aboutBuild"></dd>
<dt id="aboutCommitLabel">Commit</dt>
<dd><a id="aboutCommit" target="_blank" rel="noopener noreferrer"></a></dd>
<dt>Built</dt>
<dd><time id="aboutBuiltAt"></time></dd>
</dl>
<p><a href="https://github.com/microsoft/MHA" target="_blank" rel="noopener noreferrer">Project on GitHub</a></p>
<p><a href="https://github.com/microsoft/MHA/issues" target="_blank" rel="noopener noreferrer">Submit feedback on GitHub</a></p>
</section>
<footer class="dialog-actions">
<fluent-button appearance="primary" id="aboutCloseButton">Close</fluent-button>
</footer>
</fluent-dialog>

<template id="violation-inline-template">
<span class="violation-inline">
<span class="severity-badge"></span>
Expand Down
21 changes: 21 additions & 0 deletions src/Scripts/BuildInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getBuildInfo, getCommitUrl, isLocalBuild } from "./BuildInfo";

describe("BuildInfo", () => {
const testCommit = "0".repeat(40);

test("returns the injected build metadata", () => {
expect(getBuildInfo()).toEqual({
buildNumber: "local",
commit: testCommit,
builtAt: "1970-01-01T00:00:00.000Z"
});
});

test("builds the GitHub commit URL from the full SHA", () => {
expect(getCommitUrl()).toBe(`https://github.com/microsoft/MHA/commit/${testCommit}`);
});

test("identifies local builds", () => {
expect(isLocalBuild()).toBe(true);
});
});
17 changes: 17 additions & 0 deletions src/Scripts/BuildInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface BuildInfo {
readonly buildNumber: string;
readonly commit: string;
readonly builtAt: string;
}

export function getBuildInfo(): BuildInfo {
return mhaBuildInfo;
}

export function getCommitUrl(): string {
return `https://github.com/microsoft/MHA/commit/${getBuildInfo().commit}`;
}

export function isLocalBuild(): boolean {
return getBuildInfo().buildNumber === "local";
}
9 changes: 5 additions & 4 deletions src/Scripts/Diag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { ApplicationInsights, ICustomProperties, IEventTelemetry, ITelemetryItem
import stackTrace from "stacktrace-js";

import { aikey } from "./aikey";
import { buildTime } from "./buildTime";
import { mhaVersion } from "./mhaVersion";
import { getBuildInfo, isLocalBuild } from "./BuildInfo";
import { ParentFrame } from "./ParentFrame";
import { GetHeaders } from "./ui/getHeaders/GetHeaders";
import { GetHeadersAPI } from "./ui/getHeaders/GetHeadersAPI";
Expand Down Expand Up @@ -178,8 +177,10 @@ class Diag {
this.appDiagnostics["ui"] = "standalone";
}

this.appDiagnostics["Last Update"] = buildTime();
this.appDiagnostics["mhaVersion"] = mhaVersion();
const buildInfo = getBuildInfo();
this.appDiagnostics["Build"] = isLocalBuild() ? "Local" : buildInfo.buildNumber;
this.appDiagnostics[isLocalBuild() ? "Base commit" : "Commit"] = buildInfo.commit;
this.appDiagnostics["Built"] = buildInfo.builtAt;

if (window.Office) {
delete this.appDiagnostics["Office"];
Expand Down
2 changes: 0 additions & 2 deletions src/Scripts/buildTime.ts

This file was deleted.

2 changes: 0 additions & 2 deletions src/Scripts/mhaVersion.ts

This file was deleted.

33 changes: 33 additions & 0 deletions src/Scripts/ui/StandaloneAbout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { initializeStandaloneAbout } from "./StandaloneAbout";

describe("StandaloneAbout", () => {
test("renders local build metadata and opens and closes the dialog", () => {
document.body.innerHTML = `
<button id="aboutButton"></button>
<div id="aboutDialog" hidden>
<span id="aboutBuild"></span>
<a id="aboutCommit"></a>
<time id="aboutBuiltAt"></time>
<button id="aboutCloseButton"></button>
</div>`;

const dialog = document.getElementById("aboutDialog") as HTMLDivElement & { show: jest.Mock; hide: jest.Mock };
dialog.show = jest.fn();
dialog.hide = jest.fn();

initializeStandaloneAbout();

expect(document.getElementById("aboutBuild")?.textContent).toBe("Local");
expect(document.getElementById("aboutCommit")?.textContent).toBe("0".repeat(40));
expect((document.getElementById("aboutCommit") as HTMLAnchorElement).href)
.toBe(`https://github.com/microsoft/MHA/commit/${"0".repeat(40)}`);
expect(document.getElementById("aboutBuiltAt")?.textContent).toBe("1970-01-01T00:00:00.000Z");
expect(dialog.hidden).toBe(false);

document.getElementById("aboutButton")?.click();
expect(dialog.show).toHaveBeenCalledTimes(1);

document.getElementById("aboutCloseButton")?.click();
expect(dialog.hide).toHaveBeenCalledTimes(1);
});
});
31 changes: 31 additions & 0 deletions src/Scripts/ui/StandaloneAbout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getBuildInfo, getCommitUrl, isLocalBuild } from "../BuildInfo";

interface FluentDialog extends HTMLElement {
show(): void;
hide(): void;
}
Comment thread
stephenegriffin marked this conversation as resolved.

export function initializeStandaloneAbout(): void {
const dialog = document.getElementById("aboutDialog") as FluentDialog;
const aboutButton = document.getElementById("aboutButton") as HTMLButtonElement;
const closeButton = document.getElementById("aboutCloseButton") as HTMLButtonElement;
const buildElement = document.getElementById("aboutBuild") as HTMLElement;
const commitLabel = document.getElementById("aboutCommitLabel") as HTMLElement | null;
const commitLink = document.getElementById("aboutCommit") as HTMLAnchorElement;
const builtAtElement = document.getElementById("aboutBuiltAt") as HTMLTimeElement;
const buildInfo = getBuildInfo();

buildElement.textContent = isLocalBuild() ? "Local" : buildInfo.buildNumber;
if (commitLabel) commitLabel.textContent = isLocalBuild() ? "Base commit" : "Commit";
commitLink.textContent = buildInfo.commit;
commitLink.href = getCommitUrl();
builtAtElement.textContent = buildInfo.builtAt;
builtAtElement.dateTime = buildInfo.builtAt;
dialog.hidden = false;

aboutButton.onclick = (): void => dialog.show();
closeButton.onclick = (): void => dialog.hide();
dialog.addEventListener("click", (event: Event): void => {
if (event.target === dialog) dialog.hide();
});
Comment thread
stephenegriffin marked this conversation as resolved.
}
3 changes: 3 additions & 0 deletions src/Scripts/ui/mha.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "@fluentui/web-components/button.js";
import "@fluentui/web-components/dialog.js";
import "../fluentTheme";
import "../../Content/fluentCommon.css";
import "../../Content/Office.css";
Expand All @@ -9,6 +10,7 @@ import { HeaderModel } from "../HeaderModel";
import { mhaStrings } from "../mhaStrings";
import { Strings } from "../Strings";
import { DomUtils } from "./domUtils";
import { initializeStandaloneAbout } from "./StandaloneAbout";
import { Table } from "./Table";

let viewModel: HeaderModel;
Expand Down Expand Up @@ -135,6 +137,7 @@ function copy() {

document.addEventListener("DOMContentLoaded", function() {
diagnostics.set("API used", "standalone");
initializeStandaloneAbout();
table = new Table();
table.initializeTableUI();
table.makeResizablePane("inputHeaders", "sectionHeader", mhaStrings.mhaPrompt, () => true);
Expand Down
Loading
Loading