Skip to content

Commit c8fe0f8

Browse files
committed
add code generator
1 parent eec0fc5 commit c8fe0f8

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"react-joyride": "^2.5.3",
5050
"react-reflex": "^4.0.9",
5151
"react-responsive-carousel": "^3.2.23",
52+
"react-syntax-highlighter": "^15.6.1",
5253
"sjcl": "^1.0.8",
5354
"string-to-color": "^2.2.2",
5455
"string.prototype.replaceall": "^1.0.6",

src/components/CodeGenerator.tsx

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import {AlertDialog} from "@/playground-ui/AlertDialog.tsx";
2+
import { useMemo, useState } from "react";
3+
4+
import SyntaxHighlighter from 'react-syntax-highlighter';
5+
6+
import React from "react";
7+
import Grid from '@material-ui/core/Grid';
8+
import Button from "@material-ui/core/Button";
9+
10+
function generateRelUpdateJs(yaml: string): string {
11+
let result = ""
12+
for (const tuple of yaml.split("\n")) {
13+
const match = tuple.match(/([^#]+)#([^@]+)@(.+)/);
14+
if (!match) {
15+
continue
16+
}
17+
const [obj, rel, sub] = [match[1], match[2], match[3]]
18+
const [subType, subId] = sub.split(":");
19+
const [objType, objId] = obj.split(":");
20+
result += `v1.RelationshipUpdate.create({
21+
relationship: v1.Relationship.create({
22+
resource: v1.ObjectReference.create({
23+
objectType: '${objType}',
24+
objectId: '${objId}',
25+
}),
26+
relation: '${rel}',
27+
subject: v1.SubjectReference.create({
28+
object: v1.ObjectReference.create({
29+
objectType: '${subType}',
30+
objectId: '${subId}',
31+
}),
32+
}),
33+
}),
34+
operation: v1.RelationshipUpdate_Operation.CREATE,
35+
}),`
36+
}
37+
return result
38+
39+
}
40+
41+
function LangGenerator(props: {schema: string, relationshipsYaml: string, lang: string}) {
42+
if (props.lang == "Go") {
43+
return `package main
44+
45+
import (
46+
"context"
47+
"google.golang.org/grpc"
48+
"google.golang.org/grpc/credentials/insecure"
49+
"log"
50+
51+
"github.com/authzed/authzed-go/proto/authzed/api/v1"
52+
"github.com/authzed/authzed-go/v1"
53+
"github.com/authzed/grpcutil"
54+
)
55+
56+
func main() {
57+
client, err := authzed.NewClient(
58+
"localhost:50051",
59+
grpc.WithTransportCredentials(insecure.NewCredentials()),
60+
grpcutil.WithInsecureBearerToken("your_token_here"),
61+
)
62+
if err != nil {
63+
log.Fatalf("unable to initialize client: %s", err)
64+
}
65+
66+
// Write the schema.
67+
resp, err := client.WriteSchema(context.Background(), &v1.WriteSchemaRequest{
68+
Schema: ` + "`" + props.schema + "`" + `,
69+
})
70+
if err != nil {
71+
log.Fatalf("failed to write schema: %s", err)
72+
}
73+
log.Println("Written at: " + resp.WrittenAt.String())
74+
75+
// Write relationships.
76+
// TODO
77+
}
78+
79+
`} else if (props.lang == "JS") {
80+
return `import { v1 } from '@authzed/authzed-node';
81+
// set up it on localhost like this:
82+
const client = v1.NewClient('your_token_here', 'localhost:50051', v1.ClientSecurity.INSECURE_LOCALHOST_ALLOWED);
83+
// const client = v1.NewClient('your_token_here');
84+
const { promises: promiseClient } = client; // access client.promises after instantiating client
85+
86+
const writeRequest = v1.WriteSchemaRequest.create({
87+
schema: ` + "`" + props.schema + "`" + `,
88+
});
89+
90+
// Write the schema.
91+
await new Promise((resolve, reject) => {
92+
client.writeSchema(writeRequest, function (err, response) {
93+
if (err) reject(err);
94+
resolve(response);
95+
});
96+
});
97+
98+
// Write relationships.
99+
const writeRelationshipRequest = v1.WriteRelationshipsRequest.create({
100+
updates: [` +generateRelUpdateJs(props.relationshipsYaml) + `
101+
],
102+
});
103+
104+
await new Promise((resolve, reject) => {
105+
client.writeRelationships(writeRelationshipRequest, function (err, response) {
106+
if (err) reject(err);
107+
resolve(response);
108+
});
109+
});
110+
111+
`
112+
} else {
113+
return ""
114+
}
115+
}
116+
117+
export function GenerateContent({ schema, relationshipsYaml, langPicked, setLangPicked }) {
118+
const generatedCode = useMemo(
119+
() => LangGenerator({ lang: langPicked, schema, relationshipsYaml }),
120+
[langPicked, schema, relationshipsYaml]
121+
);
122+
123+
const syntaxLang = langPicked === "Go" ? "go" : "javascript";
124+
125+
return (
126+
<Grid container spacing={1} sx={{ width: '900px' }}>
127+
<Grid item xs={2}>
128+
<Button
129+
variant={langPicked === "Go" ? "contained" : "outlined"}
130+
onClick={() => setLangPicked("Go")}
131+
>
132+
Go
133+
</Button>
134+
<Button
135+
variant={langPicked === "JS" ? "contained" : "outlined"}
136+
onClick={() => setLangPicked("JS")}
137+
>
138+
JS
139+
</Button>
140+
</Grid>
141+
<Grid item xs={10}>
142+
<SyntaxHighlighter language={syntaxLang} customStyle={{ fontSize: 11 }}>
143+
{generatedCode}
144+
</SyntaxHighlighter>
145+
</Grid>
146+
</Grid>
147+
);
148+
}
149+
150+
export function CodeGenerator(props: {schema: string, relationshipsYaml: string}) {
151+
const [showAlert, setShowAlert] = useState(true);
152+
const [langPicked, setLangPicked] = useState("Go");
153+
154+
// Always up-to-date code based on current language
155+
const generatedCode = useMemo(
156+
() => LangGenerator({ lang: langPicked, schema: props.schema, relationshipsYaml: props.relationshipsYaml }),
157+
[langPicked, props.schema, props.relationshipsYaml]
158+
);
159+
160+
const handleClose = () => {
161+
navigator.clipboard.writeText(generatedCode)
162+
setShowAlert(false)
163+
};
164+
165+
return (
166+
<AlertDialog
167+
isOpen={showAlert}
168+
handleClose={handleClose}
169+
title=""
170+
content={
171+
<GenerateContent
172+
schema={props.schema}
173+
relationshipsYaml={props.relationshipsYaml}
174+
langPicked={langPicked}
175+
setLangPicked={setLangPicked}
176+
/>
177+
}
178+
buttonTitle="Copy"
179+
/>
180+
);
181+
}

src/components/FullPlayground.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import { TerminalPanel, TerminalSummary } from "./panels/terminal";
8787
import { ValidationPanel, ValidationSummary } from "./panels/validation";
8888
import { VisualizerPanel, VisualizerSummary } from "./panels/visualizer";
8989
import { WatchesPanel, WatchesSummary } from "./panels/watches";
90+
import {CodeGenerator} from "@/components/CodeGenerator.tsx";
9091

9192
const TOOLBAR_BREAKPOINT = 1550; // pixels
9293

@@ -547,6 +548,39 @@ export function ThemedAppView(props: { datastore: DataStore }) {
547548
}
548549
};
549550

551+
function CodeGeneratorButton () {
552+
const [showCodeGenerator, setShowCodeGenerator] = useState(false);
553+
const [codeGenProps, setCodeGenProps] = useState({ schema: "", relationshipsYaml: "" });
554+
555+
const handleOpenCodeGenerator = () => {
556+
const schema = datastore.getSingletonByKind(DataStoreItemKind.SCHEMA).editableContents!;
557+
const relationshipsYaml = datastore.getSingletonByKind(DataStoreItemKind.RELATIONSHIPS).editableContents!;
558+
if (schema && relationshipsYaml) {
559+
setCodeGenProps({ schema, relationshipsYaml });
560+
setShowCodeGenerator(true);
561+
}
562+
};
563+
564+
return (
565+
<>
566+
<Button
567+
className={clsx(classes.hideTextOnMed)}
568+
size="small"
569+
onClick={handleOpenCodeGenerator}
570+
startIcon={<CodeIcon />}
571+
>
572+
Generate code
573+
</Button>
574+
{showCodeGenerator && (
575+
<CodeGenerator
576+
schema={codeGenProps.schema}
577+
relationshipsYaml={codeGenProps.relationshipsYaml}
578+
/>
579+
)}
580+
</>
581+
);
582+
}
583+
550584
const datastoreUpdated = () => {
551585
if (sharingState.status !== SharingStatus.NOT_RUN) {
552586
setSharingState({
@@ -772,6 +806,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
772806
) : (
773807
<span />
774808
)}
809+
<CodeGeneratorButton />
775810
<Button
776811
className={clsx(TourElementClass.share, classes.hideTextOnMed, {
777812
[classes.hide]: !isSharingEnabled,

0 commit comments

Comments
 (0)