Skip to content

Commit 04bd7c0

Browse files
author
zhogan
committed
add validation/debug info
1 parent 246dbc6 commit 04bd7c0

File tree

11 files changed

+170
-36
lines changed

11 files changed

+170
-36
lines changed

bin/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,22 @@ async function main() {
4242

4343
program
4444
.version(packageJson.version)
45-
.requiredOption(
45+
.option(
4646
"-i, --id <project>",
4747
"The id of the google datastore project.",
4848
process.env.PROJECT_ID
4949
)
50-
.requiredOption(
50+
.option(
5151
"-e, --emulator-host <host>",
5252
"The url of the emulator",
5353
process.env.DATASTORE_EMULATOR_HOST
5454
)
55-
.requiredOption(
55+
.option(
5656
"-b, --backup-bucket <bucket>",
5757
"The google cloud storage backup bucket",
5858
process.env.DATASTORE_BACKUP_BUCKET || ""
5959
)
60-
.requiredOption(
60+
.option(
6161
"-d, --backup-dir <dir>",
6262
"The google cloud storage backup bucket",
6363
process.env.DATASTORE_BACKUP_DIR || ""

src/client/src/pages/Backups/Backups.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ const Backups: React.FC = () => {
170170
height="100%"
171171
alignItems="center"
172172
justifyContent="center"
173+
whiteSpace="pre"
173174
>
174175
{error?.message ?? "No rows backups found."}
175176
</Stack>
@@ -181,6 +182,7 @@ const Backups: React.FC = () => {
181182
height="100%"
182183
alignItems="center"
183184
justifyContent="center"
185+
whiteSpace="pre"
184186
>
185187
{error?.message ?? "No rows backups found."}
186188
</Stack>

src/client/src/pages/Entities/Entities.tsx

+76-30
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ import Box from "@material-ui/core/Box";
33
import Button from "@material-ui/core/Button";
44
import useKinds from "../../hooks/useKinds";
55
import useEntitiesByKind from "../../hooks/useEntitiesByKind";
6-
import { DataGrid, GridCellEditCommitParams, GridCellParams, GridColDef } from "@mui/x-data-grid";
6+
import {
7+
DataGrid,
8+
GridCellEditCommitParams,
9+
GridCellParams,
10+
GridColDef,
11+
} from "@mui/x-data-grid";
712
import { LabelDisplayedRowsArgs } from "@material-ui/core";
813
import { FilterModel, SortModel } from "../../types/graphql";
914
import useUpdateEntity from "../../hooks/useUpdateEntity";
1015
import EntityDrawer from "./EntityDrawer";
1116
import EntityKinds from "./EntityKinds";
1217
import EntityFilters from "./EntityFilters";
18+
import Stack from "@mui/material/Stack";
1319

1420
function isObjectOrArray(
1521
value: any
@@ -31,7 +37,11 @@ const Entities: React.FC = () => {
3137
const [sortModel, setSortModel] = useState<SortModel[] | null>(null);
3238
const [currentCell, setCurrentCell] = useState<GridCellParams | null>(null);
3339

34-
const { data: kindsData, loading: isLoadingKinds } = useKinds({
40+
const {
41+
data: kindsData,
42+
loading: isLoadingKinds,
43+
error: kindsError,
44+
} = useKinds({
3545
onCompleted: (data) => {
3646
if (!kind) setKind(data.getKinds[0]);
3747
},
@@ -42,7 +52,11 @@ const Entities: React.FC = () => {
4252
const {
4353
result: [
4454
fetchEntities,
45-
{ data: getEntitiesData, loading: isLoadingEntities },
55+
{
56+
data: getEntitiesData,
57+
loading: isLoadingEntities,
58+
error: entitiesError,
59+
},
4660
],
4761
changePage,
4862
page,
@@ -57,6 +71,8 @@ const Entities: React.FC = () => {
5771

5872
const { getEntities: entitiesData } = getEntitiesData ?? {};
5973

74+
const error = kindsError ?? entitiesError;
75+
6076
const entities = useMemo(() => {
6177
if (entitiesData?.entities) {
6278
return entitiesData?.entities.map(({ entity, key, path }) => {
@@ -66,7 +82,6 @@ const Entities: React.FC = () => {
6682
return [];
6783
}, [entitiesData?.entities]);
6884

69-
7085
const dataGridColumns = useMemo<GridColDef[]>(() => {
7186
const columns: GridColDef[] = (entitiesData?.columns || []).map((key) => {
7287
const type = entitiesData?.typesMap[key];
@@ -133,48 +148,53 @@ const Entities: React.FC = () => {
133148
return entitiesData.columns.map((key) => ({
134149
label: key,
135150
value: key,
136-
type: entitiesData.typesMap[key]
151+
type: entitiesData.typesMap[key],
137152
}));
138153
}
139154
return [];
140155
}, [entitiesData]);
141156

142-
const onCellEditCommit = useCallback(async (data: GridCellEditCommitParams) => {
143-
if (currentCell) {
144-
const { value, field } = data;
145-
const type = entitiesData?.typesMap[field];
146-
const isArrayOrObject = type === "array" || type === "object";
157+
const onCellEditCommit = useCallback(
158+
async (data: GridCellEditCommitParams) => {
159+
if (currentCell) {
160+
const { value, field } = data;
161+
const type = entitiesData?.typesMap[field];
162+
const isArrayOrObject = type === "array" || type === "object";
147163

148-
try {
149-
const updates = {
150-
[field]: isArrayOrObject
151-
? JSON.parse(value as any)
152-
: value instanceof Date
164+
try {
165+
const updates = {
166+
[field]: isArrayOrObject
167+
? JSON.parse(value as any)
168+
: value instanceof Date
153169
? value
154170
: value,
155-
};
156-
157-
const path = currentCell.row.__path;
158-
159-
await updateEntity({
160-
variables: {
161-
input: {
162-
path,
163-
updates,
171+
};
172+
173+
const path = currentCell.row.__path;
174+
175+
await updateEntity({
176+
variables: {
177+
input: {
178+
path,
179+
updates,
180+
},
164181
},
165-
},
166-
});
167-
} catch (error) {
168-
console.error(error);
182+
});
183+
} catch (error) {
184+
console.error(error);
185+
}
169186
}
170-
}
171-
}, [currentCell, entitiesData?.typesMap, updateEntity]);
187+
},
188+
[currentCell, entitiesData?.typesMap, updateEntity]
189+
);
172190

173191
useEffect(() => {
174192
if (kind) fetchEntities();
175193
// eslint-disable-next-line react-hooks/exhaustive-deps
176194
}, [sortModel, kind]);
177195

196+
console.log(kindsError ?? entitiesError);
197+
178198
return (
179199
<Box>
180200
<EntityKinds kind={kind} kinds={kinds} setKind={setKind} />
@@ -221,6 +241,32 @@ const Entities: React.FC = () => {
221241
},
222242
},
223243
}}
244+
components={{
245+
NoRowsOverlay: () => {
246+
return (
247+
<Stack
248+
height="100%"
249+
alignItems="center"
250+
justifyContent="center"
251+
whiteSpace="pre"
252+
>
253+
{error?.message ?? "No entities found"}
254+
</Stack>
255+
);
256+
},
257+
NoResultsOverlay: () => {
258+
return (
259+
<Stack
260+
height="100%"
261+
alignItems="center"
262+
justifyContent="center"
263+
whiteSpace="pre"
264+
>
265+
{error?.message ?? "No entities found"}
266+
</Stack>
267+
);
268+
},
269+
}}
224270
onSortModelChange={(sortModel) =>
225271
setSortModel(sortModel as SortModel[])
226272
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { createMethodDecorator } from "type-graphql";
2+
import { Context } from "../types";
3+
import checkIfEmulatorRunning from "../utils/checkIfEmulatorRunning";
4+
5+
function ValidateEmulatorRunning() {
6+
return createMethodDecorator<Context>(async (_, next) => {
7+
8+
const emulatorRunning = await checkIfEmulatorRunning();
9+
10+
if (!emulatorRunning) {
11+
throw new Error(
12+
`Unable to connect to google datastore emulator running at: ${process.env.DATASTORE_EMULATOR_HOST}. \nMake sure you either set the environment variable DATASTORE_EMULATOR_HOST or run the google-datastore-emulator with the -e or --emulator-host option.`
13+
);
14+
}
15+
16+
return next();
17+
});
18+
}
19+
20+
export default ValidateEmulatorRunning;
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { createMethodDecorator } from "type-graphql";
2+
import { Context } from "../types";
3+
4+
const hints: Record<string, string> = {
5+
'DATASTORE_BACKUP_BUCKET': 'You can either set the envionrment variable or run google-datastore-emulator-ui with the -b or --backup-bucket option.',
6+
'PROJECT_ID': 'You can either set the envionrment variable or run google-datastore-emulator-ui with the -i or --id option.',
7+
'DATASTORE_BACKUP_DIR': 'You can either set the envionrment variable or run google-datastore-emulator-ui with the -d or --backup-dir option.'
8+
}
9+
10+
function ValidateEnv(vars: string | string[]) {
11+
return createMethodDecorator<Context>(async (_, next) => {
12+
13+
const varsToCheck = Array.isArray(vars) ? vars : [vars];
14+
15+
for (const varToCheck of varsToCheck) {
16+
if (process.env[varToCheck]) {
17+
throw new Error(
18+
`The envionment variable ${varToCheck} is not set. ${hints[varToCheck]}`
19+
);
20+
}
21+
}
22+
23+
return next();
24+
});
25+
}
26+
27+
export default ValidateEnv;

src/server/src/schema/entities/resolver.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Key } from "@google-cloud/datastore";
22
import { Arg, Ctx, Mutation, Query, Resolver } from "type-graphql";
3+
import ValidateEmulatorRunning from "../../decorators/ValidateEmulatorRunning";
34
import { Context } from "../../types";
45
import isNullOrUndefined from "../../utils/isNullOrUndefined";
56
import normalizeAndSortColumns from "../../utils/normalizeAndSortColumns";
@@ -26,6 +27,7 @@ class EntitiesResolver {
2627
}
2728

2829
@Query(() => EntitiesResult)
30+
@ValidateEmulatorRunning()
2931
async getEntities(
3032
@Arg("input", { nullable: false })
3133
{ kind, page, pageSize, filters, sortModel }: GetEntitiesInput,
@@ -98,6 +100,7 @@ class EntitiesResolver {
98100
}
99101

100102
@Mutation(() => Entity)
103+
@ValidateEmulatorRunning()
101104
async updateEntity(
102105
@Arg("input", { nullable: false }) { path, updates }: UpdateEntityInput,
103106
@Ctx() { datastore }: Context

src/server/src/schema/gsutil/resolver.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { DatastoreBackup } from "./types";
55
import removeTrailingSlash from "../../utils/removeTrailingSlash";
66
import * as fs from "fs";
77
import * as path from "path";
8-
import got from "got";
98
import env from "../../env";
9+
import ValidateEmulatorRunning from "../../decorators/ValidateEmulatorRunning";
10+
import ValidateEnv from "../../decorators/ValidateEnv";
1011

1112
const execAsync = promisify(exec);
1213

@@ -38,6 +39,7 @@ class GsUtilResolver {
3839
}
3940

4041
@Query(() => [DatastoreBackup])
42+
@ValidateEnv(['DATASTORE_BACKUP_BUCKET'])
4143
async getBackups(): Promise<DatastoreBackup[]> {
4244
const { stdout, stderr } = await execAsync(
4345
`gsutil ls gs://${env.DATASTORE_BACKUP_BUCKET}`
@@ -63,6 +65,7 @@ class GsUtilResolver {
6365
}
6466

6567
@Mutation(() => String)
68+
@ValidateEnv(['DATASTORE_BACKUP_BUCKET', 'PROJECT_ID'])
6669
async startBackup(): Promise<string> {
6770
const command = `gcloud datastore export gs://${env.DATASTORE_BACKUP_BUCKET} --project='${env.PROJECT_ID}' --format=json`;
6871

@@ -82,6 +85,7 @@ class GsUtilResolver {
8285
}
8386

8487
@Mutation(() => String)
88+
@ValidateEnv(['DATASTORE_BACKUP_BUCKET', 'DATASTORE_BACKUP_DIR'])
8589
async downloadBackup(@Arg("name") name: string): Promise<string> {
8690
const backup_bucket = env.DATASTORE_BACKUP_BUCKET;
8791
const outputDir = path.join(env.DATASTORE_BACKUP_DIR, name);
@@ -100,6 +104,7 @@ class GsUtilResolver {
100104
}
101105

102106
@Mutation(() => String)
107+
@ValidateEmulatorRunning()
103108
async importBackup(@Arg("name") name: string): Promise<string> {
104109
const input_url = path.join(
105110
env.DATASTORE_BACKUP_DIR,

src/server/src/schema/kinds/resolver.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Ctx, Query, Resolver } from "type-graphql";
2+
import ValidateEmulatorRunning from "../../decorators/ValidateEmulatorRunning";
23
import { Context } from "../../types";
34
import isNullOrUndefined from "../../utils/isNullOrUndefined";
45

56
@Resolver()
67
class KindsResolver {
78
@Query(() => [String])
9+
@ValidateEmulatorRunning()
810
async getKinds(@Ctx() { datastore }: Context): Promise<string[]> {
911
const query = datastore.createQuery("__kind__").select("__key__");
1012
const [results] = await datastore.runQuery(query);

src/server/src/schema/namespaces/resolver.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Ctx, Query, Resolver } from "type-graphql";
2+
import ValidateEmulatorRunning from "../../decorators/ValidateEmulatorRunning";
23
import { Context } from "../../types";
34
import isNullOrUndefined from "../../utils/isNullOrUndefined";
45

56
@Resolver()
67
class NamespaceResolver {
78
@Query(() => [String])
9+
@ValidateEmulatorRunning()
810
async getNamespaces(@Ctx() { datastore }: Context): Promise<string[]> {
911
const query = datastore.createQuery("__namespace__").select("__key__");
1012
const results = await datastore.runQuery(query);

src/server/src/server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function setEnv({ projectId, emulatorHost, port, backupBucket, backupDir }: Boos
3232

3333
async function boostrap({ projectId, emulatorHost, port, backupBucket, backupDir }: BoostrapOptions) {
3434
setEnv({ projectId, emulatorHost, port, backupBucket, backupDir });
35-
35+
3636
const app = express();
3737
const httpServer = http.createServer(app);
3838
const datastore = createDatastore();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { exec } from "child_process";
2+
import { promisify } from "util";
3+
import env from "../env";
4+
5+
const execAsync = promisify(exec);
6+
7+
async function checkIfEmulatorRunning() {
8+
try {
9+
const { stderr, stdout } = await execAsync(
10+
`curl -X GET -L ${env.DATASTORE_EMULATOR_HOST}`
11+
);
12+
13+
if (stderr) {
14+
return false;
15+
}
16+
17+
if (stdout === "ok") {
18+
return true;
19+
}
20+
21+
return false;
22+
} catch (error) {
23+
return false;
24+
}
25+
}
26+
27+
export default checkIfEmulatorRunning;

0 commit comments

Comments
 (0)