您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Read the README
// ==UserScript== // @name Sentry to Datadog RUM and Log buttons // @version 3 // @grant none // @match https://*.sentry.io/issues/* // @license MIT // @namespace happyviking // @description Read the README // ==/UserScript== // This script requires getting some react props from these HTML elements. // Methodology for this is borrowed (and modified) from here: // https://stackoverflow.com/questions/70507318/how-to-get-react-element-props-from-html-element-with-javascript // But it requires some more DOM access than userscripts (and extensions) normally have // https://stackoverflow.com/questions/72726773/access-react-props-functions-via-native-javascript // so I've put everything in an actual script tag // Logic still behaves the same, comment out the first line below <COMMENT LINE IF EDITING> to get syntax highligting if you need to edit the script // <COMMENT LINE BELOW IF EDITING> const logic = ` //https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } const main = async () => { const info = {} //ID await waitForElm('div[data-test-id="event-tags"]') //Just waiting till it loads const ID = document.querySelector('td[data-test-id="user-context-id-value"]')?.querySelector(".val-string > span")?.textContent info.id = ID //RUM const RUMTable = document.querySelector('div[data-test-id="event-section-context-datadog"]')?.nextElementSibling if (RUMTable){ //https://stackoverflow.com/questions/37098405/javascript-queryselector-find-div-by-innertext const tableKey = document.evaluate('//td[text()="RUM"]', RUMTable, null, XPathResult.ANY_TYPE, null).iterateNext() const RUM = tableKey?.nextSibling?.querySelector(".val-string > span")?.textContent info.RUM = RUM } //Timestamp const timeElement = document.querySelector("time") let props = getReactProps(timeElement); const exactTimestamp = new Date(props.date) const imrovedTimeString = exactTimestamp.toLocaleString(undefined, { weekday: undefined, year: undefined, month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZone: "UTC" }) timeElement.textContent = imrovedTimeString info.time = imrovedTimeString const buttonHolder = document.createElement("div") buttonHolder.id = "thebuttonholder" const parent = document.querySelector("header")?.parentElement if (!parent) return if (!document.getElementById("thebuttonholder")) parent.insertBefore(buttonHolder, document.querySelector('div[role=tabpanel]')) if (!document.getElementById("rum-shortcut")){ if (info.RUM){ buttonHolder.appendChild(makeButton("Provided RUM","rum-shortcut", info.RUM)) }else{ const text = document.createElement("p") text.textContent = "NO PROVIDED RUM 🥲" text.style.color = "red" text.style.fontSize = "18px" text.id = "rum-shortcut" buttonHolder.appendChild(text) } } if (info.time && info.id && !document.getElementById("manual-rum-shortcut")){ //Adding more info to the date so that the resultant date object is accurate info.time += " " + (new Date()).getFullYear() + " UTC" const eventTime = new Date(Date.parse(info.time)).getTime() const OFFSET = 300000 //5 minutes in milliseconfs const OFFSET_SHORTER = 60000 //1 minutes in milliseconfs const OFFSET_FOR_HIGHLIGHTING = 30000 //30 seconds in milliseconfs //Adding inferred RUM button const manualRumURL = new URL("https://app.datadoghq.com/rum/sessions?query=%40type%3Aerror&cols=&tab=session&viz=stream&live=false") manualRumURL.searchParams.set("query", (manualRumURL.searchParams.get("query") || "") + " @usr.id:" + info.id) manualRumURL.searchParams.set("from_ts", eventTime - OFFSET ) manualRumURL.searchParams.set("to_ts", eventTime + OFFSET) //For my "Datadog RUM log highlighting" script manualRumURL.searchParams.set("highlight_from", eventTime - OFFSET_FOR_HIGHLIGHTING ) manualRumURL.searchParams.set("highlight_to", eventTime + OFFSET_FOR_HIGHLIGHTING) buttonHolder.appendChild(makeButton("Inferred RUM","manual-rum-shortcut", manualRumURL.toString())) //Adding Logs button const logsUrl = new URL("https://app.datadoghq.com/logs?cols=host%2Cservice%2C%40accountName%2C%40args.url&index=&messageDisplay=inline&refresh_mode=sliding&stream_sort=time%2Cdesc&viz=stream&live=false") logsUrl.searchParams.set("query", "@usr.id:" + info.id) logsUrl.searchParams.set("from_ts", eventTime - OFFSET_SHORTER ) logsUrl.searchParams.set("to_ts", eventTime + OFFSET_SHORTER) //For my "Datadog Log log highlighting" script logsUrl.searchParams.set("highlight_from", eventTime - OFFSET_FOR_HIGHLIGHTING ) logsUrl.searchParams.set("highlight_to", eventTime + OFFSET_FOR_HIGHLIGHTING) buttonHolder.appendChild(makeButton("Relevant Logs","logs-shortcut", logsUrl.toString())) } } const makeButton = (text, id, href) => { const button = document.createElement("button") button.textContent = text const link = document.createElement("a") link.appendChild(button) link.href = href link.target="_blank" link.id = id button.className = document.querySelector('button[aria-label="Resolve"]')?.className ?? "" return link } const getReactProps = (target) => { let keyof_ReactProps = undefined let parent = target.parentElement while (parent) { keyof_ReactProps = Object.keys(parent).find(k => k.startsWith("__reactProps$")); if (!keyof_ReactProps){ parent = parent.parentElement }else{ break } } const symof_ReactFragment = Symbol.for("react.fragment"); //Find the path from target to parent let path = []; let elem = target; while (elem !== parent) { let index = 0; for (let sibling = elem; sibling != null;) { if (sibling[keyof_ReactProps]) index++; sibling = sibling.previousElementSibling; } path.push({ child: elem, index }); elem = elem.parentElement; } //Walk down the path to find the react state props let state = elem[keyof_ReactProps]; for (let i = path.length - 1; i >= 0 && state != null; i--) { //Find the target child state index let childStateIndex = 0, childElemIndex = 0; while (childStateIndex < state.children.length) { let childState = state.children[childStateIndex]; if (childState instanceof Object) { //Fragment children are inlined in the parent DOM element let isFragment = childState.type === symof_ReactFragment && childState.props.children.length; childElemIndex += isFragment ? childState.props.children.length : 1; if (childElemIndex === path[i].index) break; } childStateIndex++; } let childState = state.children[childStateIndex] ?? (childStateIndex === 0 ? state.children : null); state = childState?.props; elem = path[i].child; } return state; } main() // ` //DOn't remove this line, this terminales the script string when the line below the <COMMENT LINE IF EDITING> line is not commented out const attachScript = () => { const id = "button-adder-script" if (document.getElementById(id)) return console.log("Adding script for Sentry to Datadog userscript") const script = document.createElement("script"); script.textContent = logic; script.id = id document.body.appendChild(script); } //There are probably cleaner ways to do this but I don't really care, this works and this //is supposed to be fast let currentPage = location.href; attachScript() setInterval(() => { if (currentPage != location.href){ currentPage = location.href; attachScript() } }, 500);