Skip to content

Commit 3cfab07

Browse files
NicolappsConvex, Inc.
authored andcommitted
Fix system table references in the dashboard Data page (#40807)
This fixes the system table references in the data view to: - Show the user-facing table names `_file_storage` and `_scheduled_jobs`, instead of the internal names `_storage` and `_scheduled_functions` - Modify the _Go to Reference_ link to go to the Files / Scheduled Functions pages instead of the Data page. These links don’t link to a particular row, but it’s still more helpful than the current behavior. Closes ENG-9739 GitOrigin-RevId: 07a8929c02d5ba26bfca0476d8995124c2f50e99
1 parent d66e4e5 commit 3cfab07

File tree

8 files changed

+191
-30
lines changed

8 files changed

+191
-30
lines changed

npm-packages/dashboard-common/src/elements/ObjectEditor/ObjectEditor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export function ObjectEditor(props: ObjectEditorProps) {
192192
],
193193
);
194194

195-
const { deploymentsURI } = useContext(DeploymentInfoContext);
195+
const { deploymentsURI, captureMessage } = useContext(DeploymentInfoContext);
196196

197197
const { resolvedTheme: currentTheme } = useTheme();
198198
const prefersDark = currentTheme === "dark";
@@ -265,7 +265,7 @@ export function ObjectEditor(props: ObjectEditorProps) {
265265
setMonaco(m);
266266
}}
267267
onMount={(editor, m) => {
268-
registerIdCommands(m, deploymentsURI);
268+
registerIdCommands({ monaco: m, deploymentsURI, captureMessage });
269269

270270
editor.onKeyDown((e) => {
271271
if (e.keyCode === m.KeyCode.Tab) {

npm-packages/dashboard-common/src/elements/ObjectEditor/useIdDecorations.tsx

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
copyTextToClipboard,
1414
documentHref,
1515
getReferencedTableName,
16+
getVisibleTableName,
1617
toast,
1718
} from "@common/lib/utils";
1819
import { useNents } from "@common/lib/useNents";
@@ -125,26 +126,28 @@ function hoverMessageForDoc(
125126
) {
126127
return [
127128
{
128-
value: `Document in ${colorizeHelperText(tableName, prefersDark)}, created ${colorizeHelperText(new Date(doc._creationTime as number).toLocaleString(), prefersDark)}`,
129+
value: `Document in ${colorizeHelperText(getVisibleTableName(tableName), prefersDark)}, created ${colorizeHelperText(new Date(doc._creationTime as number).toLocaleString(), prefersDark)}`,
129130
supportHtml: true,
130131
isTrusted: true,
131132
},
132133
{
133-
value: `${createMarkdownLink(
134-
"Open in new tab",
135-
GO_TO_DOCUMENT_COMMAND,
136-
"codicon-link-external",
137-
{ id, tableName, componentId },
138-
)}
139-
    
140-
${createMarkdownLink(
141-
"Copy Document",
142-
COPY_DOCUMENT_COMMAND,
143-
"codicon-copy",
144-
{
145-
docString: stringifyValue(doc, true),
146-
},
147-
)}`,
134+
value:
135+
(!tableName.startsWith("_")
136+
? `${createMarkdownLink(
137+
"Open in new tab",
138+
GO_TO_DOCUMENT_COMMAND,
139+
"codicon-link-external",
140+
{ id, tableName, componentId },
141+
)}    `
142+
: "") +
143+
createMarkdownLink(
144+
"Copy Document",
145+
COPY_DOCUMENT_COMMAND,
146+
"codicon-copy",
147+
{
148+
docString: stringifyValue(doc, true),
149+
},
150+
),
148151
supportHtml: true,
149152
isTrusted: {
150153
enabledCommands: [GO_TO_DOCUMENT_COMMAND, COPY_DOCUMENT_COMMAND],
@@ -257,7 +260,9 @@ async function provideDecoration({
257260
doc ? "codicon-link mtk23" : "codicon-warning mtk11",
258261
),
259262
after: {
260-
content: showTableNames ? `Id<"${tableName}">` : "Id",
263+
content: showTableNames
264+
? `Id<"${getVisibleTableName(tableName)}">`
265+
: "Id",
261266
inlineClassName: cn(doc ? "mtk23" : "mtk11", "mtki"),
262267
cursorStops: monaco.editor.InjectedTextCursorStops.None,
263268
},
@@ -289,10 +294,15 @@ function colorizeHelperText(text: string, prefersDark: boolean) {
289294
const GO_TO_DOCUMENT_COMMAND = "goToDocument";
290295
const COPY_DOCUMENT_COMMAND = "copyDocument";
291296

292-
export function registerIdCommands(
293-
monaco: Parameters<BeforeMount>[0],
294-
deploymentsURI: string,
295-
) {
297+
export function registerIdCommands({
298+
monaco,
299+
deploymentsURI,
300+
captureMessage,
301+
}: {
302+
monaco: Parameters<BeforeMount>[0];
303+
deploymentsURI: string;
304+
captureMessage: (message: string) => void;
305+
}) {
296306
monaco.editor.registerCommand(
297307
GO_TO_DOCUMENT_COMMAND,
298308
(
@@ -308,6 +318,7 @@ export function registerIdCommands(
308318
tableName: args.tableName,
309319
id: args.id,
310320
componentId: args.componentId,
321+
captureMessage,
311322
});
312323
window.open(url.format(href), "_blank");
313324
},

npm-packages/dashboard-common/src/features/data/components/Table/DataCell/DataCell.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ const mockClient = mockConvexReactClient()
6868
])
6969
.registerQueryFake(udfs.getTableMapping.default, () => ({
7070
10001: "testTable",
71+
539: "_scheduled_jobs",
72+
540: "_file_storage",
7173
}));
7274

7375
describe("DataCell", () => {
@@ -359,6 +361,36 @@ describe("DataCell", () => {
359361
);
360362
});
361363

364+
it("navigates to the files page when pressing meta+g on a file id", async () => {
365+
const { getByTestId } = renderWithProvider({
366+
value: "kg267e113cftx1jpeepypezsa57q9wvp",
367+
});
368+
const button = getByTestId("cell-editor-button");
369+
await user.click(button);
370+
jest.spyOn(window, "open").mockImplementation(() => null);
371+
await user.keyboard("{Meta>}g");
372+
expect(window.open).toHaveBeenCalledTimes(1);
373+
expect(window.open).toHaveBeenCalledWith(
374+
"http://localhost/files",
375+
"_blank",
376+
);
377+
});
378+
379+
it("navigates to the scheduled functions page when pressing meta+g on a scheduled function id", async () => {
380+
const { getByTestId } = renderWithProvider({
381+
value: "kc2f44mqfb0kgr6dbnqwpeb2bs7q8bds",
382+
});
383+
const button = getByTestId("cell-editor-button");
384+
await user.click(button);
385+
jest.spyOn(window, "open").mockImplementation(() => null);
386+
await user.keyboard("{Meta>}g");
387+
expect(window.open).toHaveBeenCalledTimes(1);
388+
expect(window.open).toHaveBeenCalledWith(
389+
"http://localhost/schedules/functions",
390+
"_blank",
391+
);
392+
});
393+
362394
it("does not navigate to the document when pressing ctrl+g for a non-id value", async () => {
363395
const { getByTestId } = renderWithProvider();
364396
const button = getByTestId("cell-editor-button");

npm-packages/dashboard-common/src/features/data/components/Table/DataCell/utils/useIdReferenceLink.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function useIdReferenceLink(value: Value, columnName: string) {
1717
const referencedTableName = getReferencedTableName(tableMapping, value);
1818
const isReference = referencedTableName !== null;
1919

20-
const { deploymentsURI } = useContext(DeploymentInfoContext);
20+
const { deploymentsURI, captureMessage } = useContext(DeploymentInfoContext);
2121

2222
if (columnName === "_id") {
2323
return undefined;
@@ -30,6 +30,7 @@ export function useIdReferenceLink(value: Value, columnName: string) {
3030
tableName: referencedTableName,
3131
id: stringValue,
3232
componentId,
33+
captureMessage,
3334
})
3435
: undefined;
3536

npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.test.tsx

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe("TableContextMenu", () => {
166166
expect(defaultProps.close).toHaveBeenCalledTimes(1);
167167
});
168168

169-
it("should redirect to the document", async () => {
169+
it("should link to the document reference", async () => {
170170
const { getByTestId } = renderWithProvider({
171171
state: {
172172
target: { x: 0, y: 0 },
@@ -189,10 +189,67 @@ describe("TableContextMenu", () => {
189189
});
190190

191191
const link = getByTestId("table-context-menu").children[0];
192+
expect(link).toHaveTextContent("Go to Reference");
192193
expect(link).toHaveAttribute("href", "/document/1");
193194
expect(link).toHaveAttribute("target", "_blank");
194195
});
195196

197+
it("should link to the scheduled functions page", async () => {
198+
const { getByTestId } = renderWithProvider({
199+
state: {
200+
target: { x: 0, y: 0 },
201+
selectedCell: {
202+
rowId: "1",
203+
column: "name",
204+
value: "Document 1",
205+
callbacks: {
206+
copy: jest.fn(),
207+
copyDoc: jest.fn(),
208+
goToRef: jest.fn(),
209+
edit: jest.fn(),
210+
editDoc: jest.fn(),
211+
view: jest.fn(),
212+
viewDoc: jest.fn(),
213+
docRefLink: { pathname: "/schedules/functions" },
214+
},
215+
},
216+
},
217+
});
218+
219+
const link = getByTestId("table-context-menu").children[0];
220+
expect(link).toHaveTextContent("Go to Scheduled Functions");
221+
expect(link).toHaveAttribute("href", "/schedules/functions");
222+
expect(link).toHaveAttribute("target", "_blank");
223+
});
224+
225+
it("should link to the files page", async () => {
226+
const { getByTestId } = renderWithProvider({
227+
state: {
228+
target: { x: 0, y: 0 },
229+
selectedCell: {
230+
rowId: "1",
231+
column: "name",
232+
value: "Document 1",
233+
callbacks: {
234+
copy: jest.fn(),
235+
copyDoc: jest.fn(),
236+
goToRef: jest.fn(),
237+
edit: jest.fn(),
238+
editDoc: jest.fn(),
239+
view: jest.fn(),
240+
viewDoc: jest.fn(),
241+
docRefLink: { pathname: "/files" },
242+
},
243+
},
244+
},
245+
});
246+
247+
const link = getByTestId("table-context-menu").children[0];
248+
expect(link).toHaveTextContent("Go to Files");
249+
expect(link).toHaveAttribute("href", "/files");
250+
expect(link).toHaveAttribute("target", "_blank");
251+
});
252+
196253
it.each`
197254
callbackName | buttonIndex
198255
${"view"} | ${0}

npm-packages/dashboard-common/src/features/data/components/Table/TableContextMenu.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {
22
ClipboardCopyIcon,
33
EnterFullScreenIcon,
44
ExternalLinkIcon,
5+
FileIcon,
56
MixerHorizontalIcon,
67
Pencil1Icon,
78
ResetIcon,
9+
StopwatchIcon,
810
TrashIcon,
911
} from "@radix-ui/react-icons";
1012
import React, { useCallback, useContext, useState } from "react";
@@ -301,15 +303,33 @@ function CellActions({
301303
)
302304
);
303305

306+
const isFileRef =
307+
state.selectedCell?.callbacks?.docRefLink?.pathname?.endsWith("/files");
308+
const isScheduledFunctionRef =
309+
state.selectedCell?.callbacks?.docRefLink?.pathname?.endsWith(
310+
"/schedules/functions",
311+
);
312+
304313
const cellActions =
305314
state.selectedCell?.rowId && state.selectedCell.callbacks
306315
? [
307316
state.selectedCell.callbacks.docRefLink !== undefined
308317
? {
309-
action: state.selectedCell.callbacks.docRefLink as UrlObject,
318+
action: state.selectedCell.callbacks
319+
.docRefLink satisfies UrlObject,
310320
shortcut: ["CtrlOrCmd", "G"] satisfies Key[],
311-
icon: <ExternalLinkIcon aria-hidden="true" />,
312-
label: "Go to reference",
321+
icon: isFileRef ? (
322+
<FileIcon aria-hidden="true" />
323+
) : isScheduledFunctionRef ? (
324+
<StopwatchIcon aria-hidden="true" />
325+
) : (
326+
<ExternalLinkIcon aria-hidden="true" />
327+
),
328+
label: isFileRef
329+
? "Go to Files"
330+
: isScheduledFunctionRef
331+
? "Go to Scheduled Functions"
332+
: "Go to Reference",
313333
disabled: false,
314334
tip: null,
315335
}

npm-packages/dashboard-common/src/lib/utils.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,60 @@ export function getReferencedTableName(
8080
return tableMapping[tableNumber] ?? null;
8181
}
8282

83+
/**
84+
* System tables _file_storage and _scheduled_jobs have a different name
85+
* in user-facing contexts.
86+
*/
87+
export function getVisibleTableName(tableName: string) {
88+
if (tableName === "_file_storage") {
89+
return "_storage";
90+
}
91+
92+
if (tableName === "_scheduled_jobs") {
93+
return "_scheduled_functions";
94+
}
95+
96+
return tableName;
97+
}
98+
8399
export function documentHref({
84100
deploymentsURI,
85101
tableName,
86102
id,
87103
componentId,
104+
captureMessage,
88105
}: {
89106
deploymentsURI: string;
90107
tableName: string;
91108
id: string;
92109
componentId: string | null;
110+
captureMessage: (message: string) => void;
93111
}): {
94112
pathname: string;
95113
query: { [key: string]: string };
96114
} {
115+
if (tableName === "_scheduled_jobs") {
116+
return {
117+
pathname: `${deploymentsURI}/schedules/functions`,
118+
query: {
119+
// FIXME: This could include query parameters one day to link to a specific job
120+
},
121+
};
122+
}
123+
124+
if (tableName === "_file_storage") {
125+
return {
126+
pathname: `${deploymentsURI}/files`,
127+
query: {
128+
// FIXME: This could include query parameters one day to link to a specific file
129+
},
130+
};
131+
}
132+
133+
if (tableName.startsWith("_")) {
134+
captureMessage(`Linking to an unsupported system table: ${tableName}`);
135+
}
136+
97137
const filter: DatabaseFilterExpression = {
98138
clauses: [
99139
{
@@ -104,7 +144,6 @@ export function documentHref({
104144
},
105145
],
106146
};
107-
108147
return {
109148
pathname: `${deploymentsURI}/data`,
110149
query: {

npm-packages/dashboard/src/components/health/EventsForInsight.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ function EventOccDocumentId({
543543
event: FormattedOccEvent;
544544
componentId: ComponentId | undefined;
545545
}) {
546-
const { deploymentsURI } = useContext(DeploymentInfoContext);
546+
const { deploymentsURI, captureMessage } = useContext(DeploymentInfoContext);
547547
return (
548548
<div className="flex w-[16rem]">
549549
{event.occDocumentId && insight.details.occTableName ? (
@@ -553,6 +553,7 @@ function EventOccDocumentId({
553553
tableName: insight.details.occTableName,
554554
id: event.occDocumentId,
555555
componentId: componentId ?? null,
556+
captureMessage,
556557
})}
557558
target="_blank"
558559
className="flex items-center gap-1 text-content-link hover:underline"

0 commit comments

Comments
 (0)