diff --git a/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.hbs b/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.hbs
index 9702ac9e5..e4144bbbd 100644
--- a/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.hbs
+++ b/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.hbs
@@ -1,4 +1,8 @@
{{#if owner}}
-
{{t 'testResult.owner.name'}}
- {{owner}}
-{{/if}}
\ No newline at end of file
+ {{t 'testResult.owner.name'}}:
+ {{#if owner.url}}
+ {{owner.displayName}}
+ {{else}}
+ {{owner.displayName}}
+ {{/if}}
+{{/if}}
diff --git a/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.js b/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.js
index 2cb24ed0b..c84a599e8 100644
--- a/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.js
+++ b/allure-generator/src/main/javascript/plugins/testresult-owner/OwnerView.js
@@ -1,5 +1,6 @@
import { View } from "backbone.marionette";
import { className } from "../../decorators/index";
+import parseAddress from "../../utils/parseAddress";
import template from "./OwnerView.hbs";
@className("pane__section")
@@ -9,7 +10,7 @@ class OwnerView extends View {
serializeData() {
const extra = this.model.get("extra");
return {
- owner: extra ? extra.owner : null,
+ owner: extra ? parseAddress(extra.owner) : null,
};
}
}
diff --git a/allure-generator/src/main/javascript/utils/parseAddress.js b/allure-generator/src/main/javascript/utils/parseAddress.js
new file mode 100644
index 000000000..43fe1f1e7
--- /dev/null
+++ b/allure-generator/src/main/javascript/utils/parseAddress.js
@@ -0,0 +1,60 @@
+const RFC2822_ADDRESS = /^(.*) <(.*)>$/;
+const LOOKS_LIKE_EMAIL = /^[^@]+@[^@]+$/;
+
+/**
+ * Parse a potentially RFC 2822 address into a display name and an address.
+ *
+ * @param {string | null | undefined} maybeAddress
+ * @returns {{ displayName: string, url?: string } | null
+ */
+export default function parseAddress(maybeAddress) {
+ if (!maybeAddress) {
+ return null;
+ }
+
+ const match = maybeAddress.match(RFC2822_ADDRESS);
+ if (match) {
+ return {
+ displayName: match[1],
+ url: toHref(match[2]),
+ };
+ }
+
+ return {
+ displayName: maybeAddress,
+ url: toHref(maybeAddress),
+ };
+}
+
+/**
+ * If the address is a valid URL, returns the URL.
+ * If the address resembles an email address, returns a mailto: URL.
+ * Otherwise, returns undefined.
+ *
+ * @param {string} address
+ * @returns {string | undefined}
+ */
+function toHref(address) {
+ if (isValidURL(address)) {
+ return address;
+ }
+
+ if (LOOKS_LIKE_EMAIL.test(address)) {
+ return `mailto:${address}`;
+ }
+}
+
+/**
+ * If the address is a valid URL, returns the URL.
+ *
+ * @param {string} maybeURL
+ * @returns {boolean}
+ */
+function isValidURL(maybeURL) {
+ try {
+ new URL(maybeURL);
+ return true;
+ } catch (_error) {
+ return false;
+ }
+}