UNPKG

@activecollab/components

Version:

ActiveCollab Components

1,006 lines (982 loc) 43.7 kB
"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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }; 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