Skip to content

Commit f1fa049

Browse files
author
Cheryl Kong
committed
feat: add copied confirmation and fix copilot complaints
Signed-off-by: Cheryl Kong <cherylkong50@gmail.com>
1 parent 07bff75 commit f1fa049

4 files changed

Lines changed: 51 additions & 4 deletions

File tree

.changeset/mermaid-export.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@
33
---
44

55
Add mermaid export functionality
6+
flowchart TD
7+
root-entry-node(( ))
8+
root-exit-node((( )))
9+
/do/0/getPet["getPet"]
10+
/do/0/getPet --> root-exit-node
11+
root-entry-node --> /do/0/getPet
12+
13+
classDef hidden width: 1px, height: 1px;

packages/serverless-workflow-diagram-editor/src/core/mermaidExport.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,17 @@ export function exportToMermaid(workflow: Specification.Workflow): string {
2727
}
2828

2929
export function copyMermaidToClipboard(mermaidCode: string): Promise<void> {
30+
if (typeof navigator === "undefined" || !navigator.clipboard) {
31+
return Promise.reject(new Error("Clipboard API is not available in this environment"));
32+
}
3033
return navigator.clipboard.writeText(mermaidCode);
3134
}
3235

3336
export function downloadMermaidFile(mermaidCode: string, filename: string = "mermaid.mmd"): void {
37+
if (typeof document === "undefined") {
38+
throw new Error("Document API is not available in this environment");
39+
}
40+
3441
const blob = new Blob([mermaidCode], { type: "text/plain" });
3542
const url = URL.createObjectURL(blob);
3643
const link = document.createElement("a");
@@ -39,5 +46,7 @@ export function downloadMermaidFile(mermaidCode: string, filename: string = "mer
3946
document.body.appendChild(link);
4047
link.click();
4148
document.body.removeChild(link);
42-
URL.revokeObjectURL(url);
49+
setTimeout(() => {
50+
URL.revokeObjectURL(url);
51+
}, 10);
4352
}

packages/serverless-workflow-diagram-editor/src/side-panel/SidePanel.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function SidePanel() {
3838
const { model, nodes, selectedNodeId } = useDiagramEditorContext();
3939
const { setOpen } = useSidebar();
4040
const { t } = useI18n();
41+
const [isCopied, setIsCopied] = React.useState(false);
4142

4243
const selectedNode = React.useMemo(
4344
() =>
@@ -68,7 +69,8 @@ export function SidePanel() {
6869
try {
6970
const mermaidCode = exportToMermaid(model);
7071
await copyMermaidToClipboard(mermaidCode);
71-
console.log("mermaid copied to clipboard");
72+
setIsCopied(true);
73+
setTimeout(() => setIsCopied(false), 2000);
7274
} catch (error) {
7375
console.error("Failed to copy mermaid code:", error);
7476
}
@@ -126,7 +128,7 @@ export function SidePanel() {
126128
<>
127129
<Button onClick={handleCopyMermaid} variant="outline" size="sm">
128130
<Copy />
129-
{t("sidebar.exportMermaid.copy")}
131+
{isCopied ? "Copied!" : t("sidebar.exportMermaid.copy")}
130132
</Button>
131133
<Button onClick={handleDownloadMermaid} variant="outline" size="sm">
132134
<Download />

packages/serverless-workflow-diagram-editor/tests/side-panel/SidePanel.test.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { describe, it, expect } from "vitest";
17+
import { describe, it, expect, vi, beforeEach } from "vitest";
1818
import { screen } from "@testing-library/react";
19+
import { userEvent } from "@testing-library/user-event";
1920
import { SidePanel } from "../../src/side-panel/SidePanel";
2021
import { parseWorkflow } from "../../src/core/workflowSdk";
2122
import { renderWithProviders } from "../test-utils/render-helpers";
2223
import { WORKFLOW_WITH_METADATA_JSON } from "../fixtures/workflows";
24+
import * as mermaidExport from "../../src/core/mermaidExport";
2325

2426
describe("SidePanel", () => {
2527
it("renders sidebar with workflow info when model is present", () => {
@@ -71,4 +73,30 @@ describe("SidePanel", () => {
7173
expect(screen.queryByText(/Copy Mermaid Code/i)).not.toBeInTheDocument();
7274
expect(screen.queryByText(/Download as Mermaid File/i)).not.toBeInTheDocument();
7375
});
76+
77+
it("should call copyMermaidToClipboard when copy button is clicked", async () => {
78+
const user = userEvent.setup();
79+
const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
80+
const copySpy = vi.spyOn(mermaidExport, "copyMermaidToClipboard").mockResolvedValue(undefined);
81+
vi.spyOn(mermaidExport, "exportToMermaid").mockReturnValue("mermaid code");
82+
83+
renderWithProviders(<SidePanel />, { model });
84+
const copyButton = screen.getByText(/Copy Mermaid Code/i);
85+
await user.click(copyButton);
86+
87+
expect(copySpy).toHaveBeenCalledWith("mermaid code");
88+
});
89+
90+
it("should call downloadMermaidFile when download button is clicked", async () => {
91+
const user = userEvent.setup();
92+
const { model } = parseWorkflow(WORKFLOW_WITH_METADATA_JSON);
93+
const downloadSpy = vi.spyOn(mermaidExport, "downloadMermaidFile").mockImplementation(() => {});
94+
vi.spyOn(mermaidExport, "exportToMermaid").mockReturnValue("mermaid code");
95+
96+
renderWithProviders(<SidePanel />, { model });
97+
const downloadButton = screen.getByText(/Download as Mermaid File/i);
98+
await user.click(downloadButton);
99+
100+
expect(downloadSpy).toHaveBeenCalledWith("mermaid code");
101+
});
74102
});

0 commit comments

Comments
 (0)