src/components/ChartContextMenu.svelte (244 lines of code) (raw):

<script> import { timeFormat } from 'd3-time-format'; import { store, showContextMenu } from '../state/store'; import GitBranch from './icons/GitBranch.svelte'; import ZoomIn from './icons/ZoomIn.svelte'; import Graphs from './icons/Graphs.svelte'; import BarGraph from './icons/BarGraph.svelte'; import { SUPPORTED_METRICS as GLEAN_METRICS } from '../config/glean-base'; export let data; export let x; export let y; export let clickedRef; export let clickedHov; export let zoomUrl; export let pushlogUrl; export let distViewButtonId; let elem; function onClickOutside(e) { if (elem && !elem.contains(e.target)) { $showContextMenu = false; } } function onEscape(e) { if (e.key === 'Escape' && $showContextMenu) { $showContextMenu = false; } } function closeMenu() { $showContextMenu = false; } function engageZoom() { store.setField('ref', clickedRef); store.setField('hov', clickedHov); store.setField('timeHorizon', 'ZOOM'); } function openDistributionView() { document.getElementById(distViewButtonId).click(); } function getDateFromPoint(p) { if (p) { const found = data.find((d) => d.build_id === p); if (found) { return found.label; } } return data[data.length - 1].label; } const dateFormatter = timeFormat('%Y-%m-%d'); const timeFormatter = timeFormat('%H:%M:%S'); const getTelemetryPath = () => { if ($store.probe.type === 'histogram') { return ['main', 'parent'].includes($store.productDimensions.process) ? `payload.histograms.${$store.probe.name}` : `payload.processes.${$store.productDimensions.process}.histograms.${$store.probe.name}`; } return undefined; }; const table = $store.productDimensions.channel === 'nightly' ? 'main_nightly' : 'main_1pct'; const REDASH_PROBE_COMPARISON_URL = 'https://sql.telemetry.mozilla.org/dashboard/test_38?'; const getComparisonViewinSTMO = (clicked, hovered) => { const queryParams = new URLSearchParams({ p_Table: `telemetry.${table}`, p_Probe: getTelemetryPath(), 'p_Build 1': clicked, 'p_Build 2': hovered, 'p_Start Date': dateFormatter(getDateFromPoint(clicked)), 'p_Start Date 2': dateFormatter(getDateFromPoint(hovered)), p_channel: $store.productDimensions.channel, // do not add OS filter to the query if 'All OSes' is selected 'p_OS Filter': $store.productDimensions.os === '*' ? ' ' : `AND normalized_os="${$store.productDimensions.os}"`, }); return REDASH_PROBE_COMPARISON_URL + queryParams.toString(); }; const canCompareDistributions = function () { return ( ['firefox', 'fog', 'fenix'].includes($store.product) && ['histogram', 'scalar'] .concat(GLEAN_METRICS) .includes($store.probe.type) && !!document.getElementById(distViewButtonId) ); }; let STMOComparisonLink; $: STMOComparisonLink = getComparisonViewinSTMO(clickedRef, clickedHov); </script> <style> div#menu { position: absolute; display: grid; background-color: white; width: 300px; border: 2px solid #e5e7eb; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); border-radius: 0.375rem; font-size: var(--text-02); color: var(--cool-gray-600); } div.head { margin-bottom: 5px; font-size: var(--text-01); } div.range { padding: 1px 15px; grid-gap: 5px; display: flex; flex-direction: row; } div.key { width: 20%; font-weight: bold; } div.value { font-family: var(--main-mono-font); width: 80%; } div.value span { font-weight: bold; } div.options { box-sizing: border-box; display: grid; grid-template-columns: 30px 1fr; grid-template-areas: 'option-icon option-text'; } div.option { display: contents; } div.option:hover > div { background-color: #e5e7eb; } div.option-icon { padding: 5px 5px 5px 15px; } div.option-link { padding: 5px 15px 5px 5px; } div.option-icon a { display: block; cursor: pointer; text-decoration: none; color: var(--digital-blue-500); } div.option-link a { text-transform: uppercase; display: block; cursor: pointer; text-decoration: none; color: var(--digital-blue-500); font-size: var(--text-02); } </style> <svelte:body on:click={onClickOutside} on:keydown|stopPropagation={onEscape} /> {#if $showContextMenu} <!-- svelte-ignore a11y-click-events-have-key-events --> <div id="menu" style="top: {y + window.scrollY}px; left: {x + window.scrollX}px;" on:click={closeMenu} bind:this={elem} > <div class="head"> <div class="range"> <div class="key">Range:</div> <div class="value"> <span>{dateFormatter(getDateFromPoint(clickedHov))}</span> {timeFormatter(getDateFromPoint(clickedHov))} </div> </div> <div class="range"> <div class="key">to:</div> <div class="value"> <span>{dateFormatter(getDateFromPoint(clickedRef))}</span> {timeFormatter(getDateFromPoint(clickedRef))} </div> </div> </div> <div class="options"> <div class="option"> <div class="option-icon"> <a href={zoomUrl} on:click|preventDefault={engageZoom}> <ZoomIn size="12" /> </a> </div> <div class="option-link"> <a href={zoomUrl} on:click|preventDefault={engageZoom}> Zoom to Range </a> </div> </div> {#if canCompareDistributions()} <div class="option"> <div class="option-icon"> <a href="distribution-view" on:click|preventDefault={openDistributionView} > <BarGraph /> </a> </div> <div class="option-link"> <a href="distribution-view" on:click|preventDefault={openDistributionView} target="_blank">Distribution comparison</a > </div> </div> {/if} {#if $store.product === 'firefox' && $store.probe.type === 'histogram'} <div class="option"> <div class="option-icon"> <a href={STMOComparisonLink} target="_blank"> <Graphs /> </a> </div> <div class="option-link"> <a href={STMOComparisonLink} target="_blank" >View Comparison in STMO</a > </div> </div> {/if} {#if pushlogUrl} <div class="option"> <div class="option-icon"> <a href={pushlogUrl} target="pushlog"> <GitBranch size="12" /> </a> </div> <div class="option-link"> <a href={pushlogUrl} target="pushlog">View Changesets in Range</a> </div> </div> {/if} </div> </div> {/if}