@agent-native/core 0.149.1 → 0.149.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +122 -9
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +69 -60
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -3
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -27
- package/corpus/templates/slides/app/pages/Index.tsx +240 -89
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +129 -10
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +2 -2
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
package/corpus/README.md
CHANGED
|
@@ -514,6 +514,8 @@ type PanelConfig = Record<string, unknown> & {
|
|
|
514
514
|
// Series to plot against a second, right-hand y-axis. See "Dual-Axis Charts".
|
|
515
515
|
rightYKeys?: string[];
|
|
516
516
|
rightYFormatter?: "number" | "currency" | "percent";
|
|
517
|
+
// Exact series-key to display-label aliases for legends and tooltips.
|
|
518
|
+
seriesLabels?: Record<string, string>;
|
|
517
519
|
};
|
|
518
520
|
|
|
519
521
|
type PanelPatch = {
|
|
@@ -592,7 +592,12 @@ function usesPrometheusPresentation(panel: SqlPanel): boolean {
|
|
|
592
592
|
return panel.source === "prometheus" || panel.source === "demo";
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
-
function formatSeriesLabelForPanel(
|
|
595
|
+
export function formatSeriesLabelForPanel(
|
|
596
|
+
panel: SqlPanel,
|
|
597
|
+
value: string,
|
|
598
|
+
): string {
|
|
599
|
+
const alias = panel.config?.seriesLabels?.[value]?.trim();
|
|
600
|
+
if (alias) return alias;
|
|
596
601
|
return usesPrometheusPresentation(panel) ? formatSeriesLabel(value) : value;
|
|
597
602
|
}
|
|
598
603
|
|
|
@@ -1241,6 +1246,7 @@ interface SqlChartProps {
|
|
|
1241
1246
|
loadData?: boolean;
|
|
1242
1247
|
reportScreenshot?: boolean;
|
|
1243
1248
|
onExportCsvChange?: (handler: (() => void) | null) => void;
|
|
1249
|
+
onCopyTableChange?: (handler: (() => Promise<void>) | null) => void;
|
|
1244
1250
|
/** Dashboard/panel state sent to slot-backed extension boxes. */
|
|
1245
1251
|
extensionContext?: Record<string, unknown> | null;
|
|
1246
1252
|
}
|
|
@@ -1251,6 +1257,7 @@ export function SqlChart({
|
|
|
1251
1257
|
loadData = true,
|
|
1252
1258
|
reportScreenshot = false,
|
|
1253
1259
|
onExportCsvChange,
|
|
1260
|
+
onCopyTableChange,
|
|
1254
1261
|
extensionContext,
|
|
1255
1262
|
}: SqlChartProps) {
|
|
1256
1263
|
const t = useT();
|
|
@@ -1426,6 +1433,7 @@ export function SqlChart({
|
|
|
1426
1433
|
rows={rows}
|
|
1427
1434
|
panel={panel}
|
|
1428
1435
|
onExportCsvChange={onExportCsvChange}
|
|
1436
|
+
onCopyTableChange={onCopyTableChange}
|
|
1429
1437
|
/>,
|
|
1430
1438
|
);
|
|
1431
1439
|
}
|
|
@@ -1605,21 +1613,68 @@ function MetricRenderer({
|
|
|
1605
1613
|
);
|
|
1606
1614
|
}
|
|
1607
1615
|
|
|
1608
|
-
function
|
|
1616
|
+
function numericTableValue(value: unknown): number | null {
|
|
1617
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
1618
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
1619
|
+
const numeric = Number(value);
|
|
1620
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
export type TableSort = {
|
|
1624
|
+
key: string;
|
|
1625
|
+
direction: "asc" | "desc";
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
export function sortTableRows(
|
|
1629
|
+
rows: Record<string, unknown>[],
|
|
1630
|
+
sorts: TableSort[],
|
|
1631
|
+
): Record<string, unknown>[] {
|
|
1632
|
+
if (sorts.length === 0) return rows;
|
|
1633
|
+
|
|
1634
|
+
return rows
|
|
1635
|
+
.map((row, index) => ({ row, index }))
|
|
1636
|
+
.sort((a, b) => {
|
|
1637
|
+
for (const sort of sorts) {
|
|
1638
|
+
const av = a.row[sort.key];
|
|
1639
|
+
const bv = b.row[sort.key];
|
|
1640
|
+
if (av == null && bv == null) continue;
|
|
1641
|
+
if (av == null) return 1;
|
|
1642
|
+
if (bv == null) return -1;
|
|
1643
|
+
|
|
1644
|
+
const an = numericTableValue(av);
|
|
1645
|
+
const bn = numericTableValue(bv);
|
|
1646
|
+
const comparison =
|
|
1647
|
+
an !== null && bn !== null
|
|
1648
|
+
? an - bn
|
|
1649
|
+
: String(av).localeCompare(String(bv));
|
|
1650
|
+
if (comparison !== 0) {
|
|
1651
|
+
return sort.direction === "asc" ? comparison : -comparison;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
return a.index - b.index;
|
|
1655
|
+
})
|
|
1656
|
+
.map(({ row }) => row);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
export function formatCell(
|
|
1660
|
+
value: unknown,
|
|
1661
|
+
format: ColumnFormat | undefined,
|
|
1662
|
+
): string {
|
|
1609
1663
|
if (value == null) return "";
|
|
1610
|
-
|
|
1611
|
-
|
|
1664
|
+
const numeric = numericTableValue(value);
|
|
1665
|
+
if (format === "number" && numeric !== null) {
|
|
1666
|
+
return numeric.toLocaleString();
|
|
1612
1667
|
}
|
|
1613
|
-
if (format === "currency" &&
|
|
1614
|
-
return `$${
|
|
1668
|
+
if (format === "currency" && numeric !== null) {
|
|
1669
|
+
return `$${numeric.toLocaleString()}`;
|
|
1615
1670
|
}
|
|
1616
|
-
if (format === "percent" &&
|
|
1617
|
-
const pct =
|
|
1671
|
+
if (format === "percent" && numeric !== null) {
|
|
1672
|
+
const pct = numeric <= 1 && numeric >= -1 ? numeric * 100 : numeric;
|
|
1618
1673
|
return `${pct.toFixed(2)}%`;
|
|
1619
1674
|
}
|
|
1620
|
-
if (format === "delta" &&
|
|
1621
|
-
const sign =
|
|
1622
|
-
return `${sign}${
|
|
1675
|
+
if (format === "delta" && numeric !== null) {
|
|
1676
|
+
const sign = numeric > 0 ? "+" : "";
|
|
1677
|
+
return `${sign}${numeric.toFixed(1)}%`;
|
|
1623
1678
|
}
|
|
1624
1679
|
if (format === "date") {
|
|
1625
1680
|
const d = new Date(String(value));
|
|
@@ -1634,6 +1689,23 @@ function formatCell(value: unknown, format: ColumnFormat | undefined): string {
|
|
|
1634
1689
|
return String(value);
|
|
1635
1690
|
}
|
|
1636
1691
|
|
|
1692
|
+
function clipboardCell(value: string): string {
|
|
1693
|
+
return value.replace(/[\t\r\n]+/g, " ");
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
export function buildTableClipboardText(
|
|
1697
|
+
columns: TableColumnConfig[],
|
|
1698
|
+
rows: Record<string, unknown>[],
|
|
1699
|
+
): string {
|
|
1700
|
+
const tableRows = [
|
|
1701
|
+
columns.map((col) => col.label ?? col.key),
|
|
1702
|
+
...rows.map((row) =>
|
|
1703
|
+
columns.map((col) => formatCell(row[col.key], col.format)),
|
|
1704
|
+
),
|
|
1705
|
+
];
|
|
1706
|
+
return tableRows.map((row) => row.map(clipboardCell).join("\t")).join("\n");
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1637
1709
|
export function safeDashboardLinkHref(value: unknown): string | null {
|
|
1638
1710
|
if (typeof value !== "string") return null;
|
|
1639
1711
|
const href = value.trim();
|
|
@@ -1653,13 +1725,14 @@ export function safeDashboardLinkHref(value: unknown): string | null {
|
|
|
1653
1725
|
}
|
|
1654
1726
|
|
|
1655
1727
|
function renderDeltaCell(value: unknown): ReactNode {
|
|
1656
|
-
|
|
1728
|
+
const numeric = numericTableValue(value);
|
|
1729
|
+
if (numeric === null) {
|
|
1657
1730
|
return <span className="text-muted-foreground">-</span>;
|
|
1658
1731
|
}
|
|
1659
|
-
const sign =
|
|
1660
|
-
const text = `${sign}${
|
|
1661
|
-
const isPositive =
|
|
1662
|
-
const isNegative =
|
|
1732
|
+
const sign = numeric > 0 ? "+" : "";
|
|
1733
|
+
const text = `${sign}${numeric.toFixed(1)}%`;
|
|
1734
|
+
const isPositive = numeric > 0;
|
|
1735
|
+
const isNegative = numeric < 0;
|
|
1663
1736
|
const colorClass = isPositive
|
|
1664
1737
|
? "text-emerald-500"
|
|
1665
1738
|
: isNegative
|
|
@@ -1670,7 +1743,7 @@ function renderDeltaCell(value: unknown): ReactNode {
|
|
|
1670
1743
|
: isNegative
|
|
1671
1744
|
? IconTrendingDown
|
|
1672
1745
|
: null;
|
|
1673
|
-
const critical = Math.abs(
|
|
1746
|
+
const critical = Math.abs(numeric) > 30;
|
|
1674
1747
|
return (
|
|
1675
1748
|
<span
|
|
1676
1749
|
className={`inline-flex items-center justify-end gap-1 ${colorClass}`}
|
|
@@ -1715,10 +1788,12 @@ function TableRenderer({
|
|
|
1715
1788
|
rows,
|
|
1716
1789
|
panel,
|
|
1717
1790
|
onExportCsvChange,
|
|
1791
|
+
onCopyTableChange,
|
|
1718
1792
|
}: {
|
|
1719
1793
|
rows: Record<string, unknown>[];
|
|
1720
1794
|
panel: SqlPanel;
|
|
1721
1795
|
onExportCsvChange?: (handler: (() => void) | null) => void;
|
|
1796
|
+
onCopyTableChange?: (handler: (() => Promise<void>) | null) => void;
|
|
1722
1797
|
}) {
|
|
1723
1798
|
const t = useT();
|
|
1724
1799
|
const config = panel.config;
|
|
@@ -1744,39 +1819,40 @@ function TableRenderer({
|
|
|
1744
1819
|
return limit != null && rows.length > limit ? rows.slice(0, limit) : rows;
|
|
1745
1820
|
}, [rows, config?.limit]);
|
|
1746
1821
|
|
|
1747
|
-
const [
|
|
1748
|
-
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
|
1822
|
+
const [sortColumns, setSortColumns] = useState<TableSort[]>([]);
|
|
1749
1823
|
const [page, setPage] = useState(0);
|
|
1750
1824
|
const [pageSize, setPageSize] = useState(10);
|
|
1751
1825
|
|
|
1752
1826
|
const sortedRows = useMemo(() => {
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
const av = a[sortKey];
|
|
1756
|
-
const bv = b[sortKey];
|
|
1757
|
-
if (av == null && bv == null) return 0;
|
|
1758
|
-
if (av == null) return 1;
|
|
1759
|
-
if (bv == null) return -1;
|
|
1760
|
-
if (typeof av === "number" && typeof bv === "number") {
|
|
1761
|
-
return av - bv;
|
|
1762
|
-
}
|
|
1763
|
-
return String(av).localeCompare(String(bv));
|
|
1764
|
-
});
|
|
1765
|
-
if (sortDir === "desc") sorted.reverse();
|
|
1766
|
-
return sorted;
|
|
1767
|
-
}, [limitedRows, sortKey, sortDir, sortable]);
|
|
1827
|
+
return sortable ? sortTableRows(limitedRows, sortColumns) : limitedRows;
|
|
1828
|
+
}, [limitedRows, sortColumns, sortable]);
|
|
1768
1829
|
|
|
1769
1830
|
const pageCount = Math.ceil(sortedRows.length / pageSize);
|
|
1770
1831
|
const displayRows = sortedRows.slice(page * pageSize, (page + 1) * pageSize);
|
|
1771
1832
|
|
|
1772
|
-
const handleHeaderClick = (key: string) => {
|
|
1833
|
+
const handleHeaderClick = (key: string, shiftKey: boolean) => {
|
|
1773
1834
|
if (!sortable) return;
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1835
|
+
setSortColumns((current) => {
|
|
1836
|
+
const index = current.findIndex((sort) => sort.key === key);
|
|
1837
|
+
if (!shiftKey) {
|
|
1838
|
+
const existing = index >= 0 ? current[index] : undefined;
|
|
1839
|
+
return [
|
|
1840
|
+
{
|
|
1841
|
+
key,
|
|
1842
|
+
direction: existing?.direction === "desc" ? "asc" : "desc",
|
|
1843
|
+
},
|
|
1844
|
+
];
|
|
1845
|
+
}
|
|
1846
|
+
if (index < 0) return [...current, { key, direction: "desc" }];
|
|
1847
|
+
return current.map((sort, sortIndex) =>
|
|
1848
|
+
sortIndex === index
|
|
1849
|
+
? {
|
|
1850
|
+
...sort,
|
|
1851
|
+
direction: sort.direction === "desc" ? "asc" : "desc",
|
|
1852
|
+
}
|
|
1853
|
+
: sort,
|
|
1854
|
+
);
|
|
1855
|
+
});
|
|
1780
1856
|
setPage(0);
|
|
1781
1857
|
};
|
|
1782
1858
|
|
|
@@ -1801,10 +1877,23 @@ function TableRenderer({
|
|
|
1801
1877
|
URL.revokeObjectURL(url);
|
|
1802
1878
|
}, [columns, panel.id, sortedRows]);
|
|
1803
1879
|
|
|
1880
|
+
const handleCopyTable = useCallback(async () => {
|
|
1881
|
+
if (!navigator.clipboard?.writeText) {
|
|
1882
|
+
throw new Error("Clipboard unavailable");
|
|
1883
|
+
}
|
|
1884
|
+
await navigator.clipboard.writeText(
|
|
1885
|
+
buildTableClipboardText(columns, sortedRows),
|
|
1886
|
+
);
|
|
1887
|
+
}, [columns, sortedRows]);
|
|
1888
|
+
|
|
1804
1889
|
useEffect(() => {
|
|
1805
1890
|
onExportCsvChange?.(handleExportCsv);
|
|
1806
|
-
|
|
1807
|
-
|
|
1891
|
+
onCopyTableChange?.(handleCopyTable);
|
|
1892
|
+
return () => {
|
|
1893
|
+
onExportCsvChange?.(null);
|
|
1894
|
+
onCopyTableChange?.(null);
|
|
1895
|
+
};
|
|
1896
|
+
}, [handleCopyTable, handleExportCsv, onCopyTableChange, onExportCsvChange]);
|
|
1808
1897
|
|
|
1809
1898
|
return (
|
|
1810
1899
|
<div className={`space-y-1 ${TABLE_PANEL_MIN_HEIGHT_CLASS}`}>
|
|
@@ -1814,22 +1903,37 @@ function TableRenderer({
|
|
|
1814
1903
|
<tr className="border-b border-border">
|
|
1815
1904
|
{columns.map((col) => {
|
|
1816
1905
|
const label = col.label ?? col.key;
|
|
1817
|
-
const
|
|
1906
|
+
const sort = sortColumns.find((item) => item.key === col.key);
|
|
1907
|
+
const isSorted = sort !== undefined;
|
|
1818
1908
|
return (
|
|
1819
1909
|
<th
|
|
1820
1910
|
key={col.key}
|
|
1911
|
+
aria-sort={
|
|
1912
|
+
sort
|
|
1913
|
+
? sort.direction === "asc"
|
|
1914
|
+
? "ascending"
|
|
1915
|
+
: "descending"
|
|
1916
|
+
: "none"
|
|
1917
|
+
}
|
|
1821
1918
|
className={`text-left py-1.5 px-2 font-medium text-muted-foreground whitespace-nowrap ${
|
|
1822
1919
|
sortable
|
|
1823
1920
|
? "cursor-pointer select-none hover:text-foreground"
|
|
1824
1921
|
: ""
|
|
1825
1922
|
}`}
|
|
1826
|
-
onClick={() =>
|
|
1923
|
+
onClick={(event) =>
|
|
1924
|
+
handleHeaderClick(col.key, event.shiftKey)
|
|
1925
|
+
}
|
|
1926
|
+
title={
|
|
1927
|
+
sortable
|
|
1928
|
+
? t("sqlDashboard.multiColumnSortHelp")
|
|
1929
|
+
: undefined
|
|
1930
|
+
}
|
|
1827
1931
|
>
|
|
1828
1932
|
<span className="inline-flex items-center gap-1">
|
|
1829
1933
|
{label}
|
|
1830
1934
|
{sortable &&
|
|
1831
1935
|
(isSorted ? (
|
|
1832
|
-
|
|
1936
|
+
sort?.direction === "asc" ? (
|
|
1833
1937
|
<IconSortAscending className="h-3 w-3" />
|
|
1834
1938
|
) : (
|
|
1835
1939
|
<IconSortDescending className="h-3 w-3" />
|
|
@@ -673,6 +673,8 @@ const enUS = {
|
|
|
673
673
|
refresh: "Refresh",
|
|
674
674
|
refreshing: "Refreshing...",
|
|
675
675
|
downloadCsv: "Download CSV",
|
|
676
|
+
copyTable: "Copy table",
|
|
677
|
+
multiColumnSortHelp: "Click to sort. Shift-click to add another sort.",
|
|
676
678
|
exportToGoogleSheets: "Export to Google Sheets",
|
|
677
679
|
exportingToGoogleSheets: "Exporting to Google Sheets...",
|
|
678
680
|
googleSheetsExported: "Exported to Google Sheets",
|
|
@@ -688,6 +690,7 @@ const enUS = {
|
|
|
688
690
|
copied: "Copied",
|
|
689
691
|
copy: "Copy",
|
|
690
692
|
couldNotCopySql: "Couldn't copy SQL",
|
|
693
|
+
couldNotCopyTable: "Couldn't copy table",
|
|
691
694
|
failedToFormatSql: "Failed to format SQL",
|
|
692
695
|
close: "Close",
|
|
693
696
|
save: "Save",
|
|
@@ -7142,6 +7145,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7142
7145
|
copyResolvedSql: "复制解析 SQL",
|
|
7143
7146
|
copySql: "复制 SQL",
|
|
7144
7147
|
couldNotCopySql: "无法复制 SQL",
|
|
7148
|
+
couldNotCopyTable: "无法复制表格",
|
|
7145
7149
|
dashboardActions: "仪表板详细信息和操作",
|
|
7146
7150
|
deletePanelTitle: "删除面板?",
|
|
7147
7151
|
deletePermanentlyTitle: "永久删除?",
|
|
@@ -7154,6 +7158,8 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7154
7158
|
discardChanges: "放弃更改",
|
|
7155
7159
|
dismissDemoIntro: "关闭演示介绍",
|
|
7156
7160
|
downloadCsv: "下载CSV",
|
|
7161
|
+
copyTable: "复制表格",
|
|
7162
|
+
multiColumnSortHelp: "点击排序。按住 Shift 点击可添加另一个排序。",
|
|
7157
7163
|
exportToGoogleSheets: "导出到 Google Sheets",
|
|
7158
7164
|
exportingToGoogleSheets: "正在导出到 Google Sheets...",
|
|
7159
7165
|
googleSheetsExported: "已导出到 Google Sheets",
|
|
@@ -7382,6 +7388,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7382
7388
|
copyResolvedSql: "Copia resuelta SQL",
|
|
7383
7389
|
copySql: "Copiar SQL",
|
|
7384
7390
|
couldNotCopySql: "No se pudo copiar SQL",
|
|
7391
|
+
couldNotCopyTable: "No se pudo copiar la tabla",
|
|
7385
7392
|
dashboardActions: "Detalles y acciones del panel",
|
|
7386
7393
|
deletePanelTitle: "¿Eliminar panel?",
|
|
7387
7394
|
deletePermanentlyTitle: "¿Eliminar permanentemente?",
|
|
@@ -7394,6 +7401,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7394
7401
|
discardChanges: "Descartar cambios",
|
|
7395
7402
|
dismissDemoIntro: "Descartar la introducción de la demostración",
|
|
7396
7403
|
downloadCsv: "Descargar CSV",
|
|
7404
|
+
copyTable: "Copiar tabla",
|
|
7405
|
+
multiColumnSortHelp:
|
|
7406
|
+
"Haz clic para ordenar. Mantén pulsada la tecla Mayús y haz clic para añadir otro orden.",
|
|
7397
7407
|
exportToGoogleSheets: "Exportar a Google Sheets",
|
|
7398
7408
|
exportingToGoogleSheets: "Exportando a Google Sheets...",
|
|
7399
7409
|
googleSheetsExported: "Exportado a Google Sheets",
|
|
@@ -7628,6 +7638,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7628
7638
|
copyResolvedSql: "Copie résolue SQL",
|
|
7629
7639
|
copySql: "Copier SQL",
|
|
7630
7640
|
couldNotCopySql: "Impossible de copier SQL",
|
|
7641
|
+
couldNotCopyTable: "Impossible de copier le tableau",
|
|
7631
7642
|
dashboardActions: "Détails et actions du tableau de bord",
|
|
7632
7643
|
deletePanelTitle: "Supprimer le panneau ?",
|
|
7633
7644
|
deletePermanentlyTitle: "Supprimer définitivement ?",
|
|
@@ -7640,6 +7651,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7640
7651
|
discardChanges: "Ignorer les modifications",
|
|
7641
7652
|
dismissDemoIntro: "Ignorer l'intro de la démo",
|
|
7642
7653
|
downloadCsv: "Télécharger CSV",
|
|
7654
|
+
copyTable: "Copier le tableau",
|
|
7655
|
+
multiColumnSortHelp:
|
|
7656
|
+
"Cliquez pour trier. Maj-cliquez pour ajouter un autre tri.",
|
|
7643
7657
|
exportToGoogleSheets: "Exporter vers Google Sheets",
|
|
7644
7658
|
exportingToGoogleSheets: "Exportation vers Google Sheets...",
|
|
7645
7659
|
googleSheetsExported: "Exporté vers Google Sheets",
|
|
@@ -7876,6 +7890,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7876
7890
|
copyResolvedSql: "Kopieren Sie SQL aufgelöst",
|
|
7877
7891
|
copySql: "Kopieren Sie SQL",
|
|
7878
7892
|
couldNotCopySql: "SQL konnte nicht kopiert werden",
|
|
7893
|
+
couldNotCopyTable: "Tabelle konnte nicht kopiert werden",
|
|
7879
7894
|
dashboardActions: "Dashboard-Details und Aktionen",
|
|
7880
7895
|
deletePanelTitle: "Panel löschen?",
|
|
7881
7896
|
deletePermanentlyTitle: "Endgültig löschen?",
|
|
@@ -7888,6 +7903,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
7888
7903
|
discardChanges: "Änderungen verwerfen",
|
|
7889
7904
|
dismissDemoIntro: "Demo-Intro schließen",
|
|
7890
7905
|
downloadCsv: "Laden Sie CSV herunter",
|
|
7906
|
+
copyTable: "Tabelle kopieren",
|
|
7907
|
+
multiColumnSortHelp:
|
|
7908
|
+
"Zum Sortieren klicken. Mit Umschalt-Klick eine weitere Sortierung hinzufügen.",
|
|
7891
7909
|
exportToGoogleSheets: "Nach Google Sheets exportieren",
|
|
7892
7910
|
exportingToGoogleSheets: "Export nach Google Sheets...",
|
|
7893
7911
|
googleSheetsExported: "Nach Google Sheets exportiert",
|
|
@@ -8117,6 +8135,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8117
8135
|
copyResolvedSql: "解決された SQL をコピーします",
|
|
8118
8136
|
copySql: "SQLをコピーする",
|
|
8119
8137
|
couldNotCopySql: "SQLをコピーできませんでした",
|
|
8138
|
+
couldNotCopyTable: "テーブルをコピーできませんでした",
|
|
8120
8139
|
dashboardActions: "ダッシュボードの詳細とアクション",
|
|
8121
8140
|
deletePanelTitle: "パネルを削除しますか?",
|
|
8122
8141
|
deletePermanentlyTitle: "永久に削除しますか?",
|
|
@@ -8129,6 +8148,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8129
8148
|
discardChanges: "変更を破棄する",
|
|
8130
8149
|
dismissDemoIntro: "デモのイントロを閉じる",
|
|
8131
8150
|
downloadCsv: "CSVをダウンロード",
|
|
8151
|
+
copyTable: "テーブルをコピー",
|
|
8152
|
+
multiColumnSortHelp:
|
|
8153
|
+
"クリックして並べ替え。Shiftキーを押しながらクリックすると別の並べ替えを追加できます。",
|
|
8132
8154
|
exportToGoogleSheets: "Google スプレッドシートにエクスポート",
|
|
8133
8155
|
exportingToGoogleSheets: "Google スプレッドシートにエクスポート中...",
|
|
8134
8156
|
googleSheetsExported: "Google スプレッドシートにエクスポートしました",
|
|
@@ -8357,6 +8379,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8357
8379
|
copyResolvedSql: "해결된 SQL 복사",
|
|
8358
8380
|
copySql: "SQL 복사",
|
|
8359
8381
|
couldNotCopySql: "SQL을 복사할 수 없습니다.",
|
|
8382
|
+
couldNotCopyTable: "표를 복사할 수 없습니다.",
|
|
8360
8383
|
dashboardActions: "대시보드 세부정보 및 작업",
|
|
8361
8384
|
deletePanelTitle: "패널을 삭제하시겠습니까?",
|
|
8362
8385
|
deletePermanentlyTitle: "영구적으로 삭제하시겠습니까?",
|
|
@@ -8369,6 +8392,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8369
8392
|
discardChanges: "변경사항 취소",
|
|
8370
8393
|
dismissDemoIntro: "데모 소개 닫기",
|
|
8371
8394
|
downloadCsv: "CSV 다운로드",
|
|
8395
|
+
copyTable: "표 복사",
|
|
8396
|
+
multiColumnSortHelp:
|
|
8397
|
+
"클릭하여 정렬합니다. Shift 키를 누른 채 클릭하면 다른 정렬을 추가합니다.",
|
|
8372
8398
|
exportToGoogleSheets: "Google Sheets로 내보내기",
|
|
8373
8399
|
exportingToGoogleSheets: "Google Sheets로 내보내는 중...",
|
|
8374
8400
|
googleSheetsExported: "Google Sheets로 내보냈습니다",
|
|
@@ -8601,6 +8627,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8601
8627
|
copyResolvedSql: "Cópia resolvida SQL",
|
|
8602
8628
|
copySql: "Copie SQL",
|
|
8603
8629
|
couldNotCopySql: "Não foi possível copiar SQL",
|
|
8630
|
+
couldNotCopyTable: "Não foi possível copiar a tabela",
|
|
8604
8631
|
dashboardActions: "Detalhes e ações do painel",
|
|
8605
8632
|
deletePanelTitle: "Excluir painel?",
|
|
8606
8633
|
deletePermanentlyTitle: "Excluir permanentemente?",
|
|
@@ -8613,6 +8640,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8613
8640
|
discardChanges: "Descartar alterações",
|
|
8614
8641
|
dismissDemoIntro: "Ignorar introdução da demonstração",
|
|
8615
8642
|
downloadCsv: "Baixar CSV",
|
|
8643
|
+
copyTable: "Copiar tabela",
|
|
8644
|
+
multiColumnSortHelp:
|
|
8645
|
+
"Clique para ordenar. Shift-clique para adicionar outra ordenação.",
|
|
8616
8646
|
exportToGoogleSheets: "Exportar para o Google Sheets",
|
|
8617
8647
|
exportingToGoogleSheets: "Exportando para o Google Sheets...",
|
|
8618
8648
|
googleSheetsExported: "Exportado para o Google Sheets",
|
|
@@ -8842,6 +8872,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8842
8872
|
copyResolvedSql: "प्रतिलिपि हल की गई SQL",
|
|
8843
8873
|
copySql: "SQL कॉपी करें",
|
|
8844
8874
|
couldNotCopySql: "SQL की प्रतिलिपि नहीं बनाई जा सकी",
|
|
8875
|
+
couldNotCopyTable: "तालिका कॉपी नहीं की जा सकी",
|
|
8845
8876
|
dashboardActions: "डैशबोर्ड विवरण और क्रियाएँ",
|
|
8846
8877
|
deletePanelTitle: "पैनल हटाएं?",
|
|
8847
8878
|
deletePermanentlyTitle: "स्थायी रूप से हटाएँ?",
|
|
@@ -8854,6 +8885,9 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
8854
8885
|
discardChanges: "परिवर्तन त्यागें",
|
|
8855
8886
|
dismissDemoIntro: "डेमो परिचय ख़ारिज करें",
|
|
8856
8887
|
downloadCsv: "CSV डाउनलोड करें",
|
|
8888
|
+
copyTable: "तालिका कॉपी करें",
|
|
8889
|
+
multiColumnSortHelp:
|
|
8890
|
+
"क्रमित करने के लिए क्लिक करें। एक और क्रम जोड़ने के लिए Shift-क्लिक करें।",
|
|
8857
8891
|
exportToGoogleSheets: "Google Sheets में निर्यात करें",
|
|
8858
8892
|
exportingToGoogleSheets: "Google Sheets में निर्यात किया जा रहा है...",
|
|
8859
8893
|
googleSheetsExported: "Google Sheets में निर्यात किया गया",
|
|
@@ -9081,6 +9115,7 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
9081
9115
|
copyResolvedSql: "تم حل النسخة SQL",
|
|
9082
9116
|
copySql: "انسخ SQL",
|
|
9083
9117
|
couldNotCopySql: "لا يمكن نسخ SQL",
|
|
9118
|
+
couldNotCopyTable: "تعذر نسخ الجدول",
|
|
9084
9119
|
dashboardActions: "تفاصيل وإجراءات لوحة التحكم",
|
|
9085
9120
|
deletePanelTitle: "هل تريد حذف اللوحة؟",
|
|
9086
9121
|
deletePermanentlyTitle: "هل تريد الحذف نهائيًا؟",
|
|
@@ -9093,6 +9128,8 @@ const translatedAnalyticsDebtTranslations = {
|
|
|
9093
9128
|
discardChanges: "تجاهل التغييرات",
|
|
9094
9129
|
dismissDemoIntro: "رفض المقدمة التجريبية",
|
|
9095
9130
|
downloadCsv: "تحميل CSV",
|
|
9131
|
+
copyTable: "نسخ الجدول",
|
|
9132
|
+
multiColumnSortHelp: "انقر للفرز. انقر مع الضغط على Shift لإضافة فرز آخر.",
|
|
9096
9133
|
exportToGoogleSheets: "تصدير إلى Google Sheets",
|
|
9097
9134
|
exportingToGoogleSheets: "جارٍ التصدير إلى Google Sheets...",
|
|
9098
9135
|
googleSheetsExported: "تم التصدير إلى Google Sheets",
|
|
@@ -404,7 +404,7 @@ function PanelEditorContent({
|
|
|
404
404
|
`For prometheus panels, sql is a JSON descriptor: {"promql":"rate(http_requests_total[5m])","mode":"range","range":"1h","step":"30s"}. mode defaults to "range"; range defaults to "1h"; step is auto if omitted. Returned rows have shape {timestamp, series, value} — set config.xKey="timestamp", config.yKey="value", and a single series in config.yKeys for clean charting. ` +
|
|
405
405
|
`For program panels (arbitrary provider data joins/cohorts not expressible in the other sources), first save-data-program (or reuse an existing one via list-data-programs), then set sql to a JSON descriptor: {"programId":"<id>","params":{...}}. See the data-programs skill for the emit(rows, schema) contract and the Risk Meeting worked example. ` +
|
|
406
406
|
`Native dashboard panels and Data Programs come first. Add an extension panel only when the user explicitly asks for a genuinely bespoke, one-off Custom Block for this dashboard and native panels cannot represent it faithfully. For a reusable/native capability call connect-builder instead. New agent-authored Custom Blocks use config.extensionId plus config.customBlock={authoredBy:"agent",intent:"one-off",scope:"dashboard",nativeGapReason:"custom-visualization"|"custom-interaction"|"custom-layout"|"other"}; never put prompt/customer text in that metadata. Use config.extensionSlotId only when the user explicitly asks for a personal/per-viewer slot; slot installs are per-user and automated report identities may have no install. ` +
|
|
407
|
-
`Config is optional: { xKey, yKey, yKeys, yFormatter ('number'|'currency'|'percent'), rightYKeys, rightYFormatter, description, columns, pivot, limit, color, colors, stacked, legend, valueLabels }. For funnel panels, use config.xKey for the stage label, config.yKey for the non-negative count/value, and keep the SQL ORDER BY in the intended funnel order. ` +
|
|
407
|
+
`Config is optional: { xKey, yKey, yKeys, yFormatter ('number'|'currency'|'percent'), rightYKeys, rightYFormatter, seriesLabels (exact series key -> display label), description, columns, pivot, limit, color, colors, stacked, legend, valueLabels }. For funnel panels, use config.xKey for the stage label, config.yKey for the non-negative count/value, and keep the SQL ORDER BY in the intended funnel order. ` +
|
|
408
408
|
`For line/area/bar series that share an x-axis but not a unit (a count next to a rate), put the smaller-unit series on a second y-axis with config.rightYKeys (a subset of yKeys) and an optional config.rightYFormatter — do not build an extension for a dual-axis chart. Use heatmap, callout, and section panels when their native contracts fit; do not create a Custom Block for a supported native panel. ` +
|
|
409
409
|
`Chart legends render automatically; set config.legend=false only when the user explicitly asks to hide the legend. ` +
|
|
410
410
|
`Use \`get-sql-dashboard.layout.groups[].rows[].rowNumber/panelIds\` to identify and verify visible rows. ` +
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
IconTrash,
|
|
12
12
|
IconCode,
|
|
13
13
|
IconDownload,
|
|
14
|
+
IconCopy,
|
|
14
15
|
IconMessageCircle,
|
|
15
16
|
IconBrandGoogle,
|
|
16
17
|
IconArrowUpRight,
|
|
@@ -148,6 +149,9 @@ export function SqlChartCard({
|
|
|
148
149
|
const [expanded, setExpanded] = useState(false);
|
|
149
150
|
const [extRefreshKey, setExtRefreshKey] = useState(0);
|
|
150
151
|
const [exportCsv, setExportCsv] = useState<(() => void) | null>(null);
|
|
152
|
+
const [copyTable, setCopyTable] = useState<(() => Promise<void>) | null>(
|
|
153
|
+
null,
|
|
154
|
+
);
|
|
151
155
|
const [shouldLoadData, setShouldLoadData] = useState(
|
|
152
156
|
eagerLoad ||
|
|
153
157
|
panel.chartType === "section" ||
|
|
@@ -172,6 +176,23 @@ export function SqlChartCard({
|
|
|
172
176
|
setExportCsv(handler ? () => handler : null);
|
|
173
177
|
}, []);
|
|
174
178
|
|
|
179
|
+
const handleCopyTableChange = useCallback(
|
|
180
|
+
(handler: (() => Promise<void>) | null) => {
|
|
181
|
+
setCopyTable(handler ? () => handler : null);
|
|
182
|
+
},
|
|
183
|
+
[],
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const handleCopyTable = useCallback(async () => {
|
|
187
|
+
if (!copyTable) return;
|
|
188
|
+
try {
|
|
189
|
+
await copyTable();
|
|
190
|
+
toast.success(t("sqlDashboard.copied"));
|
|
191
|
+
} catch {
|
|
192
|
+
toast.error(t("sqlDashboard.couldNotCopyTable"));
|
|
193
|
+
}
|
|
194
|
+
}, [copyTable, t]);
|
|
195
|
+
|
|
175
196
|
const handleRefresh = useCallback(() => {
|
|
176
197
|
setShouldLoadData(true);
|
|
177
198
|
void queryClient.invalidateQueries({
|
|
@@ -288,6 +309,7 @@ export function SqlChartCard({
|
|
|
288
309
|
|
|
289
310
|
useEffect(() => {
|
|
290
311
|
setExportCsv(null);
|
|
312
|
+
setCopyTable(null);
|
|
291
313
|
}, [panel.id]);
|
|
292
314
|
|
|
293
315
|
useEffect(() => {
|
|
@@ -649,6 +671,15 @@ export function SqlChartCard({
|
|
|
649
671
|
{t("sqlDashboard.downloadCsv")}
|
|
650
672
|
</DropdownMenuItem>
|
|
651
673
|
)}
|
|
674
|
+
{panel.chartType === "table" && (
|
|
675
|
+
<DropdownMenuItem
|
|
676
|
+
disabled={!copyTable}
|
|
677
|
+
onSelect={() => void handleCopyTable()}
|
|
678
|
+
>
|
|
679
|
+
<IconCopy className="h-4 w-4 mr-2" />
|
|
680
|
+
{t("sqlDashboard.copyTable")}
|
|
681
|
+
</DropdownMenuItem>
|
|
682
|
+
)}
|
|
652
683
|
{panel.chartType === "table" && dashboardId ? (
|
|
653
684
|
<DropdownMenuItem
|
|
654
685
|
disabled={exportToGoogleSheets.isPending}
|
|
@@ -705,6 +736,7 @@ export function SqlChartCard({
|
|
|
705
736
|
loadData={shouldLoadData}
|
|
706
737
|
reportScreenshot={reportScreenshot}
|
|
707
738
|
onExportCsvChange={handleExportCsvChange}
|
|
739
|
+
onCopyTableChange={handleCopyTableChange}
|
|
708
740
|
extensionContext={extensionContext}
|
|
709
741
|
/>
|
|
710
742
|
</CardContent>
|
|
@@ -93,6 +93,8 @@ export interface SqlPanelConfig {
|
|
|
93
93
|
rightYKeys?: string[];
|
|
94
94
|
/** Value formatter for the right axis. Falls back to `yFormatter`. */
|
|
95
95
|
rightYFormatter?: "number" | "currency" | "percent";
|
|
96
|
+
/** Optional display labels for exact series keys, e.g. {"evt_signup_completed_v2":"Signups"}. */
|
|
97
|
+
seriesLabels?: Record<string, string>;
|
|
96
98
|
description?: string;
|
|
97
99
|
pivot?: PivotConfig;
|
|
98
100
|
/** Stack bar/area series on top of each other instead of side-by-side / overlapping. */
|