Skip to content

Commit c4652b7

Browse files
authored
[UI] Support importing/exporting pod stdout and result; fix execution_count (#399)
1 parent 554cdac commit c4652b7

File tree

5 files changed

+74
-19
lines changed

5 files changed

+74
-19
lines changed

api/src/resolver_export.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,9 @@ async function exportJSON(_, { repoId }, { userId }) {
6060
},
6161
},
6262
});
63-
// now export repo to a file
6463
if (!repo) throw Error("Repo not exists.");
65-
const filename = `${
66-
repo.name || "Untitled"
67-
}-${new Date().toISOString()}.json`;
68-
const aws_url = await uploadToS3WithExpiration(
69-
filename,
70-
JSON.stringify({ name: repo.name, version: "v0.0.1", pods: repo.pods })
71-
);
72-
return aws_url;
64+
// return the JSON string
65+
return JSON.stringify({ name: repo.name, format: "codepod", version: "v0.0.1", pods: repo.pods });
7366
}
7467

7568
interface Pod {

runtime/src/zmq-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function constructExecuteRequest({ code, msg_id, cp = {} }) {
133133
cp,
134134
// FIXME if this is true, no result is returned!
135135
silent: false,
136-
store_history: false,
136+
store_history: true,
137137
// XXX this does not seem to be used
138138
user_expressions: {
139139
x: "3+4",

ui/src/components/Canvas.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,8 @@ function CanvasImpl() {
794794
cellList = JSON.parse(String(fileContent)).cells.map((cell) => ({
795795
cellType: cell.cell_type,
796796
cellSource: cell.source.join(""),
797+
cellOutputs: cell.outputs || [],
798+
execution_count: cell.execution_count || 0,
797799
}));
798800
importScopeName = fileName.substring(0, fileName.length - 6);
799801
break;

ui/src/components/Sidebar.tsx

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,9 @@ function ExportFile() {
850850
function ExportJSON() {
851851
// an export component
852852
let { id: repoId } = useParams();
853+
const store = useContext(RepoContext);
854+
if (!store) throw new Error("Missing BearContext.Provider in the tree");
855+
const repoName = useStore(store, (state) => state.repoName);
853856
// the useMutation for exportJSON
854857
const [exportJSON, { data, loading, error }] = useMutation(
855858
gql`
@@ -860,7 +863,20 @@ function ExportJSON() {
860863
);
861864
useEffect(() => {
862865
if (data?.exportJSON) {
863-
download(data.exportJSON);
866+
let element = document.createElement("a");
867+
element.setAttribute(
868+
"href",
869+
"data:text/plain;charset=utf-8," + encodeURIComponent(data.exportJSON)
870+
);
871+
const filename = `${
872+
repoName || "Untitled"
873+
}-${new Date().toISOString()}.json`;
874+
element.setAttribute("download", filename);
875+
876+
element.style.display = "none";
877+
document.body.appendChild(element);
878+
element.click();
879+
document.body.removeChild(element);
864880
}
865881
}, [data]);
866882
return (
@@ -870,10 +886,10 @@ function ExportJSON() {
870886
size="small"
871887
color="secondary"
872888
onClick={() => {
873-
// call export graphQL api to get the AWS S3 url
889+
// call export graphQL api to get JSON string
874890
exportJSON({ variables: { repoId } });
875891
}}
876-
disabled={true}
892+
disabled={false}
877893
>
878894
Raw JSON
879895
</Button>
@@ -899,9 +915,10 @@ function ExportJupyterNB() {
899915
// Hard-code Jupyter cell format. Reference, https://nbformat.readthedocs.io/en/latest/format_description.html
900916
let jupyterCellList: {
901917
cell_type: string;
902-
execution_count: number;
918+
execution_count?: number;
903919
metadata: object;
904920
source: string[];
921+
outputs?: object[];
905922
}[] = [];
906923

907924
// Queue to sort the pods geographically
@@ -941,19 +958,36 @@ function ExportJupyterNB() {
941958
if (pod.type == "SCOPE") {
942959
q.push([pod, geoScore.substring(0, 2) + "0" + geoScore.substring(2)]);
943960
} else if (pod.type == "CODE") {
961+
let podOutput: any[] = [];
962+
if (pod.stdout) {
963+
podOutput.push({
964+
output_type: "stream",
965+
name: "stdout",
966+
text: pod.stdout.split(/\r?\n/).map((line) => line + "\n"),
967+
});
968+
}
969+
if (pod.result) {
970+
podOutput.push({
971+
output_type: "display_data",
972+
data: {
973+
"text/plain": (pod.result.text || "")
974+
.split(/\r?\n/)
975+
.map((line) => line + "\n") || [""],
976+
"image/png": pod.result.image,
977+
},
978+
});
979+
}
944980
jupyterCellList.push({
945981
cell_type: "code",
946-
// hard-code execution_count
947-
execution_count: 1,
982+
execution_count: pod.result?.count || 0,
948983
// TODO: expand other Codepod related-metadata fields, or run a real-time search in database when importing.
949984
metadata: { id: pod.id, geoScore: Number(geoScore) },
950985
source: [pod.content || ""],
986+
outputs: podOutput,
951987
});
952988
} else if (pod.type == "RICH") {
953989
jupyterCellList.push({
954990
cell_type: "markdown",
955-
// hard-code execution_count
956-
execution_count: 1,
957991
// TODO: expand other Codepod related-metadata fields, or run a real-time search in database when importing.
958992
metadata: { id: pod.id, geoScore: Number(geoScore) },
959993
source: [pod.richContent || ""],
@@ -1029,6 +1063,7 @@ function ExportJupyterNB() {
10291063
document.body.appendChild(element);
10301064
element.click();
10311065
document.body.removeChild(element);
1066+
setLoading(false);
10321067
};
10331068

10341069
return (

ui/src/lib/store/canvasSlice.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
516516
dirty: true,
517517
pending: true,
518518
});
519+
let maxLineLength = 0;
519520
if (cellList.length > 0) {
520521
for (let i = 0; i < cellList.length; i++) {
521522
const cell = cellList[i];
@@ -529,8 +530,29 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
529530
newPos
530531
);
531532
let podContent = cell.cellType == "code" ? cell.cellSource : "";
533+
534+
maxLineLength = Math.max(
535+
maxLineLength,
536+
Math.max(...podContent.split(/\r?\n/).map((line) => line.length))
537+
);
538+
532539
let podRichContent = cell.cellType == "markdown" ? cell.cellSource : "";
533540

541+
let podResult = { count: cell.execution_count, text: "", image: "" };
542+
let podStdOut = "";
543+
544+
for (const cellOutput of cell.cellOutputs) {
545+
if (
546+
cellOutput["output_type"] === "stream" &&
547+
cellOutput["name"] === "stdout"
548+
) {
549+
podStdOut = cellOutput["text"].join("");
550+
}
551+
if (cellOutput["output_type"] === "display_data") {
552+
podResult.text = cellOutput["data"]["text/plain"].join("");
553+
podResult.image = cellOutput["data"]["image/png"];
554+
}
555+
}
534556
// move the created node to scope and configure the necessary node attributes
535557
const posInsideScope = getNodePositionInsideScope(
536558
node,
@@ -572,6 +594,8 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
572594
height: node.height!,
573595
content: podContent,
574596
richContent: podRichContent,
597+
stdout: podStdOut === "" ? undefined : podStdOut,
598+
result: podResult.text === "" ? undefined : podResult,
575599
// For my local update, set dirty to true to push to DB.
576600
dirty: true,
577601
pending: true,
@@ -587,7 +611,8 @@ export const createCanvasSlice: StateCreator<MyState, [], [], CanvasSlice> = (
587611
}
588612
get().adjustLevel();
589613
get().buildNode2Children();
590-
// Set initial width as about 30 characters.
614+
// Set initial width as a scale of max line length.
615+
//get().setNodeCharWidth(scopeNode.id, Math.ceil(maxLineLength * 0.8));
591616
get().setNodeCharWidth(scopeNode.id, 30);
592617
get().updateView();
593618
},

0 commit comments

Comments
 (0)