Skip to content

Commit

Permalink
Indicate origin of generated data
Browse files Browse the repository at this point in the history
  • Loading branch information
WardBrian committed Oct 30, 2024
1 parent c612989 commit 6e1a975
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 27 deletions.
13 changes: 12 additions & 1 deletion gui/src/app/Project/ProjectDataModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,24 @@ const isProjectBase = (x: any): x is ProjectBase => {
return true;
};

export enum DataSource {
GENERATED_BY_R = "generated_by_r",
GENERATED_BY_PYTHON = "generated_by_python",
}

type ProjectMetadata = {
title: string;
dataSource?: DataSource;
};

export const isProjectMetaData = (x: any): x is ProjectMetadata => {
if (!baseObjectCheck(x)) return false;
if (typeof x.title !== "string") return false;
if (
x.dataSource !== undefined && // allow undefined for backwards compatibility
!Object.values(DataSource).includes(x.dataSource)
)
return false;
return true;
};

Expand Down Expand Up @@ -128,7 +139,7 @@ export const isProjectDataModel = (x: any): x is ProjectDataModel => {
export type ProjectPersistentDataModel = Omit<ProjectDataModel, "ephemera">;

export const initialDataModel: ProjectDataModel = {
meta: { title: "Untitled" },
meta: { title: "Untitled", dataSource: undefined },
ephemera: {
stanFileContent: "",
dataFileContent: "",
Expand Down
5 changes: 5 additions & 0 deletions gui/src/app/Project/ProjectReducer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FieldsContentsMap } from "@SpCore/FileMapping";
import {
DataSource,
initialDataModel,
ProjectDataModel,
ProjectKnownFiles,
Expand All @@ -24,6 +25,7 @@ export type ProjectReducerAction =
type: "retitle";
title: string;
}
| { type: "setDataSource"; dataSource: DataSource | undefined }
| {
type: "editFile";
content: string;
Expand Down Expand Up @@ -81,6 +83,9 @@ const ProjectReducer = (s: ProjectDataModel, a: ProjectReducerAction) => {
case "clear": {
return initialDataModel;
}
case "setDataSource": {
return { ...s, meta: { ...s.meta, dataSource: a.dataSource } };
}
default:
return unreachable(a);
}
Expand Down
2 changes: 1 addition & 1 deletion gui/src/app/Project/ProjectSerialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const loadMetaFromString = (
json: string,
clearExisting: boolean = false,
): ProjectDataModel => {
const newMeta = JSON.parse(json);
let newMeta = JSON.parse(json);
if (!isProjectMetaData(newMeta)) {
throw Error("Deserialized meta is not valid");
}
Expand Down
2 changes: 1 addition & 1 deletion gui/src/app/Scripting/DataGeneration/DataPyWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const handleHelp = () =>
);

const DataPyWindow: FunctionComponent<Props> = () => {
const { consoleRef, status, onStatus, onData } = useDataGenState();
const { consoleRef, status, onStatus, onData } = useDataGenState("python");

const callbacks = useMemo(
() => ({
Expand Down
2 changes: 1 addition & 1 deletion gui/src/app/Scripting/DataGeneration/DataRWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const handleHelp = () =>
);

const DataRWindow: FunctionComponent<Props> = () => {
const { consoleRef, status, onStatus, onData } = useDataGenState();
const { consoleRef, status, onStatus, onData } = useDataGenState("r");

const { run } = useWebR({ consoleRef, onStatus, onData });
const handleRun = useCallback(
Expand Down
11 changes: 9 additions & 2 deletions gui/src/app/Scripting/DataGeneration/useDataGenState.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useCallback, useContext, useRef, useState } from "react";
import { ProjectKnownFiles } from "@SpCore/ProjectDataModel";
import { DataSource, ProjectKnownFiles } from "@SpCore/ProjectDataModel";
import { writeConsoleOutToDiv } from "@SpScripting/OutputDivUtils";
import { InterpreterStatus } from "@SpScripting/InterpreterTypes";
import { ProjectContext } from "@SpCore/ProjectContextProvider";

// A custom hook to share logic between the Python and R data generation windows
// This contains the output div ref, the interpreter state, and the callback to update the data.
const useDataGenState = () => {
const useDataGenState = (source: "python" | "r") => {
const [status, setStatus] = useState<InterpreterStatus>("idle");
const consoleRef = useRef<HTMLDivElement>(null);

Expand All @@ -26,6 +26,13 @@ const useDataGenState = () => {
filename: ProjectKnownFiles.DATAFILE,
});
update({ type: "commitFile", filename: ProjectKnownFiles.DATAFILE });
update({
type: "setDataSource",
dataSource:
source === "python"
? DataSource.GENERATED_BY_PYTHON
: DataSource.GENERATED_BY_R,
});
// Use "stan-playground" prefix to distinguish from console output of the running code
writeConsoleOutToDiv(
consoleRef,
Expand Down
89 changes: 68 additions & 21 deletions gui/src/app/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import TextEditor from "@SpComponents/TextEditor";
import { FileNames } from "@SpCore/FileMapping";
import { ProjectContext } from "@SpCore/ProjectContextProvider";
import {
DataSource,
modelHasUnsavedChanges,
ProjectKnownFiles,
} from "@SpCore/ProjectDataModel";
Expand All @@ -17,12 +18,15 @@ import DataPyWindow from "@SpScripting/DataGeneration/DataPyWindow";
import DataRWindow from "@SpScripting/DataGeneration/DataRWindow";
import {
FunctionComponent,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import SamplingWindow from "./SamplingWindow/SamplingWindow";
import { ToolbarItem } from "@SpComponents/ToolBar";

type Props = {
//
Expand Down Expand Up @@ -92,6 +96,7 @@ type LeftViewProps = {

const LeftView: FunctionComponent<LeftViewProps> = () => {
const { data, update } = useContext(ProjectContext);

return (
<Split horizontal>
<StanFileEditor
Expand All @@ -114,31 +119,73 @@ const LeftView: FunctionComponent<LeftViewProps> = () => {
}
readOnly={false}
/>
<TextEditor
language="json"
label={FileNames.DATAFILE}
text={data.dataFileContent}
onSaveText={() =>
update({
type: "commitFile",
filename: ProjectKnownFiles.DATAFILE,
})
}
editedText={data.ephemera.dataFileContent}
onSetEditedText={(content: string) =>
update({
type: "editFile",
content,
filename: ProjectKnownFiles.DATAFILE,
})
}
readOnly={false}
contentOnEmpty={"Enter JSON data or use the data generation tab"}
/>
<DataEditor />
</Split>
);
};

const DataEditor: FunctionComponent<{}> = () => {
const { data, update } = useContext(ProjectContext);

const dataIsEdited = useMemo(() => {
return data.dataFileContent !== data.ephemera.dataFileContent;
}, [data.dataFileContent, data.ephemera.dataFileContent]);

const dataMessage: ToolbarItem[] = useMemo(() => {
if (data.meta.dataSource === undefined || dataIsEdited) {
return [];
} else {
return [
{
type: "text",
label:
"Data is generated by data." +
(data.meta.dataSource === DataSource.GENERATED_BY_PYTHON
? "py"
: "R"),
color: "info.main",
},
];
}
}, [data.meta.dataSource, dataIsEdited]);

const onSetEditedText = useCallback(
(content: string) => {
update({
type: "editFile",
content,
filename: ProjectKnownFiles.DATAFILE,
});
},
[update],
);

const onSaveText = useCallback(() => {
update({
type: "commitFile",
filename: ProjectKnownFiles.DATAFILE,
});
update({
type: "setDataSource",
dataSource: undefined,
});
}, [update]);

return (
<TextEditor
language="json"
label={FileNames.DATAFILE}
text={data.dataFileContent}
onSaveText={onSaveText}
editedText={data.ephemera.dataFileContent}
onSetEditedText={onSetEditedText}
readOnly={false}
toolbarItems={dataMessage}
contentOnEmpty={"Enter JSON data or use the data generation tab"}
/>
);
};

// adapted from https://mui.com/material-ui/react-drawer/#persistent-drawer
const MovingBox = styled(Box, {
shouldForwardProp: (prop) => prop !== "open",
Expand Down
18 changes: 18 additions & 0 deletions gui/test/app/Project/ProjectDataModel.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
DataSource,
defaultSamplingOpts,
exportedForTesting,
getStringKnownFileKeys,
Expand Down Expand Up @@ -248,6 +249,23 @@ describe("Project data model type guards", () => {
expect(isProjectMetaData({ title: 6 })).toBe(false);
expect(isProjectMetaData({ no_title: "title" })).toBe(false);
});
test("Returns true for valid data source", () => {
expect(
isProjectMetaData({
title: "title",
dataSource: DataSource.GENERATED_BY_PYTHON,
}),
).toBe(true);
expect(
isProjectMetaData({
title: "title",
dataSource: DataSource.GENERATED_BY_R,
}),
).toBe(true);
});
test("Returns false on bad data source", () => {
expect(isProjectMetaData({ title: "title", dataSource: 1 })).toBe(false);
});
});
describe("Project ephemeral-data typeguard", () => {
test("Returns true for valid project files object", () => {
Expand Down

0 comments on commit 6e1a975

Please sign in to comment.