@activecollab/components
Version:
ActiveCollab Components
1,006 lines (982 loc) • 43.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WebhookLogsDialog = exports.WEBHOOK_REPOSITORIES = exports.WEBHOOK_CONNECTIONS = void 0;
var _react = _interopRequireWildcard(require("react"));
var _styledComponents = _interopRequireDefault(require("styled-components"));
var _RoundAvatar = require("./RoundAvatar");
var _tokens = require("./tokens");
var _Button = require("../../components/Button");
var _Chip = require("../../components/Chip");
var _CompleteCheckbox = require("../../components/CompleteCheckbox");
var _Dialog = require("../../components/Dialog");
var _Filter = require("../../components/Filter");
var _IconButton = require("../../components/IconButton");
var _Icons = require("../../components/Icons");
var _Label = require("../../components/Label");
var _SelectTrigger = require("../../components/SelectTrigger");
var _Sheet = require("../../components/Sheet");
var _Tooltip = require("../../components/Tooltip");
var _Typography = require("../../components/Typography");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
/**
* "Webhook Logs" — the VCS webhook delivery log, shown as a wide dialog with a
* filterable, expandable list. Extracted into `shared/` so it can be opened
* from more than one surface (the Apps & Integrations Repositories page and the
* project "..." menu on the Project Repos story).
*
* Mirrors the app's webhook log model:
* `vcs_webhook_logs` → connection_id, event_type, processing_status
* (received / unhandled / skipped / processed /
* failed / duplicate), delivery_identifier, payload,
* created_on
* `vcs_webhook_outcomes` → one row per outcome, each with a localized summary
*
* The status is derived from the outcomes server-side; here we render the
* stored value and map it to a palette colour. The connection glyph is the
* provider icon (GitHub / GitLab, Git for anything else).
*/
/**
* Normalized event kind. The processor layer maps each provider's raw event
* header (GitHub `issues` / GitLab `Issue Hook`, GitHub `pull_request` / GitLab
* `Merge Request Hook`, …) onto one provider-agnostic kind, so the same logical
* event reads — and filters — identically across connections instead of
* appearing once per provider. The set below mirrors the app's
* `EVENT_TYPE_OPTIONS` constant.
*/
/**
* The task an outcome created or referenced. In the app each
* `vcs_webhook_outcomes` row carries a nullable `task_id`; when it is set the
* outcome offers an "Open Task" link that opens the task in a sheet on top of
* the log dialog.
*/
var WEBHOOK_CONNECTIONS = exports.WEBHOOK_CONNECTIONS = [{
id: "gh-activecollab",
name: "ActiveCollab",
service: "github"
}, {
id: "gh-abstergo",
name: "Abstergo Ltd.",
service: "github"
}, {
id: "gh-binford",
name: "Binford & Co.",
service: "github"
}, {
id: "gl-platform",
name: "Platform",
service: "gitlab"
}, {
id: "gl-content",
name: "Content",
service: "gitlab"
}, {
id: "gl-acme-ce",
name: "Acme Community",
service: "gitlab"
}];
/**
* Repositories that belong to the connections above. A webhook delivery is
* always scoped to one repository, and a repository belongs to exactly one
* connection — so the Repository filter can be narrowed to the connection(s)
* currently selected in the Connection filter.
*/
var WEBHOOK_REPOSITORIES = exports.WEBHOOK_REPOSITORIES = [{
id: "ac-feather",
path: "activecollab/feather",
connectionId: "gh-activecollab"
}, {
id: "ac-backend",
path: "activecollab/backend",
connectionId: "gh-activecollab"
}, {
id: "ab-animus",
path: "abstergo/animus",
connectionId: "gh-abstergo"
}, {
id: "bi-tool-time",
path: "binford/tool-time",
connectionId: "gh-binford"
}, {
id: "pl-gateway",
path: "platform/api-gateway",
connectionId: "gl-platform"
}, {
id: "co-handbook",
path: "content/handbook",
connectionId: "gl-content"
}, {
id: "co-marketing",
path: "content/marketing-site",
connectionId: "gl-content"
}, {
id: "acme-ce",
path: "acme/community-edition",
connectionId: "gl-acme-ce"
}];
var EVENT_KIND_LABELS = {
push: "Push",
issue: "Issue",
pull_request: "Pull Request",
pull_request_review: "Pull Request Review",
repository: "Repository",
other: "Other"
};
/**
* Fixed Event Type filter options, mirroring the app's `EVENT_TYPE_OPTIONS`.
* The app offers the full list regardless of which kinds are present in the
* current result set, so we do the same instead of deriving it from the logs.
*/
var EVENT_TYPE_OPTIONS = [{
id: "push",
label: "Push"
}, {
id: "issue",
label: "Issue"
}, {
id: "pull_request",
label: "Pull Request"
}, {
id: "pull_request_review",
label: "Pull Request Review"
}, {
id: "repository",
label: "Repository"
}, {
id: "other",
label: "Other"
}];
var STATUS_LABELS = {
received: "Received",
unhandled: "Unhandled",
skipped: "Skipped",
processed: "Processed",
failed: "Failed",
duplicate: "Duplicate"
};
/**
* Status → palette colour, chosen so the colour matches the meaning:
* processed → green (success), failed → coral (error),
* skipped → orange (warning), received → sky-blue (in progress),
* unhandled → silver (neutral / no handler matched),
* duplicate → purple (replayed delivery, dropped before processing).
*/
var STATUS_COLORS = {
processed: {
bg: "var(--color-green-pale)",
fg: "var(--color-green-pale-darker)"
},
failed: {
bg: "var(--color-coral)",
fg: "var(--color-coral-darker)"
},
skipped: {
bg: "var(--color-orange)",
fg: "var(--color-orange-darker)"
},
received: {
bg: "var(--color-blue-sky)",
fg: "var(--color-blue-sky-darker)"
},
unhandled: {
bg: "var(--color-gray-silver)",
fg: "var(--color-gray-silver-darker)"
},
duplicate: {
bg: "var(--color-purple)",
fg: "var(--color-purple-darker)"
}
};
/** Provider glyph for a connection: GitHub / GitLab, Git for anything else. */
var connectionIcon = function connectionIcon(service) {
if (service === "github") {
return _Icons.GitHubIcon;
}
if (service === "gitlab") {
return _Icons.GitLabIcon;
}
return _Icons.GitIcon;
};
var StatusChip = function StatusChip(_ref) {
var status = _ref.status;
var _STATUS_COLORS$status = STATUS_COLORS[status],
bg = _STATUS_COLORS$status.bg,
fg = _STATUS_COLORS$status.fg;
return /*#__PURE__*/_react.default.createElement(_Chip.Chip, {
label: STATUS_LABELS[status],
backgroundColor: bg,
color: fg,
typographyProps: {
variant: "Caption 1",
weight: "medium"
}
});
};
/* --- Relative "ago" time -------------------------------------------------- */
/* The Time column reads relative to a fixed "now" so the mock's examples stay */
/* stable across renders — a live clock would drift the buckets on every reload */
/* and the crafted "3 minutes ago" / "Yesterday" rows would lose their meaning. */
var NOW = new Date(2026, 7, 11, 15, 0, 0); // Tue, 11 Aug 2026, 15:00
var NOW_TS = Math.floor(NOW.getTime() / 1000);
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var pad2 = function pad2(value) {
return String(value).padStart(2, "0");
};
var startOfDay = function startOfDay(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
};
/**
* When a webhook was received, in the app's relative style:
* < 1 min → "Just now"
* < 1 hour → "N minutes ago"
* earlier today → "Today, HH:MM"
* yesterday → "Yesterday, HH:MM"
* older, this year → "MMM D"
* older, earlier years → "MMM D, YYYY"
*/
var formatReceivedAt = function formatReceivedAt(receivedAt) {
var now = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : NOW;
var then = new Date(receivedAt * 1000);
var diffMinutes = Math.floor((now.getTime() - then.getTime()) / 60000);
if (diffMinutes < 1) {
return "Just now";
}
if (diffMinutes < 60) {
return "".concat(diffMinutes, " ").concat(diffMinutes === 1 ? "minute" : "minutes", " ago");
}
var time = "".concat(pad2(then.getHours()), ":").concat(pad2(then.getMinutes()));
var dayDiff = Math.round((startOfDay(now) - startOfDay(then)) / 86400000);
if (dayDiff === 0) {
return "Today, ".concat(time);
}
if (dayDiff === 1) {
return "Yesterday, ".concat(time);
}
var date = "".concat(MONTHS[then.getMonth()], " ").concat(then.getDate());
return then.getFullYear() === now.getFullYear() ? date : "".concat(date, ", ").concat(then.getFullYear());
};
/* Timestamp helpers for the mock data below. */
var secondsAgo = function secondsAgo(seconds) {
return NOW_TS - seconds;
};
var at = function at(year, month, day, hour, minute) {
return Math.floor(new Date(year, month, day, hour, minute, 0).getTime() / 1000);
};
/* --- Mock log data (newest first is enforced by sorting on receivedAt) --- */
var PAYLOAD_ISSUE_OPENED = JSON.stringify({
action: "opened",
issue: {
id: 2002934201,
number: 42,
title: "Webhook delivery retries are not logged",
state: "open",
html_url: "https://github.com/activecollab/feather/issues/42",
labels: [{
name: "bug"
}, {
name: "vcs"
}],
user: {
login: "octocat",
id: 583231
}
},
repository: {
id: 776611234,
full_name: "activecollab/feather",
private: true
},
sender: {
login: "octocat",
id: 583231
}
}, null, 2);
var PAYLOAD_MERGE_REQUEST = JSON.stringify({
object_kind: "merge_request",
event_type: "merge_request",
user: {
id: 41,
name: "Jane Doe",
username: "jdoe"
},
project: {
id: 1801,
path_with_namespace: "content/handbook"
},
object_attributes: {
iid: 318,
title: "Add changelog for 8.2",
state: "merged",
action: "merge",
source_branch: "changelog-8-2",
target_branch: "main"
}
}, null, 2);
var PAYLOAD_PUSH = JSON.stringify({
ref: "refs/heads/main",
before: "9b2c1f0e",
after: "f4a7d3c1",
commits: [{
id: "f4a7d3c1",
message: "Fix typo in webhook log repository",
author: {
name: "Marko",
email: "marko@example.com"
}
}],
repository: {
full_name: "activecollab/feather"
}
}, null, 2);
var WEBHOOK_LOGS = [
// Just now (< 1 minute)
{
id: "log-just-now",
connectionId: "gh-activecollab",
repositoryId: "ac-feather",
eventKind: "issue",
status: "processed",
deliveryIdentifier: "d3b07384-d9a0-4c9b-8f1e-2a6f1b9c4e21",
outcomes: [{
summary: "Created task #1842 from issue #42",
task: {
id: 1842,
name: "Webhook delivery retries are not logged",
projectName: "Feather",
description: "Retries are not written to the webhook log, so a delivery that only succeeds on a retry still shows as failed. Log each attempt with its outcome."
}
}],
payload: PAYLOAD_ISSUE_OPENED,
receivedAt: secondsAgo(25)
},
// Minutes ago
{
id: "log-3m",
connectionId: "gl-content",
repositoryId: "co-handbook",
eventKind: "pull_request",
status: "skipped",
deliveryIdentifier: "b1946ac9-2f3a-4c0d-9b7e-1d2c3e4f5a6b",
outcomes: [{
summary: "Skipped: merge request has no linked issue"
}],
payload: PAYLOAD_MERGE_REQUEST,
receivedAt: secondsAgo(3 * 60)
}, {
id: "log-12m",
connectionId: "gh-abstergo",
repositoryId: "ab-animus",
eventKind: "issue",
status: "failed",
deliveryIdentifier: "9c1185a5-c5e9-4e3f-8a1b-7d6e5f4c3b2a",
outcomes: [{
summary: "Failed at task creation: project not found"
}],
payload: PAYLOAD_ISSUE_OPENED,
receivedAt: secondsAgo(12 * 60)
}, {
id: "log-47m",
connectionId: "gl-platform",
repositoryId: "pl-gateway",
eventKind: "push",
status: "processed",
deliveryIdentifier: "f7c3bc1d-8c5a-4b2e-9f0a-1b2c3d4e5f60",
outcomes: [{
summary: "Created task #1839 from issue #311",
task: {
id: 1839,
name: "Refresh the onboarding handbook",
projectName: "Handbook",
description: "The onboarding handbook is out of date. Review each section and update the steps that changed in the last release."
}
}, {
summary: "Created task #1840 from issue #312",
task: {
id: 1840,
name: "Document webhook scopes",
projectName: "Handbook",
description: "Explain the connection and repository webhook scopes, and when each one applies."
}
}],
payload: PAYLOAD_PUSH,
receivedAt: secondsAgo(47 * 60)
},
// Earlier today
{
id: "log-today-1",
connectionId: "gh-activecollab",
repositoryId: "ac-feather",
eventKind: "other",
status: "unhandled",
deliveryIdentifier: "1574bddb-75c3-4b1e-8a9c-0d1e2f3a4b5c",
outcomes: [],
payload: JSON.stringify({
zen: "Keep it logically awesome.",
hook_id: 552012
}, null, 2),
receivedAt: at(2026, 7, 11, 10, 0)
}, {
id: "log-today-2",
connectionId: "gh-binford",
repositoryId: "bi-tool-time",
eventKind: "pull_request_review",
status: "processed",
deliveryIdentifier: "5f2b1c8e-7a3d-4e6f-9b0c-1d2e3f4a5b6c",
outcomes: [{
summary: "Referenced task #1699 from pull request #58",
task: {
id: 1699,
name: "Refresh the Tool Time build badge",
projectName: "Tool Time",
description: "The build badge shows an old status. Update it when the pipeline finishes."
}
}],
payload: PAYLOAD_MERGE_REQUEST,
receivedAt: at(2026, 7, 11, 7, 12)
},
// Yesterday
{
id: "log-yesterday-1",
connectionId: "gl-content",
repositoryId: "co-marketing",
eventKind: "issue",
status: "received",
deliveryIdentifier: "0716d970-2b9e-4a1d-9c3f-5e6a7b8c9d01",
outcomes: [],
receivedAt: at(2026, 7, 10, 13, 20)
}, {
id: "log-yesterday-2",
connectionId: "gh-activecollab",
repositoryId: "ac-feather",
eventKind: "issue",
status: "duplicate",
deliveryIdentifier: "fa3ebd6742c360b2d9652b7f78d9bd7d",
outcomes: [],
payload: PAYLOAD_ISSUE_OPENED,
receivedAt: at(2026, 7, 10, 9, 5)
},
// Older, this year — date only
{
id: "log-aug-8",
connectionId: "gh-activecollab",
repositoryId: "ac-backend",
eventKind: "repository",
status: "skipped",
deliveryIdentifier: "2c9d0e1f-3a4b-4c5d-8e9f-0a1b2c3d4e5f",
outcomes: [{
summary: "Skipped: repository events are not processed"
}],
receivedAt: at(2026, 7, 8, 16, 42)
}, {
id: "log-jul-30",
connectionId: "gl-acme-ce",
repositoryId: "acme-ce",
eventKind: "push",
status: "failed",
deliveryIdentifier: "7d8e9f0a-1b2c-4d3e-9f0a-2b3c4d5e6f70",
outcomes: [{
summary: "Failed to match a repository for this push"
}],
payload: PAYLOAD_PUSH,
receivedAt: at(2026, 6, 30, 11, 9)
}, {
id: "log-jun-12",
connectionId: "gh-abstergo",
repositoryId: "ab-animus",
eventKind: "issue",
status: "processed",
deliveryIdentifier: "aa11bb22-cc33-4d44-9e55-6f7788990011",
outcomes: [{
summary: "Created task #1720 from issue #204",
task: {
id: 1720,
name: "Animus sync drops issue labels",
projectName: "Animus",
description: "Issue labels are lost when Animus syncs from GitHub. Map the incoming labels onto task labels."
}
}],
payload: PAYLOAD_ISSUE_OPENED,
receivedAt: at(2026, 5, 12, 8, 30)
},
// Older, earlier year — date with year
{
id: "log-mar-2025",
connectionId: "gl-platform",
repositoryId: "pl-gateway",
eventKind: "issue",
status: "processed",
deliveryIdentifier: "bb22cc33-dd44-4e55-9f66-7a8899001122",
outcomes: [{
summary: "Created task #1533 from issue #88",
task: {
id: 1533,
name: "Gateway times out on large payloads",
projectName: "API Gateway",
description: "Large webhook payloads time out at the gateway. Raise the body limit or stream the request."
}
}],
payload: PAYLOAD_ISSUE_OPENED,
receivedAt: at(2025, 2, 3, 10, 15)
}];
/* --- Lightweight, Prism-style JSON syntax highlighter -------------------- */
/* design-system has no `prismjs` dependency (it's an app dependency), so for */
/* this throwaway mock we tokenize JSON ourselves and colour the tokens with */
/* the same token classes Prism uses, styled via PayloadPre below. */
var escapeHtml = function escapeHtml(value) {
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
};
var highlightJson = function highlightJson(json) {
return escapeHtml(json).replace(/("(?:\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(?:true|false)\b|\bnull\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, function (match) {
var cls = "tok-number";
if (/^"/.test(match)) {
cls = /:$/.test(match) ? "tok-key" : "tok-string";
} else if (/true|false/.test(match)) {
cls = "tok-boolean";
} else if (/null/.test(match)) {
cls = "tok-null";
}
return "<span class=\"".concat(cls, "\">").concat(match, "</span>");
});
};
var PAYLOAD_COLLAPSED_LINES = 6;
var PayloadPre = _styledComponents.default.pre.withConfig({
displayName: "WebhookLogsDialog__PayloadPre",
componentId: "sc-6ol7f6-0"
})(["margin:0;padding:12px 14px;border:1px solid var(--color-theme-400);border-radius:6px;background-color:var(--color-theme-200);color:var(--color-theme-800);font-family:\"SFMono-Regular\",Consolas,\"Liberation Mono\",Menlo,monospace;font-size:12px;line-height:18px;overflow-x:auto;white-space:pre;position:relative;", " .tok-key{color:var(--color-coral-lighter);}.tok-string{color:var(--color-green-pale-lighter);}.tok-number{color:var(--color-orange-lighter);}.tok-boolean{color:var(--color-blue-sky-lighter);}.tok-null{color:var(--color-purple-lighter);}"], function (_ref2) {
var $collapsed = _ref2.$collapsed;
return $collapsed ? "max-height: ".concat(PAYLOAD_COLLAPSED_LINES * 18 + 24, "px;\n overflow-y: hidden;") : "";
});
var PayloadWrap = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__PayloadWrap",
componentId: "sc-6ol7f6-1"
})([".payload-header{display:flex;align-items:center;justify-content:space-between;min-height:24px;margin-bottom:4px;}"]);
var PayloadBlock = function PayloadBlock(_ref3) {
var payload = _ref3.payload;
var _useState = (0, _react.useState)(true),
_useState2 = _slicedToArray(_useState, 2),
collapsed = _useState2[0],
setCollapsed = _useState2[1];
var lineCount = (0, _react.useMemo)(function () {
return payload.split("\n").length;
}, [payload]);
var html = (0, _react.useMemo)(function () {
return highlightJson(payload);
}, [payload]);
var canToggle = lineCount > PAYLOAD_COLLAPSED_LINES;
return /*#__PURE__*/_react.default.createElement(PayloadWrap, null, /*#__PURE__*/_react.default.createElement("div", {
className: "payload-header"
}, /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
color: "secondary",
weight: "bold"
}, "Payload"), canToggle ? /*#__PURE__*/_react.default.createElement(_Button.Button, {
variant: "text colored",
size: "small",
onClick: function onClick() {
return setCollapsed(function (value) {
return !value;
});
}
}, collapsed ? "Show More" : "Show Less") : null), /*#__PURE__*/_react.default.createElement(PayloadPre, {
$collapsed: collapsed && canToggle,
dangerouslySetInnerHTML: {
__html: html
}
}));
};
/* --- A single, expandable log row ---------------------------------------- */
var LogRowWrap = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__LogRowWrap",
componentId: "sc-6ol7f6-2"
})(["border-top:1px solid var(--color-theme-300);.lr-summary{box-sizing:border-box;display:flex;align-items:center;gap:12px;width:100%;min-height:56px;padding:8px 8px 8px 12px;background:none;border:0;text-align:left;cursor:pointer;font-family:inherit;&:hover{background-color:var(--color-theme-200);}}.lr-expand{flex-shrink:0;fill:var(--color-theme-600);color:var(--color-theme-600);transform:rotate(180deg);transition:transform 200ms ease;}.lr-expand.open{transform:rotate(0deg);}.lr-conn{display:flex;align-items:center;gap:8px;width:180px;min-width:0;flex-shrink:0;}.lr-conn svg{width:20px;height:20px;flex-shrink:0;}.lr-conn .lr-conn-name{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}.lr-event{flex:1 1 0%;min-width:0;}.lr-time{width:120px;flex-shrink:0;text-align:right;white-space:nowrap;}.lr-status{width:110px;flex-shrink:0;display:flex;}.lr-outcomes{width:96px;flex-shrink:0;text-align:right;}.lr-detail{padding:4px 16px 20px 44px;display:flex;flex-direction:column;gap:16px;}.lr-field-label{display:block;margin-bottom:4px;}.lr-id{font-family:\"SFMono-Regular\",Consolas,\"Liberation Mono\",Menlo,monospace;word-break:break-all;}.lr-outcome-list{margin:0;padding-left:16px;display:flex;flex-direction:column;gap:6px;}.lr-outcome{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px;}.lr-open-task{padding:0;border:0;background:none;font:inherit;font-size:13px;line-height:18px;color:var(--color-theme-600);text-decoration:underline;cursor:pointer;white-space:nowrap;}.lr-open-task:hover{color:var(--color-theme-800);}"]);
var LogRow = function LogRow(_ref4) {
var _connection$name;
var log = _ref4.log,
connection = _ref4.connection,
onOpenTask = _ref4.onOpenTask;
var _useState3 = (0, _react.useState)(false),
_useState4 = _slicedToArray(_useState3, 2),
open = _useState4[0],
setOpen = _useState4[1];
var ConnIcon = connectionIcon(connection === null || connection === void 0 ? void 0 : connection.service);
return /*#__PURE__*/_react.default.createElement(LogRowWrap, null, /*#__PURE__*/_react.default.createElement("button", {
type: "button",
className: "lr-summary",
"aria-expanded": open,
onClick: function onClick() {
return setOpen(function (value) {
return !value;
});
}
}, /*#__PURE__*/_react.default.createElement(_Icons.CollapseExpandSingleIcon, {
className: open ? "lr-expand open" : "lr-expand"
}), /*#__PURE__*/_react.default.createElement("span", {
className: "lr-conn"
}, /*#__PURE__*/_react.default.createElement(ConnIcon, null), /*#__PURE__*/_react.default.createElement(_Typography.Body2, {
className: "lr-conn-name",
weight: "bold"
}, (_connection$name = connection === null || connection === void 0 ? void 0 : connection.name) !== null && _connection$name !== void 0 ? _connection$name : "Unknown")), /*#__PURE__*/_react.default.createElement(_Typography.Body2, {
className: "lr-event",
color: "tertiary"
}, EVENT_KIND_LABELS[log.eventKind]), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "lr-time",
color: "tertiary"
}, formatReceivedAt(log.receivedAt)), /*#__PURE__*/_react.default.createElement("span", {
className: "lr-status"
}, /*#__PURE__*/_react.default.createElement(StatusChip, {
status: log.status
})), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "lr-outcomes",
color: "tertiary"
}, log.outcomes.length > 0 ? "".concat(log.outcomes.length, " ").concat(log.outcomes.length === 1 ? "outcome" : "outcomes") : "")), open ? /*#__PURE__*/_react.default.createElement("div", {
className: "lr-detail"
}, /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "lr-field-label",
color: "secondary",
weight: "bold"
}, "Webhook identifier"), /*#__PURE__*/_react.default.createElement(_Typography.Body2, {
className: "lr-id",
color: "tertiary"
}, log.deliveryIdentifier)), log.outcomes.length > 0 ? /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "lr-field-label",
color: "secondary",
weight: "bold"
}, "Outcomes"), /*#__PURE__*/_react.default.createElement("ul", {
className: "lr-outcome-list"
}, log.outcomes.map(function (outcome, index) {
return /*#__PURE__*/_react.default.createElement("li", {
key: index,
className: "lr-outcome"
}, /*#__PURE__*/_react.default.createElement(_Typography.Body2, {
color: "tertiary"
}, outcome.summary), outcome.task ? /*#__PURE__*/_react.default.createElement(_Tooltip.Tooltip, {
title: "See the task without closing the log"
}, /*#__PURE__*/_react.default.createElement("button", {
type: "button",
className: "lr-open-task",
onClick: function onClick() {
return onOpenTask(outcome.task);
}
}, "Open Task")) : null);
}))) : null, log.payload ? /*#__PURE__*/_react.default.createElement(PayloadBlock, {
payload: log.payload
}) : null) : null);
};
/* --- Task preview sheet --------------------------------------------------- */
/* Opens the outcome's task in a right-side sheet, on top of the (still open) */
/* log dialog. Uses the same Sheet setup as the Task sheet stories — position */
/* "right", stretch mode, and a Close control — so it reads as the real task */
/* sheet. The body is a representative stand-in for the app's full task sheet. */
var StyledTaskSheet = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__StyledTaskSheet",
componentId: "sc-6ol7f6-3"
})(["display:grid;min-height:100%;padding:8px;grid-template-columns:1fr;@media (min-width:", "){grid-template-columns:1fr 328px;}"], _tokens.LG);
var StyledTaskMain = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__StyledTaskMain",
componentId: "sc-6ol7f6-4"
})(["padding:24px 24px 0;min-width:0;@media (min-width:", "){grid-column:1;}.task-header{display:flex;align-items:center;gap:8px;min-width:0;margin-bottom:8px;}.task-title{min-width:0;}.metadata{display:flex;flex-wrap:wrap;align-items:center;margin-bottom:24px;}.metadata .seg:not(:last-child){padding-right:12px;margin-right:12px;border-right:1px solid var(--border-primary);}.description{padding-bottom:16px;margin-bottom:16px;border-bottom:1px solid var(--border-primary);}"], _tokens.LG);
var StyledTaskProperties = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__StyledTaskProperties",
componentId: "sc-6ol7f6-5"
})(["padding:0 24px;@media (min-width:", "){grid-column:2;padding:24px;background-color:var(--color-theme-200);border-radius:8px;}.field{margin-bottom:24px;}.field:last-child{margin-bottom:0;}.field-label{display:flex;align-items:center;max-width:280px;margin-bottom:4px;}.field-control{width:100%;}.assignee-row{display:flex;align-items:center;gap:8px;}"], _tokens.LG);
var SheetField = function SheetField(_ref5) {
var label = _ref5.label,
children = _ref5.children;
return /*#__PURE__*/_react.default.createElement("div", {
className: "field"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "field-label"
}, /*#__PURE__*/_react.default.createElement(_Label.Label, {
size: "small"
}, label)), children);
};
var TaskPreviewSheet = function TaskPreviewSheet(_ref6) {
var task = _ref6.task,
open = _ref6.open,
onClose = _ref6.onClose;
if (!task) {
return null;
}
var controls = [{
tooltip: "Close",
disabled: false,
onClick: onClose,
icon: /*#__PURE__*/_react.default.createElement(_Icons.CancelCrossIcon, null),
className: ""
}];
return /*#__PURE__*/_react.default.createElement(_Sheet.Sheet, {
open: open,
onClose: onClose,
controls: controls,
position: "right",
animation: "right",
mode: "stretch"
}, /*#__PURE__*/_react.default.createElement(StyledTaskSheet, null, /*#__PURE__*/_react.default.createElement(StyledTaskMain, null, /*#__PURE__*/_react.default.createElement("div", {
className: "task-header"
}, /*#__PURE__*/_react.default.createElement(_CompleteCheckbox.CompleteCheckbox, null), /*#__PURE__*/_react.default.createElement(_Typography.Header2, {
className: "task-title"
}, "#", task.id, ": ", task.name)), /*#__PURE__*/_react.default.createElement("div", {
className: "metadata"
}, /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "seg",
color: "secondary"
}, task.projectName), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "seg",
color: "secondary"
}, "Task #", task.id), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
className: "seg",
color: "secondary"
}, "Created from a webhook delivery")), /*#__PURE__*/_react.default.createElement("div", {
className: "description"
}, /*#__PURE__*/_react.default.createElement(_Typography.Body2, null, task.description))), /*#__PURE__*/_react.default.createElement(StyledTaskProperties, null, /*#__PURE__*/_react.default.createElement(SheetField, {
label: "Task List"
}, /*#__PURE__*/_react.default.createElement(_SelectTrigger.SelectTrigger, {
className: "field-control"
}, "Inbox")), /*#__PURE__*/_react.default.createElement(SheetField, {
label: "Assignees"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "assignee-row"
}, /*#__PURE__*/_react.default.createElement(_RoundAvatar.RoundAvatar, {
$bg: _tokens.AVATAR_COLORS.purple,
$size: 28,
$bordered: false
}, "IS"), /*#__PURE__*/_react.default.createElement(_Typography.Body2, null, "Ilija Studen"))), /*#__PURE__*/_react.default.createElement(SheetField, {
label: "Start and Due Date"
}, /*#__PURE__*/_react.default.createElement(_SelectTrigger.SelectTrigger, {
className: "field-control"
}, "Not set")))));
};
/* --- The dialog ---------------------------------------------------------- */
/** Wider than the default 540px dialog so the log columns have room. */
var WebhookLogsDialogStyled = (0, _styledComponents.default)(_Dialog.Dialog).withConfig({
displayName: "WebhookLogsDialog__WebhookLogsDialogStyled",
componentId: "sc-6ol7f6-6"
})(["&&{width:calc(100vw - 64px);max-width:760px;}"]);
var DialogTitleBar = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__DialogTitleBar",
componentId: "sc-6ol7f6-7"
})(["display:flex;align-items:center;justify-content:space-between;gap:16px;.title-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;}"]);
var LogListHeader = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__LogListHeader",
componentId: "sc-6ol7f6-8"
})(["box-sizing:border-box;display:flex;align-items:center;gap:12px;padding:0 8px 8px 44px;.lh-conn{width:180px;flex-shrink:0;}.lh-event{flex:1 1 0%;min-width:0;}.lh-time{width:120px;flex-shrink:0;text-align:right;}.lh-status{width:110px;flex-shrink:0;text-align:left;}.lh-outcomes{width:96px;flex-shrink:0;text-align:right;}"]);
var EmptyLogs = _styledComponents.default.div.withConfig({
displayName: "WebhookLogsDialog__EmptyLogs",
componentId: "sc-6ol7f6-9"
})(["display:flex;flex-direction:column;align-items:center;gap:8px;padding:40px 0;text-align:center;svg{fill:var(--color-theme-500);}"]);
var WebhookLogsDialog = exports.WebhookLogsDialog = function WebhookLogsDialog(_ref7) {
var open = _ref7.open,
onClose = _ref7.onClose,
initialConnectionId = _ref7.initialConnectionId,
connectionIds = _ref7.connectionIds,
repositoryIds = _ref7.repositoryIds;
var _useState5 = (0, _react.useState)(function () {
var initial = {};
if (connectionIds && connectionIds.length > 0) {
initial.connection = connectionIds;
} else if (initialConnectionId) {
initial.connection = [initialConnectionId];
}
if (repositoryIds && repositoryIds.length > 0) {
initial.repository = repositoryIds;
}
return initial;
}),
_useState6 = _slicedToArray(_useState5, 2),
filters = _useState6[0],
setFilters = _useState6[1];
// The task opened from an outcome's "Open Task" link. The dialog stays open
// underneath; the sheet layers on top. `lastTask` keeps the content mounted
// through the sheet's slide-out so it does not blank out mid-animation.
var _useState7 = (0, _react.useState)(null),
_useState8 = _slicedToArray(_useState7, 2),
activeTask = _useState8[0],
setActiveTask = _useState8[1];
var lastTaskRef = (0, _react.useRef)(null);
if (activeTask) {
lastTaskRef.current = activeTask;
}
var connectionsById = (0, _react.useMemo)(function () {
var map = {};
WEBHOOK_CONNECTIONS.forEach(function (connection) {
map[connection.id] = connection;
});
return map;
}, []);
var filterData = (0, _react.useMemo)(function () {
var _filters$connection;
// The Repository options narrow to the connection(s) currently selected in
// the Connection filter; with no connection selected, every repository is
// offered. Displayed as "path (connection name)" (matching the project
// "..." menu Repositories list) and sorted by repository path.
var selectedConnections = (_filters$connection = filters.connection) !== null && _filters$connection !== void 0 ? _filters$connection : [];
var repositorySubmenu = WEBHOOK_REPOSITORIES.filter(function (repository) {
return selectedConnections.length === 0 || selectedConnections.includes(repository.connectionId);
}).slice().sort(function (a, b) {
return a.path.localeCompare(b.path);
}).map(function (repository) {
var _connectionsById$repo, _connectionsById$repo2;
return {
id: repository.id,
name: "".concat(repository.path, " (").concat((_connectionsById$repo = (_connectionsById$repo2 = connectionsById[repository.connectionId]) === null || _connectionsById$repo2 === void 0 ? void 0 : _connectionsById$repo2.name) !== null && _connectionsById$repo !== void 0 ? _connectionsById$repo : "Unknown", ")")
};
});
return [{
id: "connection",
title: "Connection",
submenu: WEBHOOK_CONNECTIONS.map(function (connection) {
return {
id: connection.id,
name: connection.name
};
})
}, {
id: "repository",
title: "Repository",
searchPlaceholder: "Search repositories",
disableInternalSort: true,
submenu: repositorySubmenu
}, {
id: "event_type",
title: "Event Type",
submenu: EVENT_TYPE_OPTIONS.map(function (_ref8) {
var id = _ref8.id,
label = _ref8.label;
return {
id,
name: label
};
})
}, {
id: "status",
title: "Processing Status",
submenu: Object.keys(STATUS_LABELS).map(function (status) {
return {
id: status,
name: STATUS_LABELS[status]
};
})
}];
}, [filters.connection, connectionsById]);
var visibleLogs = (0, _react.useMemo)(function () {
var _filters$connection2, _filters$repository, _filters$event_type, _filters$status;
var byConnection = (_filters$connection2 = filters.connection) !== null && _filters$connection2 !== void 0 ? _filters$connection2 : [];
var byRepository = (_filters$repository = filters.repository) !== null && _filters$repository !== void 0 ? _filters$repository : [];
var byEvent = (_filters$event_type = filters.event_type) !== null && _filters$event_type !== void 0 ? _filters$event_type : [];
var byStatus = (_filters$status = filters.status) !== null && _filters$status !== void 0 ? _filters$status : [];
return WEBHOOK_LOGS.filter(function (log) {
if (byConnection.length && !byConnection.includes(log.connectionId)) {
return false;
}
if (byRepository.length && !byRepository.includes(log.repositoryId)) {
return false;
}
if (byEvent.length && !byEvent.includes(log.eventKind)) {
return false;
}
if (byStatus.length && !byStatus.includes(log.status)) {
return false;
}
return true;
}).sort(function (a, b) {
return b.receivedAt - a.receivedAt;
});
}, [filters]);
// When the Connection filter narrows, drop any selected repositories that no
// longer belong to a selected connection — otherwise a stale repository
// selection would silently filter every log out.
var handleFiltersChange = (0, _react.useCallback)(function (next) {
var _next$connection, _next$repository;
var selectedConnections = (_next$connection = next.connection) !== null && _next$connection !== void 0 ? _next$connection : [];
var selectedRepositories = (_next$repository = next.repository) !== null && _next$repository !== void 0 ? _next$repository : [];
if (selectedConnections.length && selectedRepositories.length) {
var allowedRepositoryIds = new Set(WEBHOOK_REPOSITORIES.filter(function (repository) {
return selectedConnections.includes(repository.connectionId);
}).map(function (repository) {
return repository.id;
}));
var prunedRepositories = selectedRepositories.filter(function (id) {
return allowedRepositoryIds.has(id);
});
if (prunedRepositories.length !== selectedRepositories.length) {
var updated = _objectSpread({}, next);
if (prunedRepositories.length) {
updated.repository = prunedRepositories;
} else {
delete updated.repository;
}
setFilters(updated);
return;
}
}
setFilters(next);
}, []);
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(WebhookLogsDialogStyled, {
open: open,
onClose: onClose,
enableBackgroundClick: true
}, /*#__PURE__*/_react.default.createElement(_Dialog.Dialog.Title, {
disableDefaultHeading: true
}, /*#__PURE__*/_react.default.createElement(DialogTitleBar, null, /*#__PURE__*/_react.default.createElement(_Typography.Header3, null, "Webhook Logs"), /*#__PURE__*/_react.default.createElement("div", {
className: "title-actions"
}, /*#__PURE__*/_react.default.createElement(_Filter.Filter, {
data: filterData,
selected: filters,
label: "Filter",
clearAllText: "Clear All",
noResultText: "No results",
emptyFilterText: "No filters to show",
onChange: handleFiltersChange
}), /*#__PURE__*/_react.default.createElement(_IconButton.IconButton, {
variant: "text gray",
onClick: onClose
}, /*#__PURE__*/_react.default.createElement(_Icons.CancelCrossIcon, null))))), /*#__PURE__*/_react.default.createElement(_Dialog.Dialog.ContentDivider, null), /*#__PURE__*/_react.default.createElement(_Dialog.Dialog.Content, null, visibleLogs.length > 0 ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(LogListHeader, null, /*#__PURE__*/_react.default.createElement(_Typography.Caption2, {
className: "lh-conn",
color: "secondary",
weight: "bold"
}, "CONNECTION"), /*#__PURE__*/_react.default.createElement(_Typography.Caption2, {
className: "lh-event",
color: "secondary",
weight: "bold"
}, "EVENT TYPE"), /*#__PURE__*/_react.default.createElement(_Typography.Caption2, {
className: "lh-time",
color: "secondary",
weight: "bold"
}, "TIME"), /*#__PURE__*/_react.default.createElement(_Typography.Caption2, {
className: "lh-status",
color: "secondary",
weight: "bold"
}, "STATUS"), /*#__PURE__*/_react.default.createElement(_Typography.Caption2, {
className: "lh-outcomes",
color: "secondary",
weight: "bold"
}, "OUTCOMES")), visibleLogs.map(function (log) {
return /*#__PURE__*/_react.default.createElement(LogRow, {
key: log.id,
log: log,
connection: connectionsById[log.connectionId],
onOpenTask: setActiveTask
});
})) : /*#__PURE__*/_react.default.createElement(EmptyLogs, null, /*#__PURE__*/_react.default.createElement(_Icons.AccessLogIcon, null), /*#__PURE__*/_react.default.createElement(_Typography.Body2, {
color: "tertiary"
}, "No webhook logs match the selected filters.")))), /*#__PURE__*/_react.default.createElement(TaskPreviewSheet, {
task: activeTask !== null && activeTask !== void 0 ? activeTask : lastTaskRef.current,
open: Boolean(activeTask),
onClose: function onClose() {
return setActiveTask(null);
}
}));
};
//# sourceMappingURL=WebhookLogsDialog.js.map